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

+6
View File
@@ -0,0 +1,6 @@
*.pro.user*
cpputils/
date/
espchrono/
expected/
fmt/
+24
View File
@@ -0,0 +1,24 @@
TEMPLATE = lib
QT += core testlib
CONFIG += c++latest
win32: DESTDIR=$${OUT_PWD}
include(paths.pri)
isEmpty(ASIO_WEBSERVER_DIR): error("ASIO_WEBSERVER_DIR not set")
isEmpty(CPPUTILS_DIR): error("CPPUTILS_DIR not set")
isEmpty(ESPCHRONO_DIR): error("ESPCHRONO_DIR not set")
isEmpty(FMT_DIR): error("FMT_DIR not set")
include(dependencies.pri)
include($$ASIO_WEBSERVER_DIR/asio_webserver_src.pri)
include($$CPPUTILS_DIR/cpputils_src.pri)
include($$CPPUTILS_DIR/test/cpputilstestutils_src.pri)
include($$ESPCHRONO_DIR/espchrono_src.pri)
include($$ESPCHRONO_DIR/test/espchronotestutils_src.pri)
include($$FMT_DIR/fmt_src.pri)
HEADERS += esp_log.h
+77
View File
@@ -0,0 +1,77 @@
TEMPLATE = subdirs
equals(CLONE_CPPUTILS, 1) {
CPPUTILS_DIR = $$PWD/cpputils
message("Checking out cpputils...")
exists($$CPPUTILS_DIR/.git) {
system("git -C $$CPPUTILS_DIR pull")
} else {
isEmpty(CPPUTILS_URL) {
CPPUTILS_URL = https://github.com/0xFEEDC0DE64/cpputils.git
}
system("git clone $$CPPUTILS_URL $$CPPUTILS_DIR")
}
}
equals(CLONE_DATE, 1) {
DATE_DIR = $$PWD/date
message("Checking out date...")
exists($$DATE_DIR/.git): {
system("git -C $$DATE_DIR pull")
} else {
isEmpty(DATE_URL) {
DATE_URL = https://github.com/0xFEEDC0DE64/date.git
}
system("git clone $$DATE_URL $$DATE_DIR")
}
}
equals(CLONE_ESPCHRONO, 1) {
ESPCHRONO_DIR = $$PWD/espchrono
message("Checking out espchrono...")
exists($$ESPCHRONO_DIR/.git): {
system("git -C $$ESPCHRONO_DIR pull")
} else {
isEmpty(ESPCHRONO_URL) {
ESPCHRONO_URL = https://github.com/0xFEEDC0DE64/espchrono.git
}
system("git clone $$ESPCHRONO_URL $$ESPCHRONO_DIR")
}
}
equals(CLONE_EXPECTED, 1) {
EXPECTED_DIR = $$PWD/expected
message("Checking out expected...")
exists($$EXPECTED_DIR/.git) {
system("git -C $$EXPECTED_DIR pull")
} else {
isEmpty(EXPECTED_URL) {
EXPECTED_URL = https://github.com/0xFEEDC0DE64/expected.git
}
system("git clone $$EXPECTED_URL $$EXPECTED_DIR")
}
}
equals(CLONE_FMT, 1) {
FMT_DIR = $$PWD/fmt
message("Checking out fmt...")
exists($$FMT_DIR/.git) {
system("git -C $$FMT_DIR pull")
} else {
isEmpty(FMT_URL) {
FMT_URL = https://github.com/0xFEEDC0DE64/fmt.git
}
system("git clone $$FMT_URL $$FMT_DIR")
}
}
SUBDIRS += \
asio_webserver.pro \
webserver_example
sub-webserver_example.depends += sub-asio_webserver-pro
+17
View File
@@ -0,0 +1,17 @@
isEmpty(ASIO_WEBSERVER_DIR): error("ASIO_WEBSERVER_DIR not set")
isEmpty(CPPUTILS_DIR): error("CPPUTILS_DIR not set")
isEmpty(DATE_DIR): error("DATE_DIR not set")
isEmpty(ESPCHRONO_DIR): error("ESPCHRONO_DIR not set")
isEmpty(EXPECTED_DIR): error("EXPECTED_DIR not set")
isEmpty(FMT_DIR): error("FMT_DIR not set")
include($$ASIO_WEBSERVER_DIR/asio_webserver.pri)
include($$CPPUTILS_DIR/cpputils.pri)
include($$CPPUTILS_DIR/test/cpputilstestutils.pri)
include($$DATE_DIR/date.pri)
include($$ESPCHRONO_DIR/espchrono.pri)
include($$ESPCHRONO_DIR/test/espchronotestutils.pri)
include($$EXPECTED_DIR/expected.pri)
include($$FMT_DIR/fmt.pri)
QMAKE_CXXFLAGS += -Wno-missing-field-initializers
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <QDebug>
#define ESP_LOGV(TAG, ...) qDebug(__VA_ARGS__)
#define ESP_LOGD(TAG, ...) qDebug(__VA_ARGS__)
#define ESP_LOGI(TAG, ...) qInfo(__VA_ARGS__)
#define ESP_LOGW(TAG, ...) qWarning(__VA_ARGS__)
#define ESP_LOGE(TAG, ...) qCritical(__VA_ARGS__)
+56
View File
@@ -0,0 +1,56 @@
ASIO_WEBSERVER_DIR = $$PWD/..
equals(CLONE_CPPUTILS, 1) {
CPPUTILS_DIR = $$PWD/cpputils
!exists($$CPPUTILS_DIR) {
error("$$CPPUTILS_DIR not found, please check all dependencies")
}
} else: exists($$PWD/../../cpputils/src) {
CPPUTILS_DIR = $$PWD/../../cpputils
} else {
error("cpputils not found, please check all dependencies")
}
equals(CLONE_DATE, 1) {
DATE_DIR = $$PWD/date
!exists($$DATE_DIR) {
error("$$DATE_DIR not found, please check all dependencies")
}
} else: exists($$PWD/../../date/include) {
DATE_DIR = $$PWD/../../date
} else {
error("date not found, please check all dependencies")
}
equals(CLONE_ESPCHRONO, 1) {
ESPCHRONO_DIR = $$PWD/espchrono
!exists($$ESPCHRONO_DIR) {
error("$$ESPCHRONO_DIR not found, please check all dependencies")
}
} else: exists($$PWD/../../espchrono/src) {
ESPCHRONO_DIR = $$PWD/../../espchrono
} else {
error("espchrono not found, please check all dependencies")
}
equals(CLONE_EXPECTED, 1) {
EXPECTED_DIR = $$PWD/expected
!exists($$EXPECTED_DIR) {
error("$$EXPECTED_DIR not found, please check all dependencies")
}
} else: exists($$PWD/../../expected/include) {
EXPECTED_DIR = $$PWD/../../expected
} else {
error("expected not found, please check all dependencies")
}
equals(CLONE_FMT, 1) {
FMT_DIR = $$PWD/fmt
!exists($$FMT_DIR) {
error("$$FMT_DIR not found, please check all dependencies")
}
} else: exists($$PWD/../../fmt/include) {
FMT_DIR = $$PWD/../../fmt
} else {
error("fmt not found, please check all dependencies")
}
@@ -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