Add example chunked encoding response
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
#include "clientconnection.h"
|
#include "clientconnection.h"
|
||||||
|
|
||||||
|
// system includes
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
// esp-idf includes
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
// local includes
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
#include "responsehandler.h"
|
#include "responsehandler.h"
|
||||||
|
|
||||||
@@ -19,12 +22,16 @@ ClientConnection::ClientConnection(Webserver &webserver, asio::ip::tcp::socket s
|
|||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "new client (%s:%hi)",
|
ESP_LOGI(TAG, "new client (%s:%hi)",
|
||||||
m_remote_endpoint.address().to_string().c_str(), m_remote_endpoint.port());
|
m_remote_endpoint.address().to_string().c_str(), m_remote_endpoint.port());
|
||||||
|
|
||||||
|
m_webserver.m_clients++;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientConnection::~ClientConnection()
|
ClientConnection::~ClientConnection()
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "client destroyed (%s:%hi)",
|
ESP_LOGI(TAG, "client destroyed (%s:%hi)",
|
||||||
m_remote_endpoint.address().to_string().c_str(), m_remote_endpoint.port());
|
m_remote_endpoint.address().to_string().c_str(), m_remote_endpoint.port());
|
||||||
|
|
||||||
|
m_webserver.m_clients--;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientConnection::start()
|
void ClientConnection::start()
|
||||||
@@ -42,7 +49,7 @@ void ClientConnection::responseFinished(std::error_code ec)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if constexpr (false) // Connection: Keep
|
if constexpr (true) // Connection: Keep
|
||||||
{
|
{
|
||||||
// ESP_LOGD(TAG, "state changed to RequestLine");
|
// ESP_LOGD(TAG, "state changed to RequestLine");
|
||||||
m_state = State::RequestLine;
|
m_state = State::RequestLine;
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <asio.hpp>
|
// system includes
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
// esp-idf includes
|
||||||
|
#include <asio.hpp>
|
||||||
|
|
||||||
class Webserver;
|
class Webserver;
|
||||||
class ResponseHandler;
|
class ResponseHandler;
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
// system includes
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
class ResponseHandler
|
class ResponseHandler
|
||||||
|
@@ -1,15 +1,17 @@
|
|||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
|
|
||||||
|
// esp-idf includes
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
// local includes
|
||||||
#include "clientconnection.h"
|
#include "clientconnection.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr const char * const TAG = "ASIO_WEBSERVER";
|
constexpr const char * const TAG = "ASIO_WEBSERVER";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Webserver::Webserver(asio::io_context &io_context, short port)
|
Webserver::Webserver(asio::io_context &io_context, unsigned short port) :
|
||||||
: m_acceptor{io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)}
|
m_acceptor{io_context, asio::ip::tcp::endpoint{asio::ip::tcp::v4(), port}}
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "create webserver on port %hi", port);
|
ESP_LOGI(TAG, "create webserver on port %hi", port);
|
||||||
|
|
||||||
|
@@ -1,21 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
// system includes
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
// esp-idf includes
|
||||||
#include <asio.hpp>
|
#include <asio.hpp>
|
||||||
|
|
||||||
|
// forward declares
|
||||||
class ResponseHandler;
|
class ResponseHandler;
|
||||||
class ClientConnection;
|
class ClientConnection;
|
||||||
|
|
||||||
class Webserver
|
class Webserver
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Webserver(asio::io_context& io_context, short port);
|
Webserver(asio::io_context& io_context, unsigned short port);
|
||||||
virtual ~Webserver() = default;
|
virtual ~Webserver() = default;
|
||||||
|
|
||||||
virtual std::unique_ptr<ResponseHandler> makeResponseHandler(ClientConnection &clientConnection, std::string_view method, std::string_view path, std::string_view protocol) = 0;
|
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:
|
private:
|
||||||
void doAccept();
|
void doAccept();
|
||||||
void acceptClient(std::error_code ec, asio::ip::tcp::socket socket);
|
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)
|
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,
|
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_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.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
|
||||||
|
|
||||||
m_clientConnection.responseFinished(ec);
|
m_clientConnection.responseFinished(ec);
|
||||||
}
|
}
|
||||||
|
@@ -52,7 +52,16 @@ void ErrorResponseHandler::sendResponse()
|
|||||||
|
|
||||||
void ErrorResponseHandler::written(std::error_code ec, std::size_t length)
|
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(),
|
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.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
|
||||||
|
|
||||||
m_clientConnection.responseFinished(ec);
|
m_clientConnection.responseFinished(ec);
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,18 @@
|
|||||||
#include "examplewebserver.h"
|
#include "examplewebserver.h"
|
||||||
|
|
||||||
|
// esp-idf includes
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
// local includes
|
||||||
#include "rootresponsehandler.h"
|
#include "rootresponsehandler.h"
|
||||||
#include "debugresponsehandler.h"
|
#include "debugresponsehandler.h"
|
||||||
|
#include "chunkedresponsehandler.h"
|
||||||
#include "errorresponsehandler.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)
|
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{[&](){
|
const std::string_view processedPath{[&](){
|
||||||
@@ -14,8 +23,10 @@ std::unique_ptr<ResponseHandler> ExampleWebserver::makeResponseHandler(ClientCon
|
|||||||
|
|
||||||
if (processedPath.empty() || processedPath == "/")
|
if (processedPath.empty() || processedPath == "/")
|
||||||
return std::make_unique<RootResponseHandler>(clientConnection);
|
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);
|
return std::make_unique<DebugResponseHandler>(clientConnection, method, path, protocol);
|
||||||
|
else if (processedPath == "/chunked")
|
||||||
|
return std::make_unique<ChunkedResponseHandler>(clientConnection);
|
||||||
else
|
else
|
||||||
return std::make_unique<ErrorResponseHandler>(clientConnection, path);
|
return std::make_unique<ErrorResponseHandler>(clientConnection, path);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "asio_webserver/webserver.h"
|
// 3rdparty lib includes
|
||||||
|
#include <asio_webserver/webserver.h>
|
||||||
|
|
||||||
class ExampleWebserver : public Webserver
|
class ExampleWebserver : public Webserver
|
||||||
{
|
{
|
||||||
|
@@ -1,12 +1,25 @@
|
|||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
// esp-idf includes
|
||||||
|
#include <esp_log.h>
|
||||||
#include <asio.hpp>
|
#include <asio.hpp>
|
||||||
|
|
||||||
#include <esp_log.h>
|
// 3rdparty lib includes
|
||||||
|
#include <cpputils.h>
|
||||||
|
#include <cppmacros.h>
|
||||||
|
|
||||||
|
// local includes
|
||||||
#include "examplewebserver.h"
|
#include "examplewebserver.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr const char * const TAG = "ASIO_WEBSERVER";
|
||||||
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
CPP_UNUSED(argc)
|
||||||
|
CPP_UNUSED(argv)
|
||||||
|
|
||||||
qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} "
|
qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} "
|
||||||
"["
|
"["
|
||||||
"%{if-debug}D%{endif}"
|
"%{if-debug}D%{endif}"
|
||||||
@@ -19,9 +32,9 @@ int main(int argc, char *argv[])
|
|||||||
"%{message}"));
|
"%{message}"));
|
||||||
|
|
||||||
asio::io_context io_context;
|
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();
|
io_context.run();
|
||||||
}
|
}
|
||||||
|
@@ -41,6 +41,7 @@ void RootResponseHandler::sendResponse()
|
|||||||
"<body>"
|
"<body>"
|
||||||
"<h1>asio test webserver</h1>"
|
"<h1>asio test webserver</h1>"
|
||||||
"<a href=\"/debug\">Debug</a>"
|
"<a href=\"/debug\">Debug</a>"
|
||||||
|
"<a href=\"/chunked\">Chunked</a>"
|
||||||
"</body>"
|
"</body>"
|
||||||
"</html>");
|
"</html>");
|
||||||
|
|
||||||
@@ -59,7 +60,16 @@ void RootResponseHandler::sendResponse()
|
|||||||
|
|
||||||
void RootResponseHandler::written(std::error_code ec, std::size_t length)
|
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,
|
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.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
|
||||||
|
|
||||||
m_clientConnection.responseFinished(ec);
|
m_clientConnection.responseFinished(ec);
|
||||||
}
|
}
|
||||||
|
@@ -5,12 +5,14 @@ QT += core
|
|||||||
CONFIG += c++latest
|
CONFIG += c++latest
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
chunkedresponsehandler.h \
|
||||||
debugresponsehandler.h \
|
debugresponsehandler.h \
|
||||||
errorresponsehandler.h \
|
errorresponsehandler.h \
|
||||||
examplewebserver.h \
|
examplewebserver.h \
|
||||||
rootresponsehandler.h
|
rootresponsehandler.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
chunkedresponsehandler.cpp \
|
||||||
debugresponsehandler.cpp \
|
debugresponsehandler.cpp \
|
||||||
errorresponsehandler.cpp \
|
errorresponsehandler.cpp \
|
||||||
examplewebserver.cpp \
|
examplewebserver.cpp \
|
||||||
|
Reference in New Issue
Block a user