Add initial webserver sources

This commit is contained in:
feedc0de committed 2022-06-30 01:35:02 +02:00
1 parent 2721be2d22
commit 7b93dfa6cf
26 files changed
+1018

No files matched your search

@@ -0,0 +1,114 @@
#include "debugresponsehandler.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
DebugResponseHandler::DebugResponseHandler(ClientConnection &clientConnection, std::string_view method, std::string_view path, std::string_view protocol) :
m_clientConnection{clientConnection}, m_method{method}, m_path{path}, m_protocol{protocol}
{
ESP_LOGI(TAG, "constructed for %.*s %.*s (%s:%hi)", m_method.size(), m_method.data(), path.size(), path.data(),
m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
}
DebugResponseHandler::~DebugResponseHandler()
{
ESP_LOGI(TAG, "destructed for %.*s %.*s (%s:%hi)", 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());
}
void DebugResponseHandler::requestHeaderReceived(std::string_view key, std::string_view value)
{
m_requestHeaders.emplace_back(std::make_pair(std::string{key}, std::string{value}));
}
void DebugResponseHandler::sendResponse()
{
ESP_LOGI(TAG, "sending response for %.*s %.*s (%s:%hi)", 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_response = fmt::format("<html>"
"<head>"
"<title>Test</title>"
"</head>"
"<body>"
"<h1>Request details:</h1>"
"<table>"
"<tbody>"
"<tr><th>Method:</th><td>{}</td></tr>"
"<tr><th>Path:</th><td>{}</td></tr>"
"<tr><th>Protocol:</th><td>{}</td></tr>"
"<tr>"
"<th>Request headers:</th>"
"<td>"
"<table border=\"1\">",
m_method, m_path, m_protocol);
for (const auto &pair : m_requestHeaders)
m_response += fmt::format( "<tr><th>{}</th><td>{}</td></tr>",
pair.first, pair.second);
m_response += fmt::format( "</table>"
"</td>"
"</tr>"
"</tbody>"
"</table>"
"<form method=\"GET\">"
"<fieldset>"
"<legend>GET form with line edit</legend>"
"<label>Text-Input: <input type=\"text\" name=\"inputName\" /></label>"
"<button type=\"submit\">Go</button>"
"</fieldset>"
"</form>"
"<form method=\"POST\">"
"<fieldset>"
"<legend>POST form with line edit</legend>"
"<label>Text-Input: <input type=\"text\" name=\"inputName\" /></label>"
"<button type=\"submit\">Go</button>"
"</fieldset>"
"</form>"
"<form method=\"POST\">"
"<fieldset>"
"<legend>POST form with multiline edit</legend>"
"<label>Text-Input: <textarea name=\"inputName\"></textarea></label>"
"<button type=\"submit\">Go</button>"
"</fieldset>"
"</form>"
"<form method=\"POST\" enctype=\"multipart/form-data\">"
"<fieldset>"
"<legend>POST form with multipart form-data and line edit</legend>"
"<label>Text-Input: <input type=\"text\" name=\"inputName\" /></label>"
"<button type=\"submit\">Go</button>"
"</fieldset>"
"</form>"
"</body>"
"</html>");
m_response = fmt::format("HTTP/1.1 200 Ok\r\n"
"Content-Type: text/html\r\n"
"Content-Length: {}\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); });
}
void DebugResponseHandler::written(std::error_code ec, std::size_t 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_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
m_clientConnection.responseFinished(ec);
}
@@ -0,0 +1,36 @@
#pragma once
// system includes
#include <string_view>
#include <string>
#include <utility>
#include <vector>
#include <system_error>
// 3rdparty lib includes
#include <asio_webserver/responsehandler.h>
// forward declarations
class ClientConnection;
class DebugResponseHandler : public ResponseHandler
{
public:
DebugResponseHandler(ClientConnection &clientConnection, std::string_view method, std::string_view path, std::string_view protocol);
~DebugResponseHandler() 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_method;
std::string m_path;
std::string m_protocol;
std::vector<std::pair<std::string, std::string>> m_requestHeaders;
std::string m_response;
};
@@ -0,0 +1,58 @@
#include "errorresponsehandler.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
ErrorResponseHandler::ErrorResponseHandler(ClientConnection &clientConnection, std::string_view path) :
m_clientConnection{clientConnection},
m_path{path}
{
ESP_LOGI(TAG, "constructed for %.*s (%s:%hi)", path.size(), path.data(),
m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
}
ErrorResponseHandler::~ErrorResponseHandler()
{
ESP_LOGI(TAG, "destructed for %.*s (%s:%hi)", m_path.size(), m_path.data(),
m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
}
void ErrorResponseHandler::requestHeaderReceived(std::string_view key, std::string_view value)
{
}
void ErrorResponseHandler::sendResponse()
{
ESP_LOGI(TAG, "sending response for %.*s (%s:%hi)", m_path.size(), m_path.data(),
m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
m_response = fmt::format("Error 404 Not Found: {}", m_path);
m_response = fmt::format("HTTP/1.1 404 Not Found\r\n"
"Connection: close\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: {}\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); });
}
void ErrorResponseHandler::written(std::error_code ec, std::size_t length)
{
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);
}
@@ -0,0 +1,30 @@
#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 ErrorResponseHandler : public ResponseHandler
{
public:
ErrorResponseHandler(ClientConnection &clientConnection, std::string_view path);
~ErrorResponseHandler() 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_path;
std::string m_response;
};
@@ -0,0 +1,21 @@
#include "examplewebserver.h"
#include "rootresponsehandler.h"
#include "debugresponsehandler.h"
#include "errorresponsehandler.h"
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 auto index = path.find('?');
return index == std::string_view::npos ?
path : path.substr(0, index);
}()};
if (processedPath.empty() || processedPath == "/")
return std::make_unique<RootResponseHandler>(clientConnection);
else if (processedPath == "/debug" || processedPath.starts_with("/debug/") )
return std::make_unique<DebugResponseHandler>(clientConnection, method, path, protocol);
else
return std::make_unique<ErrorResponseHandler>(clientConnection, path);
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "asio_webserver/webserver.h"
class ExampleWebserver : public Webserver
{
public:
using Webserver::Webserver;
std::unique_ptr<ResponseHandler> makeResponseHandler(ClientConnection &clientConnection, std::string_view method, std::string_view path, std::string_view protocol) final;
};
+27
View File
@@ -0,0 +1,27 @@
#include <QLoggingCategory>
#include <asio.hpp>
#include <esp_log.h>
#include "examplewebserver.h"
int main(int argc, char *argv[])
{
qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} "
"["
"%{if-debug}D%{endif}"
"%{if-info}I%{endif}"
"%{if-warning}W%{endif}"
"%{if-critical}C%{endif}"
"%{if-fatal}F%{endif}"
"] "
"%{function}(): "
"%{message}"));
asio::io_context io_context;
ExampleWebserver server{io_context, (short int)8080};
ESP_LOGI("running mainloop");
io_context.run();
}
@@ -0,0 +1,65 @@
#include "rootresponsehandler.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
RootResponseHandler::RootResponseHandler(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());
}
RootResponseHandler::~RootResponseHandler()
{
ESP_LOGI(TAG, "destructed for (%s:%hi)",
m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
}
void RootResponseHandler::requestHeaderReceived(std::string_view key, std::string_view value)
{
}
void RootResponseHandler::sendResponse()
{
ESP_LOGI(TAG, "sending response for (%s:%hi)",
m_clientConnection.remote_endpoint().address().to_string().c_str(), m_clientConnection.remote_endpoint().port());
m_response = fmt::format("<html>"
"<head>"
"<title>asio test webserver</title>"
"</head>"
"<body>"
"<h1>asio test webserver</h1>"
"<a href=\"/debug\">Debug</a>"
"</body>"
"</html>");
m_response = fmt::format("HTTP/1.1 200 Ok\r\n"
"Connection: close\r\n"
"Content-Type: text/html\r\n"
"Content-Length: {}\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); });
}
void RootResponseHandler::written(std::error_code ec, std::size_t 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.responseFinished(ec);
}
@@ -0,0 +1,29 @@
#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 RootResponseHandler : public ResponseHandler
{
public:
RootResponseHandler(ClientConnection &clientConnection);
~RootResponseHandler() 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;
};
@@ -0,0 +1,32 @@
TEMPLATE = app
QT += core
CONFIG += c++latest
HEADERS += \
debugresponsehandler.h \
errorresponsehandler.h \
examplewebserver.h \
rootresponsehandler.h
SOURCES += \
debugresponsehandler.cpp \
errorresponsehandler.cpp \
examplewebserver.cpp \
main.cpp \
rootresponsehandler.cpp
unix: TARGET=webserver_example.bin
DESTDIR=$${OUT_PWD}/..
INCLUDEPATH += $$PWD/..
include(../paths.pri)
include(../dependencies.pri)
unix: {
LIBS += -Wl,-rpath=\\\$$ORIGIN
}
LIBS += -L$${OUT_PWD}/..
LIBS += -lasio_webserver