Add example chunked encoding response
This commit is contained in:
@@ -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;
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
// system includes
|
||||
#include <string_view>
|
||||
|
||||
class ResponseHandler
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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);
|
||||
|
99
test/webserver_example/chunkedresponsehandler.cpp
Normal file
99
test/webserver_example/chunkedresponsehandler.cpp
Normal 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);
|
||||
}
|
||||
}
|
31
test/webserver_example/chunkedresponsehandler.h
Normal file
31
test/webserver_example/chunkedresponsehandler.h
Normal 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{};
|
||||
};
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "asio_webserver/webserver.h"
|
||||
// 3rdparty lib includes
|
||||
#include <asio_webserver/webserver.h>
|
||||
|
||||
class ExampleWebserver : public Webserver
|
||||
{
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 \
|
||||
|
Reference in New Issue
Block a user