// Copyright Epic Games, Inc. All Rights Reserved. #include #include "httptracer.h" #if ZEN_WITH_PLUGINS # include "httpparser.h" # include # include # include # include # include # include # include # include # include # include # include # include # include # if ZEN_PLATFORM_WINDOWS # include # endif namespace zen { struct HttpPluginServerImpl; struct HttpPluginResponse; using namespace std::literals; const FLLMTag& GetHttppluginTag() { static FLLMTag _("httpplugin"); return _; } ////////////////////////////////////////////////////////////////////////// struct HttpPluginConnectionHandler : public TransportServerConnection, public HttpRequestParserCallbacks, RefCounted { HttpPluginConnectionHandler(); ~HttpPluginConnectionHandler(); // TransportServerConnection virtual uint32_t AddRef() const override; virtual uint32_t Release() const override; virtual void OnBytesRead(const void* Buffer, size_t DataSize) override; // HttpRequestParserCallbacks virtual void HandleRequest() override; virtual void TerminateConnection() override; void Initialize(TransportConnection* Transport, HttpPluginServerImpl& Server, uint32_t ConnectionId); private: enum class RequestState { kInitialState, kInitialRead, kReadingMore, kWriting, // Currently writing response, connection will be re-used kWritingFinal, // Writing response, connection will be closed kDone, kTerminated }; RequestState m_RequestState = RequestState::kInitialState; HttpRequestParser m_RequestParser{*this}; uint32_t m_ConnectionId = 0; std::atomic_uint32_t m_RequestCounter = 0; Ref m_PackageHandler; TransportConnection* m_TransportConnection = nullptr; HttpPluginServerImpl* m_Server = nullptr; }; ////////////////////////////////////////////////////////////////////////// struct HttpPluginServerImpl : public HttpPluginServer, TransportServer { HttpPluginServerImpl(); ~HttpPluginServerImpl(); // HttpPluginServer virtual void OnRegisterService(HttpService& Service) override; virtual void OnSetHttpRequestFilter(IHttpRequestFilter* RequestFilter) override; virtual int OnInitialize(int BasePort, std::filesystem::path DataDir) override; virtual void OnRun(bool IsInteractiveSession) override; virtual void OnRequestExit() override; virtual void OnClose() override; virtual void AddPlugin(Ref Plugin) override; virtual void RemovePlugin(Ref Plugin) override; HttpService* RouteRequest(std::string_view Url); IHttpRequestFilter::Result FilterRequest(HttpServerRequest& Request); struct ServiceEntry { std::string ServiceUrlPath; HttpService* Service; }; std::atomic m_HttpRequestFilter = nullptr; bool m_IsInitialized = false; RwLock m_Lock; std::vector m_UriHandlers; std::vector> m_Plugins; Event m_ShutdownEvent; bool m_IsRequestLoggingEnabled = false; LoggerRef m_RequestLog; std::atomic_uint32_t m_ConnectionIdCounter{0}; int m_BasePort; HttpServerTracer m_RequestTracer; // TransportServer virtual TransportServerConnection* CreateConnectionHandler(TransportConnection* Connection) override; virtual int GetBasePort() const override; }; /** This is the class which request handlers interface with when generating responses */ class HttpPluginServerRequest : public HttpServerRequest { public: HttpPluginServerRequest(HttpRequestParser& Request, HttpService& Service, IoBuffer PayloadBuffer); ~HttpPluginServerRequest(); HttpPluginServerRequest(const HttpPluginServerRequest&) = delete; HttpPluginServerRequest& operator=(const HttpPluginServerRequest&) = delete; // As this is plugin transport connection used for specialized connections we assume it is not a machine local connection virtual bool IsLocalMachineRequest() const /* override*/ { return false; } virtual std::string_view GetAuthorizationHeader() const override; virtual Oid ParseSessionId() const override; virtual uint32_t ParseRequestId() 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; virtual void WriteResponseAsync(std::function&& ContinuationHandler) override; virtual bool TryGetRanges(HttpRanges& Ranges) override; using HttpServerRequest::WriteResponse; HttpRequestParser& m_Request; IoBuffer m_PayloadBuffer; std::unique_ptr m_Response; }; ////////////////////////////////////////////////////////////////////////// struct HttpPluginResponse { public: HttpPluginResponse() = default; explicit HttpPluginResponse(HttpContentType ContentType) : m_ContentType(ContentType) {} HttpPluginResponse(const HttpPluginResponse&) = delete; HttpPluginResponse& operator=(const HttpPluginResponse&) = delete; void InitializeForPayload(uint16_t ResponseCode, std::span BlobList); inline uint16_t ResponseCode() const { return m_ResponseCode; } inline uint64_t ContentLength() const { return m_ContentLength; } inline HttpContentType ContentType() const { return m_ContentType; } const std::vector& ResponseBuffers() const { return m_ResponseBuffers; } void SuppressPayload() { m_ResponseBuffers.resize(1); } std::string_view GetHeaders(); private: uint16_t m_ResponseCode = 0; bool m_IsKeepAlive = true; HttpContentType m_ContentType = HttpContentType::kBinary; uint64_t m_ContentLength = 0; std::vector m_ResponseBuffers; ExtendableStringBuilder<160> m_Headers; }; void HttpPluginResponse::InitializeForPayload(uint16_t ResponseCode, std::span BlobList) { ZEN_MEMSCOPE(GetHttppluginTag()); ZEN_TRACE_CPU("http_plugin::InitializeForPayload"); m_ResponseCode = ResponseCode; const uint32_t ChunkCount = gsl::narrow(BlobList.size()); m_ResponseBuffers.reserve(ChunkCount + 1); m_ResponseBuffers.push_back({}); // Placeholder for header uint64_t TotalDataSize = 0; for (IoBuffer& Buffer : BlobList) { uint64_t BufferDataSize = Buffer.Size(); ZEN_ASSERT(BufferDataSize); TotalDataSize += BufferDataSize; IoBufferFileReference FileRef; if (Buffer.GetFileReference(/* out */ FileRef)) { // TODO: Use direct file transfer, via TransmitFile/sendfile m_ResponseBuffers.emplace_back(std::move(Buffer)).MakeOwned(); } else { // Send from memory m_ResponseBuffers.emplace_back(std::move(Buffer)).MakeOwned(); } } m_ContentLength = TotalDataSize; auto Headers = GetHeaders(); m_ResponseBuffers[0] = IoBufferBuilder::MakeCloneFromMemory(Headers.data(), Headers.size()); } std::string_view HttpPluginResponse::GetHeaders() { ZEN_MEMSCOPE(GetHttppluginTag()); if (m_Headers.Size() == 0) { m_Headers << "HTTP/1.1 " << ResponseCode() << " " << ReasonStringForHttpResultCode(ResponseCode()) << "\r\n" << "Content-Type: " << MapContentTypeToString(m_ContentType) << "\r\n" << "Content-Length: " << ContentLength() << "\r\n"sv; if (!m_IsKeepAlive) { m_Headers << "Connection: close\r\n"sv; } m_Headers << "\r\n"sv; } return m_Headers; } ////////////////////////////////////////////////////////////////////////// HttpPluginConnectionHandler::HttpPluginConnectionHandler() { } HttpPluginConnectionHandler::~HttpPluginConnectionHandler() { if (m_Server) { ZEN_LOG_TRACE(m_Server->m_RequestLog, "END connection #{}", m_ConnectionId); } } void HttpPluginConnectionHandler::Initialize(TransportConnection* Transport, HttpPluginServerImpl& Server, uint32_t ConnectionId) { ZEN_MEMSCOPE(GetHttppluginTag()); m_TransportConnection = Transport; m_Server = &Server; m_ConnectionId = ConnectionId; std::string_view ConnectionName; if (const char* Name = Transport->GetDebugName()) { ConnectionName = Name; } else { ConnectionName = "anonymous"; } ZEN_LOG_TRACE(m_Server->m_RequestLog, "NEW connection #{} ('')", m_ConnectionId, ConnectionName); } uint32_t HttpPluginConnectionHandler::AddRef() const { return RefCounted::AddRef(); } uint32_t HttpPluginConnectionHandler::Release() const { return RefCounted::Release(); } void HttpPluginConnectionHandler::OnBytesRead(const void* Buffer, size_t AvailableBytes) { ZEN_MEMSCOPE(GetHttppluginTag()); ZEN_ASSERT(m_Server); ZEN_LOG_TRACE(m_Server->m_RequestLog, "connection #{} OnBytesRead: {}", m_ConnectionId, AvailableBytes); while (AvailableBytes) { const size_t ConsumedBytes = m_RequestParser.ConsumeData((const char*)Buffer, AvailableBytes); if (ConsumedBytes == ~0ull) { // request parser error -- terminate connection ZEN_LOG_TRACE(m_Server->m_RequestLog, "connection #{} terminating due to request parsing error", m_ConnectionId); return TerminateConnection(); } Buffer = reinterpret_cast(Buffer) + ConsumedBytes; AvailableBytes -= ConsumedBytes; } } // HttpRequestParserCallbacks void HttpPluginConnectionHandler::HandleRequest() { ZEN_MEMSCOPE(GetHttppluginTag()); ZEN_ASSERT(m_Server); const uint32_t RequestNumber = m_RequestCounter.fetch_add(1); ZEN_LOG_TRACE(m_Server->m_RequestLog, "connection #{} ENTER HandleRequest #{}", m_ConnectionId, RequestNumber); auto $Exit = MakeGuard([&] { ZEN_LOG_TRACE(m_Server->m_RequestLog, "connection #{} EXIT HandleRequest #{}", m_ConnectionId, RequestNumber); }); if (!m_RequestParser.IsKeepAlive()) { // Once response has been written, connection is done m_RequestState = RequestState::kWritingFinal; const bool Receive = true; // We're not going to read any more data from this socket const bool Transmit = false; // We will write more data however m_TransportConnection->Shutdown(Receive, Transmit); } else { m_RequestState = RequestState::kWriting; } auto SendBuffer = [&](const IoBuffer& InBuffer) -> int64_t { const char* Buffer = reinterpret_cast(InBuffer.GetData()); size_t Bytes = InBuffer.GetSize(); return m_TransportConnection->WriteBytes(Buffer, Bytes); }; // Generate response if (HttpService* Service = m_Server->RouteRequest(m_RequestParser.Url())) { ZEN_TRACE_CPU("http_plugin::HandleRequest"); HttpPluginServerRequest Request(m_RequestParser, *Service, m_RequestParser.Body()); const HttpVerb RequestVerb = Request.RequestVerb(); const std::string_view Uri = Request.RelativeUri(); if (m_Server->m_RequestLog.ShouldLog(logging::level::Trace)) { ZEN_LOG_TRACE(m_Server->m_RequestLog, "connection #{} Handling Request: {} {} ({} bytes ({}), accept: {})", m_ConnectionId, ToString(RequestVerb), Uri, Request.ContentLength(), ToString(Request.RequestContentType()), ToString(Request.AcceptContentType())); m_Server->m_RequestTracer.WriteDebugPayload(fmt::format("request_{}_{}.bin", m_ConnectionId, RequestNumber), std::vector{Request.ReadPayload()}); } IHttpRequestFilter::Result FilterResult = m_Server->FilterRequest(Request); if (FilterResult == IHttpRequestFilter::Result::Accepted) { if (!HandlePackageOffers(*Service, Request, m_PackageHandler)) { try { Service->HandleRequest(Request); } catch (const AssertException& AssertEx) { // Drop any partially formatted response Request.m_Response.reset(); ZEN_ERROR("Caught assert exception while handling request: {}", AssertEx.FullDescription()); Request.WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, AssertEx.FullDescription()); } catch (const std::system_error& SystemError) { // Drop any partially formatted response Request.m_Response.reset(); if (IsOOM(SystemError.code()) || IsOOD(SystemError.code())) { Request.WriteResponse(HttpResponseCode::InsufficientStorage, HttpContentType::kText, SystemError.what()); } else { ZEN_WARN("Caught system error exception while handling request: {}. ({})", SystemError.what(), SystemError.code().value()); Request.WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, SystemError.what()); } } catch (const std::bad_alloc& BadAlloc) { // Drop any partially formatted response Request.m_Response.reset(); Request.WriteResponse(HttpResponseCode::InsufficientStorage, HttpContentType::kText, BadAlloc.what()); } catch (const std::exception& ex) { // Drop any partially formatted response Request.m_Response.reset(); ZEN_WARN("Caught exception while handling request: {}", ex.what()); Request.WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, ex.what()); } } } else if (FilterResult == IHttpRequestFilter::Result::Forbidden) { Request.WriteResponse(HttpResponseCode::Forbidden); } else { ZEN_ASSERT(FilterResult == IHttpRequestFilter::Result::ResponseSent); } if (std::unique_ptr Response = std::move(Request.m_Response)) { { const uint16_t ResponseCode = Response->ResponseCode(); ZEN_LOG_TRACE(m_Server->m_RequestLog, "connection #{} Response: {} {} ({} bytes, {})", m_ConnectionId, ResponseCode, ToString(HttpResponseCode(ResponseCode)), Response->ContentLength(), ToString(Response->ContentType())); } // Transmit the response if (m_RequestParser.RequestVerb() == HttpVerb::kHead) { Response->SuppressPayload(); } const std::vector& ResponseBuffers = Response->ResponseBuffers(); if (m_Server->m_RequestLog.ShouldLog(logging::level::Trace)) { m_Server->m_RequestTracer.WriteDebugPayload(fmt::format("response_{}_{}.bin", m_ConnectionId, RequestNumber), ResponseBuffers); } for (const IoBuffer& Buffer : ResponseBuffers) { ZEN_LOG_TRACE(m_Server->m_RequestLog, "connection #{} SEND: {} bytes, {}", m_ConnectionId, Buffer.GetSize(), ToString(Buffer.GetContentType())); int64_t SentBytes = SendBuffer(Buffer); if (SentBytes < 0) { TerminateConnection(); return; } } return; } } // No route found for request std::string_view Response; if (m_RequestParser.RequestVerb() == HttpVerb::kHead) { if (m_RequestParser.IsKeepAlive()) { Response = "HTTP/1.1 404 NOT FOUND\r\n" "\r\n"sv; } else { Response = "HTTP/1.1 404 NOT FOUND\r\n" "Connection: close\r\n" "\r\n"sv; } } else { if (m_RequestParser.IsKeepAlive()) { Response = "HTTP/1.1 404 NOT FOUND\r\n" "Content-Length: 23\r\n" "Content-Type: text/plain\r\n" "\r\n" "No suitable route found"sv; } else { Response = "HTTP/1.1 404 NOT FOUND\r\n" "Content-Length: 23\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n" "\r\n" "No suitable route found"sv; } } const int64_t SentBytes = SendBuffer(IoBufferBuilder::MakeFromMemory(MakeMemoryView(Response))); if (SentBytes < 0) { TerminateConnection(); return; } } void HttpPluginConnectionHandler::TerminateConnection() { ZEN_MEMSCOPE(GetHttppluginTag()); ZEN_ASSERT(m_TransportConnection); m_TransportConnection->CloseConnection(); } ////////////////////////////////////////////////////////////////////////// HttpPluginServerRequest::HttpPluginServerRequest(HttpRequestParser& Request, HttpService& Service, IoBuffer PayloadBuffer) : HttpServerRequest(Service) , m_Request(Request) , m_PayloadBuffer(std::move(PayloadBuffer)) { ZEN_MEMSCOPE(GetHttppluginTag()); const int PrefixLength = Service.UriPrefixLength(); std::string_view Uri = Request.Url(); Uri.remove_prefix(std::min(PrefixLength, static_cast(Uri.size()))); m_Uri = Uri; m_UriWithExtension = Uri; m_QueryString = Request.QueryString(); m_Verb = Request.RequestVerb(); m_ContentLength = Request.Body().Size(); m_ContentType = Request.ContentType(); HttpContentType AcceptContentType = HttpContentType::kUnknownContentType; // Parse any extension, to allow requesting a particular response encoding via the URL { std::string_view UriSuffix8{m_Uri}; const size_t LastComponentIndex = UriSuffix8.find_last_of('/'); if (LastComponentIndex != std::string_view::npos) { UriSuffix8.remove_prefix(LastComponentIndex); } const size_t LastDotIndex = UriSuffix8.find_last_of('.'); if (LastDotIndex != std::string_view::npos) { UriSuffix8.remove_prefix(LastDotIndex + 1); AcceptContentType = ParseContentType(UriSuffix8); if (AcceptContentType != HttpContentType::kUnknownContentType) { m_Uri.remove_suffix(uint32_t(UriSuffix8.size() + 1)); } } } // It an explicit content type extension was specified then we'll use that over any // Accept: header value that may be present if (AcceptContentType != HttpContentType::kUnknownContentType) { m_AcceptType = AcceptContentType; } else { m_AcceptType = Request.AcceptType(); } } HttpPluginServerRequest::~HttpPluginServerRequest() { } std::string_view HttpPluginServerRequest::GetAuthorizationHeader() const { return m_Request.AuthorizationHeader(); } Oid HttpPluginServerRequest::ParseSessionId() const { return m_Request.SessionId(); } uint32_t HttpPluginServerRequest::ParseRequestId() const { return m_Request.RequestId(); } IoBuffer HttpPluginServerRequest::ReadPayload() { return m_PayloadBuffer; } void HttpPluginServerRequest::WriteResponse(HttpResponseCode ResponseCode) { ZEN_ASSERT(!m_Response); ZEN_MEMSCOPE(GetHttppluginTag()); m_Response.reset(new HttpPluginResponse(HttpContentType::kBinary)); std::array Empty; m_Response->InitializeForPayload((uint16_t)ResponseCode, Empty); } void HttpPluginServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span Blobs) { ZEN_ASSERT(!m_Response); ZEN_MEMSCOPE(GetHttppluginTag()); m_Response.reset(new HttpPluginResponse(ContentType)); m_Response->InitializeForPayload((uint16_t)ResponseCode, Blobs); } void HttpPluginServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) { ZEN_ASSERT(!m_Response); ZEN_MEMSCOPE(GetHttppluginTag()); m_Response.reset(new HttpPluginResponse(ContentType)); IoBuffer MessageBuffer(IoBuffer::Wrap, ResponseString.data(), ResponseString.size()); std::array SingleBufferList({MessageBuffer}); m_Response->InitializeForPayload((uint16_t)ResponseCode, SingleBufferList); } void HttpPluginServerRequest::WriteResponseAsync(std::function&& ContinuationHandler) { ZEN_ASSERT(!m_Response); ZEN_MEMSCOPE(GetHttppluginTag()); // Not one bit async, innit ContinuationHandler(*this); } bool HttpPluginServerRequest::TryGetRanges(HttpRanges& Ranges) { return TryParseHttpRangeHeader(m_Request.RangeHeader(), Ranges); } ////////////////////////////////////////////////////////////////////////// HttpPluginServerImpl::HttpPluginServerImpl() : m_RequestLog(logging::Get("http_requests")) { } HttpPluginServerImpl::~HttpPluginServerImpl() { } TransportServerConnection* HttpPluginServerImpl::CreateConnectionHandler(TransportConnection* Connection) { ZEN_MEMSCOPE(GetHttppluginTag()); HttpPluginConnectionHandler* Handler{new HttpPluginConnectionHandler()}; const uint32_t ConnectionId = m_ConnectionIdCounter.fetch_add(1); Handler->Initialize(Connection, *this, ConnectionId); return Handler; } int HttpPluginServerImpl::GetBasePort() const { return m_BasePort; } int HttpPluginServerImpl::OnInitialize(int InBasePort, std::filesystem::path DataDir) { ZEN_TRACE_CPU("HttpPluginServerImpl::Initialize"); ZEN_MEMSCOPE(GetHttppluginTag()); m_RequestTracer.Initialize(DataDir); m_BasePort = InBasePort; try { RwLock::ExclusiveLockScope _(m_Lock); for (auto& Plugin : m_Plugins) { try { Plugin->Initialize(this); } catch (const std::exception& Ex) { ZEN_WARN("exception caught during plugin initialization: {}", Ex.what()); } } } catch (const std::exception& ex) { ZEN_WARN("Caught exception starting http plugin server: {}", ex.what()); } m_IsInitialized = true; return m_BasePort; } void HttpPluginServerImpl::OnSetHttpRequestFilter(IHttpRequestFilter* RequestFilter) { RwLock::ExclusiveLockScope _(m_Lock); m_HttpRequestFilter.store(RequestFilter); } void HttpPluginServerImpl::OnClose() { if (!m_IsInitialized) return; ZEN_MEMSCOPE(GetHttppluginTag()); try { RwLock::ExclusiveLockScope _(m_Lock); for (auto& Plugin : m_Plugins) { try { Plugin->Shutdown(); } catch (const std::exception& Ex) { ZEN_WARN("exception caught during plugin shutdown: {}", Ex.what()); } Plugin = nullptr; } m_Plugins.clear(); } catch (const std::exception& ex) { ZEN_WARN("Caught exception stopping http plugin server: {}", ex.what()); } m_IsInitialized = false; } void HttpPluginServerImpl::OnRun(bool IsInteractive) { ZEN_MEMSCOPE(GetHttppluginTag()); const int WaitTimeout = 1000; # if ZEN_PLATFORM_WINDOWS if (IsInteractive) { ZEN_CONSOLE("Zen Server running (plugin HTTP). Press ESC or Q to quit"); } do { if (IsInteractive && _kbhit() != 0) { char c = (char)_getch(); if (c == 27 || c == 'Q' || c == 'q') { m_ShutdownEvent.Set(); RequestApplicationExit(0); } } m_ShutdownEvent.Wait(WaitTimeout); } while (!IsApplicationExitRequested()); # else if (IsInteractive) { ZEN_CONSOLE("Zen Server running (plugin HTTP). Ctrl-C to quit"); } do { m_ShutdownEvent.Wait(WaitTimeout); } while (!IsApplicationExitRequested()); # endif } void HttpPluginServerImpl::OnRequestExit() { m_ShutdownEvent.Set(); } void HttpPluginServerImpl::AddPlugin(Ref Plugin) { ZEN_MEMSCOPE(GetHttppluginTag()); RwLock::ExclusiveLockScope _(m_Lock); m_Plugins.emplace_back(std::move(Plugin)); } void HttpPluginServerImpl::RemovePlugin(Ref Plugin) { ZEN_MEMSCOPE(GetHttppluginTag()); RwLock::ExclusiveLockScope _(m_Lock); auto It = std::find(begin(m_Plugins), end(m_Plugins), Plugin); if (It != m_Plugins.end()) { m_Plugins.erase(It); } } void HttpPluginServerImpl::OnRegisterService(HttpService& Service) { ZEN_MEMSCOPE(GetHttppluginTag()); std::string_view UrlPath(Service.BaseUri()); Service.SetUriPrefixLength(UrlPath.size()); if (!UrlPath.empty() && UrlPath.back() == '/') { UrlPath.remove_suffix(1); } RwLock::ExclusiveLockScope _(m_Lock); m_UriHandlers.push_back({std::string(UrlPath), &Service}); } HttpService* HttpPluginServerImpl::RouteRequest(std::string_view Url) { ZEN_MEMSCOPE(GetHttppluginTag()); RwLock::SharedLockScope _(m_Lock); HttpService* CandidateService = nullptr; std::string::size_type CandidateMatchSize = 0; for (const ServiceEntry& SvcEntry : m_UriHandlers) { const std::string& SvcUrl = SvcEntry.ServiceUrlPath; const std::string::size_type SvcUrlSize = SvcUrl.size(); if ((SvcUrlSize >= CandidateMatchSize) && Url.compare(0, SvcUrlSize, SvcUrl) == 0 && ((SvcUrlSize == Url.size()) || (Url[SvcUrlSize] == '/'))) { CandidateMatchSize = SvcUrl.size(); CandidateService = SvcEntry.Service; } } return CandidateService; } IHttpRequestFilter::Result HttpPluginServerImpl::FilterRequest(HttpServerRequest& Request) { if (!m_HttpRequestFilter.load()) { return IHttpRequestFilter::Result::Accepted; } RwLock::SharedLockScope _(m_Lock); IHttpRequestFilter* RequestFilter = m_HttpRequestFilter.load(); if (!RequestFilter) { return IHttpRequestFilter::Result::Accepted; } return RequestFilter->FilterRequest(Request); } ////////////////////////////////////////////////////////////////////////// struct HttpPluginServerImpl; Ref CreateHttpPluginServer() { ZEN_MEMSCOPE(GetHttppluginTag()); return Ref(new HttpPluginServerImpl); } } // namespace zen #endif