Skip to content

Commit

Permalink
Feature: HttpServer plugins (#2354)
Browse files Browse the repository at this point in the history
Co-authored-by: mikee47 <[email protected]>
  • Loading branch information
slaff and mikee47 authored Aug 1, 2021
1 parent 7e05f78 commit 41f3394
Show file tree
Hide file tree
Showing 19 changed files with 784 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
*/
#define HTTP_HEADER_FIELDNAME_MAP(XX) \
XX(ACCEPT, "Accept", 0, "Limit acceptable response types") \
XX(ACCEPT_ENCODING, "Accept-Encoding", 0, "Limit acceptable content encoding types") \
XX(ACCESS_CONTROL_ALLOW_ORIGIN, "Access-Control-Allow-Origin", 0, "") \
XX(AUTHORIZATION, "Authorization", 0, "Basic user agent authentication") \
XX(CC, "Cc", 0, "email field") \
Expand Down
87 changes: 87 additions & 0 deletions Sming/Components/Network/src/Network/Http/HttpResource.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#include "HttpResource.h"
#include "HttpServerConnection.h"

namespace
{
// Sequence to mark failed request, normally never occurs in headers
constexpr char SKIP_HEADER[]{2, 1, 0};

bool shouldSkip(const HttpRequest& request)
{
return request.headers[SKIP_HEADER] == "1";
}

int requestFailed(HttpRequest& request, int err)
{
debug_e("REQUEST FAILED");
request.headers[SKIP_HEADER] = "1";
return err;
}

} // namespace

#define FUNCTION_TEMPLATE(delegate, method, ...) \
if(shouldSkip(request)) { \
return 0; \
} \
\
bool resourceHandled = !delegate; \
for(auto& plugin : plugins) { \
if(!resourceHandled && plugin->getPriority() == 0) { \
int err = delegate(connection, request, ##__VA_ARGS__); \
if(err != 0) { \
return requestFailed(request, err); \
} \
resourceHandled = true; \
} \
if(!plugin->method(connection, request, ##__VA_ARGS__)) { \
return requestFailed(request, 0); \
} \
} \
return resourceHandled ? 0 : delegate(connection, request, ##__VA_ARGS__);

int HttpResource::handleUrl(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response)
{
FUNCTION_TEMPLATE(onUrlComplete, urlComplete, response)
}

int HttpResource::handleHeaders(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response)
{
FUNCTION_TEMPLATE(onHeadersComplete, headersComplete, response)
}

int HttpResource::handleUpgrade(HttpServerConnection& connection, HttpRequest& request, char* data, size_t length)
{
FUNCTION_TEMPLATE(onUpgrade, upgradeReceived, data, length)
}

int HttpResource::handleBody(HttpServerConnection& connection, HttpRequest& request, char*& data, size_t& length)
{
FUNCTION_TEMPLATE(onBody, bodyReceived, data, length)
}

int HttpResource::handleRequest(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response)
{
FUNCTION_TEMPLATE(onRequestComplete, requestComplete, response)
}

void HttpResource::addPlugin(HttpResourcePlugin* plugin)
{
if(plugin == nullptr) {
return;
}

auto ref = new PluginRef{plugin};
auto priority = plugin->getPriority();
auto current = plugins.head();
if(current == nullptr || (*current)->getPriority() < priority) {
plugins.insert(ref);
return;
}

while(current->next() != nullptr && (*current->getNext())->getPriority() > priority) {
current = current->getNext();
}

ref->insertAfter(current);
}
45 changes: 41 additions & 4 deletions Sming/Components/Network/src/Network/Http/HttpResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@

#pragma once

#include "WString.h"
#include "Data/ObjectMap.h"
#include <WString.h>
#include <Data/ObjectMap.h>

#include "HttpResponse.h"
#include "HttpRequest.h"
#include "Resource/HttpResourcePlugin.h"

class HttpServerConnection;

Expand Down Expand Up @@ -46,8 +45,46 @@ class HttpResource
}

public:
class PluginRef : public LinkedObjectTemplate<PluginRef>
{
public:
using OwnedList = OwnedLinkedObjectListTemplate<PluginRef>;

PluginRef(HttpResourcePlugin* plugin) : plugin(plugin)
{
}

HttpResourcePlugin* operator->() const
{
return plugin;
}

private:
HttpResourcePlugin* plugin;
};

HttpResourceDelegate onUrlComplete = nullptr; ///< URL is ready. Path and status code are available
HttpServerConnectionBodyDelegate onBody = nullptr; ///< resource wants to process the raw body data
HttpResourceDelegate onHeadersComplete = nullptr; ///< headers are ready
HttpResourceDelegate onRequestComplete = nullptr; ///< request is complete OR upgraded
HttpServerConnectionUpgradeDelegate onUpgrade = nullptr; ///< request is upgraded and raw data is passed to it

void addPlugin(HttpResourcePlugin* plugin);

template <class... Tail> void addPlugin(HttpResourcePlugin* plugin, Tail... plugins)
{
addPlugin(plugin);
addPlugin(plugins...);
}

private:
friend class HttpServerConnection;

PluginRef::OwnedList plugins;

int handleUrl(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response);
int handleHeaders(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response);
int handleUpgrade(HttpServerConnection& connection, HttpRequest& request, char* data, size_t length);
int handleBody(HttpServerConnection& connection, HttpRequest& request, char*& data, size_t& length);
int handleRequest(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response);
};
15 changes: 13 additions & 2 deletions Sming/Components/Network/src/Network/Http/HttpResourceTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,23 @@ class HttpCompatResource : public HttpResource

/* HttpResourceTree */

void HttpResourceTree::set(String path, const HttpPathDelegate& callback)
HttpResource* HttpResourceTree::set(const String& path, const HttpResourceDelegate& onRequestComplete)
{
auto resource = new HttpResource;
resource->onRequestComplete = onRequestComplete;
set(path, resource);
return resource;
}

HttpResource* HttpResourceTree::set(String path, const HttpPathDelegate& callback)
{
if(path.length() > 1 && path.endsWith("/")) {
path.remove(path.length() - 1);
}

debug_i("'%s' registered", path.c_str());

set(path, new HttpCompatResource(callback));
auto res = new HttpCompatResource(callback);
set(path, res);
return res;
}
68 changes: 58 additions & 10 deletions Sming/Components/Network/src/Network/Http/HttpResourceTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,24 @@ class HttpResourceTree : public ObjectMap<String, HttpResource>
/** @brief Set the default resource handler
* @param resource The default resource handler
*/
void setDefault(HttpResource* resource)
HttpResource* setDefault(HttpResource* resource)
{
set(RESOURCE_PATH_DEFAULT, resource);
return resource;
}

/** @brief Set the default resource handler, identified by "*" wildcard
* @param onRequestComplete The default resource handler
*/
void setDefault(const HttpResourceDelegate& onRequestComplete)
HttpResource* setDefault(const HttpResourceDelegate& onRequestComplete)
{
set(RESOURCE_PATH_DEFAULT, onRequestComplete);
return set(RESOURCE_PATH_DEFAULT, onRequestComplete);
}

/** @brief Set the default resource handler, identified by "*" wildcard */
void setDefault(const HttpPathDelegate& callback)
HttpResource* setDefault(const HttpPathDelegate& callback)
{
set(RESOURCE_PATH_DEFAULT, callback);
return set(RESOURCE_PATH_DEFAULT, callback);
}

/** @brief Get the current default resource handler, if any
Expand All @@ -62,14 +63,29 @@ class HttpResourceTree : public ObjectMap<String, HttpResource>
* @brief Set a callback to handle the given path
* @param path URL path
* @param onRequestComplete Delegate to handle this path
* @retval HttpResource* The created resource object
* @note Path should start with slash. Trailing slashes will be removed.
* @note Any existing handler for this path is replaced
*/
void set(const String& path, const HttpResourceDelegate& onRequestComplete)
HttpResource* set(const String& path, const HttpResourceDelegate& onRequestComplete);

/**
* @brief Set a callback to handle the given path, with one or more plugins
* @param path URL path
* @param onRequestComplete Delegate to handle this path
* @param plugin Plugins to register for the resource
* @retval HttpResource* The created resource object
* @note Path should start with slash. Trailing slashes will be removed.
* @note Any existing handler for this path is replaced
*/
template <class... Tail>
HttpResource* set(const String& path, const HttpResourceDelegate& onRequestComplete, HttpResourcePlugin* plugin,
Tail... plugins)
{
HttpResource* resource = new HttpResource;
resource->onRequestComplete = onRequestComplete;
set(path, resource);
registerPlugin(plugin, plugins...);
auto res = set(path, onRequestComplete);
res->addPlugin(plugin, plugins...);
return res;
}

/**
Expand All @@ -79,5 +95,37 @@ class HttpResourceTree : public ObjectMap<String, HttpResource>
* @note Path should start with slash. Trailing slashes will be removed
* @note Any existing handler for this path is replaced
*/
void set(String path, const HttpPathDelegate& callback);
HttpResource* set(String path, const HttpPathDelegate& callback);

/**
* @brief Add a new path resource with callback and one or more plugins
* @param path URL path
* @param callback The callback that will handle this path
* @param plugin - optional resource plugin
* @retval HttpResource* The created resource object
* @note Path should start with slash. Trailing slashes will be removed
* @note Any existing handler for this path is replaced
*/
template <class... Tail>
HttpResource* set(const String& path, const HttpPathDelegate& callback, HttpResourcePlugin* plugin, Tail... plugins)
{
registerPlugin(plugin, plugins...);
auto res = set(path, callback);
res->addPlugin(plugin, plugins...);
return res;
}

private:
void registerPlugin(HttpResourcePlugin* plugin)
{
loadedPlugins.add(plugin);
}

template <class... Tail> void registerPlugin(HttpResourcePlugin* plugin, Tail... plugins)
{
registerPlugin(plugin);
registerPlugin(plugins...);
}

HttpResourcePlugin::OwnedList loadedPlugins;
};
28 changes: 15 additions & 13 deletions Sming/Components/Network/src/Network/Http/HttpServerConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ int HttpServerConnection::onPath(const Url& uri)
resource = resourceTree->getDefault();
}

return 0;
return resource ? resource->handleUrl(*this, request, response) : 0;
}

int HttpServerConnection::onMessageComplete(http_parser* parser)
Expand All @@ -69,8 +69,8 @@ int HttpServerConnection::onMessageComplete(http_parser* parser)
response.code = HTTP_STATUS_BAD_REQUEST;
}

if(resource != nullptr && resource->onRequestComplete) {
hasError = resource->onRequestComplete(*this, request, response);
if(resource != nullptr) {
hasError = resource->handleRequest(*this, request, response);
}

if(request.responseStream != nullptr) {
Expand Down Expand Up @@ -104,8 +104,8 @@ int HttpServerConnection::onHeadersComplete(const HttpHeaders& headers)
int error = 0;
request.setHeaders(headers);

if(resource != nullptr && resource->onHeadersComplete) {
error = resource->onHeadersComplete(*this, request, response);
if(resource != nullptr) {
error = resource->handleHeaders(*this, request, response);
if(error != 0) {
return error;
}
Expand Down Expand Up @@ -161,22 +161,24 @@ int HttpServerConnection::onBody(const char* at, size_t length)
return 0;
}

if(bodyParser) {
const size_t consumed = bodyParser(request, at, length);
if(consumed != length) {
char* data = const_cast<char*>(at);
size_t dataLength = length;
if(resource != nullptr) {
int result = resource->handleBody(*this, request, data, dataLength);
if(result != 0) {
hasContentError = true;
if(closeOnContentError) {
return -1;
return result;
}
}
}

if(resource != nullptr && resource->onBody) {
const int result = resource->onBody(*this, request, at, length);
if(result != 0) {
if(bodyParser) {
const size_t consumed = bodyParser(request, data, dataLength);
if(consumed != length) {
hasContentError = true;
if(closeOnContentError) {
return result;
return -1;
}
}
}
Expand Down
Loading

0 comments on commit 41f3394

Please sign in to comment.