diff --git a/Sming/Components/Network/src/Network/Http/HttpHeaderFields.h b/Sming/Components/Network/src/Network/Http/HttpHeaderFields.h index 2ec1c14431..618fcf6971 100644 --- a/Sming/Components/Network/src/Network/Http/HttpHeaderFields.h +++ b/Sming/Components/Network/src/Network/Http/HttpHeaderFields.h @@ -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") \ diff --git a/Sming/Components/Network/src/Network/Http/HttpResource.cpp b/Sming/Components/Network/src/Network/Http/HttpResource.cpp new file mode 100644 index 0000000000..c8adbba2a0 --- /dev/null +++ b/Sming/Components/Network/src/Network/Http/HttpResource.cpp @@ -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); +} diff --git a/Sming/Components/Network/src/Network/Http/HttpResource.h b/Sming/Components/Network/src/Network/Http/HttpResource.h index 6e4089fb43..60824fb635 100644 --- a/Sming/Components/Network/src/Network/Http/HttpResource.h +++ b/Sming/Components/Network/src/Network/Http/HttpResource.h @@ -12,11 +12,10 @@ #pragma once -#include "WString.h" -#include "Data/ObjectMap.h" +#include +#include -#include "HttpResponse.h" -#include "HttpRequest.h" +#include "Resource/HttpResourcePlugin.h" class HttpServerConnection; @@ -46,8 +45,46 @@ class HttpResource } public: + class PluginRef : public LinkedObjectTemplate + { + public: + using OwnedList = OwnedLinkedObjectListTemplate; + + 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 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); }; diff --git a/Sming/Components/Network/src/Network/Http/HttpResourceTree.cpp b/Sming/Components/Network/src/Network/Http/HttpResourceTree.cpp index d512a0c3a1..18fbe81d2c 100644 --- a/Sming/Components/Network/src/Network/Http/HttpResourceTree.cpp +++ b/Sming/Components/Network/src/Network/Http/HttpResourceTree.cpp @@ -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; } diff --git a/Sming/Components/Network/src/Network/Http/HttpResourceTree.h b/Sming/Components/Network/src/Network/Http/HttpResourceTree.h index 870ab45dd5..fb7aee3370 100644 --- a/Sming/Components/Network/src/Network/Http/HttpResourceTree.h +++ b/Sming/Components/Network/src/Network/Http/HttpResourceTree.h @@ -29,23 +29,24 @@ class HttpResourceTree : public ObjectMap /** @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 @@ -62,14 +63,29 @@ class HttpResourceTree : public ObjectMap * @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 + 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; } /** @@ -79,5 +95,37 @@ class HttpResourceTree : public ObjectMap * @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 + 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 void registerPlugin(HttpResourcePlugin* plugin, Tail... plugins) + { + registerPlugin(plugin); + registerPlugin(plugins...); + } + + HttpResourcePlugin::OwnedList loadedPlugins; }; diff --git a/Sming/Components/Network/src/Network/Http/HttpServerConnection.cpp b/Sming/Components/Network/src/Network/Http/HttpServerConnection.cpp index c3161bfca6..c6156fd1a5 100644 --- a/Sming/Components/Network/src/Network/Http/HttpServerConnection.cpp +++ b/Sming/Components/Network/src/Network/Http/HttpServerConnection.cpp @@ -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) @@ -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) { @@ -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; } @@ -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(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; } } } diff --git a/Sming/Components/Network/src/Network/Http/Resource/Auth/ResourceBasicAuth.h b/Sming/Components/Network/src/Network/Http/Resource/Auth/ResourceBasicAuth.h new file mode 100644 index 0000000000..a88c4b2643 --- /dev/null +++ b/Sming/Components/Network/src/Network/Http/Resource/Auth/ResourceBasicAuth.h @@ -0,0 +1,66 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * + * @author: 2021 - Slavey Karadzhov + * + ****/ + +#pragma once + +#include "../HttpResourcePlugin.h" +#include + +class ResourceBasicAuth : public HttpPreFilter +{ +public: + ResourceBasicAuth(const String& realm, const String& username, const String& password) + : realm(realm), username(username), password(password) + { + } + + bool headersComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) override + { + auto& headers = request.headers; + auto authorization = headers[HTTP_HEADER_AUTHORIZATION]; + if(authorization) { + // check the authorization + authorization.trim(); + auto pos = authorization.indexOf(' '); + if(pos < 0) { + debug_w("Invalid authorization header"); + return true; + } + + auto type = authorization.substring(0, pos); + auto token = authorization.substring(pos + 1, authorization.length()); + if(!type.equalsIgnoreCase(F("Basic"))) { + return true; + } + + String text = base64_decode(token.c_str(), token.length()); + pos = text.indexOf(':'); + if(pos > 0) { + auto providedUsername = text.substring(0, pos); + auto providedPassword = text.substring(pos + 1, text.length()); + if(providedUsername == username && providedPassword == password) { + return true; + } + } + } + + // specify that the resource is protected... + response.code = HTTP_STATUS_UNAUTHORIZED; + response.headers[HTTP_HEADER_WWW_AUTHENTICATE] = F("Basic realm=\"") + realm + "\""; + + return false; + } + +private: + String realm; + String username; + String password; +}; diff --git a/Sming/Components/Network/src/Network/Http/Resource/Auth/ResourceIpAuth.h b/Sming/Components/Network/src/Network/Http/Resource/Auth/ResourceIpAuth.h new file mode 100644 index 0000000000..540e9ccd6a --- /dev/null +++ b/Sming/Components/Network/src/Network/Http/Resource/Auth/ResourceIpAuth.h @@ -0,0 +1,40 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * + * @author: 2021 - Slavey Karadzhov + * + ****/ + +#pragma once + +#include "../HttpResourcePlugin.h" +#include + +class ResourceIpAuth : public HttpPreFilter +{ +public: + ResourceIpAuth(IpAddress ip, IpAddress netmask) : ip(ip), netmask(netmask) + { + } + + bool urlComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) override + { + auto remoteIp = connection.getRemoteIp(); + if(remoteIp.compare(ip, netmask)) { + // This IP is allowed to proceed + return true; + } + + // specify that the resource is protected... + response.code = HTTP_STATUS_UNAUTHORIZED; + return false; + } + +private: + IpAddress ip; + IpAddress netmask; +}; diff --git a/Sming/Components/Network/src/Network/Http/Resource/HttpAuth.h b/Sming/Components/Network/src/Network/Http/Resource/HttpAuth.h new file mode 100644 index 0000000000..0d0d7f035e --- /dev/null +++ b/Sming/Components/Network/src/Network/Http/Resource/HttpAuth.h @@ -0,0 +1,15 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * + * @author: 2021 - Slavey Karadzhov + * + ****/ + +#pragma once + +#include "Auth/ResourceBasicAuth.h" +#include "Auth/ResourceIpAuth.h" diff --git a/Sming/Components/Network/src/Network/Http/Resource/HttpResourcePlugin.h b/Sming/Components/Network/src/Network/Http/Resource/HttpResourcePlugin.h new file mode 100644 index 0000000000..d7fbd73085 --- /dev/null +++ b/Sming/Components/Network/src/Network/Http/Resource/HttpResourcePlugin.h @@ -0,0 +1,81 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * + * @author: 2021 - Slavey Karadzhov + * + ****/ + +#pragma once + +#include +#include "../HttpRequest.h" +#include "../HttpResponse.h" + +class HttpServerConnection; + +/** + * @brief Base plugin class. Implementations should be based on either `HttpPreFilter` or `HttpPostFilter` + */ +class HttpResourcePlugin : public LinkedObjectTemplate +{ +public: + using OwnedList = OwnedLinkedObjectListTemplate; + +protected: + friend class HttpResource; + + virtual int getPriority() const = 0; + + virtual bool urlComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) + { + return true; + } + + virtual bool headersComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) + { + return true; + } + + virtual bool upgradeReceived(HttpServerConnection& connection, HttpRequest&, char* data, size_t length) + { + return true; + } + + virtual bool bodyReceived(HttpServerConnection& connection, HttpRequest& request, char*& data, size_t& length) + { + return true; + } + + virtual bool requestComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) + { + return true; + } +}; + +/** + * @brief Filter plugins run *before* the resource is invoked + */ +class HttpPreFilter : public HttpResourcePlugin +{ +private: + int getPriority() const override + { + return 1; + } +}; + +/** + * @brief Filter plugins run *after* the resource is invoked + */ +class HttpPostFilter : public HttpResourcePlugin +{ +private: + int getPriority() const override + { + return -1; + } +}; diff --git a/Sming/Core/Data/LinkedObject.h b/Sming/Core/Data/LinkedObject.h index a5e5ca64ed..95bd20379e 100644 --- a/Sming/Core/Data/LinkedObject.h +++ b/Sming/Core/Data/LinkedObject.h @@ -40,8 +40,8 @@ class LinkedObject if(object == nullptr) { return false; } - object->mNext = mNext; - mNext = object; + mNext = object->mNext; + object->mNext = this; return true; } diff --git a/samples/HttpServer_Plugins/.cproject b/samples/HttpServer_Plugins/.cproject new file mode 100644 index 0000000000..e6469c9e2f --- /dev/null +++ b/samples/HttpServer_Plugins/.cproject @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + make + -f ${ProjDirPath}/Makefile + all + true + true + true + + + make + -f ${ProjDirPath}/Makefile + clean + true + true + true + + + make + -f ${ProjDirPath}/Makefile + flash + true + true + true + + + make + -f ${ProjDirPath}/Makefile + flashonefile + true + true + true + + + make + -f ${ProjDirPath}/Makefile + flashinit + true + true + true + + + make + -f ${ProjDirPath}/Makefile + flashboot + true + true + true + + + make + -f ${ProjDirPath}/Makefile + rebuild + true + true + true + + + + + + + + + + + + + + + + + + + + diff --git a/samples/HttpServer_Plugins/.project b/samples/HttpServer_Plugins/.project new file mode 100644 index 0000000000..67c872c77a --- /dev/null +++ b/samples/HttpServer_Plugins/.project @@ -0,0 +1,28 @@ + + + HttpServer_Plugins + + + SmingFramework + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/samples/HttpServer_Plugins/Makefile b/samples/HttpServer_Plugins/Makefile new file mode 100644 index 0000000000..ff51b6c3a7 --- /dev/null +++ b/samples/HttpServer_Plugins/Makefile @@ -0,0 +1,9 @@ +##################################################################### +#### Please don't change this file. Use component.mk instead #### +##################################################################### + +ifndef SMING_HOME +$(error SMING_HOME is not set: please configure it as an environment variable) +endif + +include $(SMING_HOME)/project.mk diff --git a/samples/HttpServer_Plugins/README.rst b/samples/HttpServer_Plugins/README.rst new file mode 100644 index 0000000000..e694a3db72 --- /dev/null +++ b/samples/HttpServer_Plugins/README.rst @@ -0,0 +1,10 @@ +HttpServer Plugins +===================== + +Simple example that demonstrate extending the HttpServer functionality with additional plugins. +Using such examples one can add: + + - URL Rewriting + - Authentication. Sming has already existing plugins for Basic Authentication, IP Limiting + - Content extracting/processing. For example a gzip decoder plugin can be implemented. + diff --git a/samples/HttpServer_Plugins/app/ContentDecoder.cpp b/samples/HttpServer_Plugins/app/ContentDecoder.cpp new file mode 100644 index 0000000000..2333b6444d --- /dev/null +++ b/samples/HttpServer_Plugins/app/ContentDecoder.cpp @@ -0,0 +1,49 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * + * @author: 2021 - Slavey Karadzhov + * + ****/ + +#include "ContentDecoder.h" + +// We register two events - one is executed as soon as the client request comes to the server +// and in it we add a header to the response that inform the http client that +// we support our own content encoding called "test" +DEFINE_FSTR_LOCAL(ENCODING_NAME, "test") + +bool ContentDecoder::headersComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) +{ + if(request.headers[HTTP_HEADER_CONTENT_ENCODING] == ENCODING_NAME) { + response.headers[HTTP_HEADER_CONTENT_ENCODING] = ENCODING_NAME; + } + + return true; +} + +bool ContentDecoder::urlComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) +{ + String content = response.headers[HTTP_HEADER_ACCEPT_ENCODING]; + if(content.length() > 0) { + content += ", "; + } + content += ENCODING_NAME; + response.headers[HTTP_HEADER_ACCEPT_ENCODING] = content; + + return true; +} + +bool ContentDecoder::bodyReceived(HttpServerConnection& connection, HttpRequest& request, char*& data, size_t& length) +{ + if(request.headers[HTTP_HEADER_CONTENT_ENCODING] == ENCODING_NAME) { + for(unsigned i = 0; i < length; i++) { + data[i]++; + } + } + + return true; +} diff --git a/samples/HttpServer_Plugins/app/application.cpp b/samples/HttpServer_Plugins/app/application.cpp new file mode 100644 index 0000000000..529933e8eb --- /dev/null +++ b/samples/HttpServer_Plugins/app/application.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include "ContentDecoder.h" + +// If you want, you can define WiFi settings globally in Eclipse Environment Variables +#ifndef WIFI_SSID +#define WIFI_SSID "PleaseEnterSSID" // Put your SSID and password here +#define WIFI_PWD "PleaseEnterPass" +#endif + +namespace +{ +HttpServer* server; + +void echoContentBody(HttpRequest& request, HttpResponse& response) +{ + auto body = request.getBody(); + debug_d("Got content (after modifications): %s", body.c_str()); + + response.headers[HTTP_HEADER_CONTENT_TYPE] = request.headers[HTTP_HEADER_CONTENT_TYPE]; + response.sendString(body); +} + +void startWebServer() +{ + server = new HttpServer; + + server->listen(80); + server->paths.set("/", echoContentBody); + server->paths.setDefault(echoContentBody); + + /* + * By default the server does not store the incoming information. + * There has to be either a plugin that does this or a body parser. + * In this sample we use a body parser that stores the information into memory. + */ + server->setBodyParser(MIME_FORM_URL_ENCODED, bodyToStringParser); + + // Here we use a simple plugin that protects the access to a resource using HTTP Basic Authentication + auto pluginBasicAuth = new ResourceBasicAuth("realm", "username", "password"); + // You can add one or more authentication methods or other plugins... + server->paths.set("/auth", echoContentBody, pluginBasicAuth); + + /* + * The plugins will be registered in the order in which they are provided. + * For example in the command below the IP restriction plugin will be registered first + * followed by the basic authentication plugin. + * You can run the following curl command to test these plugins: + * + * curl -vvv http://username:password@192.168.13.10/ip-n-auth + * + * make sure to replace the IP address with the IP address of your HttpServer + */ + server->paths.set("/ip-n-auth", echoContentBody, + new ResourceIpAuth(IpAddress("192.168.13.0"), IpAddress("255.255.255.0")), pluginBasicAuth); + + /* + * This content coming to this resource is modified on the fly + * using our ContentDecoder plugin. See the source code of ContentDecoder + * to get an idea how to create your own plugin. + * You can run the following curl command to test this plugin: + * + * curl -vvv http://username@192.168.13.10/test -d "1234" -H "Content-Encoding: test" + * + * make sure to replace the IP address with the IP address of your HttpServer + */ + server->paths.set("/test", echoContentBody, new ContentDecoder()); + + Serial.println(F("\r\n=== WEB SERVER STARTED ===")); + Serial.println(WifiStation.getIP()); + Serial.println(F("==============================\r\n")); +} + +// Will be called when WiFi station becomes fully operational +void gotIP(IpAddress ip, IpAddress netmask, IpAddress gateway) +{ + startWebServer(); + debug_i("free heap = %u", system_get_free_heap_size()); +} + +} // namespace + +void init() +{ + Serial.begin(SERIAL_BAUD_RATE); // 115200 by default + Serial.systemDebugOutput(true); // Enable debug output to serial + + WifiStation.enable(true); + WifiStation.config(WIFI_SSID, WIFI_PWD); + WifiAccessPoint.enable(false); + + // Run our method when station was connected to AP + WifiEvents.onStationGotIP(gotIP); +} diff --git a/samples/HttpServer_Plugins/component.mk b/samples/HttpServer_Plugins/component.mk new file mode 100644 index 0000000000..6a0c019953 --- /dev/null +++ b/samples/HttpServer_Plugins/component.mk @@ -0,0 +1 @@ +HWCONFIG := standard diff --git a/samples/HttpServer_Plugins/include/ContentDecoder.h b/samples/HttpServer_Plugins/include/ContentDecoder.h new file mode 100644 index 0000000000..5d0f948ef6 --- /dev/null +++ b/samples/HttpServer_Plugins/include/ContentDecoder.h @@ -0,0 +1,22 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * + * @author: 2021 - Slavey Karadzhov + * + ****/ + +#pragma once + +#include + +class ContentDecoder : public HttpPreFilter +{ +public: + bool headersComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) override; + bool urlComplete(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) override; + bool bodyReceived(HttpServerConnection& connection, HttpRequest& request, char*& data, size_t& length) override; +};