write response headers as a relation v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sun, 13 Mar 2022 01:17:59 +0100
branchv_0
changeset 6 59c9ca066322
parent 5 165f6162524d
child 7 0b0374746e48
write response headers as a relation
src/HTTPClient.cpp
src/HTTPClient.h
src/HTTPHandler.h
--- a/src/HTTPClient.cpp	Sat Mar 12 20:48:25 2022 +0100
+++ b/src/HTTPClient.cpp	Sun Mar 13 01:17:59 2022 +0100
@@ -16,6 +16,7 @@
  */
 
 #include <sstream>
+#include <iostream>
 
 #include <curl/curl.h>
 
@@ -30,15 +31,38 @@
 public:
 	CURL* curl;
 	std::stringstream responseBody;
-	std::vector<std::string> responseHeaders;
+	std::stringstream responseHeaders;
 
 	HTTPClientImpl(CURL* curl) : curl(curl) {
 	}
 
-	static size_t curlWriteCallback(char* buffer, size_t size, size_t nmemb, HTTPClient::HTTPClientImpl * impl) {
-		size_t r = size * nmemb;
-		impl->responseBody.write(buffer, r);
-		return r;
+	std::vector<std::string> getResponseHeaders() {
+		std::vector<std::string> heathers;
+		std::stringstream name;
+		std::stringstream value;
+		std::stringstream* current = &name;
+		for (char ch = responseHeaders.get(); responseHeaders.good(); ch = responseHeaders.get()) {
+			if (ch == ':') {
+				current = &value;
+				for (char space = responseHeaders.get(); responseHeaders.good() && responseHeaders.peek() == ' '; space = responseHeaders.get()); // skip spaces
+			} else if (ch == '\n') {
+				if (name.tellp() > 0 && current == &value) {
+					heathers.push_back(name.str());
+					heathers.push_back(value.str());
+				} else if (name.tellp() > 0 && current == &value) {
+					// TODO: usually "HTTP/1.1 200 OK" → extract HTTP version and message?
+				}
+
+				name = std::stringstream();
+				value = std::stringstream();
+				current = &name;
+			} else if (ch == '\r') {
+				// ignore
+			} else {
+				current->put(ch);
+			}
+		}
+		return heathers;
 	}
 
 };
@@ -61,17 +85,26 @@
 
 	curl_easy_setopt(impl->curl, CURLOPT_URL, request.url.c_str());
 
-	curl_easy_setopt(impl->curl, CURLOPT_WRITEDATA, impl);
-	curl_easy_setopt(impl->curl, CURLOPT_WRITEFUNCTION, HTTPClientImpl::curlWriteCallback);
+	typedef size_t(*CurlWriteCallback)(char*, size_t, size_t, HTTPClient::HTTPClientImpl*);
 
-	// curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
-	// curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headersCurlCallback);
+	curl_easy_setopt(impl->curl, CURLOPT_WRITEDATA, impl);
+	curl_easy_setopt(impl->curl, CURLOPT_WRITEFUNCTION, (CurlWriteCallback)[](char* buffer, size_t size, size_t nmemb, HTTPClient::HTTPClientImpl * impl)->size_t {
+		size_t r = size * nmemb;
+		impl->responseBody.write(buffer, r);
+		return r;
+	});
+
+	curl_easy_setopt(impl->curl, CURLOPT_HEADERDATA, impl);
+	curl_easy_setopt(impl->curl, CURLOPT_HEADERFUNCTION, (CurlWriteCallback)[](char* buffer, size_t size, size_t nmemb, HTTPClient::HTTPClientImpl * impl)->size_t {
+		size_t r = size * nmemb;
+		impl->responseHeaders.write(buffer, r);
+		return r;
+	});
 
 	curl_easy_perform(impl->curl);
 
 	curl_easy_getinfo(impl->curl, CURLINFO_RESPONSE_CODE, &response.responseCode);
-	response.success = response.responseCode >= 200 && response.responseCode <= 299;
-
+	response.headers = impl->getResponseHeaders();
 	response.body = impl->responseBody.str();
 	impl->responseBody = std::stringstream();
 
--- a/src/HTTPClient.h	Sat Mar 12 20:48:25 2022 +0100
+++ b/src/HTTPClient.h	Sun Mar 13 01:17:59 2022 +0100
@@ -61,7 +61,6 @@
 	};
 
 	struct Response {
-		bool success = false;
 		int responseCode = 0;
 		std::vector<std::string> headers;
 		std::string body;
--- a/src/HTTPHandler.h	Sat Mar 12 20:48:25 2022 +0100
+++ b/src/HTTPHandler.h	Sun Mar 13 01:17:59 2022 +0100
@@ -49,9 +49,25 @@
 	Configuration configuration;
 	std::vector<relpipe::reader::handlers::AttributeMetadata> currentReaderMetadata;
 	std::vector<relpipe::writer::AttributeMetadata> currentWriterMetadata;
+	std::vector<relpipe::common::type::StringX> responseHeaders;
 	size_t currentAttributeIndex = 0;
 	size_t currentRecordNumber = 1;
 
+	void writeHeaders() {
+		if (responseHeaders.size()) {
+			relationalWriter->startRelation(L"response_header",{
+				// TODO: request ID instead of URL (or both)
+				{L"url", relpipe::writer::TypeId::STRING},
+				{L"name", relpipe::writer::TypeId::STRING},
+				{L"value", relpipe::writer::TypeId::STRING},
+			}, true);
+
+			for (auto s : responseHeaders) relationalWriter->writeAttribute(s);
+
+			responseHeaders.clear();
+		}
+	}
+
 public:
 
 	HTTPHandler(shared_ptr<relpipe::writer::RelationalWriter> relationalWriter, Configuration configuration) : relationalWriter(relationalWriter), configuration(configuration) {
@@ -62,6 +78,7 @@
 
 	void startRelation(relpipe::common::type::StringX name, std::vector<relpipe::reader::handlers::AttributeMetadata> attributes) override {
 
+		writeHeaders(); // from previous relation
 
 		relationalWriter->startRelation(name + L"_curl_info",{
 			{L"name", relpipe::writer::TypeId::STRING},
@@ -72,10 +89,11 @@
 		relationalWriter->writeAttribute(convertor.from_bytes(curl_version()));
 
 		relationalWriter->startRelation(name + L"_curl_response",{
+			// TODO: request ID
+			// TODO: body in hexadecimal/binary format
 			{L"url", relpipe::writer::TypeId::STRING},
 			{L"body", relpipe::writer::TypeId::STRING},
 			{L"response_code", relpipe::writer::TypeId::INTEGER},
-			// {L"success", relpipe::writer::TypeId::BOOLEAN},
 		}, true);
 
 	}
@@ -91,17 +109,19 @@
 		HTTPClient::Response response = http->exchange(request);
 		relpipe::common::type::Integer responseCode = response.responseCode;
 
-		// std::cerr << "url = >>>" << convertor.to_bytes(value) << "<<<" << std::endl;
-		// std::cerr << "body = >>>" << response.body << "<<<" << std::endl;
-
 		relationalWriter->writeAttribute(value);
 		relationalWriter->writeAttribute(convertor.from_bytes(response.body));
 		relationalWriter->writeAttribute(&responseCode, typeid (responseCode));
-		// relationalWriter->writeAttribute(&response.success, typeid (response.success));
+
+		for (size_t i = 0; i < response.headers.size(); i += 2) {
+			responseHeaders.push_back(convertor.from_bytes(request.url));
+			responseHeaders.push_back(convertor.from_bytes(response.headers[i]));
+			responseHeaders.push_back(convertor.from_bytes(response.headers[i + 1]));
+		}
 	}
 
 	void endOfPipe() {
-
+		writeHeaders(); // from last relation
 	}
 
 };