# HG changeset patch # User František Kučera # Date 1649519446 -7200 # Node ID 37a86904145cbddacadbc9d80faf5049bd9e2d1c # Parent 1184f3de553383a4ffc2e53a3065aad6c7bb6353 partial implementation of response and header templates diff -r 1184f3de5533 -r 37a86904145c src/HTTPDHandler.h --- a/src/HTTPDHandler.h Fri Apr 08 22:38:45 2022 +0200 +++ b/src/HTTPDHandler.h Sat Apr 09 17:50:46 2022 +0200 @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include @@ -45,27 +47,146 @@ shared_ptr relationalWriter; Configuration configuration; std::shared_ptr httpServer; + relpipe::common::type::StringX currentRelationName; std::vector currentReaderMetadata; std::vector currentWriterMetadata; size_t currentAttributeIndex = 0; size_t currentRecordNumber = 1; + class RequestMatcher { + public: + std::regex method = std::regex(".*"); + std::regex url = std::regex(".*"); + + virtual ~RequestMatcher() = default; + + bool matches(const std::string& method, const std::string& url) const { + bool result = true; + result &= std::regex_match(method, this->method); + result &= std::regex_match(url, this->url); + return result; + } + }; + + class HeaderTemplate { + public: + std::string name; + std::string value; + + HeaderTemplate() { + } + + HeaderTemplate(std::string name, std::string value) : name(name), value(value) { + } + + virtual ~HeaderTemplate() = default; + }; + + class GlobalHeaderTemplate : public HeaderTemplate, public RequestMatcher { + } headerTemplate; + + std::vector headerTemplates; + + class ResponseTemplate : public RequestMatcher { + public: + std::string body; + uint16_t code = 200; + std::vector headers; + } responseTemplate; + + std::vector responseTemplates; + + std::mutex templatesMutex; // TODO: read-write lock (for responseTemplates and headerTemplates) + class RequestHandler : public HTTPServer::RequestHandler { + private: + std::vector* responseTemplates; + std::vector* headerTemplates; + std::mutex* templatesMutex; public: + RequestHandler(std::mutex* templatesMutex, std::vector* responseTemplates, std::vector* headerTemplates) : templatesMutex(templatesMutex), responseTemplates(responseTemplates), headerTemplates(headerTemplates) { + } + + virtual ~RequestHandler() = default; + const HTTPServer::Response handle(const HTTPServer::Request& request) override { HTTPServer::Response response; - // TODO: return real responses - response.code = 200; - response.body = "

greetings and salutations

"; + std::lock_guard lock(*templatesMutex); + for (ResponseTemplate t : *responseTemplates) { + if (t.matches(request.method, request.url)) { + response.code = t.code; + response.body = t.body; + // TODO: replace global header values with request-specific ones instead of appending? + for (const GlobalHeaderTemplate& h : *headerTemplates) if (h.matches(request.method, request.url)) response.header.push_back(HTTPServer::Header(h.name, h.value)); + for (const HeaderTemplate& h : t.headers) response.header.push_back(HTTPServer::Header(h.name, h.value)); + return response; + } + } + response.code = 404; + response.body = "

HTTP 404: Not Found

(no response template matched)

"; return response; } }; - std::shared_ptr requestHandler = std::make_shared(); + std::shared_ptr requestHandler = std::make_shared(&templatesMutex, &responseTemplates, &headerTemplates); + + relpipe::common::type::StringX getHeaderAttributePrefix() { + // might be configurable - parametrized + return L"header."; + } + + bool isHeaderAttribute(const relpipe::common::type::StringX& attributeName) { + return attributeName.rfind(getHeaderAttributePrefix(), 0) == 0; + } + + relpipe::common::type::StringX fetchHeaderName(const relpipe::common::type::StringX& attributeName) { + // TODO: recognize several modes: header.set.*, header.add.*, header.remove.* + return attributeName.substr(getHeaderAttributePrefix().size()); + } + + void headerTemplateAttribute(const relpipe::common::type::StringX& value) { + auto attributeName = currentReaderMetadata[currentAttributeIndex].getAttributeName(); + + if (attributeName == L"url") headerTemplate.url = std::regex(value.size() ? convertor.to_bytes(value) : ".*"); + else if (attributeName == L"method") headerTemplate.method = std::regex(value.size() ? convertor.to_bytes(value) : ".*"); + else if (attributeName == L"name") headerTemplate.name = convertor.to_bytes(value); + else if (attributeName == L"value") headerTemplate.value = convertor.to_bytes(value); // TODO: header encoding? + else throw std::invalid_argument("Unsupported attribute in the request_template relation: " + convertor.to_bytes(attributeName + L" = " + value)); + + currentAttributeIndex++; + + if (currentAttributeIndex % currentReaderMetadata.size() == 0) { + std::lock_guard lock(templatesMutex); + currentAttributeIndex = 0; + headerTemplates.push_back(headerTemplate); + headerTemplate = GlobalHeaderTemplate(); + } + } + + void responseTemplateAttribute(const relpipe::common::type::StringX& value) { + auto attributeName = currentReaderMetadata[currentAttributeIndex].getAttributeName(); + + if (attributeName == L"url") responseTemplate.url = std::regex(value.size() ? convertor.to_bytes(value) : ".*"); + else if (attributeName == L"method") responseTemplate.method = std::regex(value.size() ? convertor.to_bytes(value) : ".*"); + else if (attributeName == L"code") responseTemplate.code = std::stoi(value); + else if (attributeName == L"text") responseTemplate.body = convertor.to_bytes(value); + else if (attributeName == L"data") responseTemplate.body = "TODO: read binary data: " + convertor.to_bytes(value); // TODO: read hex/binary request body + else if (isHeaderAttribute(attributeName)) responseTemplate.headers.push_back(HeaderTemplate(convertor.to_bytes(fetchHeaderName(attributeName)), convertor.to_bytes(value))); // TODO: header encoding? + else throw std::invalid_argument("Unsupported attribute in the request_template relation: " + convertor.to_bytes(attributeName + L" = " + value)); + + currentAttributeIndex++; + + if (currentAttributeIndex % currentReaderMetadata.size() == 0) { + std::lock_guard lock(templatesMutex); + currentAttributeIndex = 0; + responseTemplates.push_back(responseTemplate); + responseTemplate = ResponseTemplate(); + } + } public: @@ -77,15 +198,18 @@ } void startRelation(relpipe::common::type::StringX name, std::vector attributes) override { - + currentRelationName = name; + currentReaderMetadata = attributes; } void attribute(const relpipe::common::type::StringX& value) override { - + if (currentRelationName == L"header_template") headerTemplateAttribute(value); + else if (currentRelationName == L"response_template") responseTemplateAttribute(value); + else throw std::invalid_argument("Unsupported relation: " + convertor.to_bytes(currentRelationName)); } void endOfPipe() { - + sleep(60); // FIXME: run for configured interval or number of requests or forever (until some stop signal) } }; diff -r 1184f3de5533 -r 37a86904145c src/HTTPServer.cpp --- a/src/HTTPServer.cpp Fri Apr 08 22:38:45 2022 +0200 +++ b/src/HTTPServer.cpp Sat Apr 09 17:50:46 2022 +0200 @@ -54,17 +54,22 @@ // TODO: return also HTTP headers if (impl->requestHandler) { HTTPServer::Request request; + request.method = method; + request.url = url; + // FIXME: request.body = ... + // FIXME: multiple calls for one request const HTTPServer::Response response = impl->requestHandler->handle(request); struct MHD_Response* mhdResponse = MHD_create_response_from_buffer(response.body.size(), (void*) response.body.c_str(), MHD_RESPMEM_MUST_COPY); + for (Header h : response.header) MHD_add_response_header(mhdResponse, h.name.c_str(), h.value.c_str()); MHD_queue_response(connection, response.code, mhdResponse); MHD_destroy_response(mhdResponse); return MHD_YES; } else { // there might be a point in time when the HTTP server is started and HTTP handler is not set // TODO: just return MHD_NO? - static const char body[] = "

HTTP 404: Not found

"; + static const char body[] = "

HTTP 503: Service Unavailable

not fully started yet

"; struct MHD_Response* mhdResponse = MHD_create_response_from_buffer(sizeof (body), (void*) body, MHD_RESPMEM_PERSISTENT); - MHD_queue_response(connection, 404, mhdResponse); + MHD_queue_response(connection, 503, mhdResponse); MHD_destroy_response(mhdResponse); return MHD_YES; } diff -r 1184f3de5533 -r 37a86904145c src/HTTPServer.h --- a/src/HTTPServer.h Fri Apr 08 22:38:45 2022 +0200 +++ b/src/HTTPServer.h Sat Apr 09 17:50:46 2022 +0200 @@ -41,6 +41,12 @@ public: std::string name; std::string value; + + Header(std::string name, std::string value) : name(name), value(value) { + } + + virtual ~Header() { + } }; class Request { diff -r 1184f3de5533 -r 37a86904145c src/relpipe-tr-httpd.cpp --- a/src/relpipe-tr-httpd.cpp Fri Apr 08 22:38:45 2022 +0200 +++ b/src/relpipe-tr-httpd.cpp Sat Apr 09 17:50:46 2022 +0200 @@ -29,6 +29,7 @@ #include #include #include +#include #include "HTTPDHandler.h" #include "CLIParser.h" @@ -57,9 +58,7 @@ HttpdHandler handler(writer, configuration, httpServer); reader->addHandler(&handler); reader->process(); - resultCode = CLI::EXIT_CODE_SUCCESS; - } catch (RelpipeCLIException& e) { fwprintf(stderr, L"Caught CLI exception: %ls\n", e.getMessage().c_str()); fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());