24#include <boost/network/protocol/http/server.hpp>
27#include <nawa/RequestHandler/impl/HttpRequestHandler.h>
29#include <nawa/connection/ConnectionInitContainer.h>
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> {
70 ConnectionInitContainer connectionInit;
74 RawPostAccess rawPostAccess;
76 InputConsumingHttpHandler(
RequestHandler* requestHandler, ConnectionInitContainer connectionInit,
77 ssize_t maxPostSize,
size_t expectedSize, RawPostAccess rawPostAccess)
78 : requestHandler(requestHandler), connectionInit(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";
112 auto postContentType =
utils::toLowercase(connectionInit.requestInit.environment[
"content-type"]);
113 auto& requestInit = connectionInit.requestInit;
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) {
124 MimeMultipart postData(connectionInit.requestInit.environment[
"content-type"], move(postBody));
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(), move(pf)});
133 requestInit.postVars.insert({p.partName(), p.content()});
137 }
else if (rawPostAccess == RawPostAccess::NONSTANDARD) {
138 requestInit.rawPost = make_shared<string>(move(postBody));
144 connection.flushResponse();
151 void operator()(HttpServer::request
const& request, HttpServer::connection_ptr httpConn) {
152 auto configPtr = requestHandler->
getConfig();
154 RequestInitContainer requestInit;
155 requestInit.environment = {
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"},
168 for (
auto const& h : request.headers) {
176 stringstream baseUrl;
179 baseUrl <<
"http://" << requestInit.environment[
"host"];
181 auto baseUrlStr = baseUrl.str();
182 requestInit.environment[
"BASE_URL"] = baseUrlStr;
185 requestInit.environment[
"FULL_URL_WITH_QS"] = baseUrlStr + request.destination;
188 baseUrl << request.destination.substr(0, request.destination.find_first_of(
'?'));
189 requestInit.environment[
"FULL_URL_WITHOUT_QS"] = baseUrl.str();
192 if (request.destination.find_first_of(
'?') != string::npos) {
197 ConnectionInitContainer connectionInit;
198 connectionInit.requestInit = move(requestInit);
199 connectionInit.config = (*configPtr);
201 connectionInit.flushCallback = [httpConn](FlushCallbackContainer flushInfo) {
202 if (!flushInfo.flushedBefore) {
203 httpConn->set_status(HttpServer::connection::status_t(flushInfo.status));
204 httpConn->set_headers(flushInfo.headers);
206 httpConn->write(flushInfo.body);
210 if (request.method ==
"POST" && connectionInit.requestInit.environment.count(
"content-length")) {
212 string rawPostStr = (*configPtr)[{
"post",
"raw_access"}];
213 auto rawPostAccess = (rawPostStr ==
"never")
214 ? RawPostAccess::NEVER
215 : ((rawPostStr ==
"always") ? RawPostAccess::ALWAYS
216 : RawPostAccess::NONSTANDARD);
218 auto contentLength = stoul(connectionInit.requestInit.environment.at(
"content-length"));
219 ssize_t maxPostSize = stol((*configPtr)[{
"post",
"max_size"}]) * 1024;
221 if (contentLength > maxPostSize) {
222 sendServerError(httpConn);
226 auto inputConsumingHandler = make_shared<InputConsumingHttpHandler>(requestHandler,
227 move(connectionInit), maxPostSize,
228 contentLength, rawPostAccess);
229 httpConn->read([inputConsumingHandler](HttpServer::connection::input_range input,
230 boost::system::error_code ec,
size_t bytesTransferred,
231 HttpServer::connection_ptr httpConn) {
232 (*inputConsumingHandler)(input, ec, bytesTransferred, httpConn);
234 }
catch (invalid_argument
const&) {
235 }
catch (out_of_range
const&) {}
241 connection.flushResponse();
245struct HttpRequestHandler::Data {
246 unique_ptr<HttpHandler> handler;
247 unique_ptr<HttpServer> server;
249 vector<thread> threadPool;
250 bool requestHandlingActive =
false;
254HttpRequestHandler::HttpRequestHandler(shared_ptr<HandleRequestFunctionWrapper> handleRequestFunction,
257 data = make_unique<Data>();
259 setAppRequestHandler(move(handleRequestFunction));
260 setConfig(move(config));
261 auto configPtr = getConfig();
263 logger.setAppname(
"HttpRequestHandler");
265 data->handler = make_unique<HttpHandler>();
266 data->handler->requestHandler =
this;
267 HttpServer::options httpServerOptions(*data->handler);
270 string listenAddr = getListenAddr(configPtr);
271 string listenPort = getListenPort(configPtr);
272 bool reuseAddr = (*configPtr)[{
"http",
"reuseaddr"}] !=
"off";
273 data->server = make_unique<HttpServer>(
274 httpServerOptions.address(listenAddr).port(listenPort).reuse_address(reuseAddr));
276 if (concurrency > 0) {
277 data->concurrency = concurrency;
281 data->server->listen();
282 }
catch (exception
const& e) {
284 "Could not listen to host/port.", e.what());
288HttpRequestHandler::~HttpRequestHandler() {
289 if (data->requestHandlingActive && !data->joined) {
290 data->server->stop();
293 for (
auto& t : data->threadPool) {
296 data->threadPool.clear();
300void HttpRequestHandler::start() {
301 if (data->requestHandlingActive) {
305 throw Exception(__PRETTY_FUNCTION__, 10,
"HttpRequestHandler was already joined.");
309 for (
int i = 0; i < data->concurrency; ++i) {
310 data->threadPool.emplace_back([
this] { data->server->run(); });
312 data->requestHandlingActive =
true;
313 }
catch (exception
const& e) {
315 string(
"An error occurred during start of request handling."),
319 throw Exception(__PRETTY_FUNCTION__, 2,
"HTTP handler is not available.");
323void HttpRequestHandler::stop() noexcept {
328 data->server->stop();
332void HttpRequestHandler::terminate() noexcept {
338 data->server->stop();
342void HttpRequestHandler::join() noexcept {
346 for (
auto& t : data->threadPool) {
350 data->threadPool.clear();
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
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::unordered_multimap< std::string, std::string > splitQueryString(std::string const &queryString)
std::unordered_multimap< std::string, std::string > parseCookies(std::string const &rawCookies)
std::string generateErrorPage(unsigned int httpStatus)
std::string toLowercase(std::string s)
Contains useful functions that improve the readability and facilitate maintenance of the NAWA code.