0
私は、CPPおよびHPPファイルにプログラムを分割しようとしていますWebServer分割C++テンプレートファイル
から基本プログラムをとっています。
server.hpp
#include <boost/asio.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <unordered_map>
#include <thread>
#include <functional>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <fstream>
#include <vector>
#define CS_WEBSERVER_PORT 0xc080
namespace WebServer {
template < class socket_type > class WebServerBase
{
public:
virtual ~ WebServerBase();
class Response:public std::ostream
{
friend class WebServerBase <socket_type>;
boost::asio::streambuf streambuf;
std::shared_ptr <socket_type> socket;
Response(std::shared_ptr <socket_type> socket):std::ostream(&streambuf), socket(socket);
public:
size_t size() {
return streambuf.size();
}
};
class Content:public std::istream
{
friend class WebServerBase <socket_type>;
public:
size_t size() {
return streambuf.size();
}
const std::string string() const
{
std::stringstream ss;
ss << rdbuf();
return ss.str();
}
private:
boost::asio::streambuf & streambuf;
Content(boost::asio::streambuf & streambuf):std::istream(&streambuf), streambuf(streambuf);
};
class Request
{
friend class WebServerBase <socket_type>;
class iequal_to {
public:
bool operator() (const std::string & key1, const std::string & key2)const
{
return boost::algorithm::iequals(key1, key2);
}
};
class ihash {
public:
size_t operator() (const std::string & key)const
{
std::size_t seed = 0;
for (auto & c:key)
boost::hash_combine(seed, std::tolower(c));
return seed;
}
};
public:
std::string method, path, http_version;
Content content;
std::unordered_multimap < std::string, std::string, ihash, iequal_to > header;
boost::smatch path_match;
std::string remote_endpoint_address;
unsigned short remote_endpoint_port;
private:
Request():content(streambuf);
boost::asio::streambuf streambuf;
void read_remote_endpoint_data(socket_type & socket);
};
class Config
{
friend class WebServerBase <socket_type>;
Config(unsigned short port, size_t num_threads):num_threads(num_threads), port(port), reuse_address(true);
size_t num_threads;
public:
unsigned short port;
/*
* IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
* If empty, the address will be any address.
*/
std::string address;
/*Set to false to avoid binding the socket to an address that is already in use.*/
bool reuse_address;
};
///Set before calling start().
Config config;
std::unordered_map < std::string, std::unordered_map < std::string,
std::function < void (std::shared_ptr < typename WebServerBase <socket_type>::Response >,
std::shared_ptr < typename WebServerBase <socket_type>::Request >) > >>resource;
std::unordered_map < std::string,
std::function < void (std::shared_ptr < typename WebServerBase <socket_type>::Response >,
std::shared_ptr < typename WebServerBase <socket_type>::Request >) > >default_resource;
private:
std::vector < std::pair < std::string, std::vector < std::pair < boost::regex,
std::function < void (std::shared_ptr < typename WebServerBase <socket_type>::Response >,
std::shared_ptr < typename WebServerBase <socket_type>::Request >) > >>>>opt_resource;
public:
void start();
void stop();
///Use this function if you need to recursively send parts of a longer message
void send(std::shared_ptr <Response> response,
const std::function < void (const boost::system::error_code &) > &callback = nullptr) const;
protected:
boost::asio::io_service io_service;
boost::asio::ip::tcp::acceptor acceptor;
std::vector <std::thread> threads;
long timeout_request;
long timeout_content;
WebServerBase(unsigned short port, size_t num_threads, long timeout_request,
long timeout_send_or_receive):config(port, num_threads), acceptor(io_service),
timeout_request(timeout_request), timeout_content(timeout_send_or_receive)
{
}
virtual void accept() = 0;
std::shared_ptr <boost::asio::deadline_timer> set_timeout_on_socket(std::shared_ptr <socket_type> socket,
long seconds);
void read_request_and_content(std::shared_ptr <socket_type> socket);
bool parse_request(std::shared_ptr <Request> request, std::istream & stream) const;
void find_resource(std::shared_ptr <socket_type> socket, std::shared_ptr <Request> request);
void write_response(std::shared_ptr <socket_type> socket, std::shared_ptr <Request> request,
std::function < void (std::shared_ptr < typename WebServerBase <socket_type>::Response >,
std::shared_ptr < typename WebServerBase <socket_type>::Request >) >
&resource_function);
template < class socket_type > class Server:public WebServerBase <socket_type> {
};
typedef boost::asio::ip::tcp::socket HTTP;
template <> class Server <HTTP>:public WebServerBase <HTTP>
{
public:
Server(unsigned short port, size_t num_threads = 1, long timeout_request = 5, long timeout_content = 300):
WebServerBase <HTTP>::WebServerBase(port, num_threads, timeout_request, timeout_content)
{
}
private:
void accept();
};
}
}
とserver.cpp
#define WEBSERVER_PORT 0x8080
namespace WebServer {
void WebServerBase::Request::read_remote_endpoint_data(socket_type & socket)
{
try {
remote_endpoint_address = socket.lowest_layer().remote_endpoint().address().to_string();
remote_endpoint_port = socket.lowest_layer().remote_endpoint().port();
}
catch(const std::exception &)
{
}
}
void WebServerBase::start()
{
/*Copy the resources to opt_resource for more efficient request processing*/
opt_resource.clear();
for (auto & res:resource)
{
for (auto & res_method:res.second)
{
auto it = opt_resource.end();
for (auto opt_it = opt_resource.begin(); opt_it != opt_resource.end(); opt_it++)
{
if (res_method.first == opt_it->first)
{
it = opt_it;
break;
}
}
if (it == opt_resource.end())
{
opt_resource.emplace_back();
it = opt_resource.begin() + (opt_resource.size() - 1);
it->first = res_method.first;
}
it->second.emplace_back(boost::regex(res.first), res_method.second);
}
}
if (io_service.stopped())
io_service.reset();
boost::asio::ip::tcp::endpoint endpoint;
if (config.address.size() > 0)
endpoint =
boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(config.address), config.port);
else
endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), config.port);
acceptor.open(endpoint.protocol());
acceptor.set_option(boost::asio::socket_base::reuse_address(config.reuse_address));
acceptor.bind(endpoint);
acceptor.listen();
accept();
//If num_threads>1, start m_io_service.run() in (num_threads-1) threads for thread-pooling
threads.clear();
for (size_t c = 1; c < config.num_threads; c++)
{
threads.emplace_back([this]() {
io_service.run();});
}
//Main thread
io_service.run();
//Wait for the rest of the threads, if any, to finish as well
for (auto & t:threads) {
t.join();
}
}
void WebServerBase::stop() {
acceptor.close();
io_service.stop();
}
///Use this function if you need to recursively send parts of a longer message
void WebServerBase::send(std::shared_ptr <Response> response,
const std::function < void (const boost::system::error_code &) > &callback = nullptr) const
{
boost::asio::async_write(*response->socket, response->streambuf,
[this, response, callback] (const boost::system::error_code & ec,
size_t /*bytes_transferred */)
{
if (callback)
callback(ec);
}) ;
}
std::shared_ptr <boost::asio::deadline_timer> WebServerBase::set_timeout_on_socket(std::shared_ptr <socket_type> socket,
long seconds)
{
std::shared_ptr <boost::asio::deadline_timer> timer(new boost::asio::deadline_timer(io_service));
timer->expires_from_now(boost::posix_time::seconds(seconds));
timer->async_wait([socket] (const boost::system::error_code & ec)
{
if (!ec)
{
boost::system::error_code ec;
socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
socket->lowest_layer().close();}
}) ;
return timer;
}
void WebServerBase::read_request_and_content(std::shared_ptr <socket_type> socket)
{
//Create new streambuf (Request::streambuf) for async_read_until()
//shared_ptr is used to pass temporary objects to the asynchronous functions
std::shared_ptr <Request> request(new Request());
request->read_remote_endpoint_data(*socket);
//Set timeout on the following boost::asio::async-read or write function
std::shared_ptr <boost::asio::deadline_timer> timer;
if (timeout_request > 0)
timer = set_timeout_on_socket(socket, timeout_request);
boost::asio::async_read_until(*socket, request->streambuf, "\r\n\r\n",
[this, socket, request, timer] (const boost::system::error_code & ec,
size_t bytes_transferred)
{
if (timeout_request > 0)
timer->cancel();
if (!ec)
{
/**
* request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
* "After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
* The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
* streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
*/
size_t num_additional_bytes = request->streambuf.size() - bytes_transferred;
if (!parse_request(request, request->content))
return;
//If content, read that as well
auto it = request->header.find("Content-Length");
if (it != request->header.end())
{
//Set timeout on the following boost::asio::async-read or write function
std::shared_ptr <boost::asio::deadline_timer> timer;
if (timeout_content > 0)
timer = set_timeout_on_socket(socket, timeout_content);
unsigned long long content_length;
try {
content_length = stoull(it->second);
}
catch(const std::exception &)
{
return;
}
if (content_length > num_additional_bytes)
{
boost::asio::async_read(*socket, request->streambuf,
boost::asio::transfer_exactly(content_length -
num_additional_bytes),
[this, socket, request, timer]
(const boost::system::error_code & ec,
size_t /*bytes_transferred */)
{
if (timeout_content > 0)
timer->cancel();
if (!ec)
find_resource(socket, request);
});
}
else
{
if (timeout_content > 0)
timer->cancel();
find_resource(socket, request);
}
}
else
{
find_resource(socket, request);}
}
}) ;
}
bool WebServerBase::parse_request(std::shared_ptr <Request> request, std::istream & stream) const
{
std::string line;
getline(stream, line);
size_t method_end;
if ((method_end = line.find(' ')) != std::string::npos)
{
size_t path_end;
if ((path_end = line.find(' ', method_end + 1)) != std::string::npos)
{
request->method = line.substr(0, method_end);
request->path = line.substr(method_end + 1, path_end - method_end - 1);
size_t protocol_end;
if ((protocol_end = line.find('/', path_end + 1)) != std::string::npos)
{
if (line.substr(path_end + 1, protocol_end - path_end - 1) != "HTTP")
return false;
request->http_version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
}
else
return false;
getline(stream, line);
size_t param_end;
while ((param_end = line.find(':')) != std::string::npos)
{
size_t value_start = param_end + 1;
if ((value_start) < line.size())
{
if (line[value_start] == ' ')
value_start++;
if (value_start < line.size())
request->header.insert(std::make_pair(line.substr(0, param_end),
line.substr(value_start, line.size() - value_start - 1)));
}
getline(stream, line);
}
} else
return false;
} else
return false;
return true;
}
void WebServerBase::find_resource(std::shared_ptr <socket_type> socket, std::shared_ptr <Request> request)
{
//Find path- and method-match, and call write_response
for (auto & res:opt_resource)
{
if (request->method == res.first)
{
for (auto & res_path:res.second)
{
boost::smatch sm_res;
if (boost::regex_match(request->path, sm_res, res_path.first))
{
request->path_match = std::move(sm_res);
write_response(socket, request, res_path.second);
return;
}
}
}
}
auto it_method = default_resource.find(request->method);
if (it_method != default_resource.end())
{
write_response(socket, request, it_method->second);
}
}
void WebServerBase::write_response(std::shared_ptr <socket_type> socket, std::shared_ptr <Request> request,
std::function < void (std::shared_ptr < typename WebServerBase <socket_type>::Response >,
std::shared_ptr < typename WebServerBase <socket_type>::Request >) >
&resource_function)
{
//Set timeout on the following boost::asio::async-read or write function
std::shared_ptr <boost::asio::deadline_timer> timer;
if (timeout_content > 0)
timer = set_timeout_on_socket(socket, timeout_content);
auto response = std::shared_ptr <Response> (new Response(socket),[this, request, timer] (Response * response_ptr)
{
auto response = std::shared_ptr <Response> (response_ptr);
send(response,[this, response, request,timer] (const boost::system::error_code & ec)
{
if (!ec)
{
if (timeout_content > 0)
timer->cancel(); float http_version;
try {
http_version = stof(request->http_version);}
catch(const std::exception &)
{
return;
}
auto range = request->header.equal_range("Connection");
for (auto it = range.first; it != range.second; it++)
{
if (boost::iequals(it->second, "close"))
return;
}
if (http_version > 1.05)
read_request_and_content(response->socket);
}
}
);}
);
try {
resource_function(response, request);
}
catch(const std::exception &)
{
return;
}
}
template < class socket_type > class Server:public WebServerBase <socket_type> {
};
typedef boost::asio::ip::tcp::socket HTTP;
template <> class Server <HTTP>:public WebServerBase <HTTP>
{
void Server::accept()
{
//Create new socket for this connection
//Shared_ptr is used to pass temporary objects to the asynchronous functions
std::shared_ptr <HTTP> socket(new HTTP(io_service));
acceptor.async_accept(*socket,[this, socket] (const boost::system::error_code & ec)
{
//Immediately start accepting a new connection
accept(); if (!ec) {
boost::asio::ip::tcp::no_delay option(true);
socket->set_option(option); read_request_and_content(socket);}
}) ;
}
};
}
typedef WebServer::Server <WebServer::HTTP> HttpServer;
void default_resource_send(const HttpServer &server, std::shared_ptr<HttpServer::Response> response,
std::shared_ptr<std::ifstream> ifs, std::shared_ptr<std::vector<char> > buffer);
int main()
{
//HTTP-server at port c080 using 1 thread
HttpServer server(WEBSERVER_PORT, 1);
server.resource["^/match/([0-9]+)$"]["GET"] =
[&server] (std::shared_ptr <HttpServer::Response> response, std::shared_ptr <HttpServer::Request> request) {
std::string number = request->path_match[1];
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
};
using namespace boost::property_tree;
server.default_resource["GET"]=
[&server](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> request)
{
const auto web_root_path=boost::filesystem::canonical("web");
boost::filesystem::path path=web_root_path;
path/=request->path;
if(boost::filesystem::exists(path))
{
path=boost::filesystem::canonical(path);
//Check if path is within web_root_path
if(std::distance(web_root_path.begin(), web_root_path.end())<=std::distance(path.begin(), path.end()) &&
std::equal(web_root_path.begin(), web_root_path.end(), path.begin()))
{
if(boost::filesystem::is_directory(path))
path/="index.html";
if(boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path))
{
auto ifs=std::make_shared<std::ifstream>();
ifs->open(path.string(), std::ifstream::in | std::ios::binary);
if(*ifs)
{
//read and send 128 KB at a time
std::streamsize buffer_size=131072;
auto buffer=std::make_shared<std::vector<char> >(buffer_size);
ifs->seekg(0, std::ios::end);
auto length=ifs->tellg();
ifs->seekg(0, std::ios::beg);
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << length << "\r\n\r\n";
default_resource_send(server, response, ifs, buffer);
return;
}
}
}
}
std::string content="Could not open path "+request->path;
*response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
};
std::thread server_thread([&server]()
{
//Start server
server.start();
});
server_thread.join();
return 0;
}
void default_resource_send(const HttpServer &server, std::shared_ptr<HttpServer::Response> response,
std::shared_ptr<std::ifstream> ifs, std::shared_ptr<std::vector<char> > buffer)
{
std::streamsize read_length;
if((read_length=ifs->read(&(*buffer)[0], buffer->size()).gcount())>0)
{
response->write(&(*buffer)[0], read_length);
if(read_length==static_cast<std::streamsize>(buffer->size()))
{
server.send(response, [&server, response, ifs, buffer](const boost::system::error_code &ec)
{
if(!ec)
default_resource_send(server, response, ifs, buffer);
else
std::cerr << "Connection interrupted" << std::endl;
});
}
}
}
次のように私はそれをコンパイルします。
g++ -std=c++11 server.cpp -lboost_system -lboost_thread -lboost_filesystem -lboost_regex -lpthread -o server
は、しかし、私は次のエラーが
In file included from httpserver.cpp:18:0:
httpserver.hpp:165:24: error: declaration of ‘class socket_type’
template < class socket_type > class Server:public WebServerBase <socket_type> {
^
server.hpp:23:16: error: shadows template parm ‘class socket_type’
template < class socket_type > class WebServerBase
^
server.hpp:170:23: error: explicit specialization in non-namespace scope ‘class WebServer::WebServerBase<socket_type>’
template <> class Server <HTTP>:public WebServerBase <HTTP>
^
server.hpp:170:31: error: template parameters not deducible in partial specialization:
template <> class Server <HTTP>:public WebServerBase <HTTP>
^
server.hpp:170:31: note: ‘socket_type’
server.hpp:182:5: error: expected ‘;’ after class definition
}
^
server.hpp: In constructor ‘WebServer::WebServerBase<socket_type>::Response::Response(std::shared_ptr<_Tp1>)’:
server.hpp:32:101: error: expected ‘{’ at end of input
Response(std::shared_ptr <socket_type> socket):std::ostream(&streambuf), socket(socket);
^
server.hpp: In constructor ‘WebServer::WebServerBase<socket_type>::Content::Content(boost::asio::streambuf&)’:
server.hpp:54:102: error: expected ‘{’ at end of input
Content(boost::asio::streambuf & streambuf):std::istream(&streambuf), streambuf(streambuf);
^
server.hpp: In constructor ‘WebServer::WebServerBase<socket_type>::Request::Request()’:
server.hpp:87:44: error: expected ‘{’ at end of input
Request():content(streambuf);
^
server.hpp: In constructor ‘WebServer::WebServerBase<socket_type>::Config::Config(short unsigned int, size_t)’:
server.hpp:97:121: error: expected ‘{’ at end of input
Config(unsigned short port, size_t num_threads):num_threads(num_threads), port(port), reuse_address(true);
^
server.cpp: At global scope:
server.cpp:24:6: error: ‘template<class socket_type> class WebServer::WebServerBase’ used without template parameters
void WebServerBase::Request::read_remote_endpoint_data(socket_type & socket)
^
server.cpp:24:56: error: variable or field ‘read_remote_endpoint_data’ declared void
void WebServerBase::Request::read_remote_endpoint_data(socket_type & socket)
^
server.cpp:24:56: error: ‘socket_type’ was not declared in this scope
server.cpp:24:56: note: suggested alternative:
‘boost::asio::detail::socket_type’
typedef int socket_type;
^
server.cpp:435:1: error: expected ‘}’ at end of input
}
^
私はこれらのエラーは何を意味するのかを把握することはできません取得します。角括弧は私の目に正確に一致しているようです。もし誰かが私を助けることができれば素晴らしいだろう。
でなければなりません。たとえば、cppで 'socket_type'を使用することはできません。 –
@ dau_sama-それ以外の方法は何ですか? – liv2hak
テンプレートを宣言し、同じ翻訳単位で実装する必要があります。別々の '.h'ファイルと' .cpp'ファイルに分割することはできず、あなたが思っているように '.cpp'をそれ自身の翻訳単位としてコンパイルできると期待しています。しかし、あなたができることは、同じ翻訳単位内のテンプレート実装からテンプレート宣言を分割し、そのテンプレートを宣言した後で '.h'ファイル'#include'sを実行する別のファイルに実装を移すことができます。 –