From dff4f2b3a072810bc20b94289207e47ce8d577d1 Mon Sep 17 00:00:00 2001 From: peri4 Date: Sat, 23 Nov 2024 17:54:22 +0300 Subject: [PATCH] add http_client library, using libcurl take out common http entities to http_common dir --- CMakeLists.txt | 20 +- cmake/FindPIP.cmake | 4 +- libs/http_client/curl_thread_pool_p.cpp | 75 +++++ libs/http_client/curl_thread_pool_p.h | 32 ++ libs/http_client/pihttpclient.cpp | 210 +++++++++++++ libs/http_server/microhttpd_server_p.cpp | 118 ++++---- libs/http_server/pihttpserver.cpp | 24 +- libs/main/containers/pideque.h | 2 +- libs/main/containers/pivector.h | 2 +- libs/main/http_client/pihttpclient.h | 61 ++++ libs/main/http_client/pihttpclientmodule.h | 54 ++++ libs/main/http_common/pihttpconstants.h | 328 +++++++++++++++++++++ libs/main/http_common/pihttptypes.cpp | 18 ++ libs/main/http_common/pihttptypes.h | 52 ++++ libs/main/http_server/microhttpd_server.h | 43 +-- libs/main/http_server/pihttpserver.h | 8 +- main.cpp | 41 ++- 17 files changed, 955 insertions(+), 137 deletions(-) create mode 100644 libs/http_client/curl_thread_pool_p.cpp create mode 100644 libs/http_client/curl_thread_pool_p.h create mode 100644 libs/http_client/pihttpclient.cpp create mode 100644 libs/main/http_client/pihttpclient.h create mode 100644 libs/main/http_client/pihttpclientmodule.h create mode 100644 libs/main/http_common/pihttpconstants.h create mode 100644 libs/main/http_common/pihttptypes.cpp create mode 100644 libs/main/http_common/pihttptypes.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f5bd7b07..496cdbff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,7 @@ set(PIP_UTILS_LIST) set(PIP_TESTS_LIST) set(PIP_EXPORTS) -set(PIP_SRC_MODULES "console;crypt;compress;usb;fftw;opencl;io_utils;client_server;cloud;lua;http_server") +set(PIP_SRC_MODULES "console;crypt;compress;usb;fftw;opencl;io_utils;client_server;cloud;lua;http_client;http_server") foreach(_m ${PIP_SRC_MODULES}) set(PIP_MSG_${_m} "no") endforeach() @@ -498,10 +498,8 @@ if (NOT CROSSTOOLS) list(APPEND HDRS ${_lua_src_hdr}) # libmicrohttpd - find_library(microhttpd_LIBRARIES microhttpd HINTS "${MINGW_LIB}") - set(microhttpd_FOUND FALSE) - if (microhttpd_LIBRARIES) - set(microhttpd_FOUND TRUE) + pip_find_lib(microhttpd HINTS "${MINGW_LIB}") + if (microhttpd_FOUND) set(_microhttpd_add_libs microhttpd) if(WIN32) if("${C_COMPILER}" STREQUAL "cl.exe") @@ -528,6 +526,12 @@ if (NOT CROSSTOOLS) pip_module(http_server "${_microhttpd_add_libs}" "PIP HTTP server" "" "" "") endif() + # libcurl + pip_find_lib(curl HINTS "${MINGW_LIB}") + if (curl_FOUND) + pip_module(http_client curl "PIP HTTP client" "" "" "") + endif() + # Test program if(PIP_UTILS) @@ -535,9 +539,9 @@ if (NOT CROSSTOOLS) #target_link_libraries(pip_plugin pip) if (NOT DEFINED ANDROID_PLATFORM) - if(microhttpd_FOUND) + if(curl_FOUND) add_executable(pip_test "main.cpp") - target_link_libraries(pip_test pip pip_io_utils pip_client_server pip_http_server) + target_link_libraries(pip_test pip pip_io_utils pip_client_server pip_http_server pip_http_client) if(sodium_FOUND) add_executable(pip_cloud_test "main_picloud_test.cpp") target_link_libraries(pip_cloud_test pip_cloud) @@ -667,7 +671,7 @@ if ((NOT PIP_FREERTOS) AND (NOT CROSSTOOLS)) find_package(Doxygen) if(DOXYGEN_FOUND) set(DOXY_DEFINES "${PIP_EXPORTS}") - foreach (_m "console" "usb" "compress" "crypt" "client_server" "cloud" "fftw" "opencl" "io_utils" "lua") + foreach (_m "console" "usb" "compress" "crypt" "client_server" "cloud" "fftw" "opencl" "io_utils" "lua" "http_server" "http_client") string(TOUPPER "${_m}" _mdef) list(APPEND DOXY_DEFINES "PIP_${_mdef}_EXPORT") endforeach() diff --git a/cmake/FindPIP.cmake b/cmake/FindPIP.cmake index 223c2655..11b96555 100644 --- a/cmake/FindPIP.cmake +++ b/cmake/FindPIP.cmake @@ -12,6 +12,7 @@ Create imported targets: * PIP::ClientServer * PIP::Cloud * PIP::Lua + * PIP::HTTPClient * PIP::HTTPServer These targets include directories and depends on @@ -27,7 +28,7 @@ shstk_set_find_dirs(PIP) set(__libs "usb;crypt;console;fftw;compress;opencl;io_utils;client_server;cloud;lua;http_server") if (BUILDING_PIP) - #set(_libs "pip;pip_usb;pip_console;pip_crypt;pip_fftw;pip_compress;pip_opencl;pip_io_utils;pip_cloud;pip_lua;pip_http_server") + #set(_libs "pip;pip_usb;pip_console;pip_crypt;pip_fftw;pip_compress;pip_opencl;pip_io_utils;pip_cloud;pip_lua;pip_http_client;pip_http_server") #set(_bins "pip_cmg;pip_rc;deploy_tool") #get_target_property(_path pip BINARY_DIR) #get_target_property(_path pip LIBRARY_OUTPUT_NAME) @@ -97,6 +98,7 @@ set(__module_io_utils IOUtils ) set(__module_client_server ClientServer) set(__module_cloud Cloud ) set(__module_lua Lua ) +set(__module_http_client HTTPClient ) set(__module_http_server HTTPServer ) foreach (_l ${__libs}) diff --git a/libs/http_client/curl_thread_pool_p.cpp b/libs/http_client/curl_thread_pool_p.cpp new file mode 100644 index 00000000..84866e58 --- /dev/null +++ b/libs/http_client/curl_thread_pool_p.cpp @@ -0,0 +1,75 @@ + +#include "curl_thread_pool_p.h" + +#include "pihttpclient.h" +#include "pitime.h" + + +CurlThreadPool::CurlThreadPool() { + piForTimes(10) { + auto * t = new PIThread([this]() { threadFunc(); }, true); + threads << t; + } +} + + +CurlThreadPool::~CurlThreadPool() { + exiting = true; + for (auto * t: threads) + t->stop(); + { + auto cr = clients.getRef(); + for (auto c: *cr) + c->abort(); + } + sem.release(threads.size()); + for (auto * t: threads) { + t->waitForFinish(); + t->setDebug(false); + t->terminate(); + } + piDeleteAllAndClear(threads); + { + auto cr = clients.getRef(); + for (auto c: *cr) + delete c; + } +} + + +void CurlThreadPool::threadFunc() { + if (exiting) return; + // piCout << "threadFuncl w ..."; + sem.acquire(); + // piCout << "threadFuncl wdone"; + if (exiting) return; + PIHTTPClient * c = nullptr; + { + auto cr = clients.getRef(); + if (cr->isEmpty()) return; + c = cr->dequeue(); + // piCout << "threadFuncl get c"; + } + // piCout << "threadFuncl proc c"; + procClient(c); + // piCout << "threadFuncl end"; +} + + +void CurlThreadPool::procClient(PIHTTPClient * c) { + if (c->init()) c->perform(); + delete c; +} + + +void CurlThreadPool::registerClient(PIHTTPClient * c) { + clients.getRef()->enqueue(c); + sem.release(); + // piCout << "registerClient"; +} + + +CurlThreadPool * CurlThreadPool::instance() { + static CurlThreadPool ret; + return &ret; +} diff --git a/libs/http_client/curl_thread_pool_p.h b/libs/http_client/curl_thread_pool_p.h new file mode 100644 index 00000000..effc3674 --- /dev/null +++ b/libs/http_client/curl_thread_pool_p.h @@ -0,0 +1,32 @@ +#ifndef curl_thread_pool_p_H +#define curl_thread_pool_p_H + +#include "piconditionvar.h" +#include "piprotectedvariable.h" +#include "pisemaphore.h" +#include "pithread.h" + +class PIHTTPClient; + +class CurlThreadPool { +public: + void registerClient(PIHTTPClient * c); + + static CurlThreadPool * instance(); + +private: + NO_COPY_CLASS(CurlThreadPool) + CurlThreadPool(); + ~CurlThreadPool(); + + void threadFunc(); + void procClient(PIHTTPClient * c); + + PIProtectedVariable> clients; + PISemaphore sem; + PIVector threads; + std::atomic_bool exiting = {false}; +}; + + +#endif diff --git a/libs/http_client/pihttpclient.cpp b/libs/http_client/pihttpclient.cpp new file mode 100644 index 00000000..29467ba8 --- /dev/null +++ b/libs/http_client/pihttpclient.cpp @@ -0,0 +1,210 @@ +#include "pihttpclient.h" + +#include "curl_thread_pool_p.h" +#include "piliterals_bytes.h" +#include "piliterals_string.h" +#include "pisystemtime.h" + +#include + + +int xfer_callback(void * ptr, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + return reinterpret_cast(ptr)->__infoFunc(dltotal, dlnow, ultotal, ulnow); +} + +int debug_callback(CURL * handle, curl_infotype type, char * data, size_t size, void * ptr) { + return reinterpret_cast(ptr)->__debugFunc(type, data, size); +} + + +PRIVATE_DEFINITION_START(PIHTTPClient) + CURL * handle = nullptr; +PRIVATE_DEFINITION_END(PIHTTPClient) + + +PIHTTPClient::PIHTTPClient() {} + + +PIHTTPClient::~PIHTTPClient() {} + + +bool PIHTTPClient::init() { + if (is_cancel) return false; + PRIVATE->handle = curl_easy_init(); + if (!PRIVATE->handle) return false; + curl_easy_setopt(PRIVATE->handle, CURLOPT_WRITEDATA, this); + curl_easy_setopt(PRIVATE->handle, CURLOPT_READDATA, this); + curl_easy_setopt(PRIVATE->handle, CURLOPT_XFERINFODATA, this); + curl_easy_setopt(PRIVATE->handle, CURLOPT_DEBUGDATA, this); + curl_easy_setopt(PRIVATE->handle, CURLOPT_HEADERDATA, this); + curl_easy_setopt(PRIVATE->handle, CURLOPT_WRITEFUNCTION, writeMemoryFunc); + curl_easy_setopt(PRIVATE->handle, CURLOPT_READFUNCTION, readMemoryFunc); + curl_easy_setopt(PRIVATE->handle, CURLOPT_XFERINFOFUNCTION, xfer_callback); + curl_easy_setopt(PRIVATE->handle, CURLOPT_DEBUGFUNCTION, debug_callback); + curl_easy_setopt(PRIVATE->handle, CURLOPT_HEADERFUNCTION, headerFunc); + curl_easy_setopt(PRIVATE->handle, CURLOPT_URL, url.dataUTF8()); + curl_easy_setopt(PRIVATE->handle, CURLOPT_CUSTOMREQUEST, PIHTTP::methodName(request.method())); + curl_easy_setopt(PRIVATE->handle, CURLOPT_NOPROGRESS, 0L); + // curl_easy_setopt(PRIVATE->handle, CURLOPT_VERBOSE, 1L); + // curl_easy_setopt(PRIVATE->handle, CURLOPT_ERRORBUFFER, buffer_error.data()); + curl_easy_setopt(PRIVATE->handle, CURLOPT_SSL_VERIFYPEER, 0L); + if (request.body().isNotEmpty()) { + curl_easy_setopt(PRIVATE->handle, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(PRIVATE->handle, CURLOPT_INFILESIZE_LARGE, static_cast(request.body().size())); + } + return true; +} + + +void PIHTTPClient::perform() { + if (!PRIVATE->handle) return; + if (!is_cancel) { + // piCout << "perform ..."; + PITimeMeasurer tm; + CURLcode res = curl_easy_perform(PRIVATE->handle); + // piCout << "done" << res << "in" << tm.elapsed_m() << ", bytes" << buffer_out.size(); + if (res == CURLE_OK) { + reply.setBody(std::move(buffer_out)); + if (on_finish) on_finish(reply); + } else { + if (res == CURLE_ABORTED_BY_CALLBACK || is_cancel) { + // piCerr << "curl_easy_perform() failed:" << curl_easy_strerror(res); + if (on_abort) on_abort(reply); + } else { + last_error = curl_easy_strerror(res); + if (on_error) on_error(reply); + } + } + } + // piCout << last_error; + curl_easy_cleanup(PRIVATE->handle); + PRIVATE->handle = nullptr; +} + + +void PIHTTPClient::procHeaderLine(PIString & line) { + if (line.startsWith("HTTP"_a)) { + // HTTP/Версия КодСостояния Пояснение + line.cutLeft(5); + line.takeWord(); + int code = line.takeWord().toInt(); + // piCout << "code" << code; + reply.setCode(static_cast(code)); + return; + } + int ind = line.find(':'); + if (ind < 0) return; + PIString hname = line.takeLeft(ind); + line.cutLeft(1).trim(); + reply.addHeader(hname, line); +} + + +size_t PIHTTPClient::writeMemoryFunc(void * contents, size_t size, size_t nmemb, void * ptr) { + size_t bytes = size * nmemb; + piCout << "writeMemoryFunc" << bytes; + auto client = reinterpret_cast(ptr); + if (client->is_cancel) return CURL_WRITEFUNC_ERROR; + client->buffer_out.append(contents, bytes); + return bytes; +} + + +size_t PIHTTPClient::readMemoryFunc(void * contents, size_t size, size_t nmemb, void * ptr) { + size_t bytes = size * nmemb; + piCout << "readMemoryFunc" << bytes; + auto client = reinterpret_cast(ptr); + if (client->is_cancel) return CURL_READFUNC_ABORT; + const auto & buffer(client->request.body()); + if (buffer.isEmpty()) return 0; + // piCout << bytes; + ssize_t ret = piClamp(bytes, 0, buffer.size_s() - client->read_pos); + if (ret < 0) ret = 0; + if (ret > 0) memcpy(contents, buffer.data(client->read_pos), ret); + return ret; +} + + +size_t PIHTTPClient::headerFunc(char * contents, size_t size, size_t nmemb, void * ptr) { + size_t bytes = size * nmemb; + auto client = reinterpret_cast(ptr); + if (client->is_cancel) return CURL_WRITEFUNC_ERROR; + PIString line = PIString::fromUTF8(contents, bytes).trim(); + if (line.isNotEmpty()) client->procHeaderLine(line); + return bytes; +} + + +int PIHTTPClient::infoFunc(ssize_t dltotal, ssize_t dlnow, ssize_t ultotal, ssize_t ulnow) { + // piCout << "infoFunc" << dltotal << dlnow << ultotal << ulnow; + if (is_cancel) return 1; + return 0; +} + + +int PIHTTPClient::debugFunc(int type, char * data, size_t size) { + // piCout << "debugFunc" << type << PIString::fromUTF8(data, size); + return 0; +} + + +PIHTTPClient * PIHTTPClient::create(const PIString & url_, PIHTTP::Method method, const PIHTTP::MessageConst & req) { + PIHTTPClient * ret = new PIHTTPClient(); + static_cast(ret->request) = req; + ret->request.setMethod(method); + ret->url = url_; + return ret; +} + + +PIHTTPClient * PIHTTPClient::onFinish(std::function f) { + return onFinish([f](const PIHTTP::MessageConst &) { f(); }); +} + + +PIHTTPClient * PIHTTPClient::onFinish(std::function f) { + on_finish = f; + return this; +} + + +PIHTTPClient * PIHTTPClient::onError(std::function f) { + return onError([f](const PIHTTP::MessageConst &) { f(); }); +} + + +PIHTTPClient * PIHTTPClient::onError(std::function f) { + on_error = f; + return this; +} + + +PIHTTPClient * PIHTTPClient::onAbort(std::function f) { + return onAbort([f](const PIHTTP::MessageConst &) { f(); }); +} + + +PIHTTPClient * PIHTTPClient::onAbort(std::function f) { + on_abort = f; + return this; +} + + +void PIHTTPClient::start() { + CurlThreadPool::instance()->registerClient(this); +} + + +void PIHTTPClient::abort() { + is_cancel = true; +} + + +int PIHTTPClientBase::__infoFunc(ssize_t dltotal, ssize_t dlnow, ssize_t ultotal, ssize_t ulnow) { + return reinterpret_cast(this)->infoFunc(dltotal, dlnow, ultotal, ulnow); +} + + +int PIHTTPClientBase::__debugFunc(int type, char * data, size_t size) { + return reinterpret_cast(this)->debugFunc(type, data, size); +} diff --git a/libs/http_server/microhttpd_server_p.cpp b/libs/http_server/microhttpd_server_p.cpp index 06c3d0ee..d25f02b9 100644 --- a/libs/http_server/microhttpd_server_p.cpp +++ b/libs/http_server/microhttpd_server_p.cpp @@ -1,4 +1,5 @@ #include "microhttpd_server.h" +#include "piliterals_bytes.h" #include "piliterals_string.h" #include "piliterals_time.h" @@ -31,11 +32,11 @@ struct MicrohttpdServerConnection { bool ready(); - int send_reply(const MicrohttpdServer::Reply & r); - int send_error(); + int sendReply(const PIHTTP::MessageMutable & r); + int sendError(); - bool done = false; - MicrohttpdServer::Method method = MicrohttpdServer::Method::Unknown; + bool done = false; + PIHTTP::Method method = PIHTTP::Method::Unknown; PIString path; PIByteArray body; PIMap headers, args, post; @@ -49,47 +50,48 @@ bool MicrohttpdServerConnection::ready() { if (!server) return false; if (done) return true; done = true; - MicrohttpdServer::Reply rep; - if (method == MicrohttpdServer::Method::Get) { + PIHTTP::MessageMutable rep; + if (method == PIHTTP::Method::Get) { if (path == "/favicon.ico"_a) { // piCout << "send favicon" << server->favicon.size() << "bytes"; rep.setBody(server->favicon); - send_reply(rep); + sendReply(rep); return true; } } // piCout << "ready" << (int)method << path; - MicrohttpdServer::Request req; - req.method = method; - req.path = path; - req.body = body; - req.headers = headers; - req.args = args; - rep.setCode(MHD_HTTP_BAD_REQUEST); + PIHTTP::MessageMutable req; + req.setMethod(method); + req.setPath(path); + req.setBody(body); + req.headers() = headers; + req.arguments() = args; + rep.setCode(PIHTTP::Code::BadRequest); if (server->callback) rep = server->callback(req); - rep.addFixedHeaders(); - send_reply(rep); + MicrohttpdServer::addFixedHeaders(rep); + sendReply(rep); + // piCout << "ready ok" << (int)rep.code() << rep.body().size(); return true; } -int MicrohttpdServerConnection::send_reply(const MicrohttpdServer::Reply & r) { - MHD_Response * response = MHD_create_response_from_buffer(r.body.size(), (void *)r.body.data(), MHD_RESPMEM_MUST_COPY); +int MicrohttpdServerConnection::sendReply(const PIHTTP::MessageMutable & r) { + MHD_Response * response = MHD_create_response_from_buffer(r.body().size(), (void *)r.body().data(), MHD_RESPMEM_MUST_COPY); if (!response) { // piCout << "null response" << r.body.size() << (void *)r.body.data(); return MHD_NO; } - auto it = r.headers.makeIterator(); + auto it = r.headers().makeIterator(); while (it.next()) MHD_add_response_header(response, it.key().dataAscii(), it.value().dataUTF8()); // piCout << "status" << r.code; - int ret = MHD_queue_response(connection, r.code, response); + int ret = MHD_queue_response(connection, static_cast(r.code()), response); MHD_destroy_response(response); return ret; } -int MicrohttpdServerConnection::send_error() { +int MicrohttpdServerConnection::sendError() { return MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, MHD_create_response_from_buffer(0, nullptr, MHD_RESPMEM_MUST_COPY)); } @@ -98,9 +100,9 @@ void log_callback(void * cls, const char * fmt, va_list ap) { MicrohttpdServer * server = (MicrohttpdServer *)cls; piCout << "log" << server; if (!server) return; - char buffer[1024]; - memset(buffer, 0, 1024); - std::vsnprintf(buffer, 1024, fmt, ap); + char buffer[1_KiB]; + memset(buffer, 0, 1_KiB); + std::vsnprintf(buffer, 1_KiB, fmt, ap); piCout << buffer; } @@ -158,30 +160,30 @@ int answer_to_connection(void * cls, const char * upload_data, size_t * upload_data_size, void ** con_cls) { - MicrohttpdServer * server = (MicrohttpdServer *)cls; + MicrohttpdServer * server = (MicrohttpdServer *)cls; - MicrohttpdServer::Method m = MicrohttpdServer::Method::Unknown; + PIHTTP::Method m = PIHTTP::Method::Unknown; if (0 == strcmp(method, "GET")) - m = MicrohttpdServer::Method::Get; + m = PIHTTP::Method::Get; else if (0 == strcmp(method, "POST")) - m = MicrohttpdServer::Method::Post; + m = PIHTTP::Method::Post; else if (0 == strcmp(method, "HEAD")) - m = MicrohttpdServer::Method::Head; + m = PIHTTP::Method::Head; else if (0 == strcmp(method, "PUT")) - m = MicrohttpdServer::Method::Put; + m = PIHTTP::Method::Put; else if (0 == strcmp(method, "DELETE")) - m = MicrohttpdServer::Method::Delete; + m = PIHTTP::Method::Delete; else if (0 == strcmp(method, "CONNECT")) - m = MicrohttpdServer::Method::Connect; + m = PIHTTP::Method::Connect; else if (0 == strcmp(method, "OPTIONS")) - m = MicrohttpdServer::Method::Options; + m = PIHTTP::Method::Options; else if (0 == strcmp(method, "TRACE")) - m = MicrohttpdServer::Method::Trace; + m = PIHTTP::Method::Trace; else if (0 == strcmp(method, "PATCH")) - m = MicrohttpdServer::Method::Patch; + m = PIHTTP::Method::Patch; - if (m == MicrohttpdServer::Method::Unknown) { + if (m == PIHTTP::Method::Unknown) { piCout << "[MicrohttpdServer]" << "Warning:" << "Unknown method!"; @@ -201,20 +203,20 @@ int answer_to_connection(void * cls, return MHD_YES; } - if (m == MicrohttpdServer::Method::Unknown) { - return conn->send_error(); + if (m == PIHTTP::Method::Unknown) { + return conn->sendError(); } if (*upload_data_size) { if (!conn->postprocessor) { - conn->postprocessor = MHD_create_post_processor(connection, 65536, (MHD_PostDataIterator)iterate_post, (void *)conn); + conn->postprocessor = MHD_create_post_processor(connection, 64_KiB, (MHD_PostDataIterator)iterate_post, (void *)conn); } conn->body.append(upload_data, *upload_data_size); MHD_post_process(conn->postprocessor, upload_data, *upload_data_size); *upload_data_size = 0; } else { // qDebug() << "answer ok"; - if (!conn->ready()) return conn->send_error(); + if (!conn->ready()) return conn->sendError(); } return MHD_YES; } @@ -305,34 +307,14 @@ void MicrohttpdServer::stop() { } -void MicrohttpdServer::Reply::addHeader(const PIString & header, const PIString & value) { - headers[header] = value; -} - - -void MicrohttpdServer::Reply::removeHeader(const PIString & header) { - headers.remove(header); -} - - -void MicrohttpdServer::Reply::setBody(const PIByteArray & b) { - body = b; -} - - -void MicrohttpdServer::Reply::setCode(int c) { - code = c; -} - - -void MicrohttpdServer::Reply::addFixedHeaders() { - if (!headers.contains(MHD_HTTP_HEADER_CONTENT_TYPE)) { - if (body.isNotEmpty()) { - if (body.startsWith(PIByteArray::fromAscii(""))) - addHeader(MHD_HTTP_HEADER_CONTENT_TYPE, "text/html; charset=utf-8"); - else if (body[0] == '[' || body[0] == '{') - addHeader(MHD_HTTP_HEADER_CONTENT_TYPE, "application/json; charset=utf-8"); +void MicrohttpdServer::addFixedHeaders(PIHTTP::MessageMutable & msg) { + if (!msg.headers().contains(PIHTTP::Header::ContentType)) { + if (msg.body().isNotEmpty()) { + if (msg.body().startsWith(PIByteArray::fromAscii(""))) + msg.addHeader(PIHTTP::Header::ContentType, "text/html; charset=utf-8"); + else if (msg.body()[0] == '[' || msg.body()[0] == '{') + msg.addHeader(PIHTTP::Header::ContentType, "application/json; charset=utf-8"); } } - addHeader(MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + msg.addHeader(PIHTTP::Header::AccessControlAllowOrigin, "*"); } diff --git a/libs/http_server/pihttpserver.cpp b/libs/http_server/pihttpserver.cpp index 5c49e64f..9208425d 100644 --- a/libs/http_server/pihttpserver.cpp +++ b/libs/http_server/pihttpserver.cpp @@ -4,29 +4,29 @@ PIHTTPServer::PIHTTPServer() { - setRequestCallback([this](const MicrohttpdServer::Request & r) -> MicrohttpdServer::Reply { - MicrohttpdServer::Reply rep; - rep.setCode(404); - auto in_path = r.path.split("/"); + setRequestCallback([this](const PIHTTP::MessageConst & r) -> PIHTTP::MessageMutable { + PIHTTP::MessageMutable reply; + reply.setCode(PIHTTP::Code::NotFound); + auto in_path = r.path().split("/"); in_path.removeAll(""); auto it = functions.makeReverseIterator(); bool found = false; while (it.next()) { if (it.value().function) { - if (it.value().method == r.method) { + if (it.value().method == r.method()) { if (it.value().match(in_path)) { - rep = it.value().function(r); + reply = it.value().function(r); found = true; break; } } } } - if (!found && unhandled) rep = unhandled(r); + if (!found && unhandled) reply = unhandled(r); auto hit = reply_headers.makeIterator(); while (hit.next()) - rep.addHeader(hit.key(), hit.value()); - return rep; + reply.addHeader(hit.key(), hit.value()); + return reply; }); } @@ -36,8 +36,8 @@ PIHTTPServer::~PIHTTPServer() { } -void PIHTTPServer::registerPath(const PIString & path, Method method, RequestFunction functor) { - auto & ep(functions[path + PIString::fromNumber((int)method)]); +void PIHTTPServer::registerPath(const PIString & path, PIHTTP::Method method, RequestFunction functor) { + auto & ep(functions[path + PIString::fromNumber(static_cast(method))]); ep.path = path.split("/"); ep.method = method; ep.function = functor; @@ -50,7 +50,7 @@ void PIHTTPServer::registerUnhandled(RequestFunction functor) { } -void PIHTTPServer::unregisterPath(const PIString & path, Method method) { +void PIHTTPServer::unregisterPath(const PIString & path, PIHTTP::Method method) { auto pl = path.split("/"); pl.removeAll(""); auto it = functions.makeIterator(); diff --git a/libs/main/containers/pideque.h b/libs/main/containers/pideque.h index 0e0ba6ff..a0658d56 100644 --- a/libs/main/containers/pideque.h +++ b/libs/main/containers/pideque.h @@ -2679,7 +2679,7 @@ private: fprintf(stderr, "error with PIDeque<%s>::alloc\n", __PIP_TYPENAME__(T)); } #endif - assert(p_d); + assert(new_data); pid_data = new_data; pid_rsize = new_rsize; } diff --git a/libs/main/containers/pivector.h b/libs/main/containers/pivector.h index c480988c..b6e501e9 100644 --- a/libs/main/containers/pivector.h +++ b/libs/main/containers/pivector.h @@ -2556,7 +2556,7 @@ private: fprintf(stderr, "error with PIVector<%s>::alloc\n", __PIP_TYPENAME__(T)); } #endif - assert(p_d); + assert(new_data); piv_data = new_data; piv_rsize = new_rsize; } diff --git a/libs/main/http_client/pihttpclient.h b/libs/main/http_client/pihttpclient.h new file mode 100644 index 00000000..08d56042 --- /dev/null +++ b/libs/main/http_client/pihttpclient.h @@ -0,0 +1,61 @@ +#ifndef pihttpclient_h +#define pihttpclient_h + +#include "pihttptypes.h" +#include "pip_http_client_export.h" + + +class PIHTTPClientBase { +public: + int __infoFunc(ssize_t dltotal, ssize_t dlnow, ssize_t ultotal, ssize_t ulnow); + int __debugFunc(int type, char * data, size_t size); +}; + + +class PIP_HTTP_CLIENT_EXPORT PIHTTPClient: private PIHTTPClientBase { + friend class PIHTTPClientBase; + friend class CurlThreadPool; + +public: + static PIHTTPClient * create(const PIString & url, PIHTTP::Method method = PIHTTP::Method::Get, const PIHTTP::MessageConst & req = {}); + + PIHTTPClient * onFinish(std::function f); + PIHTTPClient * onFinish(std::function f); + PIHTTPClient * onError(std::function f); + PIHTTPClient * onError(std::function f); + PIHTTPClient * onAbort(std::function f); + PIHTTPClient * onAbort(std::function f); + + void start(); + void abort(); + + PIString lastError() const { return last_error; } + +private: + NO_COPY_CLASS(PIHTTPClient) + PIHTTPClient(); + virtual ~PIHTTPClient(); + + PRIVATE_DECLARATION(PIP_HTTP_CLIENT_EXPORT) + + bool init(); + void perform(); + void procHeaderLine(PIString & line); + + static size_t writeMemoryFunc(void * contents, size_t size, size_t nmemb, void * ptr); + static size_t readMemoryFunc(void * contents, size_t size, size_t nmemb, void * ptr); + static size_t headerFunc(char * contents, size_t size, size_t nmemb, void * ptr); + int infoFunc(ssize_t dltotal, ssize_t dlnow, ssize_t ultotal, ssize_t ulnow); + int debugFunc(int type, char * data, size_t size); + + PIString url; + PIString last_error; + PIByteArray buffer_out; + PIHTTP::MessageMutable request, reply; + std::atomic_bool is_cancel = {false}; + ssize_t read_pos = 0; + std::function on_finish, on_error, on_abort; +}; + + +#endif diff --git a/libs/main/http_client/pihttpclientmodule.h b/libs/main/http_client/pihttpclientmodule.h new file mode 100644 index 00000000..81da8d16 --- /dev/null +++ b/libs/main/http_client/pihttpclientmodule.h @@ -0,0 +1,54 @@ +/* + PIP - Platform Independent Primitives + Module includes + Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ +//! \defgroup HTTPServer HTTPServer +//! \~\brief +//! \~english HTTP client +//! \~russian HTTP сервер +//! +//! \~\details +//! \~english \section cmake_module_HTTPServer Building with CMake +//! \~russian \section cmake_module_HTTPServer Сборка с использованием CMake +//! +//! \~\code +//! find_package(PIP REQUIRED) +//! target_link_libraries([target] PIP::HTTPServer) +//! \endcode +//! +//! \~english \par Common +//! \~russian \par Общее +//! +//! \~english +//! These files provides HTTP server based on libmicrohttpd +//! +//! \~russian +//! Эти файлы обеспечивают HTTP сервер, основанный на libmicrohttpd +//! +//! \~\authors +//! \~english +//! Ivan Pelipenko peri4ko@yandex.ru; +//! \~russian +//! Иван Пелипенко peri4ko@yandex.ru; +//! + +#ifndef pihttpclientmodule_H +#define pihttpclientmodule_H + +#include "pihttpclient.h" + +#endif diff --git a/libs/main/http_common/pihttpconstants.h b/libs/main/http_common/pihttpconstants.h new file mode 100644 index 00000000..8fbef355 --- /dev/null +++ b/libs/main/http_common/pihttpconstants.h @@ -0,0 +1,328 @@ +#ifndef pihttpconstants_h +#define pihttpconstants_h + + +namespace PIHTTP { + +enum class Method { + Unknown, + Get, + Head, + Post, + Put, + Delete, + Connect, + Options, + Trace, + Patch +}; + +enum class Code { + Unknown, + Continue = 100, + SwitchingProtocols = 101, + Processing = 102, + EarlyHints = 103, + + Ok = 200, + Created = 201, + Accepted = 202, + NonAuthoritativeInformation = 203, + NoContent = 204, + ResetContent = 205, + PartialContent = 206, + MultiStatus = 207, + AlreadyReported = 208, + IMUsed = 226, + + MultipleChoices = 300, + MovedPermanently = 301, + Found = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + SwitchProxy = 306, + TemporaryRedirect = 307, + PermanentRedirect = 308, + + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + ProxyAuthenticationRequired = 407, + RequestTimeout = 408, + Conflict = 409, + Gone = 410, + LengthRequired = 411, + PreconditionFailed = 412, + ContentTooLarge = 413, + UriTooLong = 414, + UnsupportedMediaType = 415, + RangeNotSatisfiable = 416, + ExpectationFailed = 417, + MisdirectedRequest = 421, + UnprocessableContent = 422, + Locked = 423, + FailedDependency = 424, + TooEarly = 425, + UpgradeRequired = 426, + PreconditionRequired = 428, + TooManyRequests = 429, + RequestHeaderFieldsTooLarge = 431, + RetryWith = 449, + BlockedByWindowsParentalControls = 450, + UnavailableForLegalReasons = 451, + + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503, + GatewayTimeout = 504, + HttpVersionNotSupported = 505, + VariantAlsoNegotiates = 506, + InsufficientStorage = 507, + LoopDetected = 508, + NotExtended = 510, + BandwidthLimitExceeded = 509, + NetworkAuthenticationRequired = 511, +}; + +namespace Header { +constexpr static char Accept[] = "Accept"; +constexpr static char AcceptCharset[] = "Accept-Charset"; +constexpr static char AcceptEncoding[] = "Accept-Encoding"; +constexpr static char AcceptLanguage[] = "Accept-Language"; +constexpr static char AcceptRanges[] = "Accept-Ranges"; +constexpr static char Age[] = "Age"; +constexpr static char Allow[] = "Allow"; +constexpr static char AuthenticationInfo[] = "Authentication-Info"; +constexpr static char Authorization[] = "Authorization"; +constexpr static char CacheControl[] = "Cache-Control"; +constexpr static char Close[] = "Close"; +constexpr static char Connection[] = "Connection"; +constexpr static char ContentEncoding[] = "Content-Encoding"; +constexpr static char ContentLanguage[] = "Content-Language"; +constexpr static char ContentLength[] = "Content-Length"; +constexpr static char ContentLocation[] = "Content-Location"; +constexpr static char ContentRange[] = "Content-Range"; +constexpr static char ContentType[] = "Content-Type"; +constexpr static char Date[] = "Date"; +constexpr static char ETag[] = "ETag"; +constexpr static char Expect[] = "Expect"; +constexpr static char Expires[] = "Expires"; +constexpr static char From[] = "From"; +constexpr static char Host[] = "Host"; +constexpr static char IfMatch[] = "If-Match"; +constexpr static char IfModifiedSince[] = "If-Modified-Since"; +constexpr static char IfNoneMatch[] = "If-None-Match"; +constexpr static char IfRange[] = "If-Range"; +constexpr static char IfUnmodifiedSince[] = "If-Unmodified-Since"; +constexpr static char LastModified[] = "Last-Modified"; +constexpr static char Location[] = "Location"; +constexpr static char MaxForwards[] = "Max-Forwards"; +constexpr static char MimeVersion[] = "MIME-Version"; +constexpr static char Pragma[] = "Pragma"; +constexpr static char ProxyAuthenticate[] = "Proxy-Authenticate"; +constexpr static char ProxyAuthenticationInfo[] = "Proxy-Authentication-Info"; +constexpr static char ProxyAuthorization[] = "Proxy-Authorization"; +constexpr static char Range[] = "Range"; +constexpr static char Referer[] = "Referer"; +constexpr static char RetryAfter[] = "Retry-After"; +constexpr static char Server[] = "Server"; +constexpr static char TE[] = "TE"; +constexpr static char Trailer[] = "Trailer"; +constexpr static char TransferEncoding[] = "Transfer-Encoding"; +constexpr static char Upgrade[] = "Upgrade"; +constexpr static char UserAgent[] = "User-Agent"; +constexpr static char Vary[] = "Vary"; +constexpr static char Via[] = "Via"; +constexpr static char WWWAuthenticate[] = "WWW-Authenticate"; +constexpr static char Asterisk[] = "*"; +constexpr static char AIM[] = "A-IM"; +constexpr static char AcceptAdditions[] = "Accept-Additions"; +constexpr static char AcceptCH[] = "Accept-CH"; +constexpr static char AcceptDatetime[] = "Accept-Datetime"; +constexpr static char AcceptFeatures[] = "Accept-Features"; +constexpr static char AcceptPatch[] = "Accept-Patch"; +constexpr static char AcceptPost[] = "Accept-Post"; +constexpr static char AcceptSignature[] = "Accept-Signature"; +constexpr static char AccessControlAllowCredentials[] = "Access-Control-Allow-Credentials"; +constexpr static char AccessControlAllowHeaders[] = "Access-Control-Allow-Headers"; +constexpr static char AccessControlAllowMethods[] = "Access-Control-Allow-Methods"; +constexpr static char AccessControlAllowOrigin[] = "Access-Control-Allow-Origin"; +constexpr static char AccessControlExposeHeaders[] = "Access-Control-Expose-Headers"; +constexpr static char AccessControlMaxAge[] = "Access-Control-Max-Age"; +constexpr static char AccessControlRequestHeaders[] = "Access-Control-Request-Headers"; +constexpr static char AccessControlRequestMethod[] = "Access-Control-Request-Method"; +constexpr static char ALPN[] = "ALPN"; +constexpr static char AltSvc[] = "Alt-Svc"; +constexpr static char AltUsed[] = "Alt-Used"; +constexpr static char Alternates[] = "Alternates"; +constexpr static char ApplyToRedirectRef[] = "Apply-To-Redirect-Ref"; +constexpr static char AuthenticationControl[] = "Authentication-Control"; +constexpr static char CacheStatus[] = "Cache-Status"; +constexpr static char CalManagedID[] = "Cal-Managed-ID"; +constexpr static char CalDAVTimezones[] = "CalDAV-Timezones"; +constexpr static char CapsuleProtocol[] = "Capsule-Protocol"; +constexpr static char CDNCacheControl[] = "CDN-Cache-Control"; +constexpr static char CDNLoop[] = "CDN-Loop"; +constexpr static char CertNotAfter[] = "Cert-Not-After"; +constexpr static char CertNotBefore[] = "Cert-Not-Before"; +constexpr static char ClearSiteData[] = "Clear-Site-Data"; +constexpr static char ClientCert[] = "Client-Cert"; +constexpr static char ClientCertChain[] = "Client-Cert-Chain"; +constexpr static char ContentDigest[] = "Content-Digest"; +constexpr static char ContentDisposition[] = "Content-Disposition"; +constexpr static char ContentID[] = "Content-ID"; +constexpr static char ContentSecurityPolicy[] = "Content-Security-Policy"; +constexpr static char ContentSecurityPolicyReportOnly[] = "Content-Security-Policy-Report-Only"; +constexpr static char Cookie[] = "Cookie"; +constexpr static char CrossOriginEmbedderPolicy[] = "Cross-Origin-Embedder-Policy"; +constexpr static char CrossOriginEmbedderPolicyReportOnly[] = "Cross-Origin-Embedder-Policy-Report-Only"; +constexpr static char CrossOriginOpenerPolicy[] = "Cross-Origin-Opener-Policy"; +constexpr static char CrossOriginOpenerPolicyReportOnly[] = "Cross-Origin-Opener-Policy-Report-Only"; +constexpr static char CrossOriginResourcePolicy[] = "Cross-Origin-Resource-Policy"; +constexpr static char DASL[] = "DASL"; +constexpr static char DAV[] = "DAV"; +constexpr static char DeltaBase[] = "Delta-Base"; +constexpr static char Depth[] = "Depth"; +constexpr static char Destination[] = "Destination"; +constexpr static char DifferentialID[] = "Differential-ID"; +constexpr static char DPoP[] = "DPoP"; +constexpr static char DPoPNonce[] = "DPoP-Nonce"; +constexpr static char EarlyData[] = "Early-Data"; +constexpr static char ExpectCT[] = "Expect-CT"; +constexpr static char Forwarded[] = "Forwarded"; +constexpr static char Hobareg[] = "Hobareg"; +constexpr static char If[] = "If"; +constexpr static char IfScheduleTagMatch[] = "If-Schedule-Tag-Match"; +constexpr static char IM[] = "IM"; +constexpr static char IncludeReferredTokenBindingID[] = "Include-Referred-Token-Binding-ID"; +constexpr static char KeepAlive[] = "Keep-Alive"; +constexpr static char Label[] = "Label"; +constexpr static char LastEventID[] = "Last-Event-ID"; +constexpr static char Link[] = "Link"; +constexpr static char LockToken[] = "Lock-Token"; +constexpr static char MementoDatetime[] = "Memento-Datetime"; +constexpr static char Meter[] = "Meter"; +constexpr static char Negotiate[] = "Negotiate"; +constexpr static char NEL[] = "NEL"; +constexpr static char ODataEntityid[] = "OData-EntityId"; +constexpr static char ODataIsolation[] = "OData-Isolation"; +constexpr static char ODataMaxversion[] = "OData-MaxVersion"; +constexpr static char ODataVersion[] = "OData-Version"; +constexpr static char OptionalWWWAuthenticate[] = "Optional-WWW-Authenticate"; +constexpr static char OrderingType[] = "Ordering-Type"; +constexpr static char Origin[] = "Origin"; +constexpr static char OriginAgentCluster[] = "Origin-Agent-Cluster"; +constexpr static char OSCORE[] = "OSCORE"; +constexpr static char OSLCCoreVersion[] = "OSLC-Core-Version"; +constexpr static char Overwrite[] = "Overwrite"; +constexpr static char PingFrom[] = "Ping-From"; +constexpr static char PingTo[] = "Ping-To"; +constexpr static char Position[] = "Position"; +constexpr static char Prefer[] = "Prefer"; +constexpr static char PreferenceApplied[] = "Preference-Applied"; +constexpr static char Priority[] = "Priority"; +constexpr static char ProxyStatus[] = "Proxy-Status"; +constexpr static char PublicKeyPins[] = "Public-Key-Pins"; +constexpr static char PublicKeyPinsReportOnly[] = "Public-Key-Pins-Report-Only"; +constexpr static char RedirectRef[] = "Redirect-Ref"; +constexpr static char Refresh[] = "Refresh"; +constexpr static char ReplayNonce[] = "Replay-Nonce"; +constexpr static char ReprDigest[] = "Repr-Digest"; +constexpr static char ScheduleReply[] = "Schedule-Reply"; +constexpr static char ScheduleTag[] = "Schedule-Tag"; +constexpr static char SecPurpose[] = "Sec-Purpose"; +constexpr static char SecTokenBinding[] = "Sec-Token-Binding"; +constexpr static char SecWebsocketAccept[] = "Sec-WebSocket-Accept"; +constexpr static char SecWebsocketExtensions[] = "Sec-WebSocket-Extensions"; +constexpr static char SecWebsocketKey[] = "Sec-WebSocket-Key"; +constexpr static char SecWebsocketProtocol[] = "Sec-WebSocket-Protocol"; +constexpr static char SecWebsocketVersion[] = "Sec-WebSocket-Version"; +constexpr static char ServerTiming[] = "Server-Timing"; +constexpr static char SetCookie[] = "Set-Cookie"; +constexpr static char Signature[] = "Signature"; +constexpr static char SignatureInput[] = "Signature-Input"; +constexpr static char SLUG[] = "SLUG"; +constexpr static char Soapaction[] = "SoapAction"; +constexpr static char StatusURI[] = "Status-URI"; +constexpr static char StrictTransportSecurity[] = "Strict-Transport-Security"; +constexpr static char Sunset[] = "Sunset"; +constexpr static char SurrogateCapability[] = "Surrogate-Capability"; +constexpr static char SurrogateControl[] = "Surrogate-Control"; +constexpr static char TCN[] = "TCN"; +constexpr static char Timeout[] = "Timeout"; +constexpr static char Topic[] = "Topic"; +constexpr static char Traceparent[] = "Traceparent"; +constexpr static char Tracestate[] = "Tracestate"; +constexpr static char TTL[] = "TTL"; +constexpr static char Urgency[] = "Urgency"; +constexpr static char VariantVary[] = "Variant-Vary"; +constexpr static char WantContentDigest[] = "Want-Content-Digest"; +constexpr static char WantReprDigest[] = "Want-Repr-Digest"; +constexpr static char XContentTypeOptions[] = "X-Content-Type-Options"; +constexpr static char XFrameOptions[] = "X-Frame-Options"; +constexpr static char AmpCacheTransform[] = "AMP-Cache-Transform"; +constexpr static char ConfigurationContext[] = "Configuration-Context"; +constexpr static char EDIINTFeatures[] = "EDIINT-Features"; +constexpr static char Isolation[] = "Isolation"; +constexpr static char PermissionsPolicy[] = "Permissions-Policy"; +constexpr static char RepeatabilityClientID[] = "Repeatability-Client-ID"; +constexpr static char RepeatabilityFirstSent[] = "Repeatability-First-Sent"; +constexpr static char RepeatabilityRequestID[] = "Repeatability-Request-ID"; +constexpr static char RepeatabilityResult[] = "Repeatability-Result"; +constexpr static char ReportingEndpoints[] = "Reporting-Endpoints"; +constexpr static char SecGPC[] = "Sec-GPC"; +constexpr static char TimingAllowOrigin[] = "Timing-Allow-Origin"; +constexpr static char CPEPInfo[] = "C-PEP-Info"; +constexpr static char ProtocolInfo[] = "Protocol-Info"; +constexpr static char ProtocolQuery[] = "Protocol-Query"; +constexpr static char AccessControl[] = "Access-Control"; +constexpr static char CExt[] = "C-Ext"; +constexpr static char CMan[] = "C-Man"; +constexpr static char COpt[] = "C-Opt"; +constexpr static char CPEP[] = "C-PEP"; +constexpr static char ContentBase[] = "Content-Base"; +constexpr static char ContentMD5[] = "Content-MD5"; +constexpr static char ContentScriptType[] = "Content-Script-Type"; +constexpr static char ContentStyleType[] = "Content-Style-Type"; +constexpr static char ContentVersion[] = "Content-Version"; +constexpr static char Cookie2[] = "Cookie2"; +constexpr static char DefaultStyle[] = "Default-Style"; +constexpr static char DerivedFrom[] = "Derived-From"; +constexpr static char Digest[] = "Digest"; +constexpr static char Ext[] = "Ext"; +constexpr static char Getprofile[] = "GetProfile"; +constexpr static char HTTP2Settings[] = "HTTP2-Settings"; +constexpr static char Man[] = "Man"; +constexpr static char MethodCheck[] = "Method-Check"; +constexpr static char MethodCheckExpires[] = "Method-Check-Expires"; +constexpr static char Opt[] = "Opt"; +constexpr static char P3P[] = "P3P"; +constexpr static char PEP[] = "PEP"; +constexpr static char PepInfo[] = "Pep-Info"; +constexpr static char PICSLabel[] = "PICS-Label"; +constexpr static char Profileobject[] = "ProfileObject"; +constexpr static char Protocol[] = "Protocol"; +constexpr static char ProtocolRequest[] = "Protocol-Request"; +constexpr static char ProxyFeatures[] = "Proxy-Features"; +constexpr static char ProxyInstruction[] = "Proxy-Instruction"; +constexpr static char Public[] = "Public"; +constexpr static char RefererRoot[] = "Referer-Root"; +constexpr static char Safe[] = "Safe"; +constexpr static char SecurityScheme[] = "Security-Scheme"; +constexpr static char SetCookie2[] = "Set-Cookie2"; +constexpr static char Setprofile[] = "SetProfile"; +constexpr static char URI[] = "URI"; +constexpr static char WantDigest[] = "Want-Digest"; +constexpr static char Warning[] = "Warning"; +}; // namespace Header + +}; // namespace PIHTTP + + +#endif diff --git a/libs/main/http_common/pihttptypes.cpp b/libs/main/http_common/pihttptypes.cpp new file mode 100644 index 00000000..829e0765 --- /dev/null +++ b/libs/main/http_common/pihttptypes.cpp @@ -0,0 +1,18 @@ +#include "pihttptypes.h" + + +const char * PIHTTP::methodName(Method m) { + switch (m) { + case Method::Get: return "GET"; + case Method::Head: return "HEAD"; + case Method::Post: return "POST"; + case Method::Put: return "PUT"; + case Method::Delete: return "DELETE"; + case Method::Connect: return "CONNECT"; + case Method::Options: return "OPTIONS"; + case Method::Trace: return "TRACE"; + case Method::Patch: return "PATCH"; + default: break; + }; + return "UNKNOWN"; +} diff --git a/libs/main/http_common/pihttptypes.h b/libs/main/http_common/pihttptypes.h new file mode 100644 index 00000000..33157bd1 --- /dev/null +++ b/libs/main/http_common/pihttptypes.h @@ -0,0 +1,52 @@ +#ifndef pihttptypes_h +#define pihttptypes_h + +#include "pihttpconstants.h" +#include "pip_export.h" +#include "pistring.h" + + +namespace PIHTTP { + + +class PIP_EXPORT MessageConst { +public: + PIHTTP::Method method() const { return m_method; } + PIHTTP::Code code() const { return m_code; } + const PIString & path() const { return m_path; } + const PIByteArray & body() const { return m_body; } + const PIMap & headers() const { return m_headers; } + const PIMap & arguments() const { return m_arguments; } + +protected: + PIHTTP::Method m_method = PIHTTP::Method::Unknown; + PIHTTP::Code m_code = PIHTTP::Code::Ok; + PIString m_path; + PIByteArray m_body; + PIMap m_headers; + PIMap m_arguments; +}; + + +class PIP_EXPORT MessageMutable: public MessageConst { +public: + void setMethod(PIHTTP::Method m) { m_method = m; } + void setCode(PIHTTP::Code c) { m_code = c; } + void setPath(PIString p) { m_path = std::move(p); } + void setBody(PIByteArray b) { m_body = std::move(b); } + const PIMap & headers() const { return m_headers; } + const PIMap & arguments() const { return m_arguments; } + PIMap & headers() { return m_headers; } + PIMap & arguments() { return m_arguments; } + void addHeader(const PIString & header, const PIString & value) { m_headers[header] = value; } + void removeHeader(const PIString & header) { m_headers.remove(header); } +}; + + +PIP_EXPORT const char * methodName(Method m); + + +}; // namespace PIHTTP + + +#endif diff --git a/libs/main/http_server/microhttpd_server.h b/libs/main/http_server/microhttpd_server.h index 5e8c526a..c6b4a9f3 100644 --- a/libs/main/http_server/microhttpd_server.h +++ b/libs/main/http_server/microhttpd_server.h @@ -1,7 +1,7 @@ #ifndef MICROHTTPD_SERVER_P_H #define MICROHTTPD_SERVER_P_H -#include "pibase.h" +#include "pihttptypes.h" #include "piobject.h" #include "pip_http_server_export.h" @@ -15,18 +15,6 @@ public: MicrohttpdServer(); virtual ~MicrohttpdServer(); - enum class Method { - Unknown, - Get, - Head, - Post, - Put, - Delete, - Connect, - Options, - Trace, - Patch - }; enum class Option { ConnectionLimit, // uint ConnectionTimeout, // uint, sec @@ -36,30 +24,6 @@ public: HTTPSKeyPassword // const char * to passwd for key.pem }; - struct PIP_HTTP_SERVER_EXPORT Request { - MicrohttpdServer::Method method; - PIString path; - PIByteArray body; - PIMap headers; - PIMap args; - }; - - class PIP_HTTP_SERVER_EXPORT Reply { - friend struct MicrohttpdServerConnection; - - public: - void addHeader(const PIString & header, const PIString & value); - void removeHeader(const PIString & header); - void setBody(const PIByteArray & b); - void setCode(int c); - - private: - void addFixedHeaders(); - int code = 200; - PIByteArray body; - PIMap headers; - }; - void setOption(Option o, PIVariant v); void setFavicon(const PIByteArray & im); @@ -68,14 +32,15 @@ public: bool isListen() const; void stop(); - void setRequestCallback(std::function c) { callback = c; } + void setRequestCallback(std::function c) { callback = c; } private: + static void addFixedHeaders(PIHTTP::MessageMutable & msg); PRIVATE_DECLARATION(PIP_HTTP_SERVER_EXPORT) PIByteArray favicon; PIMap opts; - std::function callback; + std::function callback; PIByteArray mem_key, mem_cert, key_pass; }; diff --git a/libs/main/http_server/pihttpserver.h b/libs/main/http_server/pihttpserver.h index 39ef9947..cb0d847b 100644 --- a/libs/main/http_server/pihttpserver.h +++ b/libs/main/http_server/pihttpserver.h @@ -10,11 +10,11 @@ public: PIHTTPServer(); virtual ~PIHTTPServer(); - using RequestFunction = std::function; + using RequestFunction = std::function; - void registerPath(const PIString & path, MicrohttpdServer::Method method, RequestFunction functor); + void registerPath(const PIString & path, PIHTTP::Method method, RequestFunction functor); void registerUnhandled(RequestFunction functor); - void unregisterPath(const PIString & path, MicrohttpdServer::Method method); + void unregisterPath(const PIString & path, PIHTTP::Method method); void unregisterPath(const PIString & path); void addReplyHeader(const PIString & name, const PIString & value) { reply_headers[name] = value; } @@ -25,7 +25,7 @@ private: struct Endpoint { bool match(const PIStringList & in_path) const; PIStringList path; - MicrohttpdServer::Method method = MicrohttpdServer::Method::Unknown; + PIHTTP::Method method = PIHTTP::Method::Unknown; RequestFunction function; }; PIMap reply_headers; diff --git a/main.cpp b/main.cpp index 7e99cec4..13ce02f2 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,9 @@ +#include "pihttpclient.h" #include "pip.h" using namespace PICoutManipulators; -PIKbdListener kbd; +// PIKbdListener kbd; const char * pageTitle = "" "" "" @@ -12,16 +13,50 @@ const char * pageTitle = "" int main(int argc, char * argv[]) { + PIHTTPServer server; + server.listen({"127.0.0.1:7777"}); + server.registerUnhandled([](const PIHTTP::MessageConst & msg) -> PIHTTP::MessageMutable { + PIHTTP::MessageMutable ret; + piCout << "server rec:\n\tpath: %1\n\tmethod: %2\n\tbody: %3"_a.arg(msg.path()) + .arg((int)msg.method()) + .arg(PIString::fromUTF8(msg.body())); + ret.setCode(PIHTTP::Code::BadRequest); + ret.setBody(PIByteArray::fromAscii("hello client! 0123456789")); + return ret; + }); + + PIHTTP::MessageMutable req; + req.setBody(PIByteArray::fromAscii("hello server!")); + auto * c = PIHTTPClient::create("http://127.0.0.1:7777/api", PIHTTP::Method::Get, req); + c->onFinish([](PIHTTP::MessageConst r) { + piCout << "finish" << (int)r.code(); + piCout << "headers" << r.headers(); + piCout << "body" << PIString::fromUTF8(r.body()); + }) + ->onError([c](PIHTTP::MessageConst r) { + piCout << "error" << (int)r.code(); + piCout << "msg" << c->lastError(); + }) + ->onAbort([c](PIHTTP::MessageConst r) { + piCout << "abort" << (int)r.code(); + piCout << "msg" << c->lastError(); + }) + ->start(); + piMSleep(500); + server.stop(); + // c->abort(); + // piMSleep(500); + return 0; // piCout << PIString::readableSize(PISystemMonitor::usedRAM()); - PIVector vi; + /*PIVector vi; piForTimes(10) { piSleep(2.); vi.enlarge(1000000); piCout << "now" << vi.size() << vi.capacity(); } - piSleep(5.); + piSleep(5.);*/ /*kbd.enableExitCapture(); PIHTTPServer server;