From fb84784d1c569d000f8243db42132fe9cfb89858 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Thu, 30 Jun 2022 02:08:09 +0200 Subject: [PATCH] Add example chunked encoding response --- src/asio_webserver/clientconnection.cpp | 9 +- src/asio_webserver/clientconnection.h | 6 +- src/asio_webserver/responsehandler.h | 1 + src/asio_webserver/webserver.cpp | 6 +- src/asio_webserver/webserver.h | 10 +- .../chunkedresponsehandler.cpp | 99 +++++++++++++++++++ .../chunkedresponsehandler.h | 31 ++++++ .../debugresponsehandler.cpp | 9 ++ .../errorresponsehandler.cpp | 9 ++ test/webserver_example/examplewebserver.cpp | 13 ++- test/webserver_example/examplewebserver.h | 3 +- test/webserver_example/main.cpp | 19 +++- .../webserver_example/rootresponsehandler.cpp | 10 ++ test/webserver_example/webserver_example.pro | 2 + 14 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 test/webserver_example/chunkedresponsehandler.cpp create mode 100644 test/webserver_example/chunkedresponsehandler.h diff --git a/src/asio_webserver/clientconnection.cpp b/src/asio_webserver/clientconnection.cpp index af54f60..bc81ea2 100644 --- a/src/asio_webserver/clientconnection.cpp +++ b/src/asio_webserver/clientconnection.cpp @@ -1,10 +1,13 @@ #include "clientconnection.h" +// system includes #include #include +// esp-idf includes #include +// local includes #include "webserver.h" #include "responsehandler.h" @@ -19,12 +22,16 @@ ClientConnection::ClientConnection(Webserver &webserver, asio::ip::tcp::socket s { ESP_LOGI(TAG, "new client (%s:%hi)", m_remote_endpoint.address().to_string().c_str(), m_remote_endpoint.port()); + + m_webserver.m_clients++; } ClientConnection::~ClientConnection() { ESP_LOGI(TAG, "client destroyed (%s:%hi)", m_remote_endpoint.address().to_string().c_str(), m_remote_endpoint.port()); + + m_webserver.m_clients--; } void ClientConnection::start() @@ -42,7 +49,7 @@ void ClientConnection::responseFinished(std::error_code ec) return; } - if constexpr (false) // Connection: Keep + if constexpr (true) // Connection: Keep { // ESP_LOGD(TAG, "state changed to RequestLine"); m_state = State::RequestLine; diff --git a/src/asio_webserver/clientconnection.h b/src/asio_webserver/clientconnection.h index 892f9a0..9350cf3 100644 --- a/src/asio_webserver/clientconnection.h +++ b/src/asio_webserver/clientconnection.h @@ -1,11 +1,13 @@ #pragma once -#include +// system includes #include - #include #include +// esp-idf includes +#include + class Webserver; class ResponseHandler; diff --git a/src/asio_webserver/responsehandler.h b/src/asio_webserver/responsehandler.h index a6ce364..1ddd3f9 100644 --- a/src/asio_webserver/responsehandler.h +++ b/src/asio_webserver/responsehandler.h @@ -1,5 +1,6 @@ #pragma once +// system includes #include class ResponseHandler diff --git a/src/asio_webserver/webserver.cpp b/src/asio_webserver/webserver.cpp index cb45303..b48c314 100644 --- a/src/asio_webserver/webserver.cpp +++ b/src/asio_webserver/webserver.cpp @@ -1,15 +1,17 @@ #include "webserver.h" +// esp-idf includes #include +// local includes #include "clientconnection.h" namespace { constexpr const char * const TAG = "ASIO_WEBSERVER"; } // namespace -Webserver::Webserver(asio::io_context &io_context, short port) - : m_acceptor{io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)} +Webserver::Webserver(asio::io_context &io_context, unsigned short port) : + m_acceptor{io_context, asio::ip::tcp::endpoint{asio::ip::tcp::v4(), port}} { ESP_LOGI(TAG, "create webserver on port %hi", port); diff --git a/src/asio_webserver/webserver.h b/src/asio_webserver/webserver.h index b07044c..133d838 100644 --- a/src/asio_webserver/webserver.h +++ b/src/asio_webserver/webserver.h @@ -1,21 +1,29 @@ #pragma once +// system includes #include #include +#include +// esp-idf includes #include +// forward declares class ResponseHandler; class ClientConnection; class Webserver { public: - Webserver(asio::io_context& io_context, short port); + Webserver(asio::io_context& io_context, unsigned short port); virtual ~Webserver() = default; virtual std::unique_ptr makeResponseHandler(ClientConnection &clientConnection, std::string_view method, std::string_view path, std::string_view protocol) = 0; +protected: + friend class ClientConnection; + std::atomic m_clients; + private: void doAccept(); void acceptClient(std::error_code ec, asio::ip::tcp::socket socket); diff --git a/test/webserver_example/chunkedresponsehandler.cpp b/test/webserver_example/chunkedresponsehandler.cpp new file mode 100644 index 0000000..689fc1f --- /dev/null +++ b/test/webserver_example/chunkedresponsehandler.cpp @@ -0,0 +1,99 @@ +#include "chunkedresponsehandler.h" + +// esp-idf includes +#include +#include + +// 3rdparty lib includes +#include +#include + +namespace { +constexpr const char * const TAG = "ASIO_WEBSERVER"; +} // namespace + +ChunkedResponseHandler::ChunkedResponseHandler(ClientConnection &clientConnection) : + m_clientConnection{clientConnection} +{ + ESP_LOGI(TAG, "constructed for (%s:%hi)", + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); +} + +ChunkedResponseHandler::~ChunkedResponseHandler() +{ + ESP_LOGI(TAG, "destructed for (%s:%hi)", + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); +} + +void ChunkedResponseHandler::requestHeaderReceived(std::string_view key, std::string_view value) +{ +} + +void ChunkedResponseHandler::sendResponse() +{ + ESP_LOGI(TAG, "sending response (header) for (%s:%hi)", + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + + m_response = fmt::format("HTTP/1.1 200 Ok\r\n" + "Connection: close\r\n" + "Content-Type: text/html\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n"); + + asio::async_write(m_clientConnection.socket(), + asio::buffer(m_response.data(), m_response.size()), + [this, self=m_clientConnection.shared_from_this()](std::error_code ec, std::size_t length) + { written(ec, length); }); +} + +void ChunkedResponseHandler::written(std::error_code ec, std::size_t length) +{ + if (ec) + { + ESP_LOGW(TAG, "error: %i (%s:%hi)", ec.value(), + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + m_clientConnection.responseFinished(ec); + return; + } + + ESP_LOGI(TAG, "expected=%zd actual=%zd for (%s:%hi)", m_response.size(), length, + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + + if (m_counter < 10) + { + ESP_LOGI(TAG, "sending response (line %i) for (%s:%hi)", m_counter, + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + + m_response = fmt::format("Line number {}
\n", m_counter); + m_response = fmt::format("{:x}\r\n" + "{}\r\n", + m_response.size(), m_response); + + asio::async_write(m_clientConnection.socket(), + asio::buffer(m_response.data(), m_response.size()), + [this, self=m_clientConnection.shared_from_this()](std::error_code ec, std::size_t length) + { written(ec, length); }); + + m_counter++; + } + else if (m_counter == 10) + { + ESP_LOGI(TAG, "sending response (end) for (%s:%hi)", + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + + m_response = fmt::format("0\r\n" + "\r\n"); + + asio::async_write(m_clientConnection.socket(), + asio::buffer(m_response.data(), m_response.size()), + [this, self=m_clientConnection.shared_from_this()](std::error_code ec, std::size_t length) + { written(ec, length); }); + + m_counter++; + } + else + { + ESP_LOGI(TAG, "end"); + m_clientConnection.responseFinished(ec); + } +} diff --git a/test/webserver_example/chunkedresponsehandler.h b/test/webserver_example/chunkedresponsehandler.h new file mode 100644 index 0000000..314859f --- /dev/null +++ b/test/webserver_example/chunkedresponsehandler.h @@ -0,0 +1,31 @@ +#pragma once + +// system includes +#include +#include +#include + +// 3rdparty lib includes +#include + +// forward declarations +class ClientConnection; + +class ChunkedResponseHandler : public ResponseHandler +{ +public: + ChunkedResponseHandler(ClientConnection &clientConnection); + ~ChunkedResponseHandler() override; + + void requestHeaderReceived(std::string_view key, std::string_view value) final; + void sendResponse() final; + +private: + void written(std::error_code ec, std::size_t length); + + ClientConnection &m_clientConnection; + + std::string m_response; + + int m_counter{}; +}; diff --git a/test/webserver_example/debugresponsehandler.cpp b/test/webserver_example/debugresponsehandler.cpp index 239d3b7..68f1d50 100644 --- a/test/webserver_example/debugresponsehandler.cpp +++ b/test/webserver_example/debugresponsehandler.cpp @@ -107,8 +107,17 @@ void DebugResponseHandler::sendResponse() void DebugResponseHandler::written(std::error_code ec, std::size_t length) { + if (ec) + { + ESP_LOGW(TAG, "error: %i (%s:%hi)", ec.value(), + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + m_clientConnection.responseFinished(ec); + return; + } + ESP_LOGI(TAG, "expected=%zd actual=%zd for %.*s %.*s (%s:%hi)", m_response.size(), length, m_method.size(), m_method.data(), m_path.size(), m_path.data(), m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + m_clientConnection.responseFinished(ec); } diff --git a/test/webserver_example/errorresponsehandler.cpp b/test/webserver_example/errorresponsehandler.cpp index 7c715cf..61a23fe 100644 --- a/test/webserver_example/errorresponsehandler.cpp +++ b/test/webserver_example/errorresponsehandler.cpp @@ -52,7 +52,16 @@ void ErrorResponseHandler::sendResponse() void ErrorResponseHandler::written(std::error_code ec, std::size_t length) { + if (ec) + { + ESP_LOGW(TAG, "error: %i (%s:%hi)", ec.value(), + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + m_clientConnection.responseFinished(ec); + return; + } + ESP_LOGI(TAG, "expected=%zd actual=%zd for %.*s (%s:%hi)", m_response.size(), length, m_path.size(), m_path.data(), m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + m_clientConnection.responseFinished(ec); } diff --git a/test/webserver_example/examplewebserver.cpp b/test/webserver_example/examplewebserver.cpp index 6166968..005e552 100644 --- a/test/webserver_example/examplewebserver.cpp +++ b/test/webserver_example/examplewebserver.cpp @@ -1,9 +1,18 @@ #include "examplewebserver.h" +// esp-idf includes +#include + +// local includes #include "rootresponsehandler.h" #include "debugresponsehandler.h" +#include "chunkedresponsehandler.h" #include "errorresponsehandler.h" +namespace { +constexpr const char * const TAG = "ASIO_WEBSERVER"; +} // namespace + std::unique_ptr ExampleWebserver::makeResponseHandler(ClientConnection &clientConnection, std::string_view method, std::string_view path, std::string_view protocol) { const std::string_view processedPath{[&](){ @@ -14,8 +23,10 @@ std::unique_ptr ExampleWebserver::makeResponseHandler(ClientCon if (processedPath.empty() || processedPath == "/") return std::make_unique(clientConnection); - else if (processedPath == "/debug" || processedPath.starts_with("/debug/") ) + else if (processedPath == "/debug" || processedPath.starts_with("/debug/")) return std::make_unique(clientConnection, method, path, protocol); + else if (processedPath == "/chunked") + return std::make_unique(clientConnection); else return std::make_unique(clientConnection, path); } diff --git a/test/webserver_example/examplewebserver.h b/test/webserver_example/examplewebserver.h index 191eddd..7ad0e5e 100644 --- a/test/webserver_example/examplewebserver.h +++ b/test/webserver_example/examplewebserver.h @@ -1,6 +1,7 @@ #pragma once -#include "asio_webserver/webserver.h" +// 3rdparty lib includes +#include class ExampleWebserver : public Webserver { diff --git a/test/webserver_example/main.cpp b/test/webserver_example/main.cpp index 79eb8ef..1341cfd 100644 --- a/test/webserver_example/main.cpp +++ b/test/webserver_example/main.cpp @@ -1,12 +1,25 @@ #include + +// esp-idf includes +#include #include -#include +// 3rdparty lib includes +#include +#include +// local includes #include "examplewebserver.h" +namespace { +constexpr const char * const TAG = "ASIO_WEBSERVER"; +} // namespace + int main(int argc, char *argv[]) { + CPP_UNUSED(argc) + CPP_UNUSED(argv) + qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} " "[" "%{if-debug}D%{endif}" @@ -19,9 +32,9 @@ int main(int argc, char *argv[]) "%{message}")); asio::io_context io_context; - ExampleWebserver server{io_context, (short int)8080}; + ExampleWebserver server{io_context, (unsigned short)8080}; - ESP_LOGI("running mainloop"); + ESP_LOGI(TAG, "running mainloop"); io_context.run(); } diff --git a/test/webserver_example/rootresponsehandler.cpp b/test/webserver_example/rootresponsehandler.cpp index 2821b6a..55d0d60 100644 --- a/test/webserver_example/rootresponsehandler.cpp +++ b/test/webserver_example/rootresponsehandler.cpp @@ -41,6 +41,7 @@ void RootResponseHandler::sendResponse() "" "

asio test webserver

" "Debug" + "Chunked" "" ""); @@ -59,7 +60,16 @@ void RootResponseHandler::sendResponse() void RootResponseHandler::written(std::error_code ec, std::size_t length) { + if (ec) + { + ESP_LOGW(TAG, "error: %i (%s:%hi)", ec.value(), + m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + m_clientConnection.responseFinished(ec); + return; + } + ESP_LOGI(TAG, "expected=%zd actual=%zd for (%s:%hi)", m_response.size(), length, m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port()); + m_clientConnection.responseFinished(ec); } diff --git a/test/webserver_example/webserver_example.pro b/test/webserver_example/webserver_example.pro index db17956..83b49ef 100644 --- a/test/webserver_example/webserver_example.pro +++ b/test/webserver_example/webserver_example.pro @@ -5,12 +5,14 @@ QT += core CONFIG += c++latest HEADERS += \ + chunkedresponsehandler.h \ debugresponsehandler.h \ errorresponsehandler.h \ examplewebserver.h \ rootresponsehandler.h SOURCES += \ + chunkedresponsehandler.cpp \ debugresponsehandler.cpp \ errorresponsehandler.cpp \ examplewebserver.cpp \