/* * Authored by Alex Hultman, 2018-2019. * Intellectual property of third-party. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef HTTPCONTEXT_H #define HTTPCONTEXT_H /* This class defines the main behavior of HTTP and emits various events */ #include "Loop.h" #include "HttpContextData.h" #include "HttpResponseData.h" #include "AsyncSocket.h" #include "StaticDispatch.h" #include #include namespace uWS { template struct HttpResponse; template struct HttpContext : StaticDispatch { private: using SOCKET_CONTEXT_TYPE = typename StaticDispatch::SOCKET_CONTEXT_TYPE; using SOCKET_TYPE = typename StaticDispatch::SOCKET_TYPE; using StaticDispatch::static_dispatch; HttpContext() = delete; /* Maximum delay allowed until an HTTP connection is terminated due to outstanding request or rejected data (slow loris protection) */ static const int HTTP_IDLE_TIMEOUT_S = 10; SOCKET_CONTEXT_TYPE *getSocketContext() { return (SOCKET_CONTEXT_TYPE *) this; } static SOCKET_CONTEXT_TYPE *getSocketContext(SOCKET_TYPE *s) { return (SOCKET_CONTEXT_TYPE *) static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s); } HttpContextData *getSocketContextData() { return (HttpContextData *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(getSocketContext()); } static HttpContextData *getSocketContextDataS(SOCKET_TYPE *s) { return (HttpContextData *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(getSocketContext(s)); } /* Init the HttpContext by registering libusockets event handlers */ HttpContext *init() { /* Handle socket connections */ static_dispatch(us_ssl_socket_context_on_open, us_socket_context_on_open)(getSocketContext(), [](auto *s, int is_client) { /* Any connected socket should timeout until it has a request */ static_dispatch(us_ssl_socket_timeout, us_socket_timeout)(s, HTTP_IDLE_TIMEOUT_S); /* Init socket ext */ new (static_dispatch(us_ssl_socket_ext, us_socket_ext)(s)) HttpResponseData; /* Call filter */ HttpContextData *httpContextData = getSocketContextDataS(s); for (auto &f : httpContextData->filterHandlers) { f((HttpResponse *) s, 1); } return s; }); /* Handle socket disconnections */ static_dispatch(us_ssl_socket_context_on_close, us_socket_context_on_close)(getSocketContext(), [](auto *s) { /* Get socket ext */ HttpResponseData *httpResponseData = (HttpResponseData *) static_dispatch(us_ssl_socket_ext, us_socket_ext)(s); /* Call filter */ HttpContextData *httpContextData = getSocketContextDataS(s); for (auto &f : httpContextData->filterHandlers) { f((HttpResponse *) s, -1); } /* Signal broken HTTP request only if we have a pending request */ if (httpResponseData->onAborted) { httpResponseData->onAborted(); } /* Destruct socket ext */ httpResponseData->~HttpResponseData(); return s; }); /* Handle HTTP data streams */ static_dispatch(us_ssl_socket_context_on_data, us_socket_context_on_data)(getSocketContext(), [](auto *s, char *data, int length) { // total overhead is about 210k down to 180k // ~210k req/sec is the original perf with write in data // ~200k req/sec is with cork and formatting // ~190k req/sec is with http parsing // ~180k - 190k req/sec is with varying routing HttpContextData *httpContextData = getSocketContextDataS(s); /* Do not accept any data while in shutdown state */ if (static_dispatch(us_ssl_socket_is_shut_down, us_socket_is_shut_down)((SOCKET_TYPE *) s)) { return s; } HttpResponseData *httpResponseData = (HttpResponseData *) static_dispatch(us_ssl_socket_ext, us_socket_ext)(s); /* Cork this socket */ ((AsyncSocket *) s)->cork(); // clients need to know the cursor after http parse, not servers! // how far did we read then? we need to know to continue with websocket parsing data? or? void *returnedSocket = httpResponseData->consumePostPadded(data, length, s, [httpContextData](void *s, uWS::HttpRequest *httpRequest) -> void * { /* For every request we reset the timeout and hang until user makes action */ /* Warning: if we are in shutdown state, resetting the timer is a security issue! */ static_dispatch(us_ssl_socket_timeout, us_socket_timeout)((SOCKET_TYPE *) s, 0); /* Reset httpResponse */ HttpResponseData *httpResponseData = (HttpResponseData *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) s); httpResponseData->offset = 0; httpResponseData->state = 0; /* Are we not ready for another request yet? Terminate the connection. */ if (httpResponseData->state & HttpResponseData::HTTP_RESPONSE_PENDING) { static_dispatch(us_ssl_socket_close, us_socket_close)((SOCKET_TYPE *) s); return nullptr; } /* Mark pending request and emit it */ httpResponseData->state |= HttpResponseData::HTTP_RESPONSE_PENDING; /* Route the method and URL in two passes */ typename HttpContextData::RouterData routerData = {(HttpResponse *) s, httpRequest}; if (!httpContextData->router.route(httpRequest->getMethod(), httpRequest->getUrl(), routerData)) { /* If first pass failed, we try and match by "any" method */ if (!httpContextData->router.route("*", httpRequest->getUrl(), routerData)) { /* If second pass fail, we have to force close this socket as we have no handler for it */ static_dispatch(us_ssl_socket_close, us_socket_close)((SOCKET_TYPE *) s); return nullptr; } } /* First of all we need to check if this socket was deleted due to upgrade */ if (httpContextData->upgradedWebSocket) { /* Reset upgradedWebSocket before we return */ void *tmp = httpContextData->upgradedWebSocket; httpContextData->upgradedWebSocket = nullptr; return tmp; } /* Was the socket closed? */ if (us_socket_is_closed((struct us_socket *) s)) { return nullptr; } /* We absolutely have to terminate parsing if shutdown */ if (static_dispatch(us_ssl_socket_is_shut_down, us_socket_is_shut_down)((SOCKET_TYPE *) s)) { return nullptr; } /* Returning from a request handler without responding or attaching an onAborted handler is ill-use */ if (!((HttpResponse *) s)->hasResponded() && !httpResponseData->onAborted) { /* Throw exception here? */ std::cerr << "Error: Returning from a request handler without responding or attaching an abort handler is forbidden!" << std::endl; std::terminate(); } /* Continue parsing */ return s; }, [httpResponseData](void *user, std::string_view data, bool fin) -> void * { if (httpResponseData->inStream) { httpResponseData->inStream(data, fin); /* Was the socket closed? */ if (us_socket_is_closed((struct us_socket *) user)) { return nullptr; } /* We absolutely have to terminate parsing if shutdown */ if (static_dispatch(us_ssl_socket_is_shut_down, us_socket_is_shut_down)((SOCKET_TYPE *) user)) { return nullptr; } } return user; }, [](void *user) { /* Close any socket on HTTP errors */ static_dispatch(us_ssl_socket_close, us_socket_close)((SOCKET_TYPE *) user); return nullptr; }); // basically we need to uncork in all cases, except for nullptr if (returnedSocket != nullptr) { /* Timeout on uncork failure */ auto [written, failed] = ((AsyncSocket *) returnedSocket)->uncork(); if (failed) { // do we have the same timeout for websockets? ((AsyncSocket *) s)->timeout(HTTP_IDLE_TIMEOUT_S); } return (SOCKET_TYPE *) returnedSocket; } /* We cannot return nullptr to the underlying stack in any case */ return s; }); /* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */ static_dispatch(us_ssl_socket_context_on_writable, us_socket_context_on_writable)(getSocketContext(), [](auto *s) { /* We are now writable, so hang timeout again */ static_dispatch(us_ssl_socket_timeout, us_socket_timeout)(s, 0); AsyncSocket *asyncSocket = (AsyncSocket *) s; HttpResponseData *httpResponseData = (HttpResponseData *) asyncSocket->getExt(); /* Ask the developer to write data and return success (true) or failure (false), OR skip sending anything and return success (true). */ if (httpResponseData->onWritable) { /* We expect the developer to return whether or not write was successful (true). * If write was never called, the developer should still return true so that we may drain. */ bool success = httpResponseData->onWritable(httpResponseData->offset); /* The developer indicated that their onWritable failed. */ if (!success) { /* Skip testing if we can drain anything since that might perform an extra syscall */ return s; } } /* Drain any socket buffer and timeout on failure */ auto [written, failed] = asyncSocket->write(nullptr, 0, true, 0); if (failed) { asyncSocket->timeout(HTTP_IDLE_TIMEOUT_S); } return s; }); /* Handle FIN, HTTP does not support half-closed sockets, so simply close */ static_dispatch(us_ssl_socket_context_on_end, us_socket_context_on_end)(getSocketContext(), [](auto *s) { /* We do not care for half closed sockets */ AsyncSocket *asyncSocket = (AsyncSocket *) s; return asyncSocket->close(); }); /* Handle socket timeouts, simply close them so to not confuse client with FIN */ static_dispatch(us_ssl_socket_context_on_timeout, us_socket_context_on_timeout)(getSocketContext(), [](auto *s) { /* Force close rather than gracefully shutdown and risk confusing the client with a complete download */ AsyncSocket *asyncSocket = (AsyncSocket *) s; return asyncSocket->close(); }); return this; } public: /* Construct a new HttpContext using specified loop */ static HttpContext *create(Loop *loop, us_ssl_socket_context_options *ssl_options = nullptr) { HttpContext *httpContext; if constexpr(SSL) { httpContext = (HttpContext *) us_create_ssl_socket_context((us_loop *) loop, sizeof(HttpContextData), *ssl_options); } else { httpContext = (HttpContext *) us_create_socket_context((us_loop *) loop, sizeof(HttpContextData)); } if (!httpContext) { return nullptr; } /* Init socket context data */ new ((HttpContextData *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)((SOCKET_CONTEXT_TYPE *) httpContext)) HttpContextData(); return httpContext->init(); } /* Destruct the HttpContext, it does not follow RAII */ void free() { /* Destruct socket context data */ HttpContextData *httpContextData = getSocketContextData(); httpContextData->~HttpContextData(); /* Free the socket context in whole */ static_dispatch(us_ssl_socket_context_free, us_socket_context_free)(getSocketContext()); } void filter(fu2::unique_function *, int)> &&filterHandler) { getSocketContextData()->filterHandlers.emplace_back(std::move(filterHandler)); } /* Register an HTTP route handler acording to URL pattern */ void onHttp(std::string method, std::string pattern, std::function *, uWS::HttpRequest *)> handler) { HttpContextData *httpContextData = getSocketContextData(); httpContextData->router.add(method, pattern, [handler](typename HttpContextData::RouterData &user, std::pair params) { user.httpRequest->setYield(false); user.httpRequest->setParameters(params); handler(user.httpResponse, user.httpRequest); /* If any handler yielded, the router will keep looking for a suitable handler. */ if (user.httpRequest->getYield()) { return false; } return true; }); } // this should not be public void upgradeToWebSocket(void *newSocket) { HttpContextData *httpContextData = getSocketContextData(); httpContextData->upgradedWebSocket = newSocket; } /* Listen to port using this HttpContext */ us_listen_socket *listen(const char *host, int port, int options) { return static_dispatch(us_ssl_socket_context_listen, us_socket_context_listen)(getSocketContext(), host, port, options, sizeof(HttpResponseData)); } }; } #endif // HTTPCONTEXT_H