24#include <boost/network/protocol/http/server.hpp>
36namespace http = boost::network::http;
46 enum class RawPostAccess {
52 auto sendServerError = [](HttpServer::connection_ptr& httpConn) {
53 httpConn->set_status(HttpServer::connection::internal_server_error);
54 httpConn->set_headers(unordered_multimap<string, string>({{
"content-type",
"text/html; charset=utf-8"}}));
58 inline string getListenAddr(shared_ptr<Config const>
const& configPtr) {
59 return (*configPtr)[{
"http",
"listen"}].empty() ?
"127.0.0.1" : (*configPtr)[{
"http",
"listen"}];
62 inline string getListenPort(shared_ptr<Config const>
const& configPtr) {
63 return (*configPtr)[{
"http",
"port"}].empty() ?
"8080" : (*configPtr)[{
"http",
"port"}];
68struct InputConsumingHttpHandler :
public enable_shared_from_this<InputConsumingHttpHandler> {
74 RawPostAccess rawPostAccess;
77 ssize_t maxPostSize,
size_t expectedSize, RawPostAccess rawPostAccess)
78 : requestHandler(requestHandler), connectionInit(std::move(connectionInit)), maxPostSize(maxPostSize),
79 expectedSize(expectedSize), rawPostAccess(rawPostAccess) {}
81 void operator()(HttpServer::connection::input_range input, boost::system::error_code ec,
82 size_t bytesTransferred, HttpServer::connection_ptr httpConn) {
83 if (ec == boost::asio::error::eof) {
84 NLOG_ERROR(logger,
"Request with POST data could not be handled.")
85 NLOG_DEBUG(logger, "Debug info: boost::asio::error::eof in cpp-netlib while processing POST data")
86 sendServerError(httpConn);
91 if (postBody.size() + bytesTransferred > maxPostSize) {
92 sendServerError(httpConn);
97 postBody.insert(postBody.end(), boost::begin(input), boost::end(input));
100 if (postBody.size() < expectedSize) {
101 auto self = this->shared_from_this();
102 httpConn->read([
self](HttpServer::connection::input_range input,
103 boost::system::error_code ec,
size_t bytes_transferred,
104 HttpServer::connection_ptr httpConn) {
105 (*self)(input, ec, bytes_transferred, httpConn);
110 string const multipartContentType =
"multipart/form-data";
111 string const plainTextContentType =
"text/plain";
115 if (rawPostAccess == RawPostAccess::ALWAYS) {
116 requestInit.
rawPost = make_shared<string>(postBody);
119 if (postContentType ==
"application/x-www-form-urlencoded") {
120 requestInit.postContentType = postContentType;
122 }
else if (postContentType.substr(0, multipartContentType.length()) == multipartContentType) {
125 for (
auto const& p : postData.parts()) {
127 if (!p.filename().empty() || (!p.contentType().empty() &&
128 p.contentType().substr(0, plainTextContentType.length()) !=
129 plainTextContentType)) {
131 requestInit.postFiles.insert({p.partName(), std::move(pf)});
133 requestInit.postVars.insert({p.partName(), p.content()});
137 }
else if (rawPostAccess == RawPostAccess::NONSTANDARD) {
138 requestInit.rawPost = make_shared<string>(std::move(postBody));
144 connection.flushResponse();
151 void operator()(HttpServer::request
const& request, HttpServer::connection_ptr httpConn) {
152 auto configPtr = requestHandler->
getConfig();
156 {
"REMOTE_ADDR", request.source.substr(0, request.source.find_first_of(
':'))},
157 {
"REQUEST_URI", request.destination},
158 {
"REMOTE_PORT", to_string(request.source_port)},
159 {
"REQUEST_METHOD", request.method},
160 {
"SERVER_ADDR", getListenAddr(configPtr)},
161 {
"SERVER_PORT", getListenPort(configPtr)},
162 {
"SERVER_SOFTWARE",
"NAWA Development Web Server"},
166 for (
auto const& h : request.headers) {
174 stringstream baseUrl;
177 baseUrl <<
"http://" << requestInit.
environment[
"host"];
179 auto baseUrlStr = baseUrl.str();
183 requestInit.
environment[
"FULL_URL_WITH_QS"] = baseUrlStr + request.destination;
186 baseUrl << request.destination.substr(0, request.destination.find_first_of(
'?'));
187 requestInit.
environment[
"FULL_URL_WITHOUT_QS"] = baseUrl.str();
190 if (request.destination.find_first_of(
'?') != string::npos) {
196 connectionInit.
requestInit = std::move(requestInit);
197 connectionInit.
config = (*configPtr);
200 if (!flushInfo.flushedBefore) {
201 httpConn->set_status(HttpServer::connection::status_t(flushInfo.status));
202 httpConn->set_headers(flushInfo.headers);
204 httpConn->write(flushInfo.body);
210 string rawPostStr = (*configPtr)[{
"post",
"raw_access"}];
211 auto rawPostAccess = (rawPostStr ==
"never")
212 ? RawPostAccess::NEVER
213 : ((rawPostStr ==
"always") ? RawPostAccess::ALWAYS
214 : RawPostAccess::NONSTANDARD);
217 ssize_t maxPostSize = stol((*configPtr)[{
"post",
"max_size"}]) * 1024;
219 if (contentLength > maxPostSize) {
220 sendServerError(httpConn);
224 auto inputConsumingHandler = make_shared<InputConsumingHttpHandler>(requestHandler,
225 std::move(connectionInit), maxPostSize,
226 contentLength, rawPostAccess);
227 httpConn->read([inputConsumingHandler](HttpServer::connection::input_range input,
228 boost::system::error_code ec,
size_t bytesTransferred,
229 HttpServer::connection_ptr httpConn) {
230 (*inputConsumingHandler)(input, ec, bytesTransferred, httpConn);
232 }
catch (invalid_argument
const&) {
233 }
catch (out_of_range
const&) {}
239 connection.flushResponse();
243struct HttpRequestHandler::Data {
244 unique_ptr<HttpHandler> handler;
245 unique_ptr<HttpServer> server;
247 vector<thread> threadPool;
248 bool requestHandlingActive =
false;
252HttpRequestHandler::HttpRequestHandler(std::shared_ptr<HandleRequestFunctionWrapper> handleRequestFunction,
255 data = make_unique<Data>();
257 setAppRequestHandler(std::move(handleRequestFunction));
258 setConfig(std::move(config));
259 auto configPtr = getConfig();
261 logger.setAppname(
"HttpRequestHandler");
263 data->handler = make_unique<HttpHandler>();
264 data->handler->requestHandler =
this;
265 HttpServer::options httpServerOptions(*data->handler);
268 string listenAddr = getListenAddr(configPtr);
269 string listenPort = getListenPort(configPtr);
270 bool reuseAddr = (*configPtr)[{
"http",
"reuseaddr"}] !=
"off";
271 data->server = make_unique<HttpServer>(
272 httpServerOptions.address(listenAddr).port(listenPort).reuse_address(reuseAddr));
274 if (concurrency > 0) {
275 data->concurrency = concurrency;
279 data->server->listen();
280 }
catch (exception
const& e) {
282 "Could not listen to host/port.", e.what());
286HttpRequestHandler::~HttpRequestHandler() {
287 if (data->requestHandlingActive && !data->joined) {
288 data->server->stop();
291 for (
auto& t : data->threadPool) {
294 data->threadPool.clear();
298void HttpRequestHandler::start() {
299 if (data->requestHandlingActive) {
303 throw Exception(__PRETTY_FUNCTION__, 10,
"HttpRequestHandler was already joined.");
307 for (
int i = 0; i < data->concurrency; ++i) {
308 data->threadPool.emplace_back([
this] { data->server->run(); });
310 data->requestHandlingActive =
true;
311 }
catch (exception
const& e) {
313 string(
"An error occurred during start of request handling."),
317 throw Exception(__PRETTY_FUNCTION__, 2,
"HTTP handler is not available.");
321void HttpRequestHandler::stop() noexcept {
326 data->server->stop();
330void HttpRequestHandler::terminate() noexcept {
335 data->server->stop();
339void HttpRequestHandler::join() noexcept {
343 for (
auto& t : data->threadPool) {
347 data->threadPool.clear();
Container used by request handlers to initiate the nawa::Connection object.
Response object to be passed back to NAWA and accessor to the request.
Exception class that can be used by apps to catch errors resulting from nawa function calls.
http::server< HttpHandler > HttpServer
A request handler which creates a development web server.
Simple class for (not (yet) thread-safe) logging to stderr or to any other output stream.
#define NLOG_DEBUG(Logger, Message)
#define NLOG_ERROR(Logger, Message)
Parser for MIME multipart, especially in POST form data.
Handles and serves incoming requests via the NAWA app.
std::string & contentType() noexcept
std::shared_ptr< Config const > getConfig() const noexcept
void handleRequest(Connection &connection)
std::string toLowercase(std::string s)
std::string generateErrorPage(unsigned int httpStatus)
std::unordered_multimap< std::string, std::string > splitQueryString(std::string const &queryString)
std::unordered_multimap< std::string, std::string > parseCookies(std::string const &rawCookies)
std::unordered_multimap< std::string, std::string > getVars
std::unordered_map< std::string, std::string > environment
std::unordered_multimap< std::string, std::string > cookieVars
RequestInitContainer requestInit
FlushCallbackFunction flushCallback
std::shared_ptr< std::string > rawPost
Contains useful functions that improve the readability and facilitate maintenance of the NAWA code.