partial implementation of response and header templates v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sat, 09 Apr 2022 17:50:46 +0200
branchv_0
changeset 4 37a86904145c
parent 3 1184f3de5533
child 5 121981e6bd54
partial implementation of response and header templates
src/HTTPDHandler.h
src/HTTPServer.cpp
src/HTTPServer.h
src/relpipe-tr-httpd.cpp
--- 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 <codecvt>
 #include <regex>
 #include <stdexcept>
+#include <mutex>
+#include <shared_mutex>
 
 #include <relpipe/common/type/typedefs.h>
 #include <relpipe/reader/TypeId.h>
@@ -45,27 +47,146 @@
 	shared_ptr<relpipe::writer::RelationalWriter> relationalWriter;
 	Configuration configuration;
 	std::shared_ptr<HTTPServer> httpServer;
+	relpipe::common::type::StringX currentRelationName;
 	std::vector<relpipe::reader::handlers::AttributeMetadata> currentReaderMetadata;
 	std::vector<relpipe::writer::AttributeMetadata> 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<GlobalHeaderTemplate> headerTemplates;
+
+	class ResponseTemplate : public RequestMatcher {
+	public:
+		std::string body;
+		uint16_t code = 200;
+		std::vector<HeaderTemplate> headers;
+	} responseTemplate;
+
+	std::vector<ResponseTemplate> responseTemplates;
+
+	std::mutex templatesMutex; // TODO: read-write lock (for responseTemplates and headerTemplates)
+
 	class RequestHandler : public HTTPServer::RequestHandler {
+	private:
+		std::vector<ResponseTemplate>* responseTemplates;
+		std::vector<GlobalHeaderTemplate>* headerTemplates;
+		std::mutex* templatesMutex;
 	public:
 
+		RequestHandler(std::mutex* templatesMutex, std::vector<ResponseTemplate>* responseTemplates, std::vector<GlobalHeaderTemplate>* 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 = "<h1>greetings and salutations</h1>";
+			std::lock_guard<std::mutex> 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 = "<h1>HTTP 404: Not Found</h1><p>(no response template matched)</p>";
 			return response;
 		}
 
 	};
 
-	std::shared_ptr<RequestHandler> requestHandler = std::make_shared<RequestHandler>();
+	std::shared_ptr<RequestHandler> requestHandler = std::make_shared<RequestHandler>(&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<std::mutex> 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<std::mutex> lock(templatesMutex);
+			currentAttributeIndex = 0;
+			responseTemplates.push_back(responseTemplate);
+			responseTemplate = ResponseTemplate();
+		}
+	}
 
 public:
 
@@ -77,15 +198,18 @@
 	}
 
 	void startRelation(relpipe::common::type::StringX name, std::vector<relpipe::reader::handlers::AttributeMetadata> 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)
 	}
 
 };
--- 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[] = "<h1>HTTP 404: Not found</h1>";
+			static const char body[] = "<h1>HTTP 503: Service Unavailable</h1><p>not fully started yet</p>";
 			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;
 		}
--- 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 {
--- 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 <relpipe/writer/RelationalWriter.h>
 #include <relpipe/writer/RelpipeWriterException.h>
 #include <relpipe/writer/Factory.h>
+#include <unistd.h>
 
 #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());