Add example chunked encoding response

This commit is contained in:
2022-06-30 02:08:09 +02:00
parent 7b93dfa6cf
commit fb84784d1c
14 changed files with 216 additions and 11 deletions

View File

@@ -1,10 +1,13 @@
#include "clientconnection.h"
// system includes
#include <cstdio>
#include <utility>
// esp-idf includes
#include <esp_log.h>
// 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;

View File

@@ -1,11 +1,13 @@
#pragma once
#include <asio.hpp>
// system includes
#include <memory>
#include <string_view>
#include <string>
// esp-idf includes
#include <asio.hpp>
class Webserver;
class ResponseHandler;

View File

@@ -1,5 +1,6 @@
#pragma once
// system includes
#include <string_view>
class ResponseHandler

View File

@@ -1,15 +1,17 @@
#include "webserver.h"
// esp-idf includes
#include <esp_log.h>
// 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);

View File

@@ -1,21 +1,29 @@
#pragma once
// system includes
#include <memory>
#include <string_view>
#include <atomic>
// esp-idf includes
#include <asio.hpp>
// 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<ResponseHandler> makeResponseHandler(ClientConnection &clientConnection, std::string_view method, std::string_view path, std::string_view protocol) = 0;
protected:
friend class ClientConnection;
std::atomic<int> m_clients;
private:
void doAccept();
void acceptClient(std::error_code ec, asio::ip::tcp::socket socket);

View File

@@ -0,0 +1,99 @@
#include "chunkedresponsehandler.h"
// esp-idf includes
#include <asio.hpp>
#include <esp_log.h>
// 3rdparty lib includes
#include <fmt/core.h>
#include <asio_webserver/clientconnection.h>
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 {}<br/>\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);
}
}

View File

@@ -0,0 +1,31 @@
#pragma once
// system includes
#include <string_view>
#include <string>
#include <system_error>
// 3rdparty lib includes
#include <asio_webserver/responsehandler.h>
// 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{};
};

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -1,9 +1,18 @@
#include "examplewebserver.h"
// esp-idf includes
#include <esp_log.h>
// 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<ResponseHandler> 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<ResponseHandler> ExampleWebserver::makeResponseHandler(ClientCon
if (processedPath.empty() || processedPath == "/")
return std::make_unique<RootResponseHandler>(clientConnection);
else if (processedPath == "/debug" || processedPath.starts_with("/debug/") )
else if (processedPath == "/debug" || processedPath.starts_with("/debug/"))
return std::make_unique<DebugResponseHandler>(clientConnection, method, path, protocol);
else if (processedPath == "/chunked")
return std::make_unique<ChunkedResponseHandler>(clientConnection);
else
return std::make_unique<ErrorResponseHandler>(clientConnection, path);
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "asio_webserver/webserver.h"
// 3rdparty lib includes
#include <asio_webserver/webserver.h>
class ExampleWebserver : public Webserver
{

View File

@@ -1,12 +1,25 @@
#include <QLoggingCategory>
// esp-idf includes
#include <esp_log.h>
#include <asio.hpp>
#include <esp_log.h>
// 3rdparty lib includes
#include <cpputils.h>
#include <cppmacros.h>
// 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();
}

View File

@@ -41,6 +41,7 @@ void RootResponseHandler::sendResponse()
"<body>"
"<h1>asio test webserver</h1>"
"<a href=\"/debug\">Debug</a>"
"<a href=\"/chunked\">Chunked</a>"
"</body>"
"</html>");
@@ -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);
}

View File

@@ -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 \