From 6ec26c46d694a1d5291790a9c70bec25dce4b513 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Thu, 9 Sep 2021 15:14:08 +0200 Subject: Factored out http server related code into zenhttp module since it feels out of place in zencore --- zenhttp/httpsys.cpp | 1250 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1250 insertions(+) create mode 100644 zenhttp/httpsys.cpp (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp new file mode 100644 index 000000000..da07a13dd --- /dev/null +++ b/zenhttp/httpsys.cpp @@ -0,0 +1,1250 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "httpsys.h" + +#include +#include + +#include + +#if ZEN_PLATFORM_WINDOWS +# pragma comment(lib, "httpapi.lib") +#endif + +std::wstring +UTF8_to_wstring(const char* in) +{ + std::wstring out; + unsigned int codepoint; + + while (*in != 0) + { + unsigned char ch = static_cast(*in); + + if (ch <= 0x7f) + codepoint = ch; + else if (ch <= 0xbf) + codepoint = (codepoint << 6) | (ch & 0x3f); + else if (ch <= 0xdf) + codepoint = ch & 0x1f; + else if (ch <= 0xef) + codepoint = ch & 0x0f; + else + codepoint = ch & 0x07; + + ++in; + + if (((*in & 0xc0) != 0x80) && (codepoint <= 0x10ffff)) + { + if (sizeof(wchar_t) > 2) + { + out.append(1, static_cast(codepoint)); + } + else if (codepoint > 0xffff) + { + out.append(1, static_cast(0xd800 + (codepoint >> 10))); + out.append(1, static_cast(0xdc00 + (codepoint & 0x03ff))); + } + else if (codepoint < 0xd800 || codepoint >= 0xe000) + { + out.append(1, static_cast(codepoint)); + } + } + } + + return out; +} + +////////////////////////////////////////////////////////////////////////// +// +// http.sys implementation +// + +namespace zen { + +using namespace std::literals; + +static const uint32_t HashBinary = HashStringDjb2("application/octet-stream"sv); +static const uint32_t HashJson = HashStringDjb2("application/json"sv); +static const uint32_t HashYaml = HashStringDjb2("text/yaml"sv); +static const uint32_t HashText = HashStringDjb2("text/plain"sv); +static const uint32_t HashCompactBinary = HashStringDjb2("application/x-ue-cb"sv); +static const uint32_t HashCompactBinaryPackage = HashStringDjb2("application/x-ue-cbpkg"sv); + +HttpContentType +MapContentType(const std::string_view& ContentTypeString) +{ + if (!ContentTypeString.empty()) + { + const uint32_t CtHash = HashStringDjb2(ContentTypeString); + + if (CtHash == HashBinary) + { + return HttpContentType::kBinary; + } + else if (CtHash == HashCompactBinary) + { + return HttpContentType::kCbObject; + } + else if (CtHash == HashCompactBinaryPackage) + { + return HttpContentType::kCbPackage; + } + else if (CtHash == HashJson) + { + return HttpContentType::kJSON; + } + else if (CtHash == HashYaml) + { + return HttpContentType::kYAML; + } + else if (CtHash == HashText) + { + return HttpContentType::kText; + } + } + + return HttpContentType::kUnknownContentType; +} + +////////////////////////////////////////////////////////////////////////// + +const char* +ReasonStringForHttpResultCode(int HttpCode) +{ + switch (HttpCode) + { + // 1xx Informational + + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + + // 2xx Success + + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + + // 3xx Redirection + + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 306: + return "Switch Proxy"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + + // 4xx Client errors + + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Payload Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisifiable"; + case 417: + return "Expectation Failed"; + case 418: + return "I'm a teapot"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Entity"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 425: + return "Too Early"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + + // 5xx Server errors + + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 510: + return "Not Extended"; + case 511: + return "Network Authentication Required"; + + default: + return "Unknown Result"; + } +} + +#if ZEN_PLATFORM_WINDOWS +class HttpSysServer; +class HttpSysTransaction; +class HttpMessageResponseRequest; + +class HttpSysRequestHandler +{ +public: + HttpSysRequestHandler(HttpSysTransaction& InRequest) : m_Request(InRequest) {} + virtual ~HttpSysRequestHandler() = default; + + virtual void IssueRequest() = 0; + virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) = 0; + + HttpSysTransaction& Transaction() { return m_Request; } + +private: + HttpSysTransaction& m_Request; // Outermost HTTP transaction object +}; + +struct InitialRequestHandler : public HttpSysRequestHandler +{ + inline PHTTP_REQUEST HttpRequest() { return (PHTTP_REQUEST)m_RequestBuffer; } + inline uint32_t RequestBufferSize() const { return sizeof m_RequestBuffer; } + + InitialRequestHandler(HttpSysTransaction& InRequest) : HttpSysRequestHandler(InRequest) {} + ~InitialRequestHandler() {} + + virtual void IssueRequest() override; + virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; + + PHTTP_REQUEST m_HttpRequestPtr = (HTTP_REQUEST*)(m_RequestBuffer); + UCHAR m_RequestBuffer[16384 + sizeof(HTTP_REQUEST)]; +}; + +class HttpSysServerRequest : public HttpServerRequest +{ +public: + HttpSysServerRequest() = default; + HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& Service); + ~HttpSysServerRequest() = default; + + virtual void ReadPayload(std::function&& CompletionHandler) override; + virtual IoBuffer ReadPayload() override; + virtual void WriteResponse(HttpResponse HttpResponseCode) override; + virtual void WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::span Blobs) override; + virtual void WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) override; + + bool m_IsInitialized = false; + HttpSysTransaction& m_HttpTx; + HttpMessageResponseRequest* m_Response = nullptr; // TODO: make this more general +}; + +/** HTTP transaction + + There will be an instance of this per pending and in-flight HTTP transaction + + */ +class HttpSysTransaction final +{ +public: + HttpSysTransaction(HttpSysServer& Server) : m_HttpServer(Server), m_HttpHandler(&m_InitialHttpHandler) {} + + virtual ~HttpSysTransaction() {} + + enum class Status + { + kDone, + kRequestPending + }; + + Status HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred); + + static void __stdcall IoCompletionCallback(PTP_CALLBACK_INSTANCE Instance, + PVOID pContext /* HttpSysServer */, + PVOID pOverlapped, + ULONG IoResult, + ULONG_PTR NumberOfBytesTransferred, + PTP_IO Io); + + void IssueInitialRequest(); + PTP_IO Iocp(); + HANDLE RequestQueueHandle(); + inline OVERLAPPED* Overlapped() { return &m_HttpOverlapped; } + inline HttpSysServer& Server() { return m_HttpServer; } + + inline PHTTP_REQUEST HttpRequest() { return m_InitialHttpHandler.HttpRequest(); } + +private: + OVERLAPPED m_HttpOverlapped{}; + HttpSysServer& m_HttpServer; + HttpSysRequestHandler* m_HttpHandler{nullptr}; // Tracks which handler is due to handle the next I/O completion event + RwLock m_CompletionMutex; + InitialRequestHandler m_InitialHttpHandler{*this}; +}; + +////////////////////////////////////////////////////////////////////////// + +class HttpPayloadReadRequest : public HttpSysRequestHandler +{ +public: + HttpPayloadReadRequest(HttpSysTransaction& InRequest) : HttpSysRequestHandler(InRequest) {} + + virtual void IssueRequest() override; + virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; +}; + +void +HttpPayloadReadRequest::IssueRequest() +{ +} + +HttpSysRequestHandler* +HttpPayloadReadRequest::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) +{ + ZEN_UNUSED(IoResult, NumberOfBytesTransferred); + return nullptr; +} + +////////////////////////////////////////////////////////////////////////// + +class HttpMessageResponseRequest : public HttpSysRequestHandler +{ +public: + HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode); + HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, const char* Message); + HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, const void* Payload, size_t PayloadSize); + HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, std::span Blobs); + ~HttpMessageResponseRequest(); + + virtual void IssueRequest() override; + virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; + + void SuppressResponseBody(); + +private: + std::vector m_HttpDataChunks; + uint64_t m_TotalDataSize = 0; // Sum of all chunk sizes + + uint16_t m_HttpResponseCode = 0; + uint32_t m_NextDataChunkOffset = 0; // This is used for responses where the number of chunks exceed the maximum number for one API call + uint32_t m_RemainingChunkCount = 0; + bool m_IsInitialResponse = true; + + void Initialize(uint16_t ResponseCode, std::span Blobs); + + std::vector m_DataBuffers; +}; + +HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode) +: HttpSysRequestHandler(InRequest) +{ + std::array buffers; + + Initialize(ResponseCode, buffers); +} + +HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, const char* Message) +: HttpSysRequestHandler(InRequest) +{ + IoBuffer MessageBuffer(IoBuffer::Wrap, Message, strlen(Message)); + std::array buffers({MessageBuffer}); + + Initialize(ResponseCode, buffers); +} + +HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, + uint16_t ResponseCode, + const void* Payload, + size_t PayloadSize) +: HttpSysRequestHandler(InRequest) +{ + IoBuffer MessageBuffer(IoBuffer::Wrap, Payload, PayloadSize); + std::array buffers({MessageBuffer}); + + Initialize(ResponseCode, buffers); +} + +HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, std::span Blobs) +: HttpSysRequestHandler(InRequest) +{ + Initialize(ResponseCode, Blobs); +} + +HttpMessageResponseRequest::~HttpMessageResponseRequest() +{ +} + +void +HttpMessageResponseRequest::Initialize(uint16_t ResponseCode, std::span Blobs) +{ + m_HttpResponseCode = ResponseCode; + + const uint32_t ChunkCount = (uint32_t)Blobs.size(); + + m_HttpDataChunks.resize(ChunkCount); + m_DataBuffers.reserve(ChunkCount); + + for (IoBuffer& Buffer : Blobs) + { + m_DataBuffers.emplace_back(std::move(Buffer)).MakeOwned(); + } + + // Initialize the full array up front + + uint64_t LocalDataSize = 0; + + { + PHTTP_DATA_CHUNK ChunkPtr = m_HttpDataChunks.data(); + + for (IoBuffer& Buffer : m_DataBuffers) + { + const ULONG BufferDataSize = (ULONG)Buffer.Size(); + + ZEN_ASSERT(BufferDataSize); + + IoBufferFileReference FileRef; + if (Buffer.GetFileReference(/* out */ FileRef)) + { + ChunkPtr->DataChunkType = HttpDataChunkFromFileHandle; + ChunkPtr->FromFileHandle.FileHandle = FileRef.FileHandle; + ChunkPtr->FromFileHandle.ByteRange.StartingOffset.QuadPart = FileRef.FileChunkOffset; + ChunkPtr->FromFileHandle.ByteRange.Length.QuadPart = BufferDataSize; + } + else + { + ChunkPtr->DataChunkType = HttpDataChunkFromMemory; + ChunkPtr->FromMemory.pBuffer = (void*)Buffer.Data(); + ChunkPtr->FromMemory.BufferLength = BufferDataSize; + } + ++ChunkPtr; + + LocalDataSize += BufferDataSize; + } + } + + m_RemainingChunkCount = ChunkCount; + m_TotalDataSize = LocalDataSize; +} + +void +HttpMessageResponseRequest::SuppressResponseBody() +{ + m_RemainingChunkCount = 0; + m_HttpDataChunks.clear(); + m_DataBuffers.clear(); +} + +HttpSysRequestHandler* +HttpMessageResponseRequest::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) +{ + ZEN_UNUSED(NumberOfBytesTransferred); + ZEN_UNUSED(IoResult); + + if (m_RemainingChunkCount == 0) + { + return nullptr; // All done + } + + return this; +} + +void +HttpMessageResponseRequest::IssueRequest() +{ + HttpSysTransaction& Tx = Transaction(); + HTTP_REQUEST* const HttpReq = Tx.HttpRequest(); + PTP_IO const Iocp = Tx.Iocp(); + + StartThreadpoolIo(Iocp); + + // Split payload into batches to play well with the underlying API + + const int MaxChunksPerCall = 9999; + + const int ThisRequestChunkCount = std::min(m_RemainingChunkCount, MaxChunksPerCall); + const int ThisRequestChunkOffset = m_NextDataChunkOffset; + + m_RemainingChunkCount -= ThisRequestChunkCount; + m_NextDataChunkOffset += ThisRequestChunkCount; + + ULONG SendFlags = 0; + + if (m_RemainingChunkCount) + { + // We need to make more calls to send the full amount of data + SendFlags |= HTTP_SEND_RESPONSE_FLAG_MORE_DATA; + } + + ULONG SendResult = 0; + + if (m_IsInitialResponse) + { + // Populate response structure + + HTTP_RESPONSE HttpResponse = {}; + + HttpResponse.EntityChunkCount = USHORT(ThisRequestChunkCount); + HttpResponse.pEntityChunks = m_HttpDataChunks.data() + ThisRequestChunkOffset; + + // Content-length header + + char ContentLengthString[32]; + _ui64toa_s(m_TotalDataSize, ContentLengthString, sizeof ContentLengthString, 10); + + PHTTP_KNOWN_HEADER ContentLengthHeader = &HttpResponse.Headers.KnownHeaders[HttpHeaderContentLength]; + ContentLengthHeader->pRawValue = ContentLengthString; + ContentLengthHeader->RawValueLength = (USHORT)strlen(ContentLengthString); + + // Content-type header + + PHTTP_KNOWN_HEADER ContentTypeHeader = &HttpResponse.Headers.KnownHeaders[HttpHeaderContentType]; + + ContentTypeHeader->pRawValue = "application/octet-stream"; /* TODO! We must respect the content type specified */ + ContentTypeHeader->RawValueLength = (USHORT)strlen(ContentTypeHeader->pRawValue); + + HttpResponse.StatusCode = m_HttpResponseCode; + HttpResponse.pReason = ReasonStringForHttpResultCode(m_HttpResponseCode); + HttpResponse.ReasonLength = (USHORT)strlen(HttpResponse.pReason); + + // Cache policy + + HTTP_CACHE_POLICY CachePolicy; + + CachePolicy.Policy = HttpCachePolicyNocache; // HttpCachePolicyUserInvalidates; + CachePolicy.SecondsToLive = 0; + + // Initial response API call + + SendResult = HttpSendHttpResponse(Tx.RequestQueueHandle(), + HttpReq->RequestId, + SendFlags, + &HttpResponse, + &CachePolicy, + NULL, + NULL, + 0, + Tx.Overlapped(), + NULL); + + m_IsInitialResponse = false; + } + else + { + // Subsequent response API calls + + SendResult = HttpSendResponseEntityBody(Tx.RequestQueueHandle(), + HttpReq->RequestId, + SendFlags, + (USHORT)ThisRequestChunkCount, // EntityChunkCount + &m_HttpDataChunks[ThisRequestChunkOffset], // EntityChunks + NULL, // BytesSent + NULL, // Reserved1 + 0, // Reserved2 + Tx.Overlapped(), // Overlapped + NULL // LogData + ); + } + + if ((SendResult != NO_ERROR) // Synchronous completion, but the completion event will still be posted to IOCP + && (SendResult != ERROR_IO_PENDING) // Asynchronous completion + ) + { + // Some error occurred, no completion will be posted + + CancelThreadpoolIo(Iocp); + + spdlog::error("failed to send HTTP response (error: {}) URL: {}", SendResult, HttpReq->pRawUrl); + + throw HttpServerException("Failed to send HTTP response", SendResult); + } +} + +////////////////////////////////////////////////////////////////////////// + +HttpSysServer::HttpSysServer(int ThreadCount) : m_ThreadPool(ThreadCount) +{ + ULONG Result = HttpInitialize(HTTPAPI_VERSION_2, HTTP_INITIALIZE_SERVER, nullptr); + + if (Result != NO_ERROR) + { + return; + } + + m_IsHttpInitialized = true; + m_IsOk = true; +} + +HttpSysServer::~HttpSysServer() +{ + if (m_IsHttpInitialized) + { + HttpTerminate(HTTP_INITIALIZE_SERVER, nullptr); + } +} + +void +HttpSysServer::Initialize(const wchar_t* UrlPath) +{ + // check(bIsOk); + + ULONG Result = HttpCreateServerSession(HTTPAPI_VERSION_2, &m_HttpSessionId, 0); + + if (Result != NO_ERROR) + { + // Flag error + + return; + } + + Result = HttpCreateUrlGroup(m_HttpSessionId, &m_HttpUrlGroupId, 0); + + if (Result != NO_ERROR) + { + // Flag error + + return; + } + + m_BaseUri = UrlPath; + + Result = HttpAddUrlToUrlGroup(m_HttpUrlGroupId, UrlPath, /* #TODO UrlContext */ HTTP_URL_CONTEXT(0), 0); + + if (Result != NO_ERROR) + { + // Flag error + + return; + } + + HTTP_BINDING_INFO HttpBindingInfo = {{0}, 0}; + + Result = HttpCreateRequestQueue(HTTPAPI_VERSION_2, NULL, NULL, 0, &m_RequestQueueHandle); + + if (Result != NO_ERROR) + { + // Flag error! + + return; + } + + HttpBindingInfo.Flags.Present = 1; + HttpBindingInfo.RequestQueueHandle = m_RequestQueueHandle; + + Result = HttpSetUrlGroupProperty(m_HttpUrlGroupId, HttpServerBindingProperty, &HttpBindingInfo, sizeof(HttpBindingInfo)); + + if (Result != NO_ERROR) + { + // Flag error! + + return; + } + + // Create I/O completion port + + m_ThreadPool.CreateIocp(m_RequestQueueHandle, HttpSysTransaction::IoCompletionCallback, this); + + // Check result! +} + +void +HttpSysServer::StartServer() +{ + int RequestCount = 32; + + for (int i = 0; i < RequestCount; ++i) + { + IssueNewRequestMaybe(); + } +} + +void +HttpSysServer::Run(bool TestMode) +{ + if (TestMode == false) + { + zen::logging::ConsoleLog().info("Zen Server running. Press ESC or Q to quit"); + } + + do + { + int WaitTimeout = -1; + + if (!TestMode) + { + WaitTimeout = 1000; + } + + if (!TestMode && _kbhit() != 0) + { + char c = (char)_getch(); + + if (c == 27 || c == 'Q' || c == 'q') + { + RequestApplicationExit(0); + } + } + + m_ShutdownEvent.Wait(WaitTimeout); + } while (!IsApplicationExitRequested()); +} + +void +HttpSysServer::OnHandlingRequest() +{ + --m_PendingRequests; + + if (m_PendingRequests > m_MinPendingRequests) + { + // We have more than the minimum number of requests pending, just let someone else + // enqueue new requests + return; + } + + IssueNewRequestMaybe(); +} + +void +HttpSysServer::IssueNewRequestMaybe() +{ + if (m_PendingRequests.load(std::memory_order::relaxed) >= m_MaxPendingRequests) + { + return; + } + + std::unique_ptr Request = std::make_unique(*this); + + Request->IssueInitialRequest(); + + // This may end up exceeding the MaxPendingRequests limit, but it's not + // really a problem. I'm doing it this way mostly to avoid dealing with + // exceptions here + ++m_PendingRequests; + + Request.release(); +} + +void +HttpSysServer::RegisterService(const char* UrlPath, HttpService& Service) +{ + if (UrlPath[0] == '/') + { + ++UrlPath; + } + + const std::wstring Path16 = UTF8_to_wstring(UrlPath); + Service.SetUriPrefixLength(Path16.size() + 1 /* leading slash */); + + // Convert to wide string + + std::wstring Url16 = m_BaseUri + Path16; + + ULONG Result = HttpAddUrlToUrlGroup(m_HttpUrlGroupId, Url16.c_str(), HTTP_URL_CONTEXT(&Service), 0 /* Reserved */); + + if (Result != NO_ERROR) + { + spdlog::error("HttpAddUrlToUrlGroup failed with result {}", Result); + + return; + } +} + +void +HttpSysServer::RemoveEndpoint(const char* UrlPath, HttpService& Service) +{ + ZEN_UNUSED(Service); + + if (UrlPath[0] == '/') + { + ++UrlPath; + } + + const std::wstring Path16 = UTF8_to_wstring(UrlPath); + + // Convert to wide string + + std::wstring Url16 = m_BaseUri + Path16; + + ULONG Result = HttpRemoveUrlFromUrlGroup(m_HttpUrlGroupId, Url16.c_str(), 0); + + if (Result != NO_ERROR) + { + spdlog::error("HttpRemoveUrlFromUrlGroup failed with result {}", Result); + } +} + +////////////////////////////////////////////////////////////////////////// + +HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& Service) : m_IsInitialized(true), m_HttpTx(Tx) +{ + PHTTP_REQUEST HttpRequestPtr = Tx.HttpRequest(); + + const int PrefixLength = Service.UriPrefixLength(); + const int AbsPathLength = HttpRequestPtr->CookedUrl.AbsPathLength / sizeof(char16_t); + + if (AbsPathLength >= PrefixLength) + { + // We convert the URI immediately because most of the code involved prefers to deal + // with utf8. This has some performance impact which I'd prefer to avoid but for now + // we just have to live with it + + WideToUtf8({(char16_t*)HttpRequestPtr->CookedUrl.pAbsPath + PrefixLength, gsl::narrow(AbsPathLength - PrefixLength)}, + m_Uri); + } + else + { + m_Uri.Reset(); + } + + if (auto QueryStringLength = HttpRequestPtr->CookedUrl.QueryStringLength) + { + --QueryStringLength; + + WideToUtf8({(char16_t*)(HttpRequestPtr->CookedUrl.pQueryString) + 1, QueryStringLength / sizeof(char16_t)}, m_QueryString); + } + else + { + m_QueryString.Reset(); + } + + switch (HttpRequestPtr->Verb) + { + case HttpVerbOPTIONS: + m_Verb = HttpVerb::kOptions; + break; + + case HttpVerbGET: + m_Verb = HttpVerb::kGet; + break; + + case HttpVerbHEAD: + m_Verb = HttpVerb::kHead; + break; + + case HttpVerbPOST: + m_Verb = HttpVerb::kPost; + break; + + case HttpVerbPUT: + m_Verb = HttpVerb::kPut; + break; + + case HttpVerbDELETE: + m_Verb = HttpVerb::kDelete; + break; + + case HttpVerbCOPY: + m_Verb = HttpVerb::kCopy; + break; + + default: + // TODO: invalid request? + m_Verb = (HttpVerb)0; + break; + } + + const HTTP_KNOWN_HEADER& clh = HttpRequestPtr->Headers.KnownHeaders[HttpHeaderContentLength]; + std::string_view cl(clh.pRawValue, clh.RawValueLength); + std::from_chars(cl.data(), cl.data() + cl.size(), m_ContentLength); + + const HTTP_KNOWN_HEADER& CtHdr = HttpRequestPtr->Headers.KnownHeaders[HttpHeaderContentType]; + m_ContentType = MapContentType({CtHdr.pRawValue, CtHdr.RawValueLength}); +} + +void +HttpSysServerRequest::ReadPayload(std::function&& CompletionHandler) +{ + ZEN_UNUSED(CompletionHandler); +} + +IoBuffer +HttpSysServerRequest::ReadPayload() +{ + // This is presently synchronous for simplicity, but we + // need to implement an asynchronous version also + + HTTP_REQUEST* const HttpReq = m_HttpTx.HttpRequest(); + + IoBuffer PayloadBuffer(m_ContentLength); + + HttpContentType ContentType = RequestContentType(); + PayloadBuffer.SetContentType(ContentType); + + uint64_t BytesToRead = m_ContentLength; + + uint8_t* ReadPointer = reinterpret_cast(PayloadBuffer.MutableData()); + + // First deal with any payload which has already been copied + // into our request buffer + + const int EntityChunkCount = HttpReq->EntityChunkCount; + + for (int i = 0; i < EntityChunkCount; ++i) + { + HTTP_DATA_CHUNK& EntityChunk = HttpReq->pEntityChunks[i]; + + ZEN_ASSERT(EntityChunk.DataChunkType == HttpDataChunkFromMemory); + + const uint64_t BufferLength = EntityChunk.FromMemory.BufferLength; + + ZEN_ASSERT(BufferLength <= BytesToRead); + + memcpy(ReadPointer, EntityChunk.FromMemory.pBuffer, BufferLength); + + ReadPointer += BufferLength; + BytesToRead -= BufferLength; + } + + if (BytesToRead == 0) + { + PayloadBuffer.MakeImmutable(); + + return PayloadBuffer; + } + + // Call http.sys API to receive the remaining data SYNCHRONOUSLY + + static const uint64_t kMaxBytesPerApiCall = 1 * 1024 * 1024; + + while (BytesToRead) + { + ULONG BytesRead = 0; + + const uint64_t BytesToReadThisCall = zen::Min(BytesToRead, kMaxBytesPerApiCall); + + ULONG ApiResult = HttpReceiveRequestEntityBody(m_HttpTx.RequestQueueHandle(), + HttpReq->RequestId, + 0, /* Flags */ + ReadPointer, + gsl::narrow(BytesToReadThisCall), + &BytesRead, + NULL /* Overlapped */ + ); + + if (ApiResult != NO_ERROR && ApiResult != ERROR_HANDLE_EOF) + { + throw HttpServerException("payload read failed", ApiResult); + } + + BytesToRead -= BytesRead; + ReadPointer += BytesRead; + } + + PayloadBuffer.MakeImmutable(); + + return PayloadBuffer; +} + +void +HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode) +{ + ZEN_ASSERT(m_IsHandled == false); + + m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode); + + if (m_SuppressBody) + { + m_Response->SuppressResponseBody(); + } + + m_IsHandled = true; +} + +void +HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::span Blobs) +{ + ZEN_ASSERT(m_IsHandled == false); + ZEN_UNUSED(ContentType); + + m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode, Blobs); + + if (m_SuppressBody) + { + m_Response->SuppressResponseBody(); + } + + m_IsHandled = true; +} + +void +HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) +{ + ZEN_ASSERT(m_IsHandled == false); + ZEN_UNUSED(ContentType); + + m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode, ResponseString.data(), ResponseString.size()); + + if (m_SuppressBody) + { + m_Response->SuppressResponseBody(); + } + + m_IsHandled = true; +} + +////////////////////////////////////////////////////////////////////////// + +PTP_IO +HttpSysTransaction::Iocp() +{ + return m_HttpServer.m_ThreadPool.Iocp(); +} + +HANDLE +HttpSysTransaction::RequestQueueHandle() +{ + return m_HttpServer.m_RequestQueueHandle; +} + +void +HttpSysTransaction::IssueInitialRequest() +{ + m_InitialHttpHandler.IssueRequest(); +} + +void +HttpSysTransaction::IoCompletionCallback(PTP_CALLBACK_INSTANCE Instance, + PVOID pContext /* HttpSysServer */, + PVOID pOverlapped, + ULONG IoResult, + ULONG_PTR NumberOfBytesTransferred, + PTP_IO Io) +{ + UNREFERENCED_PARAMETER(Io); + UNREFERENCED_PARAMETER(Instance); + UNREFERENCED_PARAMETER(pContext); + + // Note that for a given transaction we may be in this completion function on more + // than one thread at any given moment. This means we need to be careful about what + // happens in here + + HttpSysTransaction* Transaction = CONTAINING_RECORD(pOverlapped, HttpSysTransaction, m_HttpOverlapped); + + if (Transaction->HandleCompletion(IoResult, NumberOfBytesTransferred) == HttpSysTransaction::Status::kDone) + { + delete Transaction; + } +} + +HttpSysTransaction::Status +HttpSysTransaction::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) +{ + // We use this to ensure sequential execution of completion handlers + // for any given transaction. + RwLock::ExclusiveLockScope _(m_CompletionMutex); + + bool RequestPending = false; + + if (HttpSysRequestHandler* CurrentHandler = m_HttpHandler) + { + const bool IsInitialRequest = (CurrentHandler == &m_InitialHttpHandler); + + if (IsInitialRequest) + { + // Ensure we have a sufficient number of pending requests outstanding + m_HttpServer.OnHandlingRequest(); + } + + m_HttpHandler = CurrentHandler->HandleCompletion(IoResult, NumberOfBytesTransferred); + + if (m_HttpHandler) + { + try + { + m_HttpHandler->IssueRequest(); + + RequestPending = true; + } + catch (std::exception& Ex) + { + spdlog::error("exception caught from IssueRequest(): {}", Ex.what()); + + // something went wrong, no request is pending + } + } + else + { + if (IsInitialRequest == false) + { + delete CurrentHandler; + } + } + } + + // Ensure new requests are enqueued + m_HttpServer.IssueNewRequestMaybe(); + + if (RequestPending) + { + return Status::kRequestPending; + } + + return Status::kDone; +} + +////////////////////////////////////////////////////////////////////////// + +void +InitialRequestHandler::IssueRequest() +{ + PTP_IO Iocp = Transaction().Iocp(); + + StartThreadpoolIo(Iocp); + + HttpSysTransaction& Tx = Transaction(); + + HTTP_REQUEST* HttpReq = Tx.HttpRequest(); + + ULONG Result = HttpReceiveHttpRequest(Tx.RequestQueueHandle(), + HTTP_NULL_ID, + HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, + HttpReq, + RequestBufferSize(), + NULL, + Tx.Overlapped()); + + if (Result != ERROR_IO_PENDING && Result != NO_ERROR) + { + CancelThreadpoolIo(Iocp); + + if (Result == ERROR_MORE_DATA) + { + // ProcessReceiveAndPostResponse(pIoRequest, pServerContext->Io, ERROR_MORE_DATA); + } + + // CleanupHttpIoRequest(pIoRequest); + + spdlog::error("HttpReceiveHttpRequest failed, error {:x}", Result); + + return; + } +} + +HttpSysRequestHandler* +InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) +{ + ZEN_UNUSED(IoResult); + ZEN_UNUSED(NumberOfBytesTransferred); + + // Route requests + + try + { + if (HttpService* Service = reinterpret_cast(m_HttpRequestPtr->UrlContext)) + { + HttpSysServerRequest ThisRequest(Transaction(), *Service); + + Service->HandleRequest(ThisRequest); + + if (!ThisRequest.IsHandled()) + { + return new HttpMessageResponseRequest(Transaction(), 404, "Not found"); + } + + if (ThisRequest.m_Response) + { + return ThisRequest.m_Response; + } + } + + // Unable to route + return new HttpMessageResponseRequest(Transaction(), 404, "Item unknown"); + } + catch (std::exception& ex) + { + // TODO provide more meaningful error output + + return new HttpMessageResponseRequest(Transaction(), 500, ex.what()); + } +} + +////////////////////////////////////////////////////////////////////////// +// +// HttpServer interface implementation +// + +void +HttpSysServer::Initialize(int BasePort) +{ + using namespace std::literals; + + WideStringBuilder<64> BaseUri; + BaseUri << u8"http://*:"sv << int64_t(BasePort) << u8"/"sv; + + Initialize(BaseUri.c_str()); + StartServer(); +} + +void +HttpSysServer::RequestExit() +{ + m_ShutdownEvent.Set(); +} +void +HttpSysServer::RegisterService(HttpService& Service) +{ + RegisterService(Service.BaseUri(), Service); +} + +#endif // ZEN_PLATFORM_WINDOWS + +} // namespace zen -- cgit v1.2.3 From a73862b954209fd1adce0f07b8f80b8cf27f2486 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Thu, 9 Sep 2021 16:29:24 +0200 Subject: Added compile time logic to toggle http.sys / null http implementation on/off --- zenhttp/httpsys.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index da07a13dd..2041be5c3 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -5,11 +5,10 @@ #include #include -#include +#if ZEN_WITH_HTTPSYS -#if ZEN_PLATFORM_WINDOWS -# pragma comment(lib, "httpapi.lib") -#endif +#include +#pragma comment(lib, "httpapi.lib") std::wstring UTF8_to_wstring(const char* in) @@ -246,7 +245,6 @@ ReasonStringForHttpResultCode(int HttpCode) } } -#if ZEN_PLATFORM_WINDOWS class HttpSysServer; class HttpSysTransaction; class HttpMessageResponseRequest; @@ -848,22 +846,22 @@ HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& // we just have to live with it WideToUtf8({(char16_t*)HttpRequestPtr->CookedUrl.pAbsPath + PrefixLength, gsl::narrow(AbsPathLength - PrefixLength)}, - m_Uri); + m_UriUtf8); } else { - m_Uri.Reset(); + m_UriUtf8.Reset(); } if (auto QueryStringLength = HttpRequestPtr->CookedUrl.QueryStringLength) { --QueryStringLength; - WideToUtf8({(char16_t*)(HttpRequestPtr->CookedUrl.pQueryString) + 1, QueryStringLength / sizeof(char16_t)}, m_QueryString); + WideToUtf8({(char16_t*)(HttpRequestPtr->CookedUrl.pQueryString) + 1, QueryStringLength / sizeof(char16_t)}, m_QueryStringUtf8); } else { - m_QueryString.Reset(); + m_QueryStringUtf8.Reset(); } switch (HttpRequestPtr->Verb) @@ -1245,6 +1243,5 @@ HttpSysServer::RegisterService(HttpService& Service) RegisterService(Service.BaseUri(), Service); } -#endif // ZEN_PLATFORM_WINDOWS - } // namespace zen +#endif \ No newline at end of file -- cgit v1.2.3 From c2d6ad4962a7d1a81c25ab3d7bf83cff8647b49a Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Thu, 9 Sep 2021 20:42:30 +0200 Subject: Parse Accept mime type (ad hoc cherry pick from main) --- zenhttp/httpsys.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index 2041be5c3..be538e7f7 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -906,6 +906,9 @@ HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& const HTTP_KNOWN_HEADER& CtHdr = HttpRequestPtr->Headers.KnownHeaders[HttpHeaderContentType]; m_ContentType = MapContentType({CtHdr.pRawValue, CtHdr.RawValueLength}); + + const HTTP_KNOWN_HEADER& AcceptHdr = HttpRequestPtr->Headers.KnownHeaders[HttpHeaderAccept]; + m_AcceptType = MapContentType({AcceptHdr.pRawValue, AcceptHdr.RawValueLength}); } void -- cgit v1.2.3 From 46aa14744ba72873975d288b568fa3b15d196a78 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Thu, 9 Sep 2021 20:46:22 +0200 Subject: clang-format --- zenhttp/httpsys.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index be538e7f7..471a8f80a 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -7,8 +7,8 @@ #if ZEN_WITH_HTTPSYS -#include -#pragma comment(lib, "httpapi.lib") +# include +# pragma comment(lib, "httpapi.lib") std::wstring UTF8_to_wstring(const char* in) @@ -1219,12 +1219,12 @@ InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesT } ////////////////////////////////////////////////////////////////////////// -// +// // HttpServer interface implementation // void -HttpSysServer::Initialize(int BasePort) +HttpSysServer::Initialize(int BasePort) { using namespace std::literals; -- cgit v1.2.3 From 49d09922716b216896fe60a92b1a126c9ba8c302 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Fri, 10 Sep 2021 18:55:30 +0200 Subject: Refactored HTTP request handling to scale better The new logic simply reads the whole payload up front before dispatching to the endpoint handler. This increases concurrency as fewer threads will be blocked waiting for payloads Similar logic will be added for compact binary package negotiation and ultimately we want to support streaming payloads to a staging directory on disk rather than keeping them all in memory --- zenhttp/httpsys.cpp | 704 +++++++++++++++++++++++++++++----------------------- 1 file changed, 395 insertions(+), 309 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index 471a8f80a..fd93aa68f 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -3,6 +3,7 @@ #include "httpsys.h" #include +#include #include #if ZEN_WITH_HTTPSYS @@ -54,13 +55,12 @@ UTF8_to_wstring(const char* in) return out; } -////////////////////////////////////////////////////////////////////////// -// -// http.sys implementation -// - namespace zen { +class HttpSysServer; +class HttpSysTransaction; +class HttpMessageResponseRequest; + using namespace std::literals; static const uint32_t HashBinary = HashStringDjb2("application/octet-stream"sv); @@ -245,14 +245,69 @@ ReasonStringForHttpResultCode(int HttpCode) } } -class HttpSysServer; -class HttpSysTransaction; -class HttpMessageResponseRequest; +HttpVerb +TranslateHttpVerb(HTTP_VERB ReqVerb) +{ + switch (ReqVerb) + { + case HttpVerbOPTIONS: + return HttpVerb::kOptions; + + case HttpVerbGET: + return HttpVerb::kGet; + + case HttpVerbHEAD: + return HttpVerb::kHead; + + case HttpVerbPOST: + return HttpVerb::kPost; + + case HttpVerbPUT: + return HttpVerb::kPut; + + case HttpVerbDELETE: + return HttpVerb::kDelete; + + case HttpVerbCOPY: + return HttpVerb::kCopy; + + default: + // TODO: invalid request? + return (HttpVerb)0; + } +} + +uint64_t +GetContentLength(const HTTP_REQUEST* HttpRequest) +{ + const HTTP_KNOWN_HEADER& clh = HttpRequest->Headers.KnownHeaders[HttpHeaderContentLength]; + std::string_view cl(clh.pRawValue, clh.RawValueLength); + uint64_t ContentLength = 0; + std::from_chars(cl.data(), cl.data() + cl.size(), ContentLength); + return ContentLength; +}; + +HttpContentType +GetContentType(const HTTP_REQUEST* HttpRequest) +{ + const HTTP_KNOWN_HEADER& CtHdr = HttpRequest->Headers.KnownHeaders[HttpHeaderContentType]; + return MapContentType({CtHdr.pRawValue, CtHdr.RawValueLength}); +}; + +HttpContentType +GetAcceptType(const HTTP_REQUEST* HttpRequest) +{ + const HTTP_KNOWN_HEADER& CtHdr = HttpRequest->Headers.KnownHeaders[HttpHeaderAccept]; + return MapContentType({CtHdr.pRawValue, CtHdr.RawValueLength}); +}; +/** + * @brief Base class for any pending or active HTTP transactions + */ class HttpSysRequestHandler { public: - HttpSysRequestHandler(HttpSysTransaction& InRequest) : m_Request(InRequest) {} + explicit HttpSysRequestHandler(HttpSysTransaction& InRequest) : m_Request(InRequest) {} virtual ~HttpSysRequestHandler() = default; virtual void IssueRequest() = 0; @@ -261,40 +316,62 @@ public: HttpSysTransaction& Transaction() { return m_Request; } private: - HttpSysTransaction& m_Request; // Outermost HTTP transaction object + HttpSysTransaction& m_Request; // Related HTTP transaction object }; +/** + * This is the handler for the initial HTTP I/O request which will receive the headers + * and however much of the remaining payload might fit in the embedded request buffer + */ struct InitialRequestHandler : public HttpSysRequestHandler { - inline PHTTP_REQUEST HttpRequest() { return (PHTTP_REQUEST)m_RequestBuffer; } + inline HTTP_REQUEST* HttpRequest() { return (HTTP_REQUEST*)m_RequestBuffer; } inline uint32_t RequestBufferSize() const { return sizeof m_RequestBuffer; } + inline bool IsInitialRequest() const { return m_IsInitialRequest; } - InitialRequestHandler(HttpSysTransaction& InRequest) : HttpSysRequestHandler(InRequest) {} - ~InitialRequestHandler() {} + InitialRequestHandler(HttpSysTransaction& InRequest); + ~InitialRequestHandler(); - virtual void IssueRequest() override; + virtual void IssueRequest() override final; virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; - PHTTP_REQUEST m_HttpRequestPtr = (HTTP_REQUEST*)(m_RequestBuffer); - UCHAR m_RequestBuffer[16384 + sizeof(HTTP_REQUEST)]; + bool m_IsInitialRequest = true; + uint64_t m_CurrentPayloadOffset = 0; + uint64_t m_ContentLength = ~uint64_t(0); + IoBuffer m_PayloadBuffer; + UCHAR m_RequestBuffer[512 + sizeof(HTTP_REQUEST)]; }; +/** + * @brief Handler used to read complete body + */ +class HttpPayloadReadRequest : public HttpSysRequestHandler +{ +public: + HttpPayloadReadRequest(HttpSysTransaction& InRequest) : HttpSysRequestHandler(InRequest) {} + + virtual void IssueRequest() override; + virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; +}; + +/** + * This is the class which request handlers use to interact with the server instance + */ + class HttpSysServerRequest : public HttpServerRequest { public: - HttpSysServerRequest() = default; - HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& Service); + HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& Service, IoBuffer PayloadBuffer); ~HttpSysServerRequest() = default; - virtual void ReadPayload(std::function&& CompletionHandler) override; virtual IoBuffer ReadPayload() override; virtual void WriteResponse(HttpResponse HttpResponseCode) override; virtual void WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::span Blobs) override; virtual void WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) override; - bool m_IsInitialized = false; HttpSysTransaction& m_HttpTx; HttpMessageResponseRequest* m_Response = nullptr; // TODO: make this more general + IoBuffer m_PayloadBuffer; }; /** HTTP transaction @@ -305,9 +382,8 @@ public: class HttpSysTransaction final { public: - HttpSysTransaction(HttpSysServer& Server) : m_HttpServer(Server), m_HttpHandler(&m_InitialHttpHandler) {} - - virtual ~HttpSysTransaction() {} + HttpSysTransaction(HttpSysServer& Server); + virtual ~HttpSysTransaction(); enum class Status { @@ -329,8 +405,7 @@ public: HANDLE RequestQueueHandle(); inline OVERLAPPED* Overlapped() { return &m_HttpOverlapped; } inline HttpSysServer& Server() { return m_HttpServer; } - - inline PHTTP_REQUEST HttpRequest() { return m_InitialHttpHandler.HttpRequest(); } + inline HTTP_REQUEST* HttpRequest() { return m_InitialHttpHandler.HttpRequest(); } private: OVERLAPPED m_HttpOverlapped{}; @@ -342,15 +417,6 @@ private: ////////////////////////////////////////////////////////////////////////// -class HttpPayloadReadRequest : public HttpSysRequestHandler -{ -public: - HttpPayloadReadRequest(HttpSysTransaction& InRequest) : HttpSysRequestHandler(InRequest) {} - - virtual void IssueRequest() override; - virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; -}; - void HttpPayloadReadRequest::IssueRequest() { @@ -369,9 +435,16 @@ class HttpMessageResponseRequest : public HttpSysRequestHandler { public: HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode); - HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, const char* Message); - HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, const void* Payload, size_t PayloadSize); - HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, std::span Blobs); + HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, std::string_view Message); + HttpMessageResponseRequest(HttpSysTransaction& InRequest, + uint16_t ResponseCode, + HttpContentType ContentType, + const void* Payload, + size_t PayloadSize); + HttpMessageResponseRequest(HttpSysTransaction& InRequest, + uint16_t ResponseCode, + HttpContentType ContentType, + std::span Blobs); ~HttpMessageResponseRequest(); virtual void IssueRequest() override; @@ -387,6 +460,7 @@ private: uint32_t m_NextDataChunkOffset = 0; // This is used for responses where the number of chunks exceed the maximum number for one API call uint32_t m_RemainingChunkCount = 0; bool m_IsInitialResponse = true; + HttpContentType m_ContentType = HttpContentType::kBinary; void Initialize(uint16_t ResponseCode, std::span Blobs); @@ -396,36 +470,42 @@ private: HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode) : HttpSysRequestHandler(InRequest) { - std::array buffers; + std::array EmptyBufferList; - Initialize(ResponseCode, buffers); + Initialize(ResponseCode, EmptyBufferList); } -HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, const char* Message) +HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, std::string_view Message) : HttpSysRequestHandler(InRequest) +, m_ContentType(HttpContentType::kText) { - IoBuffer MessageBuffer(IoBuffer::Wrap, Message, strlen(Message)); - std::array buffers({MessageBuffer}); + IoBuffer MessageBuffer(IoBuffer::Wrap, Message.data(), Message.size()); + std::array SingleBufferList({MessageBuffer}); - Initialize(ResponseCode, buffers); + Initialize(ResponseCode, SingleBufferList); } HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, + HttpContentType ContentType, const void* Payload, size_t PayloadSize) : HttpSysRequestHandler(InRequest) +, m_ContentType(ContentType) { IoBuffer MessageBuffer(IoBuffer::Wrap, Payload, PayloadSize); - std::array buffers({MessageBuffer}); + std::array SingleBufferList({MessageBuffer}); - Initialize(ResponseCode, buffers); + Initialize(ResponseCode, SingleBufferList); } -HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, std::span Blobs) +HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, + uint16_t ResponseCode, + HttpContentType ContentType, + std::span BlobList) : HttpSysRequestHandler(InRequest) { - Initialize(ResponseCode, Blobs); + Initialize(ResponseCode, BlobList); } HttpMessageResponseRequest::~HttpMessageResponseRequest() @@ -433,16 +513,16 @@ HttpMessageResponseRequest::~HttpMessageResponseRequest() } void -HttpMessageResponseRequest::Initialize(uint16_t ResponseCode, std::span Blobs) +HttpMessageResponseRequest::Initialize(uint16_t ResponseCode, std::span BlobList) { m_HttpResponseCode = ResponseCode; - const uint32_t ChunkCount = (uint32_t)Blobs.size(); + const uint32_t ChunkCount = gsl::narrow(BlobList.size()); - m_HttpDataChunks.resize(ChunkCount); + m_HttpDataChunks.reserve(ChunkCount); m_DataBuffers.reserve(ChunkCount); - for (IoBuffer& Buffer : Blobs) + for (IoBuffer& Buffer : BlobList) { m_DataBuffers.emplace_back(std::move(Buffer)).MakeOwned(); } @@ -451,36 +531,55 @@ HttpMessageResponseRequest::Initialize(uint16_t ResponseCode, std::spanDataChunkType = HttpDataChunkFromFileHandle; - ChunkPtr->FromFileHandle.FileHandle = FileRef.FileHandle; - ChunkPtr->FromFileHandle.ByteRange.StartingOffset.QuadPart = FileRef.FileChunkOffset; - ChunkPtr->FromFileHandle.ByteRange.Length.QuadPart = BufferDataSize; - } - else + Chunk.DataChunkType = HttpDataChunkFromFileHandle; + Chunk.FromFileHandle.FileHandle = FileRef.FileHandle; + Chunk.FromFileHandle.ByteRange.StartingOffset.QuadPart = FileRef.FileChunkOffset; + Chunk.FromFileHandle.ByteRange.Length.QuadPart = BufferDataSize; + } + else + { + // Send from memory, need to make sure we chunk the buffer up since + // the underlying data structure only accepts 32-bit chunk sizes for + // memory chunks. When this happens the vector will be reallocated, + // which is fine since this will be a pretty rare case and sending + // the data is going to take a lot longer than a memory allocation :) + + const uint8_t* WriteCursor = reinterpret_cast(Buffer.Data()); + + while (BufferDataSize) { - ChunkPtr->DataChunkType = HttpDataChunkFromMemory; - ChunkPtr->FromMemory.pBuffer = (void*)Buffer.Data(); - ChunkPtr->FromMemory.BufferLength = BufferDataSize; - } - ++ChunkPtr; + const ULONG ThisChunkSize = gsl::narrow(zen::Min(1 * 1024 * 1024 * 1024, BufferDataSize)); + + m_HttpDataChunks.push_back({}); + auto& Chunk = m_HttpDataChunks.back(); + + Chunk.DataChunkType = HttpDataChunkFromMemory; + Chunk.FromMemory.pBuffer = (void*)WriteCursor; + Chunk.FromMemory.BufferLength = ThisChunkSize; - LocalDataSize += BufferDataSize; + BufferDataSize -= ThisChunkSize; + WriteCursor += ThisChunkSize; + } } } - m_RemainingChunkCount = ChunkCount; + m_RemainingChunkCount = gsl::narrow(m_HttpDataChunks.size()); m_TotalDataSize = LocalDataSize; } @@ -557,8 +656,10 @@ HttpMessageResponseRequest::IssueRequest() PHTTP_KNOWN_HEADER ContentTypeHeader = &HttpResponse.Headers.KnownHeaders[HttpHeaderContentType]; - ContentTypeHeader->pRawValue = "application/octet-stream"; /* TODO! We must respect the content type specified */ - ContentTypeHeader->RawValueLength = (USHORT)strlen(ContentTypeHeader->pRawValue); + std::string_view ContentTypeString = MapContentTypeToString(m_ContentType); + + ContentTypeHeader->pRawValue = ContentTypeString.data(); + ContentTypeHeader->RawValueLength = (USHORT)ContentTypeString.size(); HttpResponse.StatusCode = m_HttpResponseCode; HttpResponse.pReason = ReasonStringForHttpResultCode(m_HttpResponseCode); @@ -617,7 +718,14 @@ HttpMessageResponseRequest::IssueRequest() } } -////////////////////////////////////////////////////////////////////////// +/** + _________ + / _____/ ______________ __ ___________ + \_____ \_/ __ \_ __ \ \/ // __ \_ __ \ + / \ ___/| | \/\ /\ ___/| | \/ + /_______ /\___ >__| \_/ \___ >__| + \/ \/ \/ +*/ HttpSysServer::HttpSysServer(int ThreadCount) : m_ThreadPool(ThreadCount) { @@ -707,9 +815,9 @@ HttpSysServer::Initialize(const wchar_t* UrlPath) void HttpSysServer::StartServer() { - int RequestCount = 32; + const int InitialRequestCount = 32; - for (int i = 0; i < RequestCount; ++i) + for (int i = 0; i < InitialRequestCount; ++i) { IssueNewRequestMaybe(); } @@ -749,9 +857,7 @@ HttpSysServer::Run(bool TestMode) void HttpSysServer::OnHandlingRequest() { - --m_PendingRequests; - - if (m_PendingRequests > m_MinPendingRequests) + if (--m_PendingRequests > m_MinPendingRequests) { // We have more than the minimum number of requests pending, just let someone else // enqueue new requests @@ -807,7 +913,7 @@ HttpSysServer::RegisterService(const char* UrlPath, HttpService& Service) } void -HttpSysServer::RemoveEndpoint(const char* UrlPath, HttpService& Service) +HttpSysServer::UnregisterService(const char* UrlPath, HttpService& Service) { ZEN_UNUSED(Service); @@ -832,218 +938,14 @@ HttpSysServer::RemoveEndpoint(const char* UrlPath, HttpService& Service) ////////////////////////////////////////////////////////////////////////// -HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& Service) : m_IsInitialized(true), m_HttpTx(Tx) -{ - PHTTP_REQUEST HttpRequestPtr = Tx.HttpRequest(); - - const int PrefixLength = Service.UriPrefixLength(); - const int AbsPathLength = HttpRequestPtr->CookedUrl.AbsPathLength / sizeof(char16_t); - - if (AbsPathLength >= PrefixLength) - { - // We convert the URI immediately because most of the code involved prefers to deal - // with utf8. This has some performance impact which I'd prefer to avoid but for now - // we just have to live with it - - WideToUtf8({(char16_t*)HttpRequestPtr->CookedUrl.pAbsPath + PrefixLength, gsl::narrow(AbsPathLength - PrefixLength)}, - m_UriUtf8); - } - else - { - m_UriUtf8.Reset(); - } - - if (auto QueryStringLength = HttpRequestPtr->CookedUrl.QueryStringLength) - { - --QueryStringLength; - - WideToUtf8({(char16_t*)(HttpRequestPtr->CookedUrl.pQueryString) + 1, QueryStringLength / sizeof(char16_t)}, m_QueryStringUtf8); - } - else - { - m_QueryStringUtf8.Reset(); - } - - switch (HttpRequestPtr->Verb) - { - case HttpVerbOPTIONS: - m_Verb = HttpVerb::kOptions; - break; - - case HttpVerbGET: - m_Verb = HttpVerb::kGet; - break; - - case HttpVerbHEAD: - m_Verb = HttpVerb::kHead; - break; - - case HttpVerbPOST: - m_Verb = HttpVerb::kPost; - break; - - case HttpVerbPUT: - m_Verb = HttpVerb::kPut; - break; - - case HttpVerbDELETE: - m_Verb = HttpVerb::kDelete; - break; - - case HttpVerbCOPY: - m_Verb = HttpVerb::kCopy; - break; - - default: - // TODO: invalid request? - m_Verb = (HttpVerb)0; - break; - } - - const HTTP_KNOWN_HEADER& clh = HttpRequestPtr->Headers.KnownHeaders[HttpHeaderContentLength]; - std::string_view cl(clh.pRawValue, clh.RawValueLength); - std::from_chars(cl.data(), cl.data() + cl.size(), m_ContentLength); - - const HTTP_KNOWN_HEADER& CtHdr = HttpRequestPtr->Headers.KnownHeaders[HttpHeaderContentType]; - m_ContentType = MapContentType({CtHdr.pRawValue, CtHdr.RawValueLength}); - - const HTTP_KNOWN_HEADER& AcceptHdr = HttpRequestPtr->Headers.KnownHeaders[HttpHeaderAccept]; - m_AcceptType = MapContentType({AcceptHdr.pRawValue, AcceptHdr.RawValueLength}); -} - -void -HttpSysServerRequest::ReadPayload(std::function&& CompletionHandler) -{ - ZEN_UNUSED(CompletionHandler); -} - -IoBuffer -HttpSysServerRequest::ReadPayload() -{ - // This is presently synchronous for simplicity, but we - // need to implement an asynchronous version also - - HTTP_REQUEST* const HttpReq = m_HttpTx.HttpRequest(); - - IoBuffer PayloadBuffer(m_ContentLength); - - HttpContentType ContentType = RequestContentType(); - PayloadBuffer.SetContentType(ContentType); - - uint64_t BytesToRead = m_ContentLength; - - uint8_t* ReadPointer = reinterpret_cast(PayloadBuffer.MutableData()); - - // First deal with any payload which has already been copied - // into our request buffer - - const int EntityChunkCount = HttpReq->EntityChunkCount; - - for (int i = 0; i < EntityChunkCount; ++i) - { - HTTP_DATA_CHUNK& EntityChunk = HttpReq->pEntityChunks[i]; - - ZEN_ASSERT(EntityChunk.DataChunkType == HttpDataChunkFromMemory); - - const uint64_t BufferLength = EntityChunk.FromMemory.BufferLength; - - ZEN_ASSERT(BufferLength <= BytesToRead); - - memcpy(ReadPointer, EntityChunk.FromMemory.pBuffer, BufferLength); - - ReadPointer += BufferLength; - BytesToRead -= BufferLength; - } - - if (BytesToRead == 0) - { - PayloadBuffer.MakeImmutable(); - - return PayloadBuffer; - } - - // Call http.sys API to receive the remaining data SYNCHRONOUSLY - - static const uint64_t kMaxBytesPerApiCall = 1 * 1024 * 1024; - - while (BytesToRead) - { - ULONG BytesRead = 0; - - const uint64_t BytesToReadThisCall = zen::Min(BytesToRead, kMaxBytesPerApiCall); - - ULONG ApiResult = HttpReceiveRequestEntityBody(m_HttpTx.RequestQueueHandle(), - HttpReq->RequestId, - 0, /* Flags */ - ReadPointer, - gsl::narrow(BytesToReadThisCall), - &BytesRead, - NULL /* Overlapped */ - ); - - if (ApiResult != NO_ERROR && ApiResult != ERROR_HANDLE_EOF) - { - throw HttpServerException("payload read failed", ApiResult); - } - - BytesToRead -= BytesRead; - ReadPointer += BytesRead; - } - - PayloadBuffer.MakeImmutable(); - - return PayloadBuffer; -} - -void -HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode) +HttpSysTransaction::HttpSysTransaction(HttpSysServer& Server) : m_HttpServer(Server), m_HttpHandler(&m_InitialHttpHandler) { - ZEN_ASSERT(m_IsHandled == false); - - m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode); - - if (m_SuppressBody) - { - m_Response->SuppressResponseBody(); - } - - m_IsHandled = true; } -void -HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::span Blobs) +HttpSysTransaction::~HttpSysTransaction() { - ZEN_ASSERT(m_IsHandled == false); - ZEN_UNUSED(ContentType); - - m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode, Blobs); - - if (m_SuppressBody) - { - m_Response->SuppressResponseBody(); - } - - m_IsHandled = true; } -void -HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) -{ - ZEN_ASSERT(m_IsHandled == false); - ZEN_UNUSED(ContentType); - - m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode, ResponseString.data(), ResponseString.size()); - - if (m_SuppressBody) - { - m_Response->SuppressResponseBody(); - } - - m_IsHandled = true; -} - -////////////////////////////////////////////////////////////////////////// - PTP_IO HttpSysTransaction::Iocp() { @@ -1090,14 +992,16 @@ HttpSysTransaction::Status HttpSysTransaction::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) { // We use this to ensure sequential execution of completion handlers - // for any given transaction. + // for any given transaction. It also ensures all member variables are + // in a consistent state for the current thread + RwLock::ExclusiveLockScope _(m_CompletionMutex); bool RequestPending = false; if (HttpSysRequestHandler* CurrentHandler = m_HttpHandler) { - const bool IsInitialRequest = (CurrentHandler == &m_InitialHttpHandler); + const bool IsInitialRequest = (CurrentHandler == &m_InitialHttpHandler) && m_InitialHttpHandler.IsInitialRequest(); if (IsInitialRequest) { @@ -1144,37 +1048,158 @@ HttpSysTransaction::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTran ////////////////////////////////////////////////////////////////////////// +HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& Service, IoBuffer PayloadBuffer) +: m_HttpTx(Tx) +, m_PayloadBuffer(std::move(PayloadBuffer)) +{ + const HTTP_REQUEST* HttpRequestPtr = Tx.HttpRequest(); + + const int PrefixLength = Service.UriPrefixLength(); + const int AbsPathLength = HttpRequestPtr->CookedUrl.AbsPathLength / sizeof(char16_t); + + if (AbsPathLength >= PrefixLength) + { + // We convert the URI immediately because most of the code involved prefers to deal + // with utf8. This has some performance impact which I'd prefer to avoid but for now + // we just have to live with it + + WideToUtf8({(char16_t*)HttpRequestPtr->CookedUrl.pAbsPath + PrefixLength, gsl::narrow(AbsPathLength - PrefixLength)}, + m_UriUtf8); + } + else + { + m_UriUtf8.Reset(); + } + + if (auto QueryStringLength = HttpRequestPtr->CookedUrl.QueryStringLength) + { + --QueryStringLength; + + WideToUtf8({(char16_t*)(HttpRequestPtr->CookedUrl.pQueryString) + 1, QueryStringLength / sizeof(char16_t)}, m_QueryStringUtf8); + } + else + { + m_QueryStringUtf8.Reset(); + } + + m_Verb = TranslateHttpVerb(HttpRequestPtr->Verb); + m_ContentLength = GetContentLength(HttpRequestPtr); + m_ContentType = GetContentType(HttpRequestPtr); + m_AcceptType = GetAcceptType(HttpRequestPtr); +} + +IoBuffer +HttpSysServerRequest::ReadPayload() +{ + return m_PayloadBuffer; +} + +void +HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode) +{ + ZEN_ASSERT(m_IsHandled == false); + + m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode); + + if (m_SuppressBody) + { + m_Response->SuppressResponseBody(); + } + + m_IsHandled = true; +} + +void +HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::span Blobs) +{ + ZEN_ASSERT(m_IsHandled == false); + + m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode, ContentType, Blobs); + + if (m_SuppressBody) + { + m_Response->SuppressResponseBody(); + } + + m_IsHandled = true; +} + +void +HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) +{ + ZEN_ASSERT(m_IsHandled == false); + + m_Response = + new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode, ContentType, ResponseString.data(), ResponseString.size()); + + if (m_SuppressBody) + { + m_Response->SuppressResponseBody(); + } + + m_IsHandled = true; +} + +////////////////////////////////////////////////////////////////////////// + +InitialRequestHandler::InitialRequestHandler(HttpSysTransaction& InRequest) : HttpSysRequestHandler(InRequest) +{ +} + +InitialRequestHandler::~InitialRequestHandler() +{ +} + void InitialRequestHandler::IssueRequest() { - PTP_IO Iocp = Transaction().Iocp(); + HttpSysTransaction& Tx = Transaction(); + PTP_IO Iocp = Tx.Iocp(); + HTTP_REQUEST* HttpReq = Tx.HttpRequest(); StartThreadpoolIo(Iocp); - HttpSysTransaction& Tx = Transaction(); + ULONG HttpApiResult; - HTTP_REQUEST* HttpReq = Tx.HttpRequest(); + if (IsInitialRequest()) + { + HttpApiResult = HttpReceiveHttpRequest(Tx.RequestQueueHandle(), + HTTP_NULL_ID, + HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, + HttpReq, + RequestBufferSize(), + NULL, + Tx.Overlapped()); + } + else + { + static const uint64_t kMaxBytesPerApiCall = 64 * 1024; - ULONG Result = HttpReceiveHttpRequest(Tx.RequestQueueHandle(), - HTTP_NULL_ID, - HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY, - HttpReq, - RequestBufferSize(), - NULL, - Tx.Overlapped()); + uint64_t BytesToRead = m_ContentLength - m_CurrentPayloadOffset; + const uint64_t BytesToReadThisCall = zen::Min(BytesToRead, kMaxBytesPerApiCall); + void* BufferWriteCursor = reinterpret_cast(m_PayloadBuffer.MutableData()) + m_CurrentPayloadOffset; + + HttpApiResult = HttpReceiveRequestEntityBody(Tx.RequestQueueHandle(), + HttpReq->RequestId, + 0, /* Flags */ + BufferWriteCursor, + gsl::narrow(BytesToReadThisCall), + nullptr, // BytesReturned + Tx.Overlapped()); + } - if (Result != ERROR_IO_PENDING && Result != NO_ERROR) + if (HttpApiResult != ERROR_IO_PENDING && HttpApiResult != NO_ERROR) { CancelThreadpoolIo(Iocp); - if (Result == ERROR_MORE_DATA) + if (HttpApiResult == ERROR_MORE_DATA) { // ProcessReceiveAndPostResponse(pIoRequest, pServerContext->Io, ERROR_MORE_DATA); } // CleanupHttpIoRequest(pIoRequest); - spdlog::error("HttpReceiveHttpRequest failed, error {:x}", Result); + spdlog::error("HttpReceiveHttpRequest failed, error {:x}", HttpApiResult); return; } @@ -1186,29 +1211,90 @@ InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesT ZEN_UNUSED(IoResult); ZEN_UNUSED(NumberOfBytesTransferred); + auto _ = MakeGuard([&] { m_IsInitialRequest = false; }); + // Route requests try { - if (HttpService* Service = reinterpret_cast(m_HttpRequestPtr->UrlContext)) + HTTP_REQUEST* HttpReq = HttpRequest(); + + if (HttpService* Service = reinterpret_cast(HttpReq->UrlContext)) { - HttpSysServerRequest ThisRequest(Transaction(), *Service); + if (m_IsInitialRequest) + { + m_ContentLength = GetContentLength(HttpReq); + HttpVerb Verb = TranslateHttpVerb(HttpReq->Verb); + + if (m_ContentLength) + { + // Handle initial chunk read by copying any payload which has already been copied + // into our embedded request buffer + + m_PayloadBuffer = IoBuffer(m_ContentLength); + + HttpContentType ContentType = GetContentType(HttpReq); + m_PayloadBuffer.SetContentType(ContentType); + + uint64_t BytesToRead = m_ContentLength; + uint8_t* const BufferBase = reinterpret_cast(m_PayloadBuffer.MutableData()); + uint8_t* BufferWriteCursor = BufferBase; + + const int EntityChunkCount = HttpReq->EntityChunkCount; + + for (int i = 0; i < EntityChunkCount; ++i) + { + HTTP_DATA_CHUNK& EntityChunk = HttpReq->pEntityChunks[i]; + + ZEN_ASSERT(EntityChunk.DataChunkType == HttpDataChunkFromMemory); + + const uint64_t BufferLength = EntityChunk.FromMemory.BufferLength; + + ZEN_ASSERT(BufferLength <= BytesToRead); + + memcpy(BufferWriteCursor, EntityChunk.FromMemory.pBuffer, BufferLength); - Service->HandleRequest(ThisRequest); + BufferWriteCursor += BufferLength; + BytesToRead -= BufferLength; + } - if (!ThisRequest.IsHandled()) + m_CurrentPayloadOffset = BufferWriteCursor - BufferBase; + } + } + else { - return new HttpMessageResponseRequest(Transaction(), 404, "Not found"); + m_CurrentPayloadOffset += NumberOfBytesTransferred; } - if (ThisRequest.m_Response) + if (m_CurrentPayloadOffset == m_ContentLength) + { + m_PayloadBuffer.MakeImmutable(); + + // Body received completely - call request handler + + HttpSysServerRequest ThisRequest(Transaction(), *Service, m_PayloadBuffer); + + Service->HandleRequest(ThisRequest); + + if (!ThisRequest.IsHandled()) + { + return new HttpMessageResponseRequest(Transaction(), 404, "Not found"sv); + } + + if (ThisRequest.m_Response) + { + return ThisRequest.m_Response; + } + } + else { - return ThisRequest.m_Response; + // Issue a read request for more body data + return this; } } // Unable to route - return new HttpMessageResponseRequest(Transaction(), 404, "Item unknown"); + return new HttpMessageResponseRequest(Transaction(), 404, "No suitable route found"sv); } catch (std::exception& ex) { -- cgit v1.2.3 From f181ee026f90d37abe536779a7c0fe9f24abe925 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Fri, 10 Sep 2021 22:05:32 +0200 Subject: Improved error reporting, tweaked request buffer size and added explicit cleanup of http API resources --- zenhttp/httpsys.cpp | 135 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 119 insertions(+), 16 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index fd93aa68f..00ffc51e9 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -9,6 +9,7 @@ #if ZEN_WITH_HTTPSYS # include +# include # pragma comment(lib, "httpapi.lib") std::wstring @@ -339,7 +340,7 @@ struct InitialRequestHandler : public HttpSysRequestHandler uint64_t m_CurrentPayloadOffset = 0; uint64_t m_ContentLength = ~uint64_t(0); IoBuffer m_PayloadBuffer; - UCHAR m_RequestBuffer[512 + sizeof(HTTP_REQUEST)]; + UCHAR m_RequestBuffer[4096 + sizeof(HTTP_REQUEST)]; }; /** @@ -744,6 +745,8 @@ HttpSysServer::~HttpSysServer() { if (m_IsHttpInitialized) { + Cleanup(); + HttpTerminate(HTTP_INITIALIZE_SERVER, nullptr); } } @@ -751,13 +754,13 @@ HttpSysServer::~HttpSysServer() void HttpSysServer::Initialize(const wchar_t* UrlPath) { - // check(bIsOk); - ULONG Result = HttpCreateServerSession(HTTPAPI_VERSION_2, &m_HttpSessionId, 0); if (Result != NO_ERROR) { - // Flag error + spdlog::error("Failed to create server session for '{}': {x}", WideToUtf8(UrlPath), Result); + + m_IsOk = false; return; } @@ -766,29 +769,33 @@ HttpSysServer::Initialize(const wchar_t* UrlPath) if (Result != NO_ERROR) { - // Flag error + spdlog::error("Failed to create URL group for '{}': {x}", WideToUtf8(UrlPath), Result); return; } m_BaseUri = UrlPath; - Result = HttpAddUrlToUrlGroup(m_HttpUrlGroupId, UrlPath, /* #TODO UrlContext */ HTTP_URL_CONTEXT(0), 0); + Result = HttpAddUrlToUrlGroup(m_HttpUrlGroupId, UrlPath, HTTP_URL_CONTEXT(0), 0); if (Result != NO_ERROR) { - // Flag error + spdlog::error("Failed to add base URL to URL group for '{}': {x}", WideToUtf8(UrlPath), Result); return; } HTTP_BINDING_INFO HttpBindingInfo = {{0}, 0}; - Result = HttpCreateRequestQueue(HTTPAPI_VERSION_2, NULL, NULL, 0, &m_RequestQueueHandle); + Result = HttpCreateRequestQueue(HTTPAPI_VERSION_2, + /* Name */ nullptr, + /* SecurityAttributes */ nullptr, + /* Flags */ 0, + &m_RequestQueueHandle); if (Result != NO_ERROR) { - // Flag error! + spdlog::error("Failed to create request queue for '{}': {x}", WideToUtf8(UrlPath), Result); return; } @@ -800,16 +807,43 @@ HttpSysServer::Initialize(const wchar_t* UrlPath) if (Result != NO_ERROR) { - // Flag error! + spdlog::error("Failed to set server binding property for '{}': {x}", WideToUtf8(UrlPath), Result); return; } // Create I/O completion port - m_ThreadPool.CreateIocp(m_RequestQueueHandle, HttpSysTransaction::IoCompletionCallback, this); + m_ThreadPool.CreateIocp(m_RequestQueueHandle, HttpSysTransaction::IoCompletionCallback, /* Context */ this); - // Check result! + if (!m_ThreadPool.Iocp()) + { + spdlog::error("Failed to create IOCP for '{}': {x}", WideToUtf8(UrlPath), Result); + } +} + +void +HttpSysServer::Cleanup() +{ + ++m_IsShuttingDown; + + if (m_RequestQueueHandle) + { + HttpCloseRequestQueue(m_RequestQueueHandle); + m_RequestQueueHandle = nullptr; + } + + if (m_HttpUrlGroupId) + { + HttpCloseUrlGroup(m_HttpUrlGroupId); + m_HttpUrlGroupId = 0; + } + + if (m_HttpSessionId) + { + HttpCloseServerSession(m_HttpSessionId); + m_HttpSessionId = 0; + } } void @@ -870,6 +904,11 @@ HttpSysServer::OnHandlingRequest() void HttpSysServer::IssueNewRequestMaybe() { + if (m_IsShuttingDown) + { + return; + } + if (m_PendingRequests.load(std::memory_order::relaxed) >= m_MaxPendingRequests) { return; @@ -1173,7 +1212,8 @@ InitialRequestHandler::IssueRequest() } else { - static const uint64_t kMaxBytesPerApiCall = 64 * 1024; + // The http.sys team recommends limiting the size to 128KB + static const uint64_t kMaxBytesPerApiCall = 128 * 1024; uint64_t BytesToRead = m_ContentLength - m_CurrentPayloadOffset; const uint64_t BytesToReadThisCall = zen::Min(BytesToRead, kMaxBytesPerApiCall); @@ -1208,17 +1248,80 @@ InitialRequestHandler::IssueRequest() HttpSysRequestHandler* InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) { - ZEN_UNUSED(IoResult); - ZEN_UNUSED(NumberOfBytesTransferred); - auto _ = MakeGuard([&] { m_IsInitialRequest = false; }); + switch (IoResult) + { + case ERROR_OPERATION_ABORTED: + return nullptr; + + case ERROR_MORE_DATA: + // Insufficient buffer space + break; + } + // Route requests try { HTTP_REQUEST* HttpReq = HttpRequest(); +# if 0 + for (int i = 0; i < HttpReq->RequestInfoCount; ++i) + { + auto& ReqInfo = HttpReq->pRequestInfo[i]; + + switch (ReqInfo.InfoType) + { + case HttpRequestInfoTypeRequestTiming: + { + const HTTP_REQUEST_TIMING_INFO* TimingInfo = reinterpret_cast(ReqInfo.pInfo); + + spdlog::info(""); + } + break; + case HttpRequestInfoTypeAuth: + spdlog::info(""); + break; + case HttpRequestInfoTypeChannelBind: + spdlog::info(""); + break; + case HttpRequestInfoTypeSslProtocol: + spdlog::info(""); + break; + case HttpRequestInfoTypeSslTokenBindingDraft: + spdlog::info(""); + break; + case HttpRequestInfoTypeSslTokenBinding: + spdlog::info(""); + break; + case HttpRequestInfoTypeTcpInfoV0: + { + const TCP_INFO_v0* TcpInfo = reinterpret_cast(ReqInfo.pInfo); + + spdlog::info(""); + } + break; + case HttpRequestInfoTypeRequestSizing: + { + const HTTP_REQUEST_SIZING_INFO* SizingInfo = reinterpret_cast(ReqInfo.pInfo); + spdlog::info(""); + } + break; + case HttpRequestInfoTypeQuicStats: + spdlog::info(""); + break; + case HttpRequestInfoTypeTcpInfoV1: + { + const TCP_INFO_v1* TcpInfo = reinterpret_cast(ReqInfo.pInfo); + + spdlog::info(""); + } + break; + } + } +# endif + if (HttpService* Service = reinterpret_cast(HttpReq->UrlContext)) { if (m_IsInitialRequest) -- cgit v1.2.3 From 62ac72cad393f1685a3ea64ddf9d4399dc430452 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Sat, 11 Sep 2021 17:29:47 +0200 Subject: Comment fixes, changed thread count args to unsigned --- zenhttp/httpsys.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index 00ffc51e9..1050bbbb7 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -432,6 +432,13 @@ HttpPayloadReadRequest::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytes ////////////////////////////////////////////////////////////////////////// +/** + * @brief HTTP request response I/O request handler + * + * Asynchronously streams out a response to an HTTP request via compound + * responses from memory or directly from file + */ + class HttpMessageResponseRequest : public HttpSysRequestHandler { public: @@ -507,6 +514,8 @@ HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InReq : HttpSysRequestHandler(InRequest) { Initialize(ResponseCode, BlobList); + + ZEN_UNUSED(ContentType); } HttpMessageResponseRequest::~HttpMessageResponseRequest() @@ -728,7 +737,7 @@ HttpMessageResponseRequest::IssueRequest() \/ \/ \/ */ -HttpSysServer::HttpSysServer(int ThreadCount) : m_ThreadPool(ThreadCount) +HttpSysServer::HttpSysServer(unsigned int ThreadCount) : m_ThreadPool(ThreadCount) { ULONG Result = HttpInitialize(HTTPAPI_VERSION_2, HTTP_INITIALIZE_SERVER, nullptr); @@ -1327,7 +1336,6 @@ InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesT if (m_IsInitialRequest) { m_ContentLength = GetContentLength(HttpReq); - HttpVerb Verb = TranslateHttpVerb(HttpReq->Verb); if (m_ContentLength) { -- cgit v1.2.3 From 822b0b1cb3868fdfc2b7159cdbf11c3df776c9dd Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Sun, 12 Sep 2021 11:51:29 +0200 Subject: HttpResponse enum -> HttpResponseCode Also removed initial CbPackage API HttpServer changes as I have decided to take a different approach --- zenhttp/httpsys.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index 1050bbbb7..7bb3bbc75 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -366,9 +366,9 @@ public: ~HttpSysServerRequest() = default; virtual IoBuffer ReadPayload() override; - virtual void WriteResponse(HttpResponse HttpResponseCode) override; - virtual void WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::span Blobs) override; - virtual void WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) override; + virtual void WriteResponse(HttpResponseCode ResponseCode) override; + virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span Blobs) override; + virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) override; HttpSysTransaction& m_HttpTx; HttpMessageResponseRequest* m_Response = nullptr; // TODO: make this more general @@ -464,7 +464,7 @@ private: std::vector m_HttpDataChunks; uint64_t m_TotalDataSize = 0; // Sum of all chunk sizes - uint16_t m_HttpResponseCode = 0; + uint16_t m_ResponseCode = 0; uint32_t m_NextDataChunkOffset = 0; // This is used for responses where the number of chunks exceed the maximum number for one API call uint32_t m_RemainingChunkCount = 0; bool m_IsInitialResponse = true; @@ -525,7 +525,7 @@ HttpMessageResponseRequest::~HttpMessageResponseRequest() void HttpMessageResponseRequest::Initialize(uint16_t ResponseCode, std::span BlobList) { - m_HttpResponseCode = ResponseCode; + m_ResponseCode = ResponseCode; const uint32_t ChunkCount = gsl::narrow(BlobList.size()); @@ -671,8 +671,8 @@ HttpMessageResponseRequest::IssueRequest() ContentTypeHeader->pRawValue = ContentTypeString.data(); ContentTypeHeader->RawValueLength = (USHORT)ContentTypeString.size(); - HttpResponse.StatusCode = m_HttpResponseCode; - HttpResponse.pReason = ReasonStringForHttpResultCode(m_HttpResponseCode); + HttpResponse.StatusCode = m_ResponseCode; + HttpResponse.pReason = ReasonStringForHttpResultCode(m_ResponseCode); HttpResponse.ReasonLength = (USHORT)strlen(HttpResponse.pReason); // Cache policy @@ -1143,11 +1143,11 @@ HttpSysServerRequest::ReadPayload() } void -HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode) +HttpSysServerRequest::WriteResponse(HttpResponseCode ResponseCode) { ZEN_ASSERT(m_IsHandled == false); - m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode); + m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)ResponseCode); if (m_SuppressBody) { @@ -1158,11 +1158,11 @@ HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode) } void -HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::span Blobs) +HttpSysServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span Blobs) { ZEN_ASSERT(m_IsHandled == false); - m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode, ContentType, Blobs); + m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)ResponseCode, ContentType, Blobs); if (m_SuppressBody) { @@ -1173,12 +1173,12 @@ HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentTy } void -HttpSysServerRequest::WriteResponse(HttpResponse HttpResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) +HttpSysServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) { ZEN_ASSERT(m_IsHandled == false); m_Response = - new HttpMessageResponseRequest(m_HttpTx, (uint16_t)HttpResponseCode, ContentType, ResponseString.data(), ResponseString.size()); + new HttpMessageResponseRequest(m_HttpTx, (uint16_t)ResponseCode, ContentType, ResponseString.data(), ResponseString.size()); if (m_SuppressBody) { -- cgit v1.2.3 From 835a7d00a9da37b07cdf450899ac7fd0125b3320 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Sun, 12 Sep 2021 13:42:23 +0200 Subject: Some error handling improvements in zenhttp Primarily replaces some exception usage with std::error_code --- zenhttp/httpsys.cpp | 143 +++++++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 64 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index 7bb3bbc75..f06460822 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -2,6 +2,7 @@ #include "httpsys.h" +#include #include #include #include @@ -62,6 +63,19 @@ class HttpSysServer; class HttpSysTransaction; class HttpMessageResponseRequest; +////////////////////////////////////////////////////////////////////////// + +HttpSysException::HttpSysException(const char* Message, uint32_t Error) +: HttpServerException(Message, std::error_code(Error, std::system_category())) +{ +} + +HttpSysException::~HttpSysException() +{ +} + +////////////////////////////////////////////////////////////////////////// + using namespace std::literals; static const uint32_t HashBinary = HashStringDjb2("application/octet-stream"sv); @@ -311,7 +325,7 @@ public: explicit HttpSysRequestHandler(HttpSysTransaction& InRequest) : m_Request(InRequest) {} virtual ~HttpSysRequestHandler() = default; - virtual void IssueRequest() = 0; + virtual void IssueRequest(std::error_code& ErrorCode) = 0; virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) = 0; HttpSysTransaction& Transaction() { return m_Request; } @@ -333,7 +347,7 @@ struct InitialRequestHandler : public HttpSysRequestHandler InitialRequestHandler(HttpSysTransaction& InRequest); ~InitialRequestHandler(); - virtual void IssueRequest() override final; + virtual void IssueRequest(std::error_code& ErrorCode) override final; virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; bool m_IsInitialRequest = true; @@ -343,18 +357,6 @@ struct InitialRequestHandler : public HttpSysRequestHandler UCHAR m_RequestBuffer[4096 + sizeof(HTTP_REQUEST)]; }; -/** - * @brief Handler used to read complete body - */ -class HttpPayloadReadRequest : public HttpSysRequestHandler -{ -public: - HttpPayloadReadRequest(HttpSysTransaction& InRequest) : HttpSysRequestHandler(InRequest) {} - - virtual void IssueRequest() override; - virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; -}; - /** * This is the class which request handlers use to interact with the server instance */ @@ -401,7 +403,7 @@ public: ULONG_PTR NumberOfBytesTransferred, PTP_IO Io); - void IssueInitialRequest(); + void IssueInitialRequest(std::error_code& ErrorCode); PTP_IO Iocp(); HANDLE RequestQueueHandle(); inline OVERLAPPED* Overlapped() { return &m_HttpOverlapped; } @@ -411,30 +413,16 @@ public: private: OVERLAPPED m_HttpOverlapped{}; HttpSysServer& m_HttpServer; - HttpSysRequestHandler* m_HttpHandler{nullptr}; // Tracks which handler is due to handle the next I/O completion event + HttpSysRequestHandler* m_CompletionHandler{nullptr}; // Tracks which handler is due to handle the next I/O completion event RwLock m_CompletionMutex; InitialRequestHandler m_InitialHttpHandler{*this}; }; ////////////////////////////////////////////////////////////////////////// -void -HttpPayloadReadRequest::IssueRequest() -{ -} - -HttpSysRequestHandler* -HttpPayloadReadRequest::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) -{ - ZEN_UNUSED(IoResult, NumberOfBytesTransferred); - return nullptr; -} - -////////////////////////////////////////////////////////////////////////// - /** * @brief HTTP request response I/O request handler - * + * * Asynchronously streams out a response to an HTTP request via compound * responses from memory or directly from file */ @@ -455,7 +443,7 @@ public: std::span Blobs); ~HttpMessageResponseRequest(); - virtual void IssueRequest() override; + virtual void IssueRequest(std::error_code& ErrorCode) override final; virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; void SuppressResponseBody(); @@ -464,7 +452,7 @@ private: std::vector m_HttpDataChunks; uint64_t m_TotalDataSize = 0; // Sum of all chunk sizes - uint16_t m_ResponseCode = 0; + uint16_t m_ResponseCode = 0; uint32_t m_NextDataChunkOffset = 0; // This is used for responses where the number of chunks exceed the maximum number for one API call uint32_t m_RemainingChunkCount = 0; bool m_IsInitialResponse = true; @@ -616,7 +604,7 @@ HttpMessageResponseRequest::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfB } void -HttpMessageResponseRequest::IssueRequest() +HttpMessageResponseRequest::IssueRequest(std::error_code& ErrorCode) { HttpSysTransaction& Tx = Transaction(); HTTP_REQUEST* const HttpReq = Tx.HttpRequest(); @@ -722,9 +710,9 @@ HttpMessageResponseRequest::IssueRequest() CancelThreadpoolIo(Iocp); - spdlog::error("failed to send HTTP response (error: {}) URL: {}", SendResult, HttpReq->pRawUrl); + spdlog::error("failed to send HTTP response (error: {}) URL: {}"sv, SendResult, HttpReq->pRawUrl); - throw HttpServerException("Failed to send HTTP response", SendResult); + ErrorCode = MakeWin32ErrorCode(SendResult); } } @@ -763,13 +751,13 @@ HttpSysServer::~HttpSysServer() void HttpSysServer::Initialize(const wchar_t* UrlPath) { + m_IsOk = false; + ULONG Result = HttpCreateServerSession(HTTPAPI_VERSION_2, &m_HttpSessionId, 0); if (Result != NO_ERROR) { - spdlog::error("Failed to create server session for '{}': {x}", WideToUtf8(UrlPath), Result); - - m_IsOk = false; + spdlog::error("Failed to create server session for '{}': {x}"sv, WideToUtf8(UrlPath), Result); return; } @@ -778,7 +766,7 @@ HttpSysServer::Initialize(const wchar_t* UrlPath) if (Result != NO_ERROR) { - spdlog::error("Failed to create URL group for '{}': {x}", WideToUtf8(UrlPath), Result); + spdlog::error("Failed to create URL group for '{}': {x}"sv, WideToUtf8(UrlPath), Result); return; } @@ -789,7 +777,7 @@ HttpSysServer::Initialize(const wchar_t* UrlPath) if (Result != NO_ERROR) { - spdlog::error("Failed to add base URL to URL group for '{}': {x}", WideToUtf8(UrlPath), Result); + spdlog::error("Failed to add base URL to URL group for '{}': {x}"sv, WideToUtf8(UrlPath), Result); return; } @@ -804,7 +792,7 @@ HttpSysServer::Initialize(const wchar_t* UrlPath) if (Result != NO_ERROR) { - spdlog::error("Failed to create request queue for '{}': {x}", WideToUtf8(UrlPath), Result); + spdlog::error("Failed to create request queue for '{}': {x}"sv, WideToUtf8(UrlPath), Result); return; } @@ -816,18 +804,23 @@ HttpSysServer::Initialize(const wchar_t* UrlPath) if (Result != NO_ERROR) { - spdlog::error("Failed to set server binding property for '{}': {x}", WideToUtf8(UrlPath), Result); + spdlog::error("Failed to set server binding property for '{}': {x}"sv, WideToUtf8(UrlPath), Result); return; } // Create I/O completion port - m_ThreadPool.CreateIocp(m_RequestQueueHandle, HttpSysTransaction::IoCompletionCallback, /* Context */ this); + std::error_code ErrorCode; + m_ThreadPool.CreateIocp(m_RequestQueueHandle, HttpSysTransaction::IoCompletionCallback, /* Context */ this, /* out */ ErrorCode); - if (!m_ThreadPool.Iocp()) + if (ErrorCode) + { + spdlog::error("Failed to create IOCP for '{}': {}"sv, WideToUtf8(UrlPath), ErrorCode.message()); + } + else { - spdlog::error("Failed to create IOCP for '{}': {x}", WideToUtf8(UrlPath), Result); + m_IsOk = true; } } @@ -913,7 +906,7 @@ HttpSysServer::OnHandlingRequest() void HttpSysServer::IssueNewRequestMaybe() { - if (m_IsShuttingDown) + if (m_IsShuttingDown.load(std::memory_order::acquire)) { return; } @@ -925,7 +918,15 @@ HttpSysServer::IssueNewRequestMaybe() std::unique_ptr Request = std::make_unique(*this); - Request->IssueInitialRequest(); + std::error_code ec; + Request->IssueInitialRequest(ec); + + if (ec) + { + // No request was actually issued. What is the appropriate response? + + return; + } // This may end up exceeding the MaxPendingRequests limit, but it's not // really a problem. I'm doing it this way mostly to avoid dealing with @@ -954,7 +955,7 @@ HttpSysServer::RegisterService(const char* UrlPath, HttpService& Service) if (Result != NO_ERROR) { - spdlog::error("HttpAddUrlToUrlGroup failed with result {}", Result); + spdlog::error("HttpAddUrlToUrlGroup failed with result {}"sv, Result); return; } @@ -980,13 +981,13 @@ HttpSysServer::UnregisterService(const char* UrlPath, HttpService& Service) if (Result != NO_ERROR) { - spdlog::error("HttpRemoveUrlFromUrlGroup failed with result {}", Result); + spdlog::error("HttpRemoveUrlFromUrlGroup failed with result {}"sv, Result); } } ////////////////////////////////////////////////////////////////////////// -HttpSysTransaction::HttpSysTransaction(HttpSysServer& Server) : m_HttpServer(Server), m_HttpHandler(&m_InitialHttpHandler) +HttpSysTransaction::HttpSysTransaction(HttpSysServer& Server) : m_HttpServer(Server), m_CompletionHandler(&m_InitialHttpHandler) { } @@ -1007,9 +1008,9 @@ HttpSysTransaction::RequestQueueHandle() } void -HttpSysTransaction::IssueInitialRequest() +HttpSysTransaction::IssueInitialRequest(std::error_code& ErrorCode) { - m_InitialHttpHandler.IssueRequest(); + m_InitialHttpHandler.IssueRequest(ErrorCode); } void @@ -1045,9 +1046,9 @@ HttpSysTransaction::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTran RwLock::ExclusiveLockScope _(m_CompletionMutex); - bool RequestPending = false; + bool IsRequestPending = false; - if (HttpSysRequestHandler* CurrentHandler = m_HttpHandler) + if (HttpSysRequestHandler* CurrentHandler = m_CompletionHandler) { const bool IsInitialRequest = (CurrentHandler == &m_InitialHttpHandler) && m_InitialHttpHandler.IsInitialRequest(); @@ -1057,19 +1058,27 @@ HttpSysTransaction::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTran m_HttpServer.OnHandlingRequest(); } - m_HttpHandler = CurrentHandler->HandleCompletion(IoResult, NumberOfBytesTransferred); + m_CompletionHandler = CurrentHandler->HandleCompletion(IoResult, NumberOfBytesTransferred); - if (m_HttpHandler) + if (m_CompletionHandler) { try { - m_HttpHandler->IssueRequest(); + std::error_code ec; + m_CompletionHandler->IssueRequest(ec); - RequestPending = true; + if (ec) + { + spdlog::error("IssueRequest() failed {}"sv, ec.message()); + } + else + { + IsRequestPending = true; + } } catch (std::exception& Ex) { - spdlog::error("exception caught from IssueRequest(): {}", Ex.what()); + spdlog::error("exception caught from IssueRequest(): {}"sv, Ex.what()); // something went wrong, no request is pending } @@ -1083,14 +1092,16 @@ HttpSysTransaction::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTran } } - // Ensure new requests are enqueued + // Ensure new requests are enqueued as necessary m_HttpServer.IssueNewRequestMaybe(); - if (RequestPending) + if (IsRequestPending) { + // There is another request pending on this transaction, so it needs to remain valid return Status::kRequestPending; } + // Transaction done, caller should clean up (delete) this instance return Status::kDone; } @@ -1199,7 +1210,7 @@ InitialRequestHandler::~InitialRequestHandler() } void -InitialRequestHandler::IssueRequest() +InitialRequestHandler::IssueRequest(std::error_code& ErrorCode) { HttpSysTransaction& Tx = Transaction(); PTP_IO Iocp = Tx.Iocp(); @@ -1248,10 +1259,14 @@ InitialRequestHandler::IssueRequest() // CleanupHttpIoRequest(pIoRequest); - spdlog::error("HttpReceiveHttpRequest failed, error {:x}", HttpApiResult); + ErrorCode = MakeWin32ErrorCode(HttpApiResult); + + spdlog::error("HttpReceiveHttpRequest failed, error {}", ErrorCode.message()); return; } + + ErrorCode = std::error_code(); } HttpSysRequestHandler* -- cgit v1.2.3 From fa1f144a4eb7d201ef84b8d369d40f26fc73dff6 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Sun, 12 Sep 2021 13:46:57 +0200 Subject: Eliminated HttpServerException and related classes --- zenhttp/httpsys.cpp | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index f06460822..8aa30344b 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -63,16 +63,6 @@ class HttpSysServer; class HttpSysTransaction; class HttpMessageResponseRequest; -////////////////////////////////////////////////////////////////////////// - -HttpSysException::HttpSysException(const char* Message, uint32_t Error) -: HttpServerException(Message, std::error_code(Error, std::system_category())) -{ -} - -HttpSysException::~HttpSysException() -{ -} ////////////////////////////////////////////////////////////////////////// -- cgit v1.2.3 From 1d6aeff046a8f9f3df564163b56e927096decc39 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 13 Sep 2021 10:07:30 +0200 Subject: Implemented generic CbPackage attachments filtering Package transmission will also need to be updated (up next) for the new scheme to be effective --- zenhttp/httpsys.cpp | 406 ++++++++++++++++++++++------------------------------ 1 file changed, 173 insertions(+), 233 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index 8aa30344b..f5f9d4249 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -2,6 +2,8 @@ #include "httpsys.h" +#include +#include #include #include #include @@ -59,197 +61,14 @@ UTF8_to_wstring(const char* in) namespace zen { +using namespace std::literals; + class HttpSysServer; class HttpSysTransaction; class HttpMessageResponseRequest; - -////////////////////////////////////////////////////////////////////////// - -using namespace std::literals; - -static const uint32_t HashBinary = HashStringDjb2("application/octet-stream"sv); -static const uint32_t HashJson = HashStringDjb2("application/json"sv); -static const uint32_t HashYaml = HashStringDjb2("text/yaml"sv); -static const uint32_t HashText = HashStringDjb2("text/plain"sv); -static const uint32_t HashCompactBinary = HashStringDjb2("application/x-ue-cb"sv); -static const uint32_t HashCompactBinaryPackage = HashStringDjb2("application/x-ue-cbpkg"sv); - -HttpContentType -MapContentType(const std::string_view& ContentTypeString) -{ - if (!ContentTypeString.empty()) - { - const uint32_t CtHash = HashStringDjb2(ContentTypeString); - - if (CtHash == HashBinary) - { - return HttpContentType::kBinary; - } - else if (CtHash == HashCompactBinary) - { - return HttpContentType::kCbObject; - } - else if (CtHash == HashCompactBinaryPackage) - { - return HttpContentType::kCbPackage; - } - else if (CtHash == HashJson) - { - return HttpContentType::kJSON; - } - else if (CtHash == HashYaml) - { - return HttpContentType::kYAML; - } - else if (CtHash == HashText) - { - return HttpContentType::kText; - } - } - - return HttpContentType::kUnknownContentType; -} - ////////////////////////////////////////////////////////////////////////// -const char* -ReasonStringForHttpResultCode(int HttpCode) -{ - switch (HttpCode) - { - // 1xx Informational - - case 100: - return "Continue"; - case 101: - return "Switching Protocols"; - - // 2xx Success - - case 200: - return "OK"; - case 201: - return "Created"; - case 202: - return "Accepted"; - case 204: - return "No Content"; - case 205: - return "Reset Content"; - case 206: - return "Partial Content"; - - // 3xx Redirection - - case 300: - return "Multiple Choices"; - case 301: - return "Moved Permanently"; - case 302: - return "Found"; - case 303: - return "See Other"; - case 304: - return "Not Modified"; - case 305: - return "Use Proxy"; - case 306: - return "Switch Proxy"; - case 307: - return "Temporary Redirect"; - case 308: - return "Permanent Redirect"; - - // 4xx Client errors - - case 400: - return "Bad Request"; - case 401: - return "Unauthorized"; - case 402: - return "Payment Required"; - case 403: - return "Forbidden"; - case 404: - return "Not Found"; - case 405: - return "Method Not Allowed"; - case 406: - return "Not Acceptable"; - case 407: - return "Proxy Authentication Required"; - case 408: - return "Request Timeout"; - case 409: - return "Conflict"; - case 410: - return "Gone"; - case 411: - return "Length Required"; - case 412: - return "Precondition Failed"; - case 413: - return "Payload Too Large"; - case 414: - return "URI Too Long"; - case 415: - return "Unsupported Media Type"; - case 416: - return "Range Not Satisifiable"; - case 417: - return "Expectation Failed"; - case 418: - return "I'm a teapot"; - case 421: - return "Misdirected Request"; - case 422: - return "Unprocessable Entity"; - case 423: - return "Locked"; - case 424: - return "Failed Dependency"; - case 425: - return "Too Early"; - case 426: - return "Upgrade Required"; - case 428: - return "Precondition Required"; - case 429: - return "Too Many Requests"; - case 431: - return "Request Header Fields Too Large"; - - // 5xx Server errors - - case 500: - return "Internal Server Error"; - case 501: - return "Not Implemented"; - case 502: - return "Bad Gateway"; - case 503: - return "Service Unavailable"; - case 504: - return "Gateway Timeout"; - case 505: - return "HTTP Version Not Supported"; - case 506: - return "Variant Also Negotiates"; - case 507: - return "Insufficient Storage"; - case 508: - return "Loop Detected"; - case 510: - return "Not Extended"; - case 511: - return "Network Authentication Required"; - - default: - return "Unknown Result"; - } -} - HttpVerb TranslateHttpVerb(HTTP_VERB ReqVerb) { @@ -296,14 +115,14 @@ HttpContentType GetContentType(const HTTP_REQUEST* HttpRequest) { const HTTP_KNOWN_HEADER& CtHdr = HttpRequest->Headers.KnownHeaders[HttpHeaderContentType]; - return MapContentType({CtHdr.pRawValue, CtHdr.RawValueLength}); + return ParseContentType({CtHdr.pRawValue, CtHdr.RawValueLength}); }; HttpContentType GetAcceptType(const HTTP_REQUEST* HttpRequest) { const HTTP_KNOWN_HEADER& CtHdr = HttpRequest->Headers.KnownHeaders[HttpHeaderAccept]; - return MapContentType({CtHdr.pRawValue, CtHdr.RawValueLength}); + return ParseContentType({CtHdr.pRawValue, CtHdr.RawValueLength}); }; /** @@ -357,11 +176,16 @@ public: HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& Service, IoBuffer PayloadBuffer); ~HttpSysServerRequest() = default; + virtual Oid SessionId() const override; + virtual uint32_t RequestId() const override; + virtual IoBuffer ReadPayload() override; virtual void WriteResponse(HttpResponseCode ResponseCode) override; virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span Blobs) override; virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) override; + using HttpServerRequest::WriteResponse; + HttpSysTransaction& m_HttpTx; HttpMessageResponseRequest* m_Response = nullptr; // TODO: make this more general IoBuffer m_PayloadBuffer; @@ -400,12 +224,18 @@ public: inline HttpSysServer& Server() { return m_HttpServer; } inline HTTP_REQUEST* HttpRequest() { return m_InitialHttpHandler.HttpRequest(); } + HttpSysServerRequest& InvokeRequestHandler(HttpService& Service, IoBuffer Payload); + private: - OVERLAPPED m_HttpOverlapped{}; - HttpSysServer& m_HttpServer; - HttpSysRequestHandler* m_CompletionHandler{nullptr}; // Tracks which handler is due to handle the next I/O completion event - RwLock m_CompletionMutex; - InitialRequestHandler m_InitialHttpHandler{*this}; + OVERLAPPED m_HttpOverlapped{}; + HttpSysServer& m_HttpServer; + + // Tracks which handler is due to handle the next I/O completion event + HttpSysRequestHandler* m_CompletionHandler = nullptr; + RwLock m_CompletionMutex; + InitialRequestHandler m_InitialHttpHandler{*this}; + std::optional m_HandlerRequest; + Ref m_PackageHandler; }; ////////////////////////////////////////////////////////////////////////// @@ -435,22 +265,19 @@ public: virtual void IssueRequest(std::error_code& ErrorCode) override final; virtual HttpSysRequestHandler* HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) override; - - void SuppressResponseBody(); + void SuppressResponseBody(); // typically used for HEAD requests private: std::vector m_HttpDataChunks; - uint64_t m_TotalDataSize = 0; // Sum of all chunk sizes - - uint16_t m_ResponseCode = 0; - uint32_t m_NextDataChunkOffset = 0; // This is used for responses where the number of chunks exceed the maximum number for one API call - uint32_t m_RemainingChunkCount = 0; - bool m_IsInitialResponse = true; - HttpContentType m_ContentType = HttpContentType::kBinary; - - void Initialize(uint16_t ResponseCode, std::span Blobs); - - std::vector m_DataBuffers; + uint64_t m_TotalDataSize = 0; // Sum of all chunk sizes + uint16_t m_ResponseCode = 0; + uint32_t m_NextDataChunkOffset = 0; // Cursor used for very large chunk lists + uint32_t m_RemainingChunkCount = 0; // Backlog for multi-call sends + bool m_IsInitialResponse = true; + HttpContentType m_ContentType = HttpContentType::kBinary; + std::vector m_DataBuffers; + + void InitializeForPayload(uint16_t ResponseCode, std::span Blobs); }; HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode) @@ -458,7 +285,7 @@ HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InReq { std::array EmptyBufferList; - Initialize(ResponseCode, EmptyBufferList); + InitializeForPayload(ResponseCode, EmptyBufferList); } HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, uint16_t ResponseCode, std::string_view Message) @@ -468,7 +295,7 @@ HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InReq IoBuffer MessageBuffer(IoBuffer::Wrap, Message.data(), Message.size()); std::array SingleBufferList({MessageBuffer}); - Initialize(ResponseCode, SingleBufferList); + InitializeForPayload(ResponseCode, SingleBufferList); } HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, @@ -482,7 +309,7 @@ HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InReq IoBuffer MessageBuffer(IoBuffer::Wrap, Payload, PayloadSize); std::array SingleBufferList({MessageBuffer}); - Initialize(ResponseCode, SingleBufferList); + InitializeForPayload(ResponseCode, SingleBufferList); } HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InRequest, @@ -490,10 +317,9 @@ HttpMessageResponseRequest::HttpMessageResponseRequest(HttpSysTransaction& InReq HttpContentType ContentType, std::span BlobList) : HttpSysRequestHandler(InRequest) +, m_ContentType(ContentType) { - Initialize(ResponseCode, BlobList); - - ZEN_UNUSED(ContentType); + InitializeForPayload(ResponseCode, BlobList); } HttpMessageResponseRequest::~HttpMessageResponseRequest() @@ -501,7 +327,7 @@ HttpMessageResponseRequest::~HttpMessageResponseRequest() } void -HttpMessageResponseRequest::Initialize(uint16_t ResponseCode, std::span BlobList) +HttpMessageResponseRequest::InitializeForPayload(uint16_t ResponseCode, std::span BlobList) { m_ResponseCode = ResponseCode; @@ -908,10 +734,10 @@ HttpSysServer::IssueNewRequestMaybe() std::unique_ptr Request = std::make_unique(*this); - std::error_code ec; - Request->IssueInitialRequest(ec); + std::error_code ErrorCode; + Request->IssueInitialRequest(ErrorCode); - if (ec) + if (ErrorCode) { // No request was actually issued. What is the appropriate response? @@ -1054,12 +880,12 @@ HttpSysTransaction::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTran { try { - std::error_code ec; - m_CompletionHandler->IssueRequest(ec); + std::error_code ErrorCode; + m_CompletionHandler->IssueRequest(ErrorCode); - if (ec) + if (ErrorCode) { - spdlog::error("IssueRequest() failed {}"sv, ec.message()); + spdlog::error("IssueRequest() failed {}"sv, ErrorCode.message()); } else { @@ -1095,6 +921,66 @@ HttpSysTransaction::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTran return Status::kDone; } +HttpSysServerRequest& +HttpSysTransaction::InvokeRequestHandler(HttpService& Service, IoBuffer Payload) +{ + HttpSysServerRequest& ThisRequest = m_HandlerRequest.emplace(*this, Service, Payload); + + if ((ThisRequest.RequestContentType() == HttpContentType::kCbPackageOffer) && (ThisRequest.RequestVerb() == HttpVerb::kPost)) + { + // The client is presenting us with a package attachments offer, we need + // to filter it down to the list of attachments we need them to send in + // the follow-up request + + m_PackageHandler = Service.HandlePackageRequest(ThisRequest); + + if (m_PackageHandler) + { + CbObject OfferMessage = LoadCompactBinaryObject(Payload); + + std::vector OfferCids; + + for (auto& CidEntry : OfferMessage["offer"]) + { + if (!CidEntry.IsHash()) + { + // Should yield bad request response? + + continue; + } + + OfferCids.push_back(CidEntry.AsHash(IoHash::Zero)); + } + + m_PackageHandler->FilterOffer(OfferCids); + + CbObjectWriter ResponseWriter; + ResponseWriter.BeginArray("need"); + + for (const IoHash& Cid : OfferCids) + { + ResponseWriter.AddHash(Cid); + } + + ResponseWriter.EndArray(); + + // Emit filter response + ThisRequest.WriteResponse(HttpResponseCode::OK, ResponseWriter.Save()); + + return ThisRequest; + } + } + else if ((ThisRequest.RequestContentType() == HttpContentType::kCbPackage) && (ThisRequest.RequestVerb() == HttpVerb::kPost)) + { + } + + // Default request handling + + Service.HandleRequest(ThisRequest); + + return ThisRequest; +} + ////////////////////////////////////////////////////////////////////////// HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& Service, IoBuffer PayloadBuffer) @@ -1137,6 +1023,62 @@ HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& m_AcceptType = GetAcceptType(HttpRequestPtr); } +Oid +HttpSysServerRequest::SessionId() const +{ + if (m_Flags & kHaveSessionId) + { + return m_SessionId; + } + + const HTTP_REQUEST* HttpRequestPtr = m_HttpTx.HttpRequest(); + + for (int i = 0; i < HttpRequestPtr->Headers.UnknownHeaderCount; ++i) + { + HTTP_UNKNOWN_HEADER& Header = HttpRequestPtr->Headers.pUnknownHeaders[i]; + std::string_view HeaderName{Header.pName, Header.NameLength}; + + if (HeaderName == "UE-Session"sv) + { + if (Header.RawValueLength == Oid::StringLength) + { + m_SessionId = Oid::FromHexString({Header.pRawValue, Header.RawValueLength}); + } + } + } + + m_Flags |= kHaveSessionId; + + return m_SessionId; +} + +uint32_t +HttpSysServerRequest::RequestId() const +{ + if (m_Flags & kHaveRequestId) + { + return m_RequestId; + } + + const HTTP_REQUEST* HttpRequestPtr = m_HttpTx.HttpRequest(); + + for (int i = 0; i < HttpRequestPtr->Headers.UnknownHeaderCount; ++i) + { + HTTP_UNKNOWN_HEADER& Header = HttpRequestPtr->Headers.pUnknownHeaders[i]; + std::string_view HeaderName{Header.pName, Header.NameLength}; + + if (HeaderName == "UE-Request"sv) + { + std::string_view RequestValue{Header.pRawValue, Header.RawValueLength}; + std::from_chars(RequestValue.data(), RequestValue.data() + RequestValue.size(), m_RequestId); + } + } + + m_Flags |= kHaveRequestId; + + return m_RequestId; +} + IoBuffer HttpSysServerRequest::ReadPayload() { @@ -1146,47 +1088,47 @@ HttpSysServerRequest::ReadPayload() void HttpSysServerRequest::WriteResponse(HttpResponseCode ResponseCode) { - ZEN_ASSERT(m_IsHandled == false); + ZEN_ASSERT(IsHandled() == false); m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)ResponseCode); - if (m_SuppressBody) + if (SuppressBody()) { m_Response->SuppressResponseBody(); } - m_IsHandled = true; + SetIsHandled(); } void HttpSysServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span Blobs) { - ZEN_ASSERT(m_IsHandled == false); + ZEN_ASSERT(IsHandled() == false); m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)ResponseCode, ContentType, Blobs); - if (m_SuppressBody) + if (SuppressBody()) { m_Response->SuppressResponseBody(); } - m_IsHandled = true; + SetIsHandled(); } void HttpSysServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) { - ZEN_ASSERT(m_IsHandled == false); + ZEN_ASSERT(IsHandled() == false); m_Response = new HttpMessageResponseRequest(m_HttpTx, (uint16_t)ResponseCode, ContentType, ResponseString.data(), ResponseString.size()); - if (m_SuppressBody) + if (SuppressBody()) { m_Response->SuppressResponseBody(); } - m_IsHandled = true; + SetIsHandled(); } ////////////////////////////////////////////////////////////////////////// @@ -1388,18 +1330,16 @@ InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesT // Body received completely - call request handler - HttpSysServerRequest ThisRequest(Transaction(), *Service, m_PayloadBuffer); - - Service->HandleRequest(ThisRequest); + HttpSysServerRequest& ThisRequest = Transaction().InvokeRequestHandler(*Service, m_PayloadBuffer); if (!ThisRequest.IsHandled()) { return new HttpMessageResponseRequest(Transaction(), 404, "Not found"sv); } - if (ThisRequest.m_Response) + if (HttpMessageResponseRequest* Response = ThisRequest.m_Response) { - return ThisRequest.m_Response; + return Response; } } else -- cgit v1.2.3 From 4e2649977d034b913413d2cb35d4a88afc30393f Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 13 Sep 2021 12:24:59 +0200 Subject: Changed interface for httpServerRequest::SessionId()/RequestId() so they share storage and lazy eval logic They now call into ParseSessionId()/ParseRequestId() when required Eliminates redundant logic in derived implementations Also moved package transport code into httpshared.(cpp|h) for easier sharing with client code Added some I/O error reporting in http.sys related code Changed IHttpPackageHandler interface to support partially updated handling flow --- zenhttp/httpsys.cpp | 139 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 54 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index f5f9d4249..cde371f2f 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -145,7 +146,10 @@ private: /** * This is the handler for the initial HTTP I/O request which will receive the headers - * and however much of the remaining payload might fit in the embedded request buffer + * and however much of the remaining payload might fit in the embedded request buffer. + * + * It is also used to receive any entity body data relating to the request + * */ struct InitialRequestHandler : public HttpSysRequestHandler { @@ -176,8 +180,8 @@ public: HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& Service, IoBuffer PayloadBuffer); ~HttpSysServerRequest() = default; - virtual Oid SessionId() const override; - virtual uint32_t RequestId() const override; + virtual Oid ParseSessionId() const override; + virtual uint32_t ParseRequestId() const override; virtual IoBuffer ReadPayload() override; virtual void WriteResponse(HttpResponseCode ResponseCode) override; @@ -409,7 +413,14 @@ HttpSysRequestHandler* HttpMessageResponseRequest::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesTransferred) { ZEN_UNUSED(NumberOfBytesTransferred); - ZEN_UNUSED(IoResult); + + if (IoResult) + { + spdlog::warn("response aborted due to error: '{}'", GetWindowsErrorAsString(IoResult)); + + // if one transmit failed there's really no need to go on + return nullptr; + } if (m_RemainingChunkCount == 0) { @@ -438,6 +449,21 @@ HttpMessageResponseRequest::IssueRequest(std::error_code& ErrorCode) m_RemainingChunkCount -= ThisRequestChunkCount; m_NextDataChunkOffset += ThisRequestChunkCount; + /* Should this code also use HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA? + + From the docs: + + This flag enables buffering of data in the kernel on a per-response basis. It should + be used by an application doing synchronous I/O, or by a an application doing + asynchronous I/O with no more than one send outstanding at a time. + + Applications using asynchronous I/O which may have more than one send outstanding at + a time should not use this flag. + + When this flag is set, it should be used consistently in calls to the + HttpSendHttpResponse function as well. + */ + ULONG SendFlags = 0; if (m_RemainingChunkCount) @@ -526,10 +552,14 @@ HttpMessageResponseRequest::IssueRequest(std::error_code& ErrorCode) CancelThreadpoolIo(Iocp); - spdlog::error("failed to send HTTP response (error: {}) URL: {}"sv, SendResult, HttpReq->pRawUrl); + spdlog::error("failed to send HTTP response (error: '{}'), request URL: {}"sv, SendResult, HttpReq->pRawUrl); ErrorCode = MakeWin32ErrorCode(SendResult); } + else + { + ErrorCode = {}; + } } /** @@ -926,53 +956,66 @@ HttpSysTransaction::InvokeRequestHandler(HttpService& Service, IoBuffer Payload) { HttpSysServerRequest& ThisRequest = m_HandlerRequest.emplace(*this, Service, Payload); - if ((ThisRequest.RequestContentType() == HttpContentType::kCbPackageOffer) && (ThisRequest.RequestVerb() == HttpVerb::kPost)) + if (ThisRequest.RequestVerb() == HttpVerb::kPost) { - // The client is presenting us with a package attachments offer, we need - // to filter it down to the list of attachments we need them to send in - // the follow-up request - - m_PackageHandler = Service.HandlePackageRequest(ThisRequest); - - if (m_PackageHandler) + if (ThisRequest.RequestContentType() == HttpContentType::kCbPackageOffer) { - CbObject OfferMessage = LoadCompactBinaryObject(Payload); + // The client is presenting us with a package attachments offer, we need + // to filter it down to the list of attachments we need them to send in + // the follow-up request - std::vector OfferCids; + m_PackageHandler = Service.HandlePackageRequest(ThisRequest); - for (auto& CidEntry : OfferMessage["offer"]) + if (m_PackageHandler) { - if (!CidEntry.IsHash()) + CbObject OfferMessage = LoadCompactBinaryObject(Payload); + + std::vector OfferCids; + + for (auto& CidEntry : OfferMessage["offer"]) { - // Should yield bad request response? + if (!CidEntry.IsHash()) + { + // Should yield bad request response? + + continue; + } - continue; + OfferCids.push_back(CidEntry.AsHash(IoHash::Zero)); } - OfferCids.push_back(CidEntry.AsHash(IoHash::Zero)); - } + m_PackageHandler->FilterOffer(OfferCids); - m_PackageHandler->FilterOffer(OfferCids); + CbObjectWriter ResponseWriter; + ResponseWriter.BeginArray("need"); - CbObjectWriter ResponseWriter; - ResponseWriter.BeginArray("need"); + for (const IoHash& Cid : OfferCids) + { + ResponseWriter.AddHash(Cid); + } - for (const IoHash& Cid : OfferCids) - { - ResponseWriter.AddHash(Cid); + ResponseWriter.EndArray(); + + // Emit filter response + ThisRequest.WriteResponse(HttpResponseCode::OK, ResponseWriter.Save()); + + return ThisRequest; } + } + else if (ThisRequest.RequestContentType() == HttpContentType::kCbPackage) + { + // Process chunks in package request - ResponseWriter.EndArray(); + m_PackageHandler = Service.HandlePackageRequest(ThisRequest); - // Emit filter response - ThisRequest.WriteResponse(HttpResponseCode::OK, ResponseWriter.Save()); + if (m_PackageHandler) + { + CbPackage Package; - return ThisRequest; + Package.TryLoad(ThisRequest.ReadPayload()); + } } } - else if ((ThisRequest.RequestContentType() == HttpContentType::kCbPackage) && (ThisRequest.RequestVerb() == HttpVerb::kPost)) - { - } // Default request handling @@ -1024,13 +1067,8 @@ HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService& } Oid -HttpSysServerRequest::SessionId() const +HttpSysServerRequest::ParseSessionId() const { - if (m_Flags & kHaveSessionId) - { - return m_SessionId; - } - const HTTP_REQUEST* HttpRequestPtr = m_HttpTx.HttpRequest(); for (int i = 0; i < HttpRequestPtr->Headers.UnknownHeaderCount; ++i) @@ -1042,24 +1080,17 @@ HttpSysServerRequest::SessionId() const { if (Header.RawValueLength == Oid::StringLength) { - m_SessionId = Oid::FromHexString({Header.pRawValue, Header.RawValueLength}); + return Oid::FromHexString({Header.pRawValue, Header.RawValueLength}); } } } - m_Flags |= kHaveSessionId; - - return m_SessionId; + return {}; } uint32_t -HttpSysServerRequest::RequestId() const +HttpSysServerRequest::ParseRequestId() const { - if (m_Flags & kHaveRequestId) - { - return m_RequestId; - } - const HTTP_REQUEST* HttpRequestPtr = m_HttpTx.HttpRequest(); for (int i = 0; i < HttpRequestPtr->Headers.UnknownHeaderCount; ++i) @@ -1070,13 +1101,13 @@ HttpSysServerRequest::RequestId() const if (HeaderName == "UE-Request"sv) { std::string_view RequestValue{Header.pRawValue, Header.RawValueLength}; - std::from_chars(RequestValue.data(), RequestValue.data() + RequestValue.size(), m_RequestId); + uint32_t RequestId = 0; + std::from_chars(RequestValue.data(), RequestValue.data() + RequestValue.size(), RequestId); + return RequestId; } } - m_Flags |= kHaveRequestId; - - return m_RequestId; + return 0; } IoBuffer -- cgit v1.2.3 From b53605d212a40cf9ebe5f18029eaf139c4952175 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 13 Sep 2021 21:44:53 +0200 Subject: Changed package parsing test code --- zenhttp/httpsys.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index cde371f2f..737cee509 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -2,6 +2,8 @@ #include "httpsys.h" +#include "httpshared.h" + #include #include #include @@ -1010,9 +1012,7 @@ HttpSysTransaction::InvokeRequestHandler(HttpService& Service, IoBuffer Payload) if (m_PackageHandler) { - CbPackage Package; - - Package.TryLoad(ThisRequest.ReadPayload()); + CbPackage Package = ParsePackageMessage(ThisRequest.ReadPayload()); } } } -- cgit v1.2.3 From f277fad0ea747807021bb92ae8fd026384901fb7 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 13 Sep 2021 22:25:03 +0200 Subject: Implemented intended package streaming API flow (but currently it "streams" from memory) --- zenhttp/httpsys.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'zenhttp/httpsys.cpp') diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp index 737cee509..9ee004c5c 100644 --- a/zenhttp/httpsys.cpp +++ b/zenhttp/httpsys.cpp @@ -1010,9 +1010,18 @@ HttpSysTransaction::InvokeRequestHandler(HttpService& Service, IoBuffer Payload) m_PackageHandler = Service.HandlePackageRequest(ThisRequest); + // TODO: this should really be done in a streaming fashion, currently this emulates + // the intended flow from an API perspective + if (m_PackageHandler) { - CbPackage Package = ParsePackageMessage(ThisRequest.ReadPayload()); + m_PackageHandler->OnRequestBegin(); + + auto CreateBuffer = [&](const IoHash& Cid, uint64_t Size) -> IoBuffer { return m_PackageHandler->CreateTarget(Cid, Size); }; + + CbPackage Package = ParsePackageMessage(ThisRequest.ReadPayload(), CreateBuffer); + + m_PackageHandler->OnRequestComplete(); } } } @@ -1313,7 +1322,8 @@ InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesT { if (m_IsInitialRequest) { - m_ContentLength = GetContentLength(HttpReq); + m_ContentLength = GetContentLength(HttpReq); + const HttpContentType ContentType = GetContentType(HttpReq); if (m_ContentLength) { @@ -1321,8 +1331,6 @@ InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesT // into our embedded request buffer m_PayloadBuffer = IoBuffer(m_ContentLength); - - HttpContentType ContentType = GetContentType(HttpReq); m_PayloadBuffer.SetContentType(ContentType); uint64_t BytesToRead = m_ContentLength; -- cgit v1.2.3