From d093886571abde82ec1a6f28af9cf46217fdcc38 Mon Sep 17 00:00:00 2001 From: Proddy Date: Tue, 2 Jan 2024 17:06:15 +0100 Subject: [PATCH] json is always chunked --- lib/PsychicHttp/src/ChunkPrinter.h | 78 +-- lib/PsychicHttp/src/PsychicClient.cpp | 89 +-- lib/PsychicHttp/src/PsychicClient.h | 14 +- lib/PsychicHttp/src/PsychicCore.h | 94 +-- lib/PsychicHttp/src/PsychicEndpoint.cpp | 120 ++-- lib/PsychicHttp/src/PsychicEndpoint.h | 31 +- lib/PsychicHttp/src/PsychicEventSource.cpp | 256 ++++---- lib/PsychicHttp/src/PsychicEventSource.h | 40 +- lib/PsychicHttp/src/PsychicFileResponse.cpp | 281 +++++---- lib/PsychicHttp/src/PsychicFileResponse.h | 21 +- lib/PsychicHttp/src/PsychicHandler.h | 58 +- lib/PsychicHttp/src/PsychicHttp.h | 3 +- lib/PsychicHttp/src/PsychicHttpServer.cpp | 457 ++++++++------- lib/PsychicHttp/src/PsychicHttpServer.h | 75 ++- lib/PsychicHttp/src/PsychicHttpsServer.cpp | 78 +-- lib/PsychicHttp/src/PsychicHttpsServer.h | 9 +- lib/PsychicHttp/src/PsychicJson.DELETEME | 150 +++++ lib/PsychicHttp/src/PsychicJson.h | 10 +- lib/PsychicHttp/src/PsychicRequest.cpp | 5 +- lib/PsychicHttp/src/PsychicRequest.h | 1 + lib/PsychicHttp/src/PsychicResponse.cpp | 199 ++++--- lib/PsychicHttp/src/PsychicResponse.h | 41 +- .../src/PsychicStaticFileHander.cpp | 294 +++++----- .../src/PsychicStaticFileHandler.h | 47 +- lib/PsychicHttp/src/PsychicStreamResponse.cpp | 89 +++ lib/PsychicHttp/src/PsychicStreamResponse.h | 33 ++ lib/PsychicHttp/src/PsychicUploadHandler.h | 62 +- lib/PsychicHttp/src/PsychicWebHandler.cpp | 94 +-- lib/PsychicHttp/src/PsychicWebHandler.h | 18 +- lib/PsychicHttp/src/PsychicWebParameter.h | 32 +- lib/PsychicHttp/src/PsychicWebSocket.cpp | 349 +++++------ lib/PsychicHttp/src/PsychicWebSocket.h | 50 +- lib/PsychicHttp/src/http_status.cpp | 27 +- lib/PsychicHttp/src/http_status.h | 16 +- lib/PsychicHttp/src_old/ChunkPrinter.h | 52 ++ lib/PsychicHttp/src_old/PsychicClient.cpp | 67 +++ lib/PsychicHttp/src_old/PsychicClient.h | 39 ++ lib/PsychicHttp/src_old/PsychicCore.h | 101 ++++ lib/PsychicHttp/src_old/PsychicEndpoint.cpp | 82 +++ lib/PsychicHttp/src_old/PsychicEndpoint.h | 36 ++ .../src_old/PsychicEventSource.cpp | 217 +++++++ lib/PsychicHttp/src_old/PsychicEventSource.h | 84 +++ .../src_old/PsychicFileResponse.cpp | 169 ++++++ lib/PsychicHttp/src_old/PsychicFileResponse.h | 28 + lib/PsychicHttp/src_old/PsychicHandler.cpp | 103 ++++ lib/PsychicHttp/src_old/PsychicHandler.h | 67 +++ lib/PsychicHttp/src_old/PsychicHttp.h | 23 + lib/PsychicHttp/src_old/PsychicHttpServer.cpp | 329 +++++++++++ lib/PsychicHttp/src_old/PsychicHttpServer.h | 86 +++ .../src_old/PsychicHttpsServer.cpp | 48 ++ lib/PsychicHttp/src_old/PsychicHttpsServer.h | 31 + lib/PsychicHttp/src_old/PsychicJson.h | 247 ++++++++ lib/PsychicHttp/src_old/PsychicRequest.cpp | 548 ++++++++++++++++++ lib/PsychicHttp/src_old/PsychicRequest.h | 97 ++++ lib/PsychicHttp/src_old/PsychicResponse.cpp | 138 +++++ lib/PsychicHttp/src_old/PsychicResponse.h | 49 ++ .../src_old/PsychicStaticFileHander.cpp | 195 +++++++ .../src_old/PsychicStaticFileHandler.h | 44 ++ .../src_old/PsychicUploadHandler.cpp | 395 +++++++++++++ .../src_old/PsychicUploadHandler.h | 68 +++ lib/PsychicHttp/src_old/PsychicWebHandler.cpp | 46 ++ lib/PsychicHttp/src_old/PsychicWebHandler.h | 26 + lib/PsychicHttp/src_old/PsychicWebParameter.h | 41 ++ lib/PsychicHttp/src_old/PsychicWebSocket.cpp | 243 ++++++++ lib/PsychicHttp/src_old/PsychicWebSocket.h | 68 +++ lib/PsychicHttp/src_old/async_worker.cpp | 203 +++++++ lib/PsychicHttp/src_old/async_worker.h | 36 ++ lib/PsychicHttp/src_old/http_status.cpp | 185 ++++++ lib/PsychicHttp/src_old/http_status.h | 15 + 69 files changed, 6010 insertions(+), 1417 deletions(-) create mode 100644 lib/PsychicHttp/src/PsychicJson.DELETEME create mode 100644 lib/PsychicHttp/src/PsychicStreamResponse.cpp create mode 100644 lib/PsychicHttp/src/PsychicStreamResponse.h create mode 100644 lib/PsychicHttp/src_old/ChunkPrinter.h create mode 100644 lib/PsychicHttp/src_old/PsychicClient.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicClient.h create mode 100644 lib/PsychicHttp/src_old/PsychicCore.h create mode 100644 lib/PsychicHttp/src_old/PsychicEndpoint.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicEndpoint.h create mode 100644 lib/PsychicHttp/src_old/PsychicEventSource.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicEventSource.h create mode 100644 lib/PsychicHttp/src_old/PsychicFileResponse.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicFileResponse.h create mode 100644 lib/PsychicHttp/src_old/PsychicHandler.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicHandler.h create mode 100644 lib/PsychicHttp/src_old/PsychicHttp.h create mode 100644 lib/PsychicHttp/src_old/PsychicHttpServer.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicHttpServer.h create mode 100644 lib/PsychicHttp/src_old/PsychicHttpsServer.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicHttpsServer.h create mode 100644 lib/PsychicHttp/src_old/PsychicJson.h create mode 100644 lib/PsychicHttp/src_old/PsychicRequest.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicRequest.h create mode 100644 lib/PsychicHttp/src_old/PsychicResponse.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicResponse.h create mode 100644 lib/PsychicHttp/src_old/PsychicStaticFileHander.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicStaticFileHandler.h create mode 100644 lib/PsychicHttp/src_old/PsychicUploadHandler.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicUploadHandler.h create mode 100644 lib/PsychicHttp/src_old/PsychicWebHandler.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicWebHandler.h create mode 100644 lib/PsychicHttp/src_old/PsychicWebParameter.h create mode 100644 lib/PsychicHttp/src_old/PsychicWebSocket.cpp create mode 100644 lib/PsychicHttp/src_old/PsychicWebSocket.h create mode 100644 lib/PsychicHttp/src_old/async_worker.cpp create mode 100644 lib/PsychicHttp/src_old/async_worker.h create mode 100644 lib/PsychicHttp/src_old/http_status.cpp create mode 100644 lib/PsychicHttp/src_old/http_status.h diff --git a/lib/PsychicHttp/src/ChunkPrinter.h b/lib/PsychicHttp/src/ChunkPrinter.h index f453e43b9..4021949c7 100644 --- a/lib/PsychicHttp/src/ChunkPrinter.h +++ b/lib/PsychicHttp/src/ChunkPrinter.h @@ -1,51 +1,55 @@ #ifndef ChunkPrinter_h #define ChunkPrinter_h -#include "PsychicCore.h" +// #include "PsychicCore.h" #include "PsychicResponse.h" #include -class ChunkPrinter : public Print { +class ChunkPrinter : public Print +{ private: - PsychicResponse * _response; - uint8_t * _buffer; - size_t _length; - size_t _pos; + PsychicResponse *_response; + uint8_t *_buffer; + size_t _length; + size_t _pos; public: - ChunkPrinter(PsychicResponse * response, uint8_t * buffer, size_t len) - : _response(response) - , _buffer(buffer) - , _length(len) - , _pos(0) { + ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) : + _response(response), + _buffer(buffer), + _length(len), + _pos(0) + {} + + virtual ~ChunkPrinter() {} + + size_t write(uint8_t c) + { + esp_err_t err; + + _buffer[_pos] = c; + _pos++; + + //if we're full, send a chunk + if (_pos == _length) + { + _pos = 0; + + err = _response->sendChunk(_buffer, _length); + if (err != ESP_OK) + return 0; + } + + return 1; } - virtual ~ChunkPrinter() { - } - - size_t write(uint8_t c) { - esp_err_t err; - - _buffer[_pos] = c; - _pos++; - - //if we're full, send a chunk - if (_pos == _length) { - _pos = 0; - - err = _response->sendChunk(_buffer, _length); - if (err != ESP_OK) - return 0; - } - - return 1; - } - - virtual void flush() override { - if (_pos) { - _response->sendChunk(_buffer, _pos); - _pos = 0; - } + virtual void flush() override + { + if (_pos) + { + _response->sendChunk(_buffer, _pos); + _pos = 0; + } } }; diff --git a/lib/PsychicHttp/src/PsychicClient.cpp b/lib/PsychicHttp/src/PsychicClient.cpp index bbee4e269..1e59d70e7 100644 --- a/lib/PsychicHttp/src/PsychicClient.cpp +++ b/lib/PsychicHttp/src/PsychicClient.cpp @@ -1,67 +1,72 @@ #include "PsychicClient.h" +#include "PsychicHttpServer.h" +#include -PsychicClient::PsychicClient(httpd_handle_t server, int socket) - : _server(server) - , _socket(socket) - , _friend(NULL) - , isNew(false) { -} +PsychicClient::PsychicClient(httpd_handle_t server, int socket) : + _server(server), + _socket(socket), + _friend(NULL), + isNew(false) +{} PsychicClient::~PsychicClient() { } httpd_handle_t PsychicClient::server() { - return _server; + return _server; } int PsychicClient::socket() { - return _socket; + return _socket; } // I'm not sure this is entirely safe to call. I was having issues with race conditions when highly loaded using this. -esp_err_t PsychicClient::close() { - esp_err_t err = httpd_sess_trigger_close(_server, _socket); - //PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. +esp_err_t PsychicClient::close() +{ + esp_err_t err = httpd_sess_trigger_close(_server, _socket); + //PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. - return err; + return err; } -IPAddress PsychicClient::localIP() { - IPAddress address(0, 0, 0, 0); +IPAddress PsychicClient::localIP() +{ + IPAddress address(0,0,0,0); - char ipstr[INET6_ADDRSTRLEN]; - struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing - socklen_t addr_size = sizeof(addr); - - if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { - ESP_LOGE(PH_TAG, "Error getting client IP"); - return address; - } - - // Convert to IPv4 string - inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); - ESP_LOGI(PH_TAG, "Client Local IP => %s", ipstr); - address.fromString(ipstr); + char ipstr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + socklen_t addr_size = sizeof(addr); + if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + ESP_LOGE(PH_TAG, "Error getting client IP"); return address; + } + + // Convert to IPv4 string + inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); + ESP_LOGI(PH_TAG, "Client Local IP => %s", ipstr); + address.fromString(ipstr); + + return address; } -IPAddress PsychicClient::remoteIP() { - IPAddress address(0, 0, 0, 0); +IPAddress PsychicClient::remoteIP() +{ + IPAddress address(0,0,0,0); - char ipstr[INET6_ADDRSTRLEN]; - struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing - socklen_t addr_size = sizeof(addr); - - if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { - ESP_LOGE(PH_TAG, "Error getting client IP"); - return address; - } - - // Convert to IPv4 string - inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); - ESP_LOGI(PH_TAG, "Client Remote IP => %s", ipstr); - address.fromString(ipstr); + char ipstr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + socklen_t addr_size = sizeof(addr); + if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + ESP_LOGE(PH_TAG, "Error getting client IP"); return address; + } + + // Convert to IPv4 string + inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); + ESP_LOGI(PH_TAG, "Client Remote IP => %s", ipstr); + address.fromString(ipstr); + + return address; } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicClient.h b/lib/PsychicHttp/src/PsychicClient.h index 3b7b79e6d..b823df77e 100644 --- a/lib/PsychicHttp/src/PsychicClient.h +++ b/lib/PsychicHttp/src/PsychicClient.h @@ -2,8 +2,6 @@ #define PsychicClient_h #include "PsychicCore.h" -#include "PsychicHttpServer.h" -#include /* * PsychicClient :: Generic wrapper around the ESP-IDF socket @@ -12,7 +10,7 @@ class PsychicClient { protected: httpd_handle_t _server; - int _socket; + int _socket; public: PsychicClient(httpd_handle_t server, int socket); @@ -20,17 +18,15 @@ class PsychicClient { //no idea if this is the right way to do it or not, but lets see. //pointer to our derived class (eg. PsychicWebSocketConnection) - void * _friend; + void *_friend; bool isNew = false; - bool operator==(PsychicClient & rhs) const { - return _socket == rhs.socket(); - } + bool operator==(PsychicClient& rhs) const { return _socket == rhs.socket(); } httpd_handle_t server(); - int socket(); - esp_err_t close(); + int socket(); + esp_err_t close(); IPAddress localIP(); IPAddress remoteIP(); diff --git a/lib/PsychicHttp/src/PsychicCore.h b/lib/PsychicHttp/src/PsychicCore.h index 5cf8232be..0d8c422c8 100644 --- a/lib/PsychicHttp/src/PsychicCore.h +++ b/lib/PsychicHttp/src/PsychicCore.h @@ -3,7 +3,7 @@ #define PH_TAG "psychic" -//version numbers +// version numbers #define PSYCHIC_HTTP_VERSION_MAJOR 1 #define PSYCHIC_HTTP_VERSION_MINOR 1 #define PSYCHIC_HTTP_VERSION_PATCH 0 @@ -16,12 +16,16 @@ #define FILE_CHUNK_SIZE 8 * 1024 #endif +#ifndef STREAM_CHUNK_SIZE +#define STREAM_CHUNK_SIZE 1024 +#endif + #ifndef MAX_UPLOAD_SIZE #define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB #endif #ifndef MAX_REQUEST_BODY_SIZE -#define MAX_REQUEST_BODY_SIZE (16 * 1024) //16K +#define MAX_REQUEST_BODY_SIZE (16 * 1024) // 16K #endif #ifdef ARDUINO @@ -37,65 +41,75 @@ #include "MD5Builder.h" #include #include "FS.h" +#include -enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; +enum HTTPAuthMethod +{ + BASIC_AUTH, + DIGEST_AUTH +}; -String urlDecode(const char * encoded); +String urlDecode(const char *encoded); class PsychicHttpServer; class PsychicRequest; class PsychicWebSocketRequest; class PsychicClient; -//filter function definition -typedef std::function PsychicRequestFilterFunction; +// filter function definition +typedef std::function PsychicRequestFilterFunction; -//client connect callback -typedef std::function PsychicClientCallback; +// client connect callback +typedef std::function PsychicClientCallback; +// callback definitions +typedef std::function PsychicHttpRequestCallback; +typedef std::function PsychicJsonRequestCallback; -struct HTTPHeader { - char * field; - char * value; +struct HTTPHeader +{ + char *field; + char *value; }; -class DefaultHeaders { - std::list _headers; +class DefaultHeaders +{ + std::list _headers; - public: - DefaultHeaders() { - } +public: + DefaultHeaders() {} - void addHeader(const String & field, const String & value) { - addHeader(field.c_str(), value.c_str()); - } + void addHeader(const String &field, const String &value) + { + addHeader(field.c_str(), value.c_str()); + } - void addHeader(const char * field, const char * value) { - HTTPHeader header; + void addHeader(const char *field, const char *value) + { + HTTPHeader header; - //these are just going to stick around forever. - header.field = (char *)malloc(strlen(field) + 1); - header.value = (char *)malloc(strlen(value) + 1); + // these are just going to stick around forever. + header.field = (char *)malloc(strlen(field) + 1); + header.value = (char *)malloc(strlen(value) + 1); - strlcpy(header.field, field, strlen(field) + 1); - strlcpy(header.value, value, strlen(value) + 1); + strlcpy(header.field, field, strlen(field) + 1); + strlcpy(header.value, value, strlen(value) + 1); - _headers.push_back(header); - } + _headers.push_back(header); + } - const std::list & getHeaders() { - return _headers; - } + const std::list &getHeaders() { return _headers; } - //delete the copy constructor, singleton class - DefaultHeaders(DefaultHeaders const &) = delete; - DefaultHeaders & operator=(DefaultHeaders const &) = delete; + // delete the copy constructor, singleton class + DefaultHeaders(DefaultHeaders const &) = delete; + DefaultHeaders &operator=(DefaultHeaders const &) = delete; - //single static class interface - static DefaultHeaders & Instance() { - static DefaultHeaders instance; - return instance; - } + // single static class interface + static DefaultHeaders &Instance() + { + static DefaultHeaders instance; + return instance; + } }; -#endif //PsychicCore_h \ No newline at end of file +#endif // PsychicCore_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEndpoint.cpp b/lib/PsychicHttp/src/PsychicEndpoint.cpp index e17ddef13..e6926583e 100644 --- a/lib/PsychicHttp/src/PsychicEndpoint.cpp +++ b/lib/PsychicHttp/src/PsychicEndpoint.cpp @@ -1,82 +1,90 @@ #include "PsychicEndpoint.h" +#include "PsychicHttpServer.h" -PsychicEndpoint::PsychicEndpoint() - : _server(NULL) - , _uri("") - , _method(HTTP_GET) - , _handler(NULL) { +PsychicEndpoint::PsychicEndpoint() : + _server(NULL), + _uri(""), + _method(HTTP_GET), + _handler(NULL) +{ } -PsychicEndpoint::PsychicEndpoint(PsychicHttpServer * server, http_method method, const char * uri) - : _server(server) - , _uri(uri) - , _method(method) - , _handler(NULL) { +PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) : + _server(server), + _uri(uri), + _method(method), + _handler(NULL) +{ } -PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler * handler) { - //clean up old / default handler - if (_handler != NULL) - delete _handler; +PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler) +{ + //clean up old / default handler + if (_handler != NULL) + delete _handler; - //get our new pointer - _handler = handler; + //get our new pointer + _handler = handler; - //keep a pointer to the server - _handler->_server = _server; + //keep a pointer to the server + _handler->_server = _server; - return this; + return this; } -PsychicHandler * PsychicEndpoint::handler() { - return _handler; +PsychicHandler * PsychicEndpoint::handler() +{ + return _handler; } String PsychicEndpoint::uri() { - return _uri; + return _uri; } -esp_err_t PsychicEndpoint::requestCallback(httpd_req_t * req) { -#ifdef ENABLE_ASYNC +esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req) +{ + #ifdef ENABLE_ASYNC if (is_on_async_worker_thread() == false) { - if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) { - return ESP_OK; - } else { - httpd_resp_set_status(req, "503 Busy"); - httpd_resp_sendstr(req, "No workers available. Server busy."); - return ESP_OK; - } + if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) { + return ESP_OK; + } else { + httpd_resp_set_status(req, "503 Busy"); + httpd_resp_sendstr(req, "No workers available. Server busy."); + return ESP_OK; + } } -#endif + #endif - PsychicEndpoint * self = (PsychicEndpoint *)req->user_ctx; - PsychicHandler * handler = self->handler(); - PsychicRequest request(self->_server, req); + PsychicEndpoint *self = (PsychicEndpoint *)req->user_ctx; + PsychicHandler *handler = self->handler(); + PsychicRequest request(self->_server, req); - //make sure we have a handler - if (handler != NULL) { - if (handler->filter(&request) && handler->canHandle(&request)) { - //check our credentials - if (handler->needsAuthentication(&request)) - return handler->authenticate(&request); + //make sure we have a handler + if (handler != NULL) + { + if (handler->filter(&request) && handler->canHandle(&request)) + { + //check our credentials + if (handler->needsAuthentication(&request)) + return handler->authenticate(&request); - //pass it to our handler - return handler->handleRequest(&request); - } - //pass it to our generic handlers - else - return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR); - } else - return request.reply(500, "text/html", "No handler registered."); + //pass it to our handler + return handler->handleRequest(&request); + } + //pass it to our generic handlers + else + return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR); + } + else + return request.reply(500, "text/html", "No handler registered."); } -PsychicEndpoint * PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) { - _handler->setFilter(fn); - return this; +PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) { + _handler->setFilter(fn); + return this; } -PsychicEndpoint * -PsychicEndpoint::setAuthentication(const char * username, const char * password, HTTPAuthMethod method, const char * realm, const char * authFailMsg) { - _handler->setAuthentication(username, password, method, realm, authFailMsg); - return this; +PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) { + _handler->setAuthentication(username, password, method, realm, authFailMsg); + return this; }; \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEndpoint.h b/lib/PsychicHttp/src/PsychicEndpoint.h index 3df414794..540b29413 100644 --- a/lib/PsychicHttp/src/PsychicEndpoint.h +++ b/lib/PsychicHttp/src/PsychicEndpoint.h @@ -2,35 +2,36 @@ #define PsychicEndpoint_h #include "PsychicCore.h" -#include "PsychicHttpServer.h" + +class PsychicHandler; #ifdef ENABLE_ASYNC -#include "async_worker.h" + #include "async_worker.h" #endif -class PsychicEndpoint { - friend PsychicHttpServer; +class PsychicEndpoint +{ + friend PsychicHttpServer; private: - PsychicHttpServer * _server; - String _uri; - http_method _method; - PsychicHandler * _handler; + PsychicHttpServer *_server; + String _uri; + http_method _method; + PsychicHandler *_handler; public: PsychicEndpoint(); - PsychicEndpoint(PsychicHttpServer * server, http_method method, const char * uri); + PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri); - PsychicEndpoint * setHandler(PsychicHandler * handler); - PsychicHandler * handler(); + PsychicEndpoint *setHandler(PsychicHandler *handler); + PsychicHandler *handler(); - PsychicEndpoint * setFilter(PsychicRequestFilterFunction fn); - PsychicEndpoint * - setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = ""); + PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn); + PsychicEndpoint* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = ""); String uri(); - static esp_err_t requestCallback(httpd_req_t * req); + static esp_err_t requestCallback(httpd_req_t *req); }; #endif // PsychicEndpoint_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEventSource.cpp b/lib/PsychicHttp/src/PsychicEventSource.cpp index d457e4754..044ebbac0 100644 --- a/lib/PsychicHttp/src/PsychicEventSource.cpp +++ b/lib/PsychicHttp/src/PsychicEventSource.cpp @@ -24,194 +24,204 @@ // PsychicEventSource - Handler /*****************************************/ -PsychicEventSource::PsychicEventSource() - : PsychicHandler() - , _onOpen(NULL) - , _onClose(NULL) { -} +PsychicEventSource::PsychicEventSource() : + PsychicHandler(), + _onOpen(NULL), + _onClose(NULL) +{} PsychicEventSource::~PsychicEventSource() { } -PsychicEventSourceClient * PsychicEventSource::getClient(int socket) { - PsychicClient * client = PsychicHandler::getClient(socket); +PsychicEventSourceClient * PsychicEventSource::getClient(int socket) +{ + PsychicClient *client = PsychicHandler::getClient(socket); - if (client == NULL) - return NULL; + if (client == NULL) + return NULL; - return (PsychicEventSourceClient *)client->_friend; + return (PsychicEventSourceClient *)client->_friend; } -PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient * client) { - return getClient(client->socket()); +PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient *client) { + return getClient(client->socket()); } -esp_err_t PsychicEventSource::handleRequest(PsychicRequest * request) { - //start our open ended HTTP response - PsychicEventSourceResponse response(request); - esp_err_t err = response.send(); +esp_err_t PsychicEventSource::handleRequest(PsychicRequest *request) +{ + //start our open ended HTTP response + PsychicEventSourceResponse response(request); + esp_err_t err = response.send(); - //lookup our client - PsychicClient * client = checkForNewClient(request->client()); - if (client->isNew) { - //did we get our last id? - if (request->hasHeader("Last-Event-ID")) { - PsychicEventSourceClient * buddy = getClient(client); - buddy->_lastId = atoi(request->header("Last-Event-ID").c_str()); - } - - //let our handler know. - openCallback(client); + //lookup our client + PsychicClient *client = checkForNewClient(request->client()); + if (client->isNew) + { + //did we get our last id? + if(request->hasHeader("Last-Event-ID")) + { + PsychicEventSourceClient *buddy = getClient(client); + buddy->_lastId = atoi(request->header("Last-Event-ID").c_str()); } - return err; + //let our handler know. + openCallback(client); + } + + return err; } PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) { - _onOpen = fn; - return this; + _onOpen = fn; + return this; } PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) { - _onClose = fn; - return this; + _onClose = fn; + return this; } -void PsychicEventSource::addClient(PsychicClient * client) { - client->_friend = new PsychicEventSourceClient(client); - PsychicHandler::addClient(client); +void PsychicEventSource::addClient(PsychicClient *client) { + client->_friend = new PsychicEventSourceClient(client); + PsychicHandler::addClient(client); } -void PsychicEventSource::removeClient(PsychicClient * client) { - PsychicHandler::removeClient(client); - delete (PsychicEventSourceClient *)client->_friend; - client->_friend = NULL; +void PsychicEventSource::removeClient(PsychicClient *client) { + PsychicHandler::removeClient(client); + delete (PsychicEventSourceClient*)client->_friend; + client->_friend = NULL; } -void PsychicEventSource::openCallback(PsychicClient * client) { - PsychicEventSourceClient * buddy = getClient(client); - if (buddy == NULL) { - TRACE(); - return; - } +void PsychicEventSource::openCallback(PsychicClient *client) { + PsychicEventSourceClient *buddy = getClient(client); + if (buddy == NULL) + { + TRACE(); + return; + } - if (_onOpen != NULL) - _onOpen(buddy); + if (_onOpen != NULL) + _onOpen(buddy); } -void PsychicEventSource::closeCallback(PsychicClient * client) { - PsychicEventSourceClient * buddy = getClient(client); - if (buddy == NULL) { - TRACE(); - return; - } +void PsychicEventSource::closeCallback(PsychicClient *client) { + PsychicEventSourceClient *buddy = getClient(client); + if (buddy == NULL) + { + TRACE(); + return; + } - if (_onClose != NULL) - _onClose(getClient(buddy)); + if (_onClose != NULL) + _onClose(getClient(buddy)); } -void PsychicEventSource::send(const char * message, const char * event, uint32_t id, uint32_t reconnect) { - String ev = generateEventMessage(message, event, id, reconnect); - for (PsychicClient * c : _clients) { - ((PsychicEventSourceClient *)c->_friend)->sendEvent(ev.c_str()); - } +void PsychicEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) +{ + String ev = generateEventMessage(message, event, id, reconnect); + for(PsychicClient *c : _clients) { + ((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str()); + } } /*****************************************/ // PsychicEventSourceClient /*****************************************/ -PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient * client) - : PsychicClient(client->server(), client->socket()) - , _lastId(0) { +PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient *client) : + PsychicClient(client->server(), client->socket()), + _lastId(0) +{ } -PsychicEventSourceClient::~PsychicEventSourceClient() { +PsychicEventSourceClient::~PsychicEventSourceClient(){ } -void PsychicEventSourceClient::send(const char * message, const char * event, uint32_t id, uint32_t reconnect) { - String ev = generateEventMessage(message, event, id, reconnect); - sendEvent(ev.c_str()); +void PsychicEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev = generateEventMessage(message, event, id, reconnect); + sendEvent(ev.c_str()); } -void PsychicEventSourceClient::sendEvent(const char * event) { - int result; - do { - result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0); - } while (result == HTTPD_SOCK_ERR_TIMEOUT); +void PsychicEventSourceClient::sendEvent(const char *event) { + int result; + do { + result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0); + } while (result == HTTPD_SOCK_ERR_TIMEOUT); - //if (result < 0) - //error log here + //if (result < 0) + //error log here } /*****************************************/ // PsychicEventSourceResponse /*****************************************/ -PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest * request) - : PsychicResponse(request) { +PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest *request) + : PsychicResponse(request) +{ } esp_err_t PsychicEventSourceResponse::send() { - //build our main header - String out = String(); - out.concat("HTTP/1.1 200 OK\r\n"); - out.concat("Content-Type: text/event-stream\r\n"); - out.concat("Cache-Control: no-cache\r\n"); - out.concat("Connection: keep-alive\r\n"); - //get our global headers out of the way first - for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) - out.concat(String(header.field) + ": " + String(header.value) + "\r\n"); + //build our main header + String out = String(); + out.concat("HTTP/1.1 200 OK\r\n"); + out.concat("Content-Type: text/event-stream\r\n"); + out.concat("Cache-Control: no-cache\r\n"); + out.concat("Connection: keep-alive\r\n"); - //separator - out.concat("\r\n"); + //get our global headers out of the way first + for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) + out.concat(String(header.field) + ": " + String(header.value) + "\r\n"); - int result; - do { - result = httpd_send(_request->request(), out.c_str(), out.length()); - } while (result == HTTPD_SOCK_ERR_TIMEOUT); + //separator + out.concat("\r\n"); - if (result < 0) - ESP_LOGE(PH_TAG, "EventSource send failed with %s", esp_err_to_name(result)); + int result; + do { + result = httpd_send(_request->request(), out.c_str(), out.length()); + } while (result == HTTPD_SOCK_ERR_TIMEOUT); - if (result > 0) - return ESP_OK; - else - return ESP_ERR_HTTPD_RESP_SEND; + if (result < 0) + ESP_LOGE(PH_TAG, "EventSource send failed with %s", esp_err_to_name(result)); + + if (result > 0) + return ESP_OK; + else + return ESP_ERR_HTTPD_RESP_SEND; } /*****************************************/ // Event Message Generator /*****************************************/ -String generateEventMessage(const char * message, const char * event, uint32_t id, uint32_t reconnect) { - String ev = ""; +String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) { + String ev = ""; - if (reconnect) { - ev += "retry: "; - ev += String(reconnect); - ev += "\r\n"; - } - - if (id) { - ev += "id: "; - ev += String(id); - ev += "\r\n"; - } - - if (event != NULL) { - ev += "event: "; - ev += String(event); - ev += "\r\n"; - } - - if (message != NULL) { - ev += "data: "; - ev += String(message); - ev += "\r\n"; - } + if(reconnect){ + ev += "retry: "; + ev += String(reconnect); ev += "\r\n"; + } - return ev; + if(id){ + ev += "id: "; + ev += String(id); + ev += "\r\n"; + } + + if(event != NULL){ + ev += "event: "; + ev += String(event); + ev += "\r\n"; + } + + if(message != NULL){ + ev += "data: "; + ev += String(message); + ev += "\r\n"; + } + ev += "\r\n"; + + return ev; } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEventSource.h b/lib/PsychicHttp/src/PsychicEventSource.h index 1406e5c30..ab3198043 100644 --- a/lib/PsychicHttp/src/PsychicEventSource.h +++ b/lib/PsychicHttp/src/PsychicEventSource.h @@ -21,8 +21,8 @@ #define PsychicEventSource_H_ #include "PsychicCore.h" -#include "PsychicClient.h" #include "PsychicHandler.h" +#include "PsychicClient.h" #include "PsychicResponse.h" class PsychicEventSource; @@ -30,23 +30,21 @@ class PsychicEventSourceResponse; class PsychicEventSourceClient; class PsychicResponse; -typedef std::function PsychicEventSourceClientCallback; +typedef std::function PsychicEventSourceClientCallback; class PsychicEventSourceClient : public PsychicClient { - friend PsychicEventSource; + friend PsychicEventSource; protected: uint32_t _lastId; public: - PsychicEventSourceClient(PsychicClient * client); + PsychicEventSourceClient(PsychicClient *client); ~PsychicEventSourceClient(); - uint32_t lastId() const { - return _lastId; - } - void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0); - void sendEvent(const char * event); + uint32_t lastId() const { return _lastId; } + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); + void sendEvent(const char *event); }; class PsychicEventSource : public PsychicHandler { @@ -59,26 +57,26 @@ class PsychicEventSource : public PsychicHandler { ~PsychicEventSource(); PsychicEventSourceClient * getClient(int socket) override; - PsychicEventSourceClient * getClient(PsychicClient * client) override; - void addClient(PsychicClient * client) override; - void removeClient(PsychicClient * client) override; - void openCallback(PsychicClient * client) override; - void closeCallback(PsychicClient * client) override; + PsychicEventSourceClient * getClient(PsychicClient *client) override; + void addClient(PsychicClient *client) override; + void removeClient(PsychicClient *client) override; + void openCallback(PsychicClient *client) override; + void closeCallback(PsychicClient *client) override; - PsychicEventSource * onOpen(PsychicEventSourceClientCallback fn); - PsychicEventSource * onClose(PsychicEventSourceClientCallback fn); + PsychicEventSource *onOpen(PsychicEventSourceClientCallback fn); + PsychicEventSource *onClose(PsychicEventSourceClientCallback fn); - esp_err_t handleRequest(PsychicRequest * request) override final; + esp_err_t handleRequest(PsychicRequest *request) override final; - void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0); + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); }; -class PsychicEventSourceResponse : public PsychicResponse { +class PsychicEventSourceResponse: public PsychicResponse { public: - PsychicEventSourceResponse(PsychicRequest * request); + PsychicEventSourceResponse(PsychicRequest *request); virtual esp_err_t send() override; }; -String generateEventMessage(const char * message, const char * event, uint32_t id, uint32_t reconnect); +String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect); #endif /* PsychicEventSource_H_ */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicFileResponse.cpp b/lib/PsychicHttp/src/PsychicFileResponse.cpp index 51827cc49..ed031560a 100644 --- a/lib/PsychicHttp/src/PsychicFileResponse.cpp +++ b/lib/PsychicHttp/src/PsychicFileResponse.cpp @@ -3,167 +3,156 @@ #include "PsychicRequest.h" -PsychicFileResponse::PsychicFileResponse(PsychicRequest * request, FS & fs, const String & path, const String & contentType, bool download) - : PsychicResponse(request) { - //_code = 200; - _path = path; +PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType, bool download) + : PsychicResponse(request) { + //_code = 200; + _path = path; - if (!download && !fs.exists(_path) && fs.exists(_path + ".gz")) { - _path = _path + ".gz"; - addHeader("Content-Encoding", "gzip"); - _sendContentLength = true; - _chunked = false; - } + if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ + _path = _path+".gz"; + addHeader("Content-Encoding", "gzip"); + _sendContentLength = true; + _chunked = false; + } - _content = fs.open(_path, "r"); - _contentLength = _content.size(); + _content = fs.open(_path, "r"); + _contentLength = _content.size(); - if (contentType == "") - _setContentType(path); - else - _contentType = contentType; - setContentType(_contentType.c_str()); + if(contentType == "") + _setContentType(path); + else + _contentType = contentType; + setContentType(_contentType.c_str()); - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26 + path.length() - filenameStart]; - char * filename = (char *)path.c_str() + filenameStart; + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; - if (download) { - // set filename and force download - snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename); - } else { - // set filename and force rendering - snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename); - } - addHeader("Content-Disposition", buf); + if(download) { + // set filename and force download + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); + } else { + // set filename and force rendering + snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); } -PsychicFileResponse::PsychicFileResponse(PsychicRequest * request, File content, const String & path, const String & contentType, bool download) - : PsychicResponse(request) { - _path = path; +PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType, bool download) + : PsychicResponse(request) { + _path = path; - if (!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")) { - addHeader("Content-Encoding", "gzip"); - _sendContentLength = true; - _chunked = false; + if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){ + addHeader("Content-Encoding", "gzip"); + _sendContentLength = true; + _chunked = false; + } + + _content = content; + _contentLength = _content.size(); + setContentLength(_contentLength); + + if(contentType == "") + _setContentType(path); + else + _contentType = contentType; + setContentType(_contentType.c_str()); + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); + } else { + snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); +} + +PsychicFileResponse::~PsychicFileResponse() +{ + if(_content) + _content.close(); +} + +void PsychicFileResponse::_setContentType(const String& path){ + if (path.endsWith(".html")) _contentType = "text/html"; + else if (path.endsWith(".htm")) _contentType = "text/html"; + else if (path.endsWith(".css")) _contentType = "text/css"; + else if (path.endsWith(".json")) _contentType = "application/json"; + else if (path.endsWith(".js")) _contentType = "application/javascript"; + else if (path.endsWith(".png")) _contentType = "image/png"; + else if (path.endsWith(".gif")) _contentType = "image/gif"; + else if (path.endsWith(".jpg")) _contentType = "image/jpeg"; + else if (path.endsWith(".ico")) _contentType = "image/x-icon"; + else if (path.endsWith(".svg")) _contentType = "image/svg+xml"; + else if (path.endsWith(".eot")) _contentType = "font/eot"; + else if (path.endsWith(".woff")) _contentType = "font/woff"; + else if (path.endsWith(".woff2")) _contentType = "font/woff2"; + else if (path.endsWith(".ttf")) _contentType = "font/ttf"; + else if (path.endsWith(".xml")) _contentType = "text/xml"; + else if (path.endsWith(".pdf")) _contentType = "application/pdf"; + else if (path.endsWith(".zip")) _contentType = "application/zip"; + else if(path.endsWith(".gz")) _contentType = "application/x-gzip"; + else _contentType = "text/plain"; +} + +esp_err_t PsychicFileResponse::send() +{ + esp_err_t err = ESP_OK; + + //just send small files directly + size_t size = getContentLength(); + if (size < FILE_CHUNK_SIZE) + { + uint8_t *buffer = (uint8_t *)malloc(size); + int readSize = _content.readBytes((char *)buffer, size); + + this->setContent(buffer, size); + err = PsychicResponse::send(); + + free(buffer); + } + else + { + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *chunk = (char *)malloc(FILE_CHUNK_SIZE); + if (chunk == NULL) + { + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; } - _content = content; - _contentLength = _content.size(); - setContentLength(_contentLength); + this->sendHeaders(); - if (contentType == "") - _setContentType(path); - else - _contentType = contentType; - setContentType(_contentType.c_str()); - - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26 + path.length() - filenameStart]; - char * filename = (char *)path.c_str() + filenameStart; - - if (download) { - snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename); - } else { - snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename); - } - addHeader("Content-Disposition", buf); -} - -PsychicFileResponse::~PsychicFileResponse() { - if (_content) - _content.close(); -} - -void PsychicFileResponse::_setContentType(const String & path) { - if (path.endsWith(".html")) - _contentType = "text/html"; - else if (path.endsWith(".htm")) - _contentType = "text/html"; - else if (path.endsWith(".css")) - _contentType = "text/css"; - else if (path.endsWith(".json")) - _contentType = "application/json"; - else if (path.endsWith(".js")) - _contentType = "application/javascript"; - else if (path.endsWith(".png")) - _contentType = "image/png"; - else if (path.endsWith(".gif")) - _contentType = "image/gif"; - else if (path.endsWith(".jpg")) - _contentType = "image/jpeg"; - else if (path.endsWith(".ico")) - _contentType = "image/x-icon"; - else if (path.endsWith(".svg")) - _contentType = "image/svg+xml"; - else if (path.endsWith(".eot")) - _contentType = "font/eot"; - else if (path.endsWith(".woff")) - _contentType = "font/woff"; - else if (path.endsWith(".woff2")) - _contentType = "font/woff2"; - else if (path.endsWith(".ttf")) - _contentType = "font/ttf"; - else if (path.endsWith(".xml")) - _contentType = "text/xml"; - else if (path.endsWith(".pdf")) - _contentType = "application/pdf"; - else if (path.endsWith(".zip")) - _contentType = "application/zip"; - else if (path.endsWith(".gz")) - _contentType = "application/x-gzip"; - else - _contentType = "text/plain"; -} - -esp_err_t PsychicFileResponse::send() { - esp_err_t err = ESP_OK; - - //just send small files directly - size_t size = getContentLength(); - if (size < FILE_CHUNK_SIZE) { - uint8_t * buffer = (uint8_t *)malloc(size); - int readSize = _content.readBytes((char *)buffer, size); - - this->setContent(buffer, size); - err = PsychicResponse::send(); - - free(buffer); - } else { - /* Retrieve the pointer to scratch buffer for temporary storage */ - char * chunk = (char *)malloc(FILE_CHUNK_SIZE); - if (chunk == NULL) { - /* Respond with 500 Internal Server Error */ - httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); - return ESP_FAIL; + size_t chunksize; + do { + /* Read file in chunks into the scratch buffer */ + chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE); + if (chunksize > 0) + { + err = this->sendChunk((uint8_t *)chunk, chunksize); + if (err != ESP_OK) + break; } - this->sendHeaders(); + /* Keep looping till the whole file is sent */ + } while (chunksize != 0); - size_t chunksize; - do { - /* Read file in chunks into the scratch buffer */ - chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE); - if (chunksize > 0) { - err = this->sendChunk((uint8_t *)chunk, chunksize); - if (err != ESP_OK) - break; - } + //keep track of our memory + free(chunk); - /* Keep looping till the whole file is sent */ - } while (chunksize != 0); - - //keep track of our memory - free(chunk); - - if (err == ESP_OK) { - ESP_LOGI(PH_TAG, "File sending complete"); - this->finishChunking(); - } - - /* Close file after sending complete */ - _content.close(); + if (err == ESP_OK) + { + ESP_LOGI(PH_TAG, "File sending complete"); + this->finishChunking(); } - return err; + /* Close file after sending complete */ + _content.close(); + } + + return err; } diff --git a/lib/PsychicHttp/src/PsychicFileResponse.h b/lib/PsychicHttp/src/PsychicFileResponse.h index 918e81bb7..85a8300b0 100644 --- a/lib/PsychicHttp/src/PsychicFileResponse.h +++ b/lib/PsychicHttp/src/PsychicFileResponse.h @@ -6,21 +6,20 @@ class PsychicRequest; -class PsychicFileResponse : public PsychicResponse { - using File = fs::File; - using FS = fs::FS; - +class PsychicFileResponse: public PsychicResponse +{ + using File = fs::File; + using FS = fs::FS; private: - File _content; + File _content; String _path; - bool _sendContentLength; - bool _chunked; + bool _sendContentLength; + bool _chunked; String _contentType; - void _setContentType(const String & path); - + void _setContentType(const String& path); public: - PsychicFileResponse(PsychicRequest * request, FS & fs, const String & path, const String & contentType = String(), bool download = false); - PsychicFileResponse(PsychicRequest * request, File content, const String & path, const String & contentType = String(), bool download = false); + PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType=String(), bool download=false); + PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType=String(), bool download=false); ~PsychicFileResponse(); esp_err_t send(); }; diff --git a/lib/PsychicHttp/src/PsychicHandler.h b/lib/PsychicHttp/src/PsychicHandler.h index 071238124..4d832f955 100644 --- a/lib/PsychicHttp/src/PsychicHandler.h +++ b/lib/PsychicHttp/src/PsychicHandler.h @@ -5,63 +5,57 @@ #include "PsychicRequest.h" class PsychicEndpoint; +class PsychicHttpServer; /* * HANDLER :: Can be attached to any endpoint or as a generic request handler. */ class PsychicHandler { - friend PsychicEndpoint; + friend PsychicEndpoint; protected: PsychicRequestFilterFunction _filter; - PsychicHttpServer * _server; + PsychicHttpServer *_server; - String _username; - String _password; + String _username; + String _password; HTTPAuthMethod _method; - String _realm; - String _authFailMsg; + String _realm; + String _authFailMsg; - std::list _clients; + std::list _clients; public: PsychicHandler(); ~PsychicHandler(); - PsychicHandler * setFilter(PsychicRequestFilterFunction fn); - bool filter(PsychicRequest * request); + PsychicHandler* setFilter(PsychicRequestFilterFunction fn); + bool filter(PsychicRequest *request); - PsychicHandler * - setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = ""); - bool needsAuthentication(PsychicRequest * request); - esp_err_t authenticate(PsychicRequest * request); + PsychicHandler* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = ""); + bool needsAuthentication(PsychicRequest *request); + esp_err_t authenticate(PsychicRequest *request); - virtual bool isWebSocket() { - return false; - }; + virtual bool isWebSocket() { return false; }; - PsychicClient * checkForNewClient(PsychicClient * client); - void checkForClosedClient(PsychicClient * client); + PsychicClient * checkForNewClient(PsychicClient *client); + void checkForClosedClient(PsychicClient *client); - virtual void addClient(PsychicClient * client); - virtual void removeClient(PsychicClient * client); + virtual void addClient(PsychicClient *client); + virtual void removeClient(PsychicClient *client); virtual PsychicClient * getClient(int socket); - virtual PsychicClient * getClient(PsychicClient * client); - virtual void openCallback(PsychicClient * client){}; - virtual void closeCallback(PsychicClient * client){}; + virtual PsychicClient * getClient(PsychicClient *client); + virtual void openCallback(PsychicClient *client) {}; + virtual void closeCallback(PsychicClient *client) {}; - bool hasClient(PsychicClient * client); - int count() { - return _clients.size(); - }; - const std::list & getClientList(); + bool hasClient(PsychicClient *client); + int count() { return _clients.size(); }; + const std::list& getClientList(); //derived classes must implement these functions - virtual bool canHandle(PsychicRequest * request) { - return true; - }; - virtual esp_err_t handleRequest(PsychicRequest * request) = 0; + virtual bool canHandle(PsychicRequest *request) { return true; }; + virtual esp_err_t handleRequest(PsychicRequest *request) = 0; }; #endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttp.h b/lib/PsychicHttp/src/PsychicHttp.h index dd863162a..ac633858a 100644 --- a/lib/PsychicHttp/src/PsychicHttp.h +++ b/lib/PsychicHttp/src/PsychicHttp.h @@ -1,7 +1,7 @@ #ifndef PsychicHttp_h #define PsychicHttp_h -//#define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread +// #define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread #include #include "PsychicHttpServer.h" @@ -11,6 +11,7 @@ #include "PsychicHandler.h" #include "PsychicStaticFileHandler.h" #include "PsychicFileResponse.h" +#include "PsychicStreamResponse.h" #include "PsychicUploadHandler.h" #include "PsychicWebSocket.h" #include "PsychicEventSource.h" diff --git a/lib/PsychicHttp/src/PsychicHttpServer.cpp b/lib/PsychicHttp/src/PsychicHttpServer.cpp index c7938ad63..e48a73b16 100644 --- a/lib/PsychicHttp/src/PsychicHttpServer.cpp +++ b/lib/PsychicHttp/src/PsychicHttpServer.cpp @@ -4,28 +4,29 @@ #include "PsychicWebHandler.h" #include "PsychicStaticFileHandler.h" #include "PsychicWebSocket.h" +#include "PsychicJson.h" #include "WiFi.h" -#include "PsychicJson.h" // added by proddy -PsychicHttpServer::PsychicHttpServer() - : _onOpen(NULL) - , _onClose(NULL) { - maxRequestBodySize = MAX_REQUEST_BODY_SIZE; - maxUploadSize = MAX_UPLOAD_SIZE; +PsychicHttpServer::PsychicHttpServer() : + _onOpen(NULL), + _onClose(NULL) +{ + maxRequestBodySize = MAX_REQUEST_BODY_SIZE; + maxUploadSize = MAX_UPLOAD_SIZE; - defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, ""); - onNotFound(PsychicHttpServer::defaultNotFoundHandler); + defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, ""); + onNotFound(PsychicHttpServer::defaultNotFoundHandler); + + //for a regular server + config = HTTPD_DEFAULT_CONFIG(); + config.open_fn = PsychicHttpServer::openCallback; + config.close_fn = PsychicHttpServer::closeCallback; + config.uri_match_fn = httpd_uri_match_wildcard; + config.global_user_ctx = this; + config.global_user_ctx_free_fn = destroy; + config.max_uri_handlers = 20; - //for a regular server - config = HTTPD_DEFAULT_CONFIG(); - config.open_fn = PsychicHttpServer::openCallback; - config.close_fn = PsychicHttpServer::closeCallback; - config.uri_match_fn = httpd_uri_match_wildcard; - config.global_user_ctx = this; - config.global_user_ctx_free_fn = destroy; - config.max_uri_handlers = 20; - -#ifdef ENABLE_ASYNC + #ifdef ENABLE_ASYNC // It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS // Why? This leaves at least one socket still available to handle // quick synchronous requests. Otherwise, all the sockets will @@ -33,297 +34,333 @@ PsychicHttpServer::PsychicHttpServer() // longer be responsive. config.max_open_sockets = ASYNC_WORKER_COUNT + 1; config.lru_purge_enable = true; -#endif + #endif } -PsychicHttpServer::~PsychicHttpServer() { - for (auto * client : _clients) - delete (client); - _clients.clear(); +PsychicHttpServer::~PsychicHttpServer() +{ + for (auto *client : _clients) + delete(client); + _clients.clear(); - for (auto * endpoint : _endpoints) - delete (endpoint); - _endpoints.clear(); + for (auto *endpoint : _endpoints) + delete(endpoint); + _endpoints.clear(); - for (auto * handler : _handlers) - delete (handler); - _handlers.clear(); + for (auto *handler : _handlers) + delete(handler); + _handlers.clear(); - delete defaultEndpoint; + delete defaultEndpoint; } -void PsychicHttpServer::destroy(void * ctx) { - PsychicHttpServer * temp = (PsychicHttpServer *)ctx; - delete temp; +void PsychicHttpServer::destroy(void *ctx) +{ + PsychicHttpServer *temp = (PsychicHttpServer *)ctx; + delete temp; } -esp_err_t PsychicHttpServer::listen(uint16_t port) { - this->_use_ssl = false; - this->config.server_port = port; +esp_err_t PsychicHttpServer::listen(uint16_t port) +{ + this->_use_ssl = false; + this->config.server_port = port; - return this->_start(); + return this->_start(); } -esp_err_t PsychicHttpServer::_start() { - esp_err_t ret; +esp_err_t PsychicHttpServer::_start() +{ + esp_err_t ret; -#ifdef ENABLE_ASYNC + #ifdef ENABLE_ASYNC // start workers start_async_req_workers(); -#endif - - //fire it up. - ret = _startServer(); - if (ret != ESP_OK) { - ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret)); - return ret; - } - - // Register handler - ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler); - if (ret != ESP_OK) - ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret)); + #endif + //fire it up. + ret = _startServer(); + if (ret != ESP_OK) + { + ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret)); return ret; + } + + // Register handler + ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret)); + + return ret; } esp_err_t PsychicHttpServer::_startServer() { - return httpd_start(&this->server, &this->config); + return httpd_start(&this->server, &this->config); } -void PsychicHttpServer::stop() { - httpd_stop(this->server); +void PsychicHttpServer::stop() +{ + httpd_stop(this->server); } -PsychicHandler & PsychicHttpServer::addHandler(PsychicHandler * handler) { - _handlers.push_back(handler); - return *handler; +PsychicHandler& PsychicHttpServer::addHandler(PsychicHandler* handler){ + _handlers.push_back(handler); + return *handler; } -void PsychicHttpServer::removeHandler(PsychicHandler * handler) { - _handlers.remove(handler); +void PsychicHttpServer::removeHandler(PsychicHandler *handler){ + _handlers.remove(handler); } -PsychicEndpoint * PsychicHttpServer::on(const char * uri) { - return on(uri, HTTP_GET); +PsychicEndpoint* PsychicHttpServer::on(const char* uri) { + return on(uri, HTTP_GET); } -PsychicEndpoint * PsychicHttpServer::on(const char * uri, PsychicHttpRequestCallback fn) { - return on(uri, HTTP_GET, fn); +PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method) +{ + PsychicWebHandler *handler = new PsychicWebHandler(); + + return on(uri, method, handler); } -PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicHttpRequestCallback fn) { - //these basic requests need a basic web handler - PsychicWebHandler * handler = new PsychicWebHandler(); - handler->onRequest(fn); - - return on(uri, method, handler); +PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler *handler) +{ + return on(uri, HTTP_GET, handler); } -// added by Proddy -PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicJsonRequestCallback fn) { - PsychicJsonHandler * handler = new PsychicJsonHandler(fn); +PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHandler *handler) +{ + //make our endpoint + PsychicEndpoint *endpoint = new PsychicEndpoint(this, method, uri); - return on(uri, method, handler); + //set our handler + endpoint->setHandler(handler); + + // URI handler structure + httpd_uri_t my_uri { + .uri = uri, + .method = method, + .handler = PsychicEndpoint::requestCallback, + .user_ctx = endpoint, + .is_websocket = handler->isWebSocket() + }; + + // Register endpoint with ESP-IDF server + esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret)); + + //save it for later + _endpoints.push_back(endpoint); + + return endpoint; } -PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method) { - PsychicWebHandler * handler = new PsychicWebHandler(); - - return on(uri, method, handler); +PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpRequestCallback fn) +{ + return on(uri, HTTP_GET, fn); } -PsychicEndpoint * PsychicHttpServer::on(const char * uri, PsychicHandler * handler) { - return on(uri, HTTP_GET, handler); +PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHttpRequestCallback fn) +{ + //these basic requests need a basic web handler + PsychicWebHandler *handler = new PsychicWebHandler(); + handler->onRequest(fn); + + return on(uri, method, handler); } -PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicHandler * handler) { - //make our endpoint - PsychicEndpoint * endpoint = new PsychicEndpoint(this, method, uri); - - //set our handler - endpoint->setHandler(handler); - - // URI handler structure - httpd_uri_t my_uri{.uri = uri, .method = method, .handler = PsychicEndpoint::requestCallback, .user_ctx = endpoint, .is_websocket = handler->isWebSocket()}; - - // Register endpoint with ESP-IDF server - esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri); - if (ret != ESP_OK) { - ESP_LOGE(PH_TAG, "Add endpoint %s failed (%s)", uri, esp_err_to_name(ret)); // modified by proddy - } - - //save it for later - _endpoints.push_back(endpoint); - - return endpoint; +PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonRequestCallback fn) +{ + return on(uri, HTTP_GET, fn); } -void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn) { - PsychicWebHandler * handler = new PsychicWebHandler(); - handler->onRequest(fn); +PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicJsonRequestCallback fn) +{ + //these basic requests need a basic web handler + PsychicJsonHandler *handler = new PsychicJsonHandler(); + handler->onRequest(fn); - this->defaultEndpoint->setHandler(handler); + return on(uri, method, handler); } -esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t * req, httpd_err_code_t err) { - PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(req->handle); - PsychicRequest request(server, req); +void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn) +{ + PsychicWebHandler *handler = new PsychicWebHandler(); + handler->onRequest(fn); - //loop through our global handlers and see if anyone wants it - for (auto * handler : server->_handlers) { - //are we capable of handling this? - if (handler->filter(&request) && handler->canHandle(&request)) { - //check our credentials - if (handler->needsAuthentication(&request)) - return handler->authenticate(&request); - else - return handler->handleRequest(&request); - } - } + this->defaultEndpoint->setHandler(handler); +} - //nothing found, give it to our defaultEndpoint - PsychicHandler * handler = server->defaultEndpoint->handler(); +esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t *req, httpd_err_code_t err) +{ + PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle); + PsychicRequest request(server, req); + + //loop through our global handlers and see if anyone wants it + for(auto *handler: server->_handlers) + { + //are we capable of handling this? if (handler->filter(&request) && handler->canHandle(&request)) + { + //check our credentials + if (handler->needsAuthentication(&request)) + return handler->authenticate(&request); + else return handler->handleRequest(&request); + } + } - //not sure how we got this far. - return ESP_ERR_HTTPD_INVALID_REQ; + //nothing found, give it to our defaultEndpoint + PsychicHandler *handler = server->defaultEndpoint->handler(); + if (handler->filter(&request) && handler->canHandle(&request)) + return handler->handleRequest(&request); + + //not sure how we got this far. + return ESP_ERR_HTTPD_INVALID_REQ; } -esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest * request) { - request->reply(404, "text/html", "That URI does not exist."); +esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest *request) +{ + request->reply(404, "text/html", "That URI does not exist."); - return ESP_OK; + return ESP_OK; } void PsychicHttpServer::onOpen(PsychicClientCallback handler) { - this->_onOpen = handler; + this->_onOpen = handler; } -esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd) { - ESP_LOGI(PH_TAG, "New client connected %d", sockfd); +esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd) +{ + ESP_LOGI(PH_TAG, "New client connected %d", sockfd); - //get our global server reference - PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(hd); + //get our global server reference + PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd); - //lookup our client - PsychicClient * client = server->getClient(sockfd); - if (client == NULL) { - client = new PsychicClient(hd, sockfd); - server->addClient(client); - } + //lookup our client + PsychicClient *client = server->getClient(sockfd); + if (client == NULL) + { + client = new PsychicClient(hd, sockfd); + server->addClient(client); + } - //user callback - if (server->_onOpen != NULL) - server->_onOpen(client); + //user callback + if (server->_onOpen != NULL) + server->_onOpen(client); - return ESP_OK; + return ESP_OK; } void PsychicHttpServer::onClose(PsychicClientCallback handler) { - this->_onClose = handler; + this->_onClose = handler; } -void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd) { - ESP_LOGI(PH_TAG, "Client disconnected %d", sockfd); +void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd) +{ + ESP_LOGI(PH_TAG, "Client disconnected %d", sockfd); - PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(hd); + PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd); - //lookup our client - PsychicClient * client = server->getClient(sockfd); - if (client != NULL) { - //give our handlers a chance to handle a disconnect first - for (PsychicEndpoint * endpoint : server->_endpoints) { - PsychicHandler * handler = endpoint->handler(); - handler->checkForClosedClient(client); - } + //lookup our client + PsychicClient *client = server->getClient(sockfd); + if (client != NULL) + { + //give our handlers a chance to handle a disconnect first + for (PsychicEndpoint * endpoint : server->_endpoints) + { + PsychicHandler *handler = endpoint->handler(); + handler->checkForClosedClient(client); + } - //do we have a callback attached? - if (server->_onClose != NULL) - server->_onClose(client); + //do we have a callback attached? + if (server->_onClose != NULL) + server->_onClose(client); - //remove it from our list - server->removeClient(client); - } else - ESP_LOGE(PH_TAG, "No client record %d", sockfd); + //remove it from our list + server->removeClient(client); + } + else + ESP_LOGE(PH_TAG, "No client record %d", sockfd); - //finally close it out. - close(sockfd); + //finally close it out. + close(sockfd); } -PsychicStaticFileHandler * PsychicHttpServer::serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control) { - PsychicStaticFileHandler * handler = new PsychicStaticFileHandler(uri, fs, path, cache_control); - this->addHandler(handler); +PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control) +{ + PsychicStaticFileHandler* handler = new PsychicStaticFileHandler(uri, fs, path, cache_control); + this->addHandler(handler); - return handler; + return handler; } -void PsychicHttpServer::addClient(PsychicClient * client) { - _clients.push_back(client); +void PsychicHttpServer::addClient(PsychicClient *client) { + _clients.push_back(client); } -void PsychicHttpServer::removeClient(PsychicClient * client) { - _clients.remove(client); - delete client; +void PsychicHttpServer::removeClient(PsychicClient *client) { + _clients.remove(client); + delete client; } PsychicClient * PsychicHttpServer::getClient(int socket) { - for (PsychicClient * client : _clients) - if (client->socket() == socket) - return client; + for (PsychicClient * client : _clients) + if (client->socket() == socket) + return client; - return NULL; + return NULL; } -PsychicClient * PsychicHttpServer::getClient(httpd_req_t * req) { - return getClient(httpd_req_to_sockfd(req)); +PsychicClient * PsychicHttpServer::getClient(httpd_req_t *req) { + return getClient(httpd_req_to_sockfd(req)); } bool PsychicHttpServer::hasClient(int socket) { - return getClient(socket) != NULL; + return getClient(socket) != NULL; } -const std::list & PsychicHttpServer::getClientList() { - return _clients; +const std::list& PsychicHttpServer::getClientList() { + return _clients; } -bool ON_STA_FILTER(PsychicRequest * request) { - return WiFi.localIP() == request->client()->localIP(); +bool ON_STA_FILTER(PsychicRequest *request) { + return WiFi.localIP() == request->client()->localIP(); } -bool ON_AP_FILTER(PsychicRequest * request) { - return WiFi.softAPIP() == request->client()->localIP(); +bool ON_AP_FILTER(PsychicRequest *request) { + return WiFi.softAPIP() == request->client()->localIP(); } -String urlDecode(const char * encoded) { - size_t length = strlen(encoded); - char * decoded = (char *)malloc(length + 1); - if (!decoded) { - return ""; - } +String urlDecode(const char* encoded) +{ + size_t length = strlen(encoded); + char* decoded = (char*)malloc(length + 1); + if (!decoded) { + return ""; + } - size_t i, j = 0; - for (i = 0; i < length; ++i) { - if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) { - // Valid percent-encoded sequence - int hex; - sscanf(encoded + i + 1, "%2x", &hex); - decoded[j++] = (char)hex; - i += 2; // Skip the two hexadecimal characters - } else if (encoded[i] == '+') { - // Convert '+' to space - decoded[j++] = ' '; - } else { - // Copy other characters as they are - decoded[j++] = encoded[i]; - } - } + size_t i, j = 0; + for (i = 0; i < length; ++i) { + if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) { + // Valid percent-encoded sequence + int hex; + sscanf(encoded + i + 1, "%2x", &hex); + decoded[j++] = (char)hex; + i += 2; // Skip the two hexadecimal characters + } else if (encoded[i] == '+') { + // Convert '+' to space + decoded[j++] = ' '; + } else { + // Copy other characters as they are + decoded[j++] = encoded[i]; + } + } - decoded[j] = '\0'; // Null-terminate the decoded string + decoded[j] = '\0'; // Null-terminate the decoded string - String output(decoded); - free(decoded); + String output(decoded); + free(decoded); - return output; + return output; } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpServer.h b/lib/PsychicHttp/src/PsychicHttpServer.h index 0407ceb19..e572c1b2d 100644 --- a/lib/PsychicHttp/src/PsychicHttpServer.h +++ b/lib/PsychicHttp/src/PsychicHttpServer.h @@ -4,27 +4,23 @@ #include "PsychicCore.h" #include "PsychicClient.h" #include "PsychicHandler.h" -#include // added by proddy class PsychicEndpoint; class PsychicHandler; class PsychicStaticFileHandler; -//callback definitions -typedef std::function PsychicHttpRequestCallback; -typedef std::function PsychicJsonRequestCallback; // added by proddy - -class PsychicHttpServer { +class PsychicHttpServer +{ protected: - bool _use_ssl = false; - std::list _endpoints; - std::list _handlers; - std::list _clients; + bool _use_ssl = false; + std::list _endpoints; + std::list _handlers; + std::list _clients; PsychicClientCallback _onOpen; PsychicClientCallback _onClose; - esp_err_t _start(); + esp_err_t _start(); virtual esp_err_t _startServer(); public: @@ -39,48 +35,47 @@ class PsychicHttpServer { unsigned long maxUploadSize; unsigned long maxRequestBodySize; - PsychicEndpoint * defaultEndpoint; + PsychicEndpoint *defaultEndpoint; - static void destroy(void * ctx); + static void destroy(void *ctx); esp_err_t listen(uint16_t port); virtual void stop(); - PsychicHandler & addHandler(PsychicHandler * handler); - void removeHandler(PsychicHandler * handler); + PsychicHandler& addHandler(PsychicHandler* handler); + void removeHandler(PsychicHandler* handler); - void addClient(PsychicClient * client); - void removeClient(PsychicClient * client); - PsychicClient * getClient(int socket); - PsychicClient * getClient(httpd_req_t * req); - bool hasClient(int socket); - int count() { - return _clients.size(); - }; - const std::list & getClientList(); + void addClient(PsychicClient *client); + void removeClient(PsychicClient *client); + PsychicClient* getClient(int socket); + PsychicClient* getClient(httpd_req_t *req); + bool hasClient(int socket); + int count() { return _clients.size(); }; + const std::list& getClientList(); - PsychicEndpoint * on(const char * uri); - PsychicEndpoint * on(const char * uri, http_method method); - PsychicEndpoint * on(const char * uri, PsychicHttpRequestCallback onRequest); - PsychicEndpoint * on(const char * uri, http_method method, PsychicHttpRequestCallback onRequest); - PsychicEndpoint * on(const char * uri, PsychicHandler * handler); - PsychicEndpoint * on(const char * uri, http_method method, PsychicHandler * handler); - PsychicEndpoint * on(const char * uri, http_method method, PsychicJsonRequestCallback onRequest); // added proddy + PsychicEndpoint* on(const char* uri); + PsychicEndpoint* on(const char* uri, http_method method); + PsychicEndpoint* on(const char* uri, PsychicHandler *handler); + PsychicEndpoint* on(const char* uri, http_method method, PsychicHandler *handler); + PsychicEndpoint* on(const char* uri, PsychicHttpRequestCallback onRequest); + PsychicEndpoint* on(const char* uri, http_method method, PsychicHttpRequestCallback onRequest); + PsychicEndpoint* on(const char* uri, PsychicJsonRequestCallback onRequest); + PsychicEndpoint* on(const char* uri, http_method method, PsychicJsonRequestCallback onRequest); - static esp_err_t notFoundHandler(httpd_req_t * req, httpd_err_code_t err); - static esp_err_t defaultNotFoundHandler(PsychicRequest * request); - void onNotFound(PsychicHttpRequestCallback fn); + static esp_err_t notFoundHandler(httpd_req_t *req, httpd_err_code_t err); + static esp_err_t defaultNotFoundHandler(PsychicRequest *request); + void onNotFound(PsychicHttpRequestCallback fn); - void onOpen(PsychicClientCallback handler); - void onClose(PsychicClientCallback handler); + void onOpen(PsychicClientCallback handler); + void onClose(PsychicClientCallback handler); static esp_err_t openCallback(httpd_handle_t hd, int sockfd); - static void closeCallback(httpd_handle_t hd, int sockfd); + static void closeCallback(httpd_handle_t hd, int sockfd); - PsychicStaticFileHandler * serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control = NULL); + PsychicStaticFileHandler* serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); }; -bool ON_STA_FILTER(PsychicRequest * request); -bool ON_AP_FILTER(PsychicRequest * request); +bool ON_STA_FILTER(PsychicRequest *request); +bool ON_AP_FILTER(PsychicRequest *request); #endif // PsychicHttpServer_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpsServer.cpp b/lib/PsychicHttp/src/PsychicHttpsServer.cpp index 489beb6a3..e62dd4ecd 100644 --- a/lib/PsychicHttp/src/PsychicHttpsServer.cpp +++ b/lib/PsychicHttp/src/PsychicHttpsServer.cpp @@ -1,48 +1,50 @@ #include "PsychicHttpsServer.h" -PsychicHttpsServer::PsychicHttpsServer() - : PsychicHttpServer() { - //for a SSL server - ssl_config = HTTPD_SSL_CONFIG_DEFAULT(); - ssl_config.httpd.open_fn = PsychicHttpServer::openCallback; - ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback; - ssl_config.httpd.uri_match_fn = httpd_uri_match_wildcard; - ssl_config.httpd.global_user_ctx = this; - ssl_config.httpd.global_user_ctx_free_fn = destroy; - ssl_config.httpd.max_uri_handlers = 20; +PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer() +{ + //for a SSL server + ssl_config = HTTPD_SSL_CONFIG_DEFAULT(); + ssl_config.httpd.open_fn = PsychicHttpServer::openCallback; + ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback; + ssl_config.httpd.uri_match_fn = httpd_uri_match_wildcard; + ssl_config.httpd.global_user_ctx = this; + ssl_config.httpd.global_user_ctx_free_fn = destroy; + ssl_config.httpd.max_uri_handlers = 20; - // each SSL connection takes about 45kb of heap - // a barebones sketch with PsychicHttp has ~150kb of heap available - // if we set it higher than 2 and use all the connections, we get lots of memory errors. - // not to mention there is no heap left over for the program itself. - ssl_config.httpd.max_open_sockets = 2; + // each SSL connection takes about 45kb of heap + // a barebones sketch with PsychicHttp has ~150kb of heap available + // if we set it higher than 2 and use all the connections, we get lots of memory errors. + // not to mention there is no heap left over for the program itself. + ssl_config.httpd.max_open_sockets = 2; } -PsychicHttpsServer::~PsychicHttpsServer() { +PsychicHttpsServer::~PsychicHttpsServer() {} + +esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, const char *private_key) +{ + this->_use_ssl = true; + + this->ssl_config.port_secure = port; + this->ssl_config.cacert_pem = (uint8_t *)cert; + this->ssl_config.cacert_len = strlen(cert)+1; + this->ssl_config.prvtkey_pem = (uint8_t *)private_key; + this->ssl_config.prvtkey_len = strlen(private_key)+1; + + return this->_start(); } -esp_err_t PsychicHttpsServer::listen(uint16_t port, const char * cert, const char * private_key) { - this->_use_ssl = true; - - this->ssl_config.port_secure = port; - this->ssl_config.cacert_pem = (uint8_t *)cert; - this->ssl_config.cacert_len = strlen(cert) + 1; - this->ssl_config.prvtkey_pem = (uint8_t *)private_key; - this->ssl_config.prvtkey_len = strlen(private_key) + 1; - - return this->_start(); +esp_err_t PsychicHttpsServer::_startServer() +{ + if (this->_use_ssl) + return httpd_ssl_start(&this->server, &this->ssl_config); + else + return httpd_start(&this->server, &this->config); } -esp_err_t PsychicHttpsServer::_startServer() { - if (this->_use_ssl) - return httpd_ssl_start(&this->server, &this->ssl_config); - else - return httpd_start(&this->server, &this->config); -} - -void PsychicHttpsServer::stop() { - if (this->_use_ssl) - httpd_ssl_stop(this->server); - else - httpd_stop(this->server); +void PsychicHttpsServer::stop() +{ + if (this->_use_ssl) + httpd_ssl_stop(this->server); + else + httpd_stop(this->server); } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpsServer.h b/lib/PsychicHttp/src/PsychicHttpsServer.h index fa20c81e8..f61043d56 100644 --- a/lib/PsychicHttp/src/PsychicHttpsServer.h +++ b/lib/PsychicHttp/src/PsychicHttpsServer.h @@ -6,12 +6,13 @@ #include #if !CONFIG_HTTPD_WS_SUPPORT -#error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration + #error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration #endif #define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features -class PsychicHttpsServer : public PsychicHttpServer { +class PsychicHttpsServer : public PsychicHttpServer +{ protected: bool _use_ssl = false; @@ -22,10 +23,10 @@ class PsychicHttpsServer : public PsychicHttpServer { httpd_ssl_config_t ssl_config; using PsychicHttpServer::listen; //keep the regular version - esp_err_t listen(uint16_t port, const char * cert, const char * private_key); + esp_err_t listen(uint16_t port, const char *cert, const char *private_key); virtual esp_err_t _startServer() override final; - virtual void stop() override final; + virtual void stop() override final; }; #endif // PsychicHttpsServer_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicJson.DELETEME b/lib/PsychicHttp/src/PsychicJson.DELETEME new file mode 100644 index 000000000..dd6830a53 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicJson.DELETEME @@ -0,0 +1,150 @@ +#include "PsychicJson.h" + +#ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray) : PsychicResponse(request) + { + setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.createArray(); + else + _root = _jsonBuffer.createObject(); + } +#else + PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray, size_t maxJsonBufferSize) : + PsychicResponse(request), + _jsonBuffer(maxJsonBufferSize) + { + setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.createNestedArray(); + else + _root = _jsonBuffer.createNestedObject(); + } +#endif + +JsonVariant &PsychicJsonResponse::getRoot() { return _root; } + +size_t PsychicJsonResponse::getLength() +{ + #ifdef ARDUINOJSON_5_COMPATIBILITY + return _root.measureLength(); + #else + return measureJson(_root); + #endif +} + +esp_err_t PsychicJsonResponse::send() +{ + esp_err_t err = ESP_OK; + size_t length = getLength(); + size_t buffer_size; + char *buffer; + + DUMP(length); + + //how big of a buffer do we want? + if (length < JSON_BUFFER_SIZE) + buffer_size = length+1; + else + buffer_size = JSON_BUFFER_SIZE; + + DUMP(buffer_size); + + buffer = (char *)malloc(buffer_size); + if (buffer == NULL) { + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + //send it in one shot or no? + if (length < JSON_BUFFER_SIZE) + { + TRACE(); + + #ifdef ARDUINOJSON_5_COMPATIBILITY + _root.printTo(buffer, buffer_size); + #else + serializeJson(_root, buffer, buffer_size); + #endif + + this->setContent((uint8_t *)buffer, length); + this->setContentType(JSON_MIMETYPE); + + err = PsychicResponse::send(); + } + else + { + //helper class that acts as a stream to print chunked responses + ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size); + + //keep our headers + this->sendHeaders(); + + //these commands write to the ChunkPrinter which does the sending + #ifdef ARDUINOJSON_5_COMPATIBILITY + _root.printTo(dest); + #else + serializeJson(_root, dest); + #endif + + //send the last bits + dest.flush(); + + //done with our chunked response too + err = this->finishChunking(); + } + + //let the buffer go + free(buffer); + + return err; +} + +#ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonHandler::PsychicJsonHandler() : + _onRequest(NULL) + {}; + + PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) : + _onRequest(onRequest) + {} +#else + PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) : + _onRequest(NULL), + _maxJsonBufferSize(maxJsonBufferSize) + {}; + + PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) : + _onRequest(onRequest), + _maxJsonBufferSize(maxJsonBufferSize) + {} +#endif + +void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) { _onRequest = fn; } + +esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest *request) +{ + //process basic stuff + PsychicWebHandler::handleRequest(request); + + if (_onRequest) + { + #ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer jsonBuffer; + JsonVariant json = jsonBuffer.parse(); + if (!json.success()) + return request->reply(400); + #else + DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize); + DeserializationError error = deserializeJson(jsonBuffer, request->body()); + if (error) + return request->reply(400); + + JsonVariant json = jsonBuffer.as(); + #endif + + return _onRequest(request, json); + } + else + return request->reply(500); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicJson.h b/lib/PsychicHttp/src/PsychicJson.h index 5f98a404d..b01abd666 100644 --- a/lib/PsychicHttp/src/PsychicJson.h +++ b/lib/PsychicHttp/src/PsychicJson.h @@ -98,10 +98,12 @@ class PsychicJsonResponse : public PsychicResponse { // } virtual esp_err_t send() override { - esp_err_t err = ESP_OK; - size_t length = getLength(); - size_t buffer_size; - char * buffer; + esp_err_t err = ESP_OK; + // size_t length = getLength(); + size_t length = JSON_BUFFER_SIZE; // TODO force chunking + + size_t buffer_size; + char * buffer; DUMP(length); diff --git a/lib/PsychicHttp/src/PsychicRequest.cpp b/lib/PsychicHttp/src/PsychicRequest.cpp index 50a163ca8..aa54b330b 100644 --- a/lib/PsychicHttp/src/PsychicRequest.cpp +++ b/lib/PsychicHttp/src/PsychicRequest.cpp @@ -1,6 +1,7 @@ #include "PsychicRequest.h" -#include "PsychicResponse.h" -#include +#include "http_status.h" +#include "PsychicHttpServer.h" + PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) : _server(server), diff --git a/lib/PsychicHttp/src/PsychicRequest.h b/lib/PsychicHttp/src/PsychicRequest.h index 8ab6340ca..e4393c78a 100644 --- a/lib/PsychicHttp/src/PsychicRequest.h +++ b/lib/PsychicHttp/src/PsychicRequest.h @@ -5,6 +5,7 @@ #include "PsychicHttpServer.h" #include "PsychicClient.h" #include "PsychicWebParameter.h" +#include "PsychicResponse.h" typedef std::map SessionData; diff --git a/lib/PsychicHttp/src/PsychicResponse.cpp b/lib/PsychicHttp/src/PsychicResponse.cpp index 0aa3ae8fc..515674e56 100644 --- a/lib/PsychicHttp/src/PsychicResponse.cpp +++ b/lib/PsychicHttp/src/PsychicResponse.cpp @@ -2,137 +2,154 @@ #include "PsychicRequest.h" #include -PsychicResponse::PsychicResponse(PsychicRequest * request) - : _request(request) - , _code(200) - , _status("") - , _contentLength(0) - , _body("") { +PsychicResponse::PsychicResponse(PsychicRequest *request) : + _request(request), + _code(200), + _status(""), + _contentLength(0), + _body("") +{ } -PsychicResponse::~PsychicResponse() { - //clean up our header variables. we have to do this since httpd_resp_send doesn't store copies - for (HTTPHeader header : _headers) { - free(header.field); - free(header.value); - } - _headers.clear(); +PsychicResponse::~PsychicResponse() +{ + //clean up our header variables. we have to do this since httpd_resp_send doesn't store copies + for (HTTPHeader header : _headers) + { + free(header.field); + free(header.value); + } + _headers.clear(); } -void PsychicResponse::addHeader(const char * field, const char * value) { - //these get freed during send() - HTTPHeader header; - header.field = (char *)malloc(strlen(field) + 1); - header.value = (char *)malloc(strlen(value) + 1); +void PsychicResponse::addHeader(const char *field, const char *value) +{ + //these get freed during send() + HTTPHeader header; + header.field =(char *)malloc(strlen(field)+1); + header.value = (char *)malloc(strlen(value)+1); - strlcpy(header.field, field, strlen(field) + 1); - strlcpy(header.value, value, strlen(value) + 1); + strlcpy(header.field, field, strlen(field)+1); + strlcpy(header.value, value, strlen(value)+1); - _headers.push_back(header); + _headers.push_back(header); } -void PsychicResponse::setCookie(const char * name, const char * value, unsigned long secondsFromNow, const char * extras) { - time_t now = time(nullptr); +void PsychicResponse::setCookie(const char *name, const char *value, unsigned long secondsFromNow, const char *extras) +{ + time_t now = time(nullptr); - String output; - output = urlEncode(name) + "=" + urlEncode(value); + String output; + output = urlEncode(name) + "=" + urlEncode(value); - //if current time isn't modern, default to using max age - if (now < 1700000000) - output += "; Max-Age=" + String(secondsFromNow); - //otherwise, set an expiration date - else { - time_t expirationTimestamp = now + secondsFromNow; + //if current time isn't modern, default to using max age + if (now < 1700000000) + output += "; Max-Age=" + String(secondsFromNow); + //otherwise, set an expiration date + else + { + time_t expirationTimestamp = now + secondsFromNow; - // Convert the expiration timestamp to a formatted string for the "expires" attribute - struct tm * tmInfo = gmtime(&expirationTimestamp); - char expires[30]; - strftime(expires, sizeof(expires), "%a, %d %b %Y %H:%M:%S GMT", tmInfo); - output += "; Expires=" + String(expires); - } + // Convert the expiration timestamp to a formatted string for the "expires" attribute + struct tm* tmInfo = gmtime(&expirationTimestamp); + char expires[30]; + strftime(expires, sizeof(expires), "%a, %d %b %Y %H:%M:%S GMT", tmInfo); + output += "; Expires=" + String(expires); + } - //did we get any extras? - if (strlen(extras)) - output += "; " + String(extras); + //did we get any extras? + if (strlen(extras)) + output += "; " + String(extras); - //okay, add it in. - addHeader("Set-Cookie", output.c_str()); + //okay, add it in. + addHeader("Set-Cookie", output.c_str()); } -// time_t now = time(nullptr); -// // Set the cookie with the "expires" attribute + // time_t now = time(nullptr); + // // Set the cookie with the "expires" attribute -void PsychicResponse::setCode(int code) { - _code = code; +void PsychicResponse::setCode(int code) +{ + _code = code; } -void PsychicResponse::setContentType(const char * contentType) { - httpd_resp_set_type(_request->request(), contentType); +void PsychicResponse::setContentType(const char *contentType) +{ + httpd_resp_set_type(_request->request(), contentType); } -void PsychicResponse::setContent(const char * content) { - _body = content; - setContentLength(strlen(content)); +void PsychicResponse::setContent(const char *content) +{ + _body = content; + setContentLength(strlen(content)); } -void PsychicResponse::setContent(const uint8_t * content, size_t len) { - _body = (char *)content; - setContentLength(len); +void PsychicResponse::setContent(const uint8_t *content, size_t len) +{ + _body = (char *)content; + setContentLength(len); } -const char * PsychicResponse::getContent() { - return _body; +const char * PsychicResponse::getContent() +{ + return _body; } -size_t PsychicResponse::getContentLength() { - return _contentLength; +size_t PsychicResponse::getContentLength() +{ + return _contentLength; } -esp_err_t PsychicResponse::send() { - //esp-idf makes you set the whole status. - sprintf(_status, "%u %s", _code, http_status_reason(_code)); - httpd_resp_set_status(_request->request(), _status); +esp_err_t PsychicResponse::send() +{ + //esp-idf makes you set the whole status. + sprintf(_status, "%u %s", _code, http_status_reason(_code)); + httpd_resp_set_status(_request->request(), _status); - //our headers too - this->sendHeaders(); + //our headers too + this->sendHeaders(); - //now send it off - esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength()); + //now send it off + esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength()); - //did something happen? - if (err != ESP_OK) - ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err)); + //did something happen? + if (err != ESP_OK) + ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err)); - return err; + return err; } -void PsychicResponse::sendHeaders() { - //get our global headers out of the way first - for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) - httpd_resp_set_hdr(_request->request(), header.field, header.value); +void PsychicResponse::sendHeaders() +{ + //get our global headers out of the way first + for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) + httpd_resp_set_hdr(_request->request(), header.field, header.value); - //now do our individual headers - for (HTTPHeader header : _headers) - httpd_resp_set_hdr(this->_request->request(), header.field, header.value); + //now do our individual headers + for (HTTPHeader header : _headers) + httpd_resp_set_hdr(this->_request->request(), header.field, header.value); } -esp_err_t PsychicResponse::sendChunk(uint8_t * chunk, size_t chunksize) { - /* Send the buffer contents as HTTP response chunk */ - esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize); - if (err != ESP_OK) { - ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err)); +esp_err_t PsychicResponse::sendChunk(uint8_t *chunk, size_t chunksize) +{ + /* Send the buffer contents as HTTP response chunk */ + esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize); + if (err != ESP_OK) + { + ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err)); - /* Abort sending file */ - httpd_resp_sendstr_chunk(this->_request->request(), NULL); + /* Abort sending file */ + httpd_resp_sendstr_chunk(this->_request->request(), NULL); - /* Respond with 500 Internal Server Error */ - httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); - } + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); + } - return err; + return err; } -esp_err_t PsychicResponse::finishChunking() { - /* Respond with an empty chunk to signal HTTP response completion */ - return httpd_resp_send_chunk(this->_request->request(), NULL, 0); +esp_err_t PsychicResponse::finishChunking() +{ + /* Respond with an empty chunk to signal HTTP response completion */ + return httpd_resp_send_chunk(this->_request->request(), NULL, 0); } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicResponse.h b/lib/PsychicHttp/src/PsychicResponse.h index 9fd11f420..a36de6577 100644 --- a/lib/PsychicHttp/src/PsychicResponse.h +++ b/lib/PsychicHttp/src/PsychicResponse.h @@ -6,44 +6,41 @@ class PsychicRequest; -class PsychicResponse { +class PsychicResponse +{ protected: - PsychicRequest * _request; + PsychicRequest *_request; - int _code; - char _status[60]; + int _code; + char _status[60]; std::list _headers; - int64_t _contentLength; - const char * _body; + int64_t _contentLength; + const char * _body; public: - PsychicResponse(PsychicRequest * request); + PsychicResponse(PsychicRequest *request); virtual ~PsychicResponse(); void setCode(int code); - void setContentType(const char * contentType); - void setContentLength(int64_t contentLength) { - _contentLength = contentLength; - } - int64_t getContentLength(int64_t contentLength) { - return _contentLength; - } + void setContentType(const char *contentType); + void setContentLength(int64_t contentLength) { _contentLength = contentLength; } + int64_t getContentLength(int64_t contentLength) { return _contentLength; } - void addHeader(const char * field, const char * value); + void addHeader(const char *field, const char *value); - void setCookie(const char * key, const char * value, unsigned long max_age = 60 * 60 * 24 * 30, const char * extras = ""); + void setCookie(const char *key, const char *value, unsigned long max_age = 60*60*24*30, const char *extras = ""); - void setContent(const char * content); - void setContent(const uint8_t * content, size_t len); + void setContent(const char *content); + void setContent(const uint8_t *content, size_t len); const char * getContent(); - size_t getContentLength(); + size_t getContentLength(); virtual esp_err_t send(); - void sendHeaders(); - esp_err_t sendChunk(uint8_t * chunk, size_t chunksize); - esp_err_t finishChunking(); + void sendHeaders(); + esp_err_t sendChunk(uint8_t *chunk, size_t chunksize); + esp_err_t finishChunking(); }; #endif // PsychicResponse_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicStaticFileHander.cpp b/lib/PsychicHttp/src/PsychicStaticFileHander.cpp index 20d6d6b61..e58f3ad88 100644 --- a/lib/PsychicHttp/src/PsychicStaticFileHander.cpp +++ b/lib/PsychicHttp/src/PsychicStaticFileHander.cpp @@ -4,192 +4,190 @@ /* PsychicStaticFileHandler */ /*************************************/ -PsychicStaticFileHandler::PsychicStaticFileHandler(const char * uri, FS & fs, const char * path, const char * cache_control) - : _fs(fs) - , _uri(uri) - , _path(path) - , _default_file("index.html") - , _cache_control(cache_control) - , _last_modified("") { - // Ensure leading '/' - if (_uri.length() == 0 || _uri[0] != '/') - _uri = "/" + _uri; - if (_path.length() == 0 || _path[0] != '/') - _path = "/" + _path; +PsychicStaticFileHandler::PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control) + : _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("") +{ + // Ensure leading '/' + if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; + if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; - // If path ends with '/' we assume a hint that this is a directory to improve performance. - // However - if it does not end with '/' we, can't assume a file, path can still be a directory. - _isDir = _path[_path.length() - 1] == '/'; + // If path ends with '/' we assume a hint that this is a directory to improve performance. + // However - if it does not end with '/' we, can't assume a file, path can still be a directory. + _isDir = _path[_path.length()-1] == '/'; - // Remove the trailing '/' so we can handle default file - // Notice that root will be "" not "/" - if (_uri[_uri.length() - 1] == '/') - _uri = _uri.substring(0, _uri.length() - 1); - if (_path[_path.length() - 1] == '/') - _path = _path.substring(0, _path.length() - 1); + // Remove the trailing '/' so we can handle default file + // Notice that root will be "" not "/" + if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); + if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); - // Reset stats - _gzipFirst = false; - _gzipStats = 0xF8; + // Reset stats + _gzipFirst = false; + _gzipStats = 0xF8; } -PsychicStaticFileHandler & PsychicStaticFileHandler::setIsDir(bool isDir) { - _isDir = isDir; - return *this; +PsychicStaticFileHandler& PsychicStaticFileHandler::setIsDir(bool isDir){ + _isDir = isDir; + return *this; } -PsychicStaticFileHandler & PsychicStaticFileHandler::setDefaultFile(const char * filename) { - _default_file = String(filename); - return *this; +PsychicStaticFileHandler& PsychicStaticFileHandler::setDefaultFile(const char* filename){ + _default_file = String(filename); + return *this; } -PsychicStaticFileHandler & PsychicStaticFileHandler::setCacheControl(const char * cache_control) { - _cache_control = String(cache_control); - return *this; +PsychicStaticFileHandler& PsychicStaticFileHandler::setCacheControl(const char* cache_control){ + _cache_control = String(cache_control); + return *this; } -PsychicStaticFileHandler & PsychicStaticFileHandler::setLastModified(const char * last_modified) { - _last_modified = String(last_modified); - return *this; +PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(const char* last_modified){ + _last_modified = String(last_modified); + return *this; } -PsychicStaticFileHandler & PsychicStaticFileHandler::setLastModified(struct tm * last_modified) { - char result[30]; - strftime(result, 30, "%a, %d %b %Y %H:%M:%S %Z", last_modified); - return setLastModified((const char *)result); +PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(struct tm* last_modified){ + char result[30]; + strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified); + return setLastModified((const char *)result); } -bool PsychicStaticFileHandler::canHandle(PsychicRequest * request) { - if (request->method() != HTTP_GET || !request->uri().startsWith(_uri)) - return false; - - if (_getFile(request)) - return true; - +bool PsychicStaticFileHandler::canHandle(PsychicRequest *request) +{ + if(request->method() != HTTP_GET || !request->uri().startsWith(_uri) ) return false; + + if (_getFile(request)) + return true; + + return false; } -bool PsychicStaticFileHandler::_getFile(PsychicRequest * request) { - // Remove the found uri - String path = request->uri().substring(_uri.length()); +bool PsychicStaticFileHandler::_getFile(PsychicRequest *request) +{ + // Remove the found uri + String path = request->uri().substring(_uri.length()); - // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' - bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/'); + // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' + bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); - path = _path + path; + path = _path + path; - // Do we have a file or .gz file - if (!canSkipFileCheck && _fileExists(path)) - return true; + // Do we have a file or .gz file + if (!canSkipFileCheck && _fileExists(path)) + return true; - // Can't handle if not default file - if (_default_file.length() == 0) - return false; + // Can't handle if not default file + if (_default_file.length() == 0) + return false; - // Try to add default file, ensure there is a trailing '/' ot the path. - if (path.length() == 0 || path[path.length() - 1] != '/') - path += "/"; - path += _default_file; + // Try to add default file, ensure there is a trailing '/' ot the path. + if (path.length() == 0 || path[path.length()-1] != '/') + path += "/"; + path += _default_file; - return _fileExists(path); + return _fileExists(path); } #define FILE_IS_REAL(f) (f == true && !f.isDirectory()) -bool PsychicStaticFileHandler::_fileExists(const String & path) { - bool fileFound = false; - bool gzipFound = false; +bool PsychicStaticFileHandler::_fileExists(const String& path) +{ + bool fileFound = false; + bool gzipFound = false; - String gzip = path + ".gz"; + String gzip = path + ".gz"; - if (_gzipFirst) { - _file = _fs.open(gzip, "r"); - gzipFound = FILE_IS_REAL(_file); - if (!gzipFound) { - _file = _fs.open(path, "r"); - fileFound = FILE_IS_REAL(_file); - } - } else { - _file = _fs.open(path, "r"); - fileFound = FILE_IS_REAL(_file); - if (!fileFound) { - _file = _fs.open(gzip, "r"); - gzipFound = FILE_IS_REAL(_file); - } + if (_gzipFirst) { + _file = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(_file); + if (!gzipFound){ + _file = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(_file); } - - bool found = fileFound || gzipFound; - - if (found) { - _filename = path; - - // Calculate gzip statistic - _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); - if (_gzipStats == 0x00) - _gzipFirst = false; // All files are not gzip - else if (_gzipStats == 0xFF) - _gzipFirst = true; // All files are gzip - else - _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first + } else { + _file = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(_file); + if (!fileFound){ + _file = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(_file); } + } - return found; + bool found = fileFound || gzipFound; + + if (found) + { + _filename = path; + + // Calculate gzip statistic + _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); + if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip + else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip + else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first + } + + return found; } -uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const { - uint8_t w = value; - uint8_t n; - for (n = 0; w != 0; n++) - w &= w - 1; - return n; +uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const +{ + uint8_t w = value; + uint8_t n; + for (n=0; w!=0; n++) w&=w-1; + return n; } -esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest * request) { - if (_file == true) { - DUMP(_filename); +esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request) +{ + if (_file == true) + { + DUMP(_filename); - //is it not modified? - String etag = String(_file.size()); - if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { - DUMP("Last Modified Hit"); - TRACE(); - _file.close(); - request->reply(304); // Not modified - } - //does our Etag match? - else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) { - DUMP("Etag Hit"); - DUMP(etag); - DUMP(_cache_control); - - _file.close(); - - PsychicResponse response(request); - response.addHeader("Cache-Control", _cache_control.c_str()); - response.addHeader("ETag", etag.c_str()); - response.setCode(304); - response.send(); - } - //nope, send them the full file. - else { - DUMP("No cache hit"); - DUMP(_last_modified); - DUMP(_cache_control); - - PsychicFileResponse response(request, _fs, _filename); - - if (_last_modified.length()) - response.addHeader("Last-Modified", _last_modified.c_str()); - if (_cache_control.length()) { - response.addHeader("Cache-Control", _cache_control.c_str()); - response.addHeader("ETag", etag.c_str()); - } - - return response.send(); - } - } else { - return request->reply(404); + //is it not modified? + String etag = String(_file.size()); + if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) + { + DUMP("Last Modified Hit"); + TRACE(); + _file.close(); + request->reply(304); // Not modified } + //does our Etag match? + else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) + { + DUMP("Etag Hit"); + DUMP(etag); + DUMP(_cache_control); - return ESP_OK; + _file.close(); + + PsychicResponse response(request); + response.addHeader("Cache-Control", _cache_control.c_str()); + response.addHeader("ETag", etag.c_str()); + response.setCode(304); + response.send(); + } + //nope, send them the full file. + else + { + DUMP("No cache hit"); + DUMP(_last_modified); + DUMP(_cache_control); + + PsychicFileResponse response(request, _fs, _filename); + + if (_last_modified.length()) + response.addHeader("Last-Modified", _last_modified.c_str()); + if (_cache_control.length()) { + response.addHeader("Cache-Control", _cache_control.c_str()); + response.addHeader("ETag", etag.c_str()); + } + + return response.send(); + } + } else { + return request->reply(404); + } + + return ESP_OK; } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicStaticFileHandler.h b/lib/PsychicHttp/src/PsychicStaticFileHandler.h index 126b9beb7..f7571113f 100644 --- a/lib/PsychicHttp/src/PsychicStaticFileHandler.h +++ b/lib/PsychicHttp/src/PsychicStaticFileHandler.h @@ -8,36 +8,33 @@ #include "PsychicFileResponse.h" class PsychicStaticFileHandler : public PsychicWebHandler { - using File = fs::File; - using FS = fs::FS; - + using File = fs::File; + using FS = fs::FS; private: - bool _getFile(PsychicRequest * request); - bool _fileExists(const String & path); + bool _getFile(PsychicRequest *request); + bool _fileExists(const String& path); uint8_t _countBits(const uint8_t value) const; - protected: - FS _fs; - File _file; - String _filename; - String _uri; - String _path; - String _default_file; - String _cache_control; - String _last_modified; - bool _isDir; - bool _gzipFirst; + FS _fs; + File _file; + String _filename; + String _uri; + String _path; + String _default_file; + String _cache_control; + String _last_modified; + bool _isDir; + bool _gzipFirst; uint8_t _gzipStats; - public: - PsychicStaticFileHandler(const char * uri, FS & fs, const char * path, const char * cache_control); - bool canHandle(PsychicRequest * request) override; - esp_err_t handleRequest(PsychicRequest * request) override; - PsychicStaticFileHandler & setIsDir(bool isDir); - PsychicStaticFileHandler & setDefaultFile(const char * filename); - PsychicStaticFileHandler & setCacheControl(const char * cache_control); - PsychicStaticFileHandler & setLastModified(const char * last_modified); - PsychicStaticFileHandler & setLastModified(struct tm * last_modified); + PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control); + bool canHandle(PsychicRequest *request) override; + esp_err_t handleRequest(PsychicRequest *request) override; + PsychicStaticFileHandler& setIsDir(bool isDir); + PsychicStaticFileHandler& setDefaultFile(const char* filename); + PsychicStaticFileHandler& setCacheControl(const char* cache_control); + PsychicStaticFileHandler& setLastModified(const char* last_modified); + PsychicStaticFileHandler& setLastModified(struct tm* last_modified); //PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} }; diff --git a/lib/PsychicHttp/src/PsychicStreamResponse.cpp b/lib/PsychicHttp/src/PsychicStreamResponse.cpp new file mode 100644 index 000000000..4b369b69f --- /dev/null +++ b/lib/PsychicHttp/src/PsychicStreamResponse.cpp @@ -0,0 +1,89 @@ +#include "PsychicStreamResponse.h" +#include "PsychicResponse.h" +#include "PsychicRequest.h" + +PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType) + : PsychicResponse(request), _buffer(NULL) { + + setContentType(contentType.c_str()); + addHeader("Content-Disposition", "inline"); +} + +PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name) + : PsychicResponse(request), _buffer(NULL) { + + setContentType(contentType.c_str()); + + char buf[26+name.length()]; + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", name); + addHeader("Content-Disposition", buf); +} + +PsychicStreamResponse::~PsychicStreamResponse() +{ + endSend(); +} + +esp_err_t PsychicStreamResponse::beginSend() +{ + if(_buffer) + return ESP_OK; + + //Buffer to hold ChunkPrinter and stream buffer. Using placement new will keep us at a single allocation. + _buffer = (uint8_t*)malloc(STREAM_CHUNK_SIZE + sizeof(ChunkPrinter)); + + if(!_buffer) + { + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + _printer = new (_buffer) ChunkPrinter(this, _buffer + sizeof(ChunkPrinter), STREAM_CHUNK_SIZE); + + sendHeaders(); + return ESP_OK; +} + +esp_err_t PsychicStreamResponse::endSend() +{ + esp_err_t err = ESP_OK; + + if(!_buffer) + err = ESP_FAIL; + else + { + //flush & send remaining data. + _printer->flush(); + err = finishChunking(); + + //Free memory + _printer->~ChunkPrinter(); + free(_buffer); + _buffer = NULL; + } + return err; +} + +void PsychicStreamResponse::flush() +{ + if(_buffer) + _printer->flush(); +} + +size_t PsychicStreamResponse::write(uint8_t data) +{ + return _buffer ? _printer->write(data) : 0; +} + +size_t PsychicStreamResponse::copyFrom(Stream &stream) +{ + size_t sentCount = 0; + + if(_buffer) + { + while(stream.available()) + sentCount += _printer->write(stream.read()); + } + return sentCount; +} diff --git a/lib/PsychicHttp/src/PsychicStreamResponse.h b/lib/PsychicHttp/src/PsychicStreamResponse.h new file mode 100644 index 000000000..25339e9d6 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicStreamResponse.h @@ -0,0 +1,33 @@ +#ifndef PsychicStreamResponse_h +#define PsychicStreamResponse_h + +#include "PsychicCore.h" +#include "PsychicResponse.h" +#include "ChunkPrinter.h" + +class PsychicRequest; + +class PsychicStreamResponse : public PsychicResponse, public Print +{ + private: + ChunkPrinter *_printer; + uint8_t *_buffer; + public: + + PsychicStreamResponse(PsychicRequest *request, const String& contentType); + PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name); //Download + + ~PsychicStreamResponse(); + + esp_err_t beginSend(); + esp_err_t endSend(); + + virtual void flush() override; + + size_t write(uint8_t data); + size_t copyFrom(Stream &stream); + + using Print::write; +}; + +#endif // PsychicStreamResponse_h diff --git a/lib/PsychicHttp/src/PsychicUploadHandler.h b/lib/PsychicHttp/src/PsychicUploadHandler.h index df8497334..59e62b6a4 100644 --- a/lib/PsychicHttp/src/PsychicUploadHandler.h +++ b/lib/PsychicHttp/src/PsychicUploadHandler.h @@ -8,7 +8,7 @@ #include "PsychicWebParameter.h" //callback definitions -typedef std::function PsychicUploadCallback; +typedef std::function PsychicUploadCallback; /* * HANDLER :: Can be attached to any endpoint or as a generic request handler. @@ -18,25 +18,25 @@ class PsychicUploadHandler : public PsychicWebHandler { protected: PsychicUploadCallback _uploadCallback; - PsychicRequest * _request; + PsychicRequest *_request; - String _temp; - size_t _parsedLength; - uint8_t _multiParseState; - String _boundary; - uint8_t _boundaryPosition; - size_t _itemStartIndex; - size_t _itemSize; - String _itemName; - String _itemFilename; - String _itemType; - String _itemValue; - uint8_t * _itemBuffer; - size_t _itemBufferIndex; - bool _itemIsFile; + String _temp; + size_t _parsedLength; + uint8_t _multiParseState; + String _boundary; + uint8_t _boundaryPosition; + size_t _itemStartIndex; + size_t _itemSize; + String _itemName; + String _itemFilename; + String _itemType; + String _itemValue; + uint8_t *_itemBuffer; + size_t _itemBufferIndex; + bool _itemIsFile; - esp_err_t _basicUploadHandler(PsychicRequest * request); - esp_err_t _multipartUploadHandler(PsychicRequest * request); + esp_err_t _basicUploadHandler(PsychicRequest *request); + esp_err_t _multipartUploadHandler(PsychicRequest *request); void _handleUploadByte(uint8_t data, bool last); void _parseMultipartPostByte(uint8_t data, bool last); @@ -45,24 +45,24 @@ class PsychicUploadHandler : public PsychicWebHandler { PsychicUploadHandler(); ~PsychicUploadHandler(); - bool canHandle(PsychicRequest * request) override; - esp_err_t handleRequest(PsychicRequest * request) override; + bool canHandle(PsychicRequest *request) override; + esp_err_t handleRequest(PsychicRequest *request) override; PsychicUploadHandler * onUpload(PsychicUploadCallback fn); }; enum { - EXPECT_BOUNDARY, - PARSE_HEADERS, - WAIT_FOR_RETURN1, - EXPECT_FEED1, - EXPECT_DASH1, - EXPECT_DASH2, - BOUNDARY_OR_DATA, - DASH3_OR_RETURN2, - EXPECT_FEED2, - PARSING_FINISHED, - PARSE_ERROR + EXPECT_BOUNDARY, + PARSE_HEADERS, + WAIT_FOR_RETURN1, + EXPECT_FEED1, + EXPECT_DASH1, + EXPECT_DASH2, + BOUNDARY_OR_DATA, + DASH3_OR_RETURN2, + EXPECT_FEED2, + PARSING_FINISHED, + PARSE_ERROR }; #endif // PsychicUploadHandler_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebHandler.cpp b/lib/PsychicHttp/src/PsychicWebHandler.cpp index dbe8ee051..d1b79168c 100644 --- a/lib/PsychicHttp/src/PsychicWebHandler.cpp +++ b/lib/PsychicHttp/src/PsychicWebHandler.cpp @@ -1,46 +1,74 @@ #include "PsychicWebHandler.h" -PsychicWebHandler::PsychicWebHandler() - : PsychicHandler() - , _requestCallback(NULL) { -} -PsychicWebHandler::~PsychicWebHandler() { +PsychicWebHandler::PsychicWebHandler() : + PsychicHandler(), + _requestCallback(NULL), + _onOpen(NULL), + _onClose(NULL) + {} +PsychicWebHandler::~PsychicWebHandler() {} + +bool PsychicWebHandler::canHandle(PsychicRequest *request) { + return true; } -bool PsychicWebHandler::canHandle(PsychicRequest * request) { - return true; -} +esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request) +{ + //lookup our client + PsychicClient *client = checkForNewClient(request->client()); + if (client->isNew) + openCallback(client); -esp_err_t PsychicWebHandler::handleRequest(PsychicRequest * request) { - /* Request body cannot be larger than a limit */ - if (request->contentLength() > request->server()->maxRequestBodySize) { - ESP_LOGE(PH_TAG, "Request body too large : %d bytes", request->contentLength()); + /* Request body cannot be larger than a limit */ + if (request->contentLength() > request->server()->maxRequestBodySize) + { + ESP_LOGE(PH_TAG, "Request body too large : %d bytes", request->contentLength()); - /* Respond with 400 Bad Request */ - char error[60]; - sprintf(error, "Request body must be less than %u bytes!", request->server()->maxRequestBodySize); - httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); + /* Respond with 400 Bad Request */ + char error[60]; + sprintf(error, "Request body must be less than %u bytes!", request->server()->maxRequestBodySize); + httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); - /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ - return ESP_FAIL; - } - - //get our body loaded up. - esp_err_t err = request->loadBody(); - if (err != ESP_OK) - return err; - - //load our params in. - request->loadParams(); - - //okay, pass on to our callback. - if (this->_requestCallback != NULL) - err = this->_requestCallback(request); + /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ + return ESP_FAIL; + } + //get our body loaded up. + esp_err_t err = request->loadBody(); + if (err != ESP_OK) return err; + + //load our params in. + request->loadParams(); + + //okay, pass on to our callback. + if (this->_requestCallback != NULL) + err = this->_requestCallback(request); + + return err; } PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) { - _requestCallback = fn; - return this; + _requestCallback = fn; + return this; +} + +void PsychicWebHandler::openCallback(PsychicClient *client) { + if (_onOpen != NULL) + _onOpen(client); +} + +void PsychicWebHandler::closeCallback(PsychicClient *client) { + if (_onClose != NULL) + _onClose(getClient(client)); +} + +PsychicWebHandler * PsychicWebHandler::onOpen(PsychicClientCallback fn) { + _onOpen = fn; + return this; +} + +PsychicWebHandler * PsychicWebHandler::onClose(PsychicClientCallback fn) { + _onClose = fn; + return this; } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebHandler.h b/lib/PsychicHttp/src/PsychicWebHandler.h index 5562dfd10..07a6780a6 100644 --- a/lib/PsychicHttp/src/PsychicWebHandler.h +++ b/lib/PsychicHttp/src/PsychicWebHandler.h @@ -1,9 +1,9 @@ #ifndef PsychicWebHandler_h #define PsychicWebHandler_h -#include "PsychicCore.h" -#include "PsychicHttpServer.h" -#include "PsychicRequest.h" +// #include "PsychicCore.h" +// #include "PsychicHttpServer.h" +// #include "PsychicRequest.h" #include "PsychicHandler.h" /* @@ -13,14 +13,22 @@ class PsychicWebHandler : public PsychicHandler { protected: PsychicHttpRequestCallback _requestCallback; + PsychicClientCallback _onOpen; + PsychicClientCallback _onClose; public: PsychicWebHandler(); ~PsychicWebHandler(); - virtual bool canHandle(PsychicRequest * request) override; - virtual esp_err_t handleRequest(PsychicRequest * request) override; + virtual bool canHandle(PsychicRequest *request) override; + virtual esp_err_t handleRequest(PsychicRequest *request) override; PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn); + + virtual void openCallback(PsychicClient *client); + virtual void closeCallback(PsychicClient *client); + + PsychicWebHandler *onOpen(PsychicClientCallback fn); + PsychicWebHandler *onClose(PsychicClientCallback fn); }; #endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebParameter.h b/lib/PsychicHttp/src/PsychicWebParameter.h index fe6efc7e0..e09136b1e 100644 --- a/lib/PsychicHttp/src/PsychicWebParameter.h +++ b/lib/PsychicHttp/src/PsychicWebParameter.h @@ -10,32 +10,16 @@ class PsychicWebParameter { String _name; String _value; size_t _size; - bool _isForm; - bool _isFile; + bool _isForm; + bool _isFile; public: - PsychicWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0) - : _name(name) - , _value(value) - , _size(size) - , _isForm(form) - , _isFile(file) { - } - const String & name() const { - return _name; - } - const String & value() const { - return _value; - } - size_t size() const { - return _size; - } - bool isPost() const { - return _isForm; - } - bool isFile() const { - return _isFile; - } + PsychicWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} + const String& name() const { return _name; } + const String& value() const { return _value; } + size_t size() const { return _size; } + bool isPost() const { return _isForm; } + bool isFile() const { return _isFile; } }; #endif //PsychicWebParameter_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebSocket.cpp b/lib/PsychicHttp/src/PsychicWebSocket.cpp index 24c99ca73..45f004b90 100644 --- a/lib/PsychicHttp/src/PsychicWebSocket.cpp +++ b/lib/PsychicHttp/src/PsychicWebSocket.cpp @@ -4,240 +4,257 @@ /* PsychicWebSocketRequest */ /*************************************/ -PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest * req) - : PsychicRequest(req->server(), req->request()) - , _client(req->client()) { +PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest *req) : + PsychicRequest(req->server(), req->request()), + _client(req->client()) +{ } -PsychicWebSocketRequest::~PsychicWebSocketRequest() { +PsychicWebSocketRequest::~PsychicWebSocketRequest() +{ } PsychicWebSocketClient * PsychicWebSocketRequest::client() { - return &_client; + return &_client; } -esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t * ws_pkt) { - return httpd_ws_send_frame(this->_req, ws_pkt); +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t * ws_pkt) +{ + return httpd_ws_send_frame(this->_req, ws_pkt); +} + +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void *data, size_t len) +{ + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t*)data; + ws_pkt.len = len; + ws_pkt.type = op; + + return this->reply(&ws_pkt); } -esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void * data, size_t len) { - httpd_ws_frame_t ws_pkt; - memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); - - ws_pkt.payload = (uint8_t *)data; - ws_pkt.len = len; - ws_pkt.type = op; - - return this->reply(&ws_pkt); -} - -esp_err_t PsychicWebSocketRequest::reply(const char * buf) { - return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +esp_err_t PsychicWebSocketRequest::reply(const char *buf) +{ + return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); } /*************************************/ /* PsychicWebSocketClient */ /*************************************/ -PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient * client) - : PsychicClient(client->server(), client->socket()) { +PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient *client) + : PsychicClient(client->server(), client->socket()) +{ } PsychicWebSocketClient::~PsychicWebSocketClient() { } -esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt) { - return httpd_ws_send_frame_async(this->server(), this->socket(), ws_pkt); +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt) +{ + return httpd_ws_send_frame_async(this->server(), this->socket(), ws_pkt); +} + +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void *data, size_t len) +{ + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t*)data; + ws_pkt.len = len; + ws_pkt.type = op; + + return this->sendMessage(&ws_pkt); } -esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void * data, size_t len) { - httpd_ws_frame_t ws_pkt; - memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); - - ws_pkt.payload = (uint8_t *)data; - ws_pkt.len = len; - ws_pkt.type = op; - - return this->sendMessage(&ws_pkt); +esp_err_t PsychicWebSocketClient::sendMessage(const char *buf) +{ + return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); } -esp_err_t PsychicWebSocketClient::sendMessage(const char * buf) { - return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); -} - -PsychicWebSocketHandler::PsychicWebSocketHandler() - : PsychicHandler() - , _onOpen(NULL) - , _onFrame(NULL) - , _onClose(NULL) { -} +PsychicWebSocketHandler::PsychicWebSocketHandler() : + PsychicHandler(), + _onOpen(NULL), + _onFrame(NULL), + _onClose(NULL) + { + } PsychicWebSocketHandler::~PsychicWebSocketHandler() { } -PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket) { - PsychicClient * client = PsychicHandler::getClient(socket); - if (client == NULL) - return NULL; +PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket) +{ + PsychicClient *client = PsychicHandler::getClient(socket); + if (client == NULL) + return NULL; - if (client->_friend == NULL) { - DUMP(socket); - return NULL; - } + if (client->_friend == NULL) + { + DUMP(socket); + return NULL; + } - return (PsychicWebSocketClient *)client->_friend; + return (PsychicWebSocketClient *)client->_friend; } -PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClient * client) { - return getClient(client->socket()); +PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClient *client) { + return getClient(client->socket()); } -void PsychicWebSocketHandler::addClient(PsychicClient * client) { - client->_friend = new PsychicWebSocketClient(client); - PsychicHandler::addClient(client); +void PsychicWebSocketHandler::addClient(PsychicClient *client) { + client->_friend = new PsychicWebSocketClient(client); + PsychicHandler::addClient(client); } -void PsychicWebSocketHandler::removeClient(PsychicClient * client) { - PsychicHandler::removeClient(client); - delete (PsychicWebSocketClient *)client->_friend; - client->_friend = NULL; +void PsychicWebSocketHandler::removeClient(PsychicClient *client) { + PsychicHandler::removeClient(client); + delete (PsychicWebSocketClient*)client->_friend; + client->_friend = NULL; } -void PsychicWebSocketHandler::openCallback(PsychicClient * client) { - PsychicWebSocketClient * buddy = getClient(client); - if (buddy == NULL) { - TRACE(); - return; - } +void PsychicWebSocketHandler::openCallback(PsychicClient *client) { + PsychicWebSocketClient *buddy = getClient(client); + if (buddy == NULL) + { + TRACE(); + return; + } - if (_onOpen != NULL) - _onOpen(getClient(buddy)); + if (_onOpen != NULL) + _onOpen(getClient(buddy)); } -void PsychicWebSocketHandler::closeCallback(PsychicClient * client) { - PsychicWebSocketClient * buddy = getClient(client); - if (buddy == NULL) { - TRACE(); - return; - } +void PsychicWebSocketHandler::closeCallback(PsychicClient *client) { + PsychicWebSocketClient *buddy = getClient(client); + if (buddy == NULL) + { + TRACE(); + return; + } - if (_onClose != NULL) - _onClose(getClient(buddy)); + if (_onClose != NULL) + _onClose(getClient(buddy)); } -bool PsychicWebSocketHandler::isWebSocket() { - return true; -} +bool PsychicWebSocketHandler::isWebSocket() { return true; } -esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest * request) { - //lookup our client - PsychicClient * client = checkForNewClient(request->client()); +esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request) +{ + //lookup our client + PsychicClient *client = checkForNewClient(request->client()); - // beginning of the ws URI handler and our onConnect hook - if (request->method() == HTTP_GET) { - if (client->isNew) - openCallback(client); + // beginning of the ws URI handler and our onConnect hook + if (request->method() == HTTP_GET) + { + if (client->isNew) + openCallback(client); - return ESP_OK; - } + return ESP_OK; + } - //prep our request - PsychicWebSocketRequest wsRequest(request); + //prep our request + PsychicWebSocketRequest wsRequest(request); - //init our memory for storing the packet - httpd_ws_frame_t ws_pkt; - memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); - ws_pkt.type = HTTPD_WS_TYPE_TEXT; - uint8_t * buf = NULL; - - /* Set max_len = 0 to get the frame len */ - esp_err_t ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, 0); - if (ret != ESP_OK) { - ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed to get frame len with %s", esp_err_to_name(ret)); - return ret; - } - - //okay, now try to load the packet - ESP_LOGI(PH_TAG, "frame len is %d", ws_pkt.len); - if (ws_pkt.len) { - /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ - buf = (uint8_t *)calloc(1, ws_pkt.len + 1); - if (buf == NULL) { - ESP_LOGE(PH_TAG, "Failed to calloc memory for buf"); - return ESP_ERR_NO_MEM; - } - ws_pkt.payload = buf; - /* Set max_len = ws_pkt.len to get the frame payload */ - ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, ws_pkt.len); - if (ret != ESP_OK) { - ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed with %s", esp_err_to_name(ret)); - free(buf); - return ret; - } - ESP_LOGI(PH_TAG, "Got packet with message: %s", ws_pkt.payload); - } - - // Text messages are our payload. - if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) { - if (this->_onFrame != NULL) - ret = this->_onFrame(&wsRequest, &ws_pkt); - } - - //logging housekeeping - if (ret != ESP_OK) - ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret)); - ESP_LOGI(PH_TAG, - "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", - request->server(), - httpd_req_to_sockfd(request->request()), - httpd_ws_get_fd_info(request->server(), httpd_req_to_sockfd(request->request()))); - - //dont forget to release our buffer memory - free(buf); + //init our memory for storing the packet + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + uint8_t *buf = NULL; + /* Set max_len = 0 to get the frame len */ + esp_err_t ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, 0); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed to get frame len with %s", esp_err_to_name(ret)); return ret; + } + + //okay, now try to load the packet + ESP_LOGI(PH_TAG, "frame len is %d", ws_pkt.len); + if (ws_pkt.len) { + /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ + buf = (uint8_t*) calloc(1, ws_pkt.len + 1); + if (buf == NULL) { + ESP_LOGE(PH_TAG, "Failed to calloc memory for buf"); + return ESP_ERR_NO_MEM; + } + ws_pkt.payload = buf; + /* Set max_len = ws_pkt.len to get the frame payload */ + ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, ws_pkt.len); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed with %s", esp_err_to_name(ret)); + free(buf); + return ret; + } + ESP_LOGI(PH_TAG, "Got packet with message: %s", ws_pkt.payload); + } + + // Text messages are our payload. + if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) + { + if (this->_onFrame != NULL) + ret = this->_onFrame(&wsRequest, &ws_pkt); + } + + //logging housekeeping + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret)); + ESP_LOGI(PH_TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", request->server(), + httpd_req_to_sockfd(request->request()), httpd_ws_get_fd_info(request->server(), httpd_req_to_sockfd(request->request()))); + + //dont forget to release our buffer memory + free(buf); + + return ret; } PsychicWebSocketHandler * PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn) { - _onOpen = fn; - return this; + _onOpen = fn; + return this; } PsychicWebSocketHandler * PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn) { - _onFrame = fn; - return this; + _onFrame = fn; + return this; } PsychicWebSocketHandler * PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn) { - _onClose = fn; - return this; + _onClose = fn; + return this; } -void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt) { - for (PsychicClient * client : _clients) { - ESP_LOGI(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket()); +void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt) +{ + for (PsychicClient *client : _clients) + { + ESP_LOGI(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket()); - if (client->_friend == NULL) { - TRACE(); - return; - } - - if (((PsychicWebSocketClient *)client->_friend)->sendMessage(ws_pkt) != ESP_OK) - break; + if (client->_friend == NULL) + { + TRACE(); + return; } + + if (((PsychicWebSocketClient*)client->_friend)->sendMessage(ws_pkt) != ESP_OK) + break; + } } -void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void * data, size_t len) { - httpd_ws_frame_t ws_pkt; - memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); +void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void *data, size_t len) +{ + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); - ws_pkt.payload = (uint8_t *)data; - ws_pkt.len = len; - ws_pkt.type = op; + ws_pkt.payload = (uint8_t*)data; + ws_pkt.len = len; + ws_pkt.type = op; - this->sendAll(&ws_pkt); + this->sendAll(&ws_pkt); } -void PsychicWebSocketHandler::sendAll(const char * buf) { - this->sendAll(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +void PsychicWebSocketHandler::sendAll(const char *buf) +{ + this->sendAll(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebSocket.h b/lib/PsychicHttp/src/PsychicWebSocket.h index bea702197..01917de25 100644 --- a/lib/PsychicHttp/src/PsychicWebSocket.h +++ b/lib/PsychicHttp/src/PsychicWebSocket.h @@ -8,38 +8,40 @@ class PsychicWebSocketRequest; class PsychicWebSocketClient; //callback function definitions -typedef std::function PsychicWebSocketClientCallback; -typedef std::function PsychicWebSocketFrameCallback; +typedef std::function PsychicWebSocketClientCallback; +typedef std::function PsychicWebSocketFrameCallback; -class PsychicWebSocketClient : public PsychicClient { +class PsychicWebSocketClient : public PsychicClient +{ public: - PsychicWebSocketClient(PsychicClient * client); + PsychicWebSocketClient(PsychicClient *client); ~PsychicWebSocketClient(); - + esp_err_t sendMessage(httpd_ws_frame_t * ws_pkt); - esp_err_t sendMessage(httpd_ws_type_t op, const void * data, size_t len); - esp_err_t sendMessage(const char * buf); + esp_err_t sendMessage(httpd_ws_type_t op, const void *data, size_t len); + esp_err_t sendMessage(const char *buf); }; -class PsychicWebSocketRequest : public PsychicRequest { +class PsychicWebSocketRequest : public PsychicRequest +{ private: PsychicWebSocketClient _client; public: - PsychicWebSocketRequest(PsychicRequest * req); + PsychicWebSocketRequest(PsychicRequest *req); virtual ~PsychicWebSocketRequest(); PsychicWebSocketClient * client() override; esp_err_t reply(httpd_ws_frame_t * ws_pkt); - esp_err_t reply(httpd_ws_type_t op, const void * data, size_t len); - esp_err_t reply(const char * buf); + esp_err_t reply(httpd_ws_type_t op, const void *data, size_t len); + esp_err_t reply(const char *buf); }; class PsychicWebSocketHandler : public PsychicHandler { protected: PsychicWebSocketClientCallback _onOpen; - PsychicWebSocketFrameCallback _onFrame; + PsychicWebSocketFrameCallback _onFrame; PsychicWebSocketClientCallback _onClose; public: @@ -47,22 +49,22 @@ class PsychicWebSocketHandler : public PsychicHandler { ~PsychicWebSocketHandler(); PsychicWebSocketClient * getClient(int socket) override; - PsychicWebSocketClient * getClient(PsychicClient * client) override; - void addClient(PsychicClient * client) override; - void removeClient(PsychicClient * client) override; - void openCallback(PsychicClient * client) override; - void closeCallback(PsychicClient * client) override; + PsychicWebSocketClient * getClient(PsychicClient *client) override; + void addClient(PsychicClient *client) override; + void removeClient(PsychicClient *client) override; + void openCallback(PsychicClient *client) override; + void closeCallback(PsychicClient *client) override; - bool isWebSocket() override final; - esp_err_t handleRequest(PsychicRequest * request) override; + bool isWebSocket() override final; + esp_err_t handleRequest(PsychicRequest *request) override; - PsychicWebSocketHandler * onOpen(PsychicWebSocketClientCallback fn); - PsychicWebSocketHandler * onFrame(PsychicWebSocketFrameCallback fn); - PsychicWebSocketHandler * onClose(PsychicWebSocketClientCallback fn); + PsychicWebSocketHandler *onOpen(PsychicWebSocketClientCallback fn); + PsychicWebSocketHandler *onFrame(PsychicWebSocketFrameCallback fn); + PsychicWebSocketHandler *onClose(PsychicWebSocketClientCallback fn); void sendAll(httpd_ws_frame_t * ws_pkt); - void sendAll(httpd_ws_type_t op, const void * data, size_t len); - void sendAll(const char * buf); + void sendAll(httpd_ws_type_t op, const void *data, size_t len); + void sendAll(const char *buf); }; #endif // PsychicWebSocket_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/http_status.cpp b/lib/PsychicHttp/src/http_status.cpp index ae4b0ee5a..64e0c0708 100644 --- a/lib/PsychicHttp/src/http_status.cpp +++ b/lib/PsychicHttp/src/http_status.cpp @@ -1,30 +1,37 @@ #include "http_status.h" -bool http_informational(int code) { +bool http_informational(int code) +{ return code >= 100 && code < 200; } -bool http_success(int code) { +bool http_success(int code) +{ return code >= 200 && code < 300; } -bool http_redirection(int code) { +bool http_redirection(int code) +{ return code >= 300 && code < 400; } -bool http_client_error(int code) { +bool http_client_error(int code) +{ return code >= 400 && code < 500; } -bool http_server_error(int code) { +bool http_server_error(int code) +{ return code >= 500 && code < 600; } -bool http_failure(int code) { +bool http_failure(int code) +{ return code >= 400 && code < 600; } -const char * http_status_group(int code) { +const char *http_status_group(int code) +{ if (http_informational(code)) return "Informational"; @@ -43,8 +50,10 @@ const char * http_status_group(int code) { return "Unknown"; } -const char * http_status_reason(int code) { - switch (code) { +const char *http_status_reason(int code) +{ + switch (code) + { /*####### 1xx - Informational #######*/ case 100: return "Continue"; diff --git a/lib/PsychicHttp/src/http_status.h b/lib/PsychicHttp/src/http_status.h index 12ad33281..e03b7357d 100644 --- a/lib/PsychicHttp/src/http_status.h +++ b/lib/PsychicHttp/src/http_status.h @@ -3,13 +3,13 @@ #include -bool http_informational(int code); -bool http_success(int code); -bool http_redirection(int code); -bool http_client_error(int code); -bool http_server_error(int code); -bool http_failure(int code); -const char * http_status_group(int code); -const char * http_status_reason(int code); +bool http_informational(int code); +bool http_success(int code); +bool http_redirection(int code); +bool http_client_error(int code); +bool http_server_error(int code); +bool http_failure(int code); +const char *http_status_group(int code); +const char *http_status_reason(int code); #endif // MICRO_HTTP_STATUS_H \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/ChunkPrinter.h b/lib/PsychicHttp/src_old/ChunkPrinter.h new file mode 100644 index 000000000..f453e43b9 --- /dev/null +++ b/lib/PsychicHttp/src_old/ChunkPrinter.h @@ -0,0 +1,52 @@ +#ifndef ChunkPrinter_h +#define ChunkPrinter_h + +#include "PsychicCore.h" +#include "PsychicResponse.h" +#include + +class ChunkPrinter : public Print { + private: + PsychicResponse * _response; + uint8_t * _buffer; + size_t _length; + size_t _pos; + + public: + ChunkPrinter(PsychicResponse * response, uint8_t * buffer, size_t len) + : _response(response) + , _buffer(buffer) + , _length(len) + , _pos(0) { + } + + virtual ~ChunkPrinter() { + } + + size_t write(uint8_t c) { + esp_err_t err; + + _buffer[_pos] = c; + _pos++; + + //if we're full, send a chunk + if (_pos == _length) { + _pos = 0; + + err = _response->sendChunk(_buffer, _length); + if (err != ESP_OK) + return 0; + } + + return 1; + } + + virtual void flush() override { + if (_pos) { + _response->sendChunk(_buffer, _pos); + _pos = 0; + } + } +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicClient.cpp b/lib/PsychicHttp/src_old/PsychicClient.cpp new file mode 100644 index 000000000..bbee4e269 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicClient.cpp @@ -0,0 +1,67 @@ +#include "PsychicClient.h" + +PsychicClient::PsychicClient(httpd_handle_t server, int socket) + : _server(server) + , _socket(socket) + , _friend(NULL) + , isNew(false) { +} + +PsychicClient::~PsychicClient() { +} + +httpd_handle_t PsychicClient::server() { + return _server; +} + +int PsychicClient::socket() { + return _socket; +} + +// I'm not sure this is entirely safe to call. I was having issues with race conditions when highly loaded using this. +esp_err_t PsychicClient::close() { + esp_err_t err = httpd_sess_trigger_close(_server, _socket); + //PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. + + return err; +} + +IPAddress PsychicClient::localIP() { + IPAddress address(0, 0, 0, 0); + + char ipstr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + socklen_t addr_size = sizeof(addr); + + if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + ESP_LOGE(PH_TAG, "Error getting client IP"); + return address; + } + + // Convert to IPv4 string + inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); + ESP_LOGI(PH_TAG, "Client Local IP => %s", ipstr); + address.fromString(ipstr); + + return address; +} + +IPAddress PsychicClient::remoteIP() { + IPAddress address(0, 0, 0, 0); + + char ipstr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + socklen_t addr_size = sizeof(addr); + + if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + ESP_LOGE(PH_TAG, "Error getting client IP"); + return address; + } + + // Convert to IPv4 string + inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); + ESP_LOGI(PH_TAG, "Client Remote IP => %s", ipstr); + address.fromString(ipstr); + + return address; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicClient.h b/lib/PsychicHttp/src_old/PsychicClient.h new file mode 100644 index 000000000..3b7b79e6d --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicClient.h @@ -0,0 +1,39 @@ +#ifndef PsychicClient_h +#define PsychicClient_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include + +/* +* PsychicClient :: Generic wrapper around the ESP-IDF socket +*/ + +class PsychicClient { + protected: + httpd_handle_t _server; + int _socket; + + public: + PsychicClient(httpd_handle_t server, int socket); + ~PsychicClient(); + + //no idea if this is the right way to do it or not, but lets see. + //pointer to our derived class (eg. PsychicWebSocketConnection) + void * _friend; + + bool isNew = false; + + bool operator==(PsychicClient & rhs) const { + return _socket == rhs.socket(); + } + + httpd_handle_t server(); + int socket(); + esp_err_t close(); + + IPAddress localIP(); + IPAddress remoteIP(); +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicCore.h b/lib/PsychicHttp/src_old/PsychicCore.h new file mode 100644 index 000000000..5cf8232be --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicCore.h @@ -0,0 +1,101 @@ +#ifndef PsychicCore_h +#define PsychicCore_h + +#define PH_TAG "psychic" + +//version numbers +#define PSYCHIC_HTTP_VERSION_MAJOR 1 +#define PSYCHIC_HTTP_VERSION_MINOR 1 +#define PSYCHIC_HTTP_VERSION_PATCH 0 + +#ifndef MAX_COOKIE_SIZE +#define MAX_COOKIE_SIZE 512 +#endif + +#ifndef FILE_CHUNK_SIZE +#define FILE_CHUNK_SIZE 8 * 1024 +#endif + +#ifndef MAX_UPLOAD_SIZE +#define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB +#endif + +#ifndef MAX_REQUEST_BODY_SIZE +#define MAX_REQUEST_BODY_SIZE (16 * 1024) //16K +#endif + +#ifdef ARDUINO +#include +#include +#endif + +#include +#include +#include +#include +#include "esp_random.h" +#include "MD5Builder.h" +#include +#include "FS.h" + +enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; + +String urlDecode(const char * encoded); + +class PsychicHttpServer; +class PsychicRequest; +class PsychicWebSocketRequest; +class PsychicClient; + +//filter function definition +typedef std::function PsychicRequestFilterFunction; + +//client connect callback +typedef std::function PsychicClientCallback; + + +struct HTTPHeader { + char * field; + char * value; +}; + +class DefaultHeaders { + std::list _headers; + + public: + DefaultHeaders() { + } + + void addHeader(const String & field, const String & value) { + addHeader(field.c_str(), value.c_str()); + } + + void addHeader(const char * field, const char * value) { + HTTPHeader header; + + //these are just going to stick around forever. + header.field = (char *)malloc(strlen(field) + 1); + header.value = (char *)malloc(strlen(value) + 1); + + strlcpy(header.field, field, strlen(field) + 1); + strlcpy(header.value, value, strlen(value) + 1); + + _headers.push_back(header); + } + + const std::list & getHeaders() { + return _headers; + } + + //delete the copy constructor, singleton class + DefaultHeaders(DefaultHeaders const &) = delete; + DefaultHeaders & operator=(DefaultHeaders const &) = delete; + + //single static class interface + static DefaultHeaders & Instance() { + static DefaultHeaders instance; + return instance; + } +}; + +#endif //PsychicCore_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicEndpoint.cpp b/lib/PsychicHttp/src_old/PsychicEndpoint.cpp new file mode 100644 index 000000000..e17ddef13 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicEndpoint.cpp @@ -0,0 +1,82 @@ +#include "PsychicEndpoint.h" + +PsychicEndpoint::PsychicEndpoint() + : _server(NULL) + , _uri("") + , _method(HTTP_GET) + , _handler(NULL) { +} + +PsychicEndpoint::PsychicEndpoint(PsychicHttpServer * server, http_method method, const char * uri) + : _server(server) + , _uri(uri) + , _method(method) + , _handler(NULL) { +} + +PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler * handler) { + //clean up old / default handler + if (_handler != NULL) + delete _handler; + + //get our new pointer + _handler = handler; + + //keep a pointer to the server + _handler->_server = _server; + + return this; +} + +PsychicHandler * PsychicEndpoint::handler() { + return _handler; +} + +String PsychicEndpoint::uri() { + return _uri; +} + +esp_err_t PsychicEndpoint::requestCallback(httpd_req_t * req) { +#ifdef ENABLE_ASYNC + if (is_on_async_worker_thread() == false) { + if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) { + return ESP_OK; + } else { + httpd_resp_set_status(req, "503 Busy"); + httpd_resp_sendstr(req, "No workers available. Server busy."); + return ESP_OK; + } + } +#endif + + PsychicEndpoint * self = (PsychicEndpoint *)req->user_ctx; + PsychicHandler * handler = self->handler(); + PsychicRequest request(self->_server, req); + + //make sure we have a handler + if (handler != NULL) { + if (handler->filter(&request) && handler->canHandle(&request)) { + //check our credentials + if (handler->needsAuthentication(&request)) + return handler->authenticate(&request); + + //pass it to our handler + return handler->handleRequest(&request); + } + //pass it to our generic handlers + else + return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR); + } else + return request.reply(500, "text/html", "No handler registered."); +} + +PsychicEndpoint * PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) { + _handler->setFilter(fn); + return this; +} + +PsychicEndpoint * +PsychicEndpoint::setAuthentication(const char * username, const char * password, HTTPAuthMethod method, const char * realm, const char * authFailMsg) { + _handler->setAuthentication(username, password, method, realm, authFailMsg); + return this; +}; \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicEndpoint.h b/lib/PsychicHttp/src_old/PsychicEndpoint.h new file mode 100644 index 000000000..3df414794 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicEndpoint.h @@ -0,0 +1,36 @@ +#ifndef PsychicEndpoint_h +#define PsychicEndpoint_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" + +#ifdef ENABLE_ASYNC +#include "async_worker.h" +#endif + +class PsychicEndpoint { + friend PsychicHttpServer; + + private: + PsychicHttpServer * _server; + String _uri; + http_method _method; + PsychicHandler * _handler; + + public: + PsychicEndpoint(); + PsychicEndpoint(PsychicHttpServer * server, http_method method, const char * uri); + + PsychicEndpoint * setHandler(PsychicHandler * handler); + PsychicHandler * handler(); + + PsychicEndpoint * setFilter(PsychicRequestFilterFunction fn); + PsychicEndpoint * + setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = ""); + + String uri(); + + static esp_err_t requestCallback(httpd_req_t * req); +}; + +#endif // PsychicEndpoint_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicEventSource.cpp b/lib/PsychicHttp/src_old/PsychicEventSource.cpp new file mode 100644 index 000000000..d457e4754 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicEventSource.cpp @@ -0,0 +1,217 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "PsychicEventSource.h" + +/*****************************************/ +// PsychicEventSource - Handler +/*****************************************/ + +PsychicEventSource::PsychicEventSource() + : PsychicHandler() + , _onOpen(NULL) + , _onClose(NULL) { +} + +PsychicEventSource::~PsychicEventSource() { +} + +PsychicEventSourceClient * PsychicEventSource::getClient(int socket) { + PsychicClient * client = PsychicHandler::getClient(socket); + + if (client == NULL) + return NULL; + + return (PsychicEventSourceClient *)client->_friend; +} + +PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient * client) { + return getClient(client->socket()); +} + +esp_err_t PsychicEventSource::handleRequest(PsychicRequest * request) { + //start our open ended HTTP response + PsychicEventSourceResponse response(request); + esp_err_t err = response.send(); + + //lookup our client + PsychicClient * client = checkForNewClient(request->client()); + if (client->isNew) { + //did we get our last id? + if (request->hasHeader("Last-Event-ID")) { + PsychicEventSourceClient * buddy = getClient(client); + buddy->_lastId = atoi(request->header("Last-Event-ID").c_str()); + } + + //let our handler know. + openCallback(client); + } + + return err; +} + +PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) { + _onOpen = fn; + return this; +} + +PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) { + _onClose = fn; + return this; +} + +void PsychicEventSource::addClient(PsychicClient * client) { + client->_friend = new PsychicEventSourceClient(client); + PsychicHandler::addClient(client); +} + +void PsychicEventSource::removeClient(PsychicClient * client) { + PsychicHandler::removeClient(client); + delete (PsychicEventSourceClient *)client->_friend; + client->_friend = NULL; +} + +void PsychicEventSource::openCallback(PsychicClient * client) { + PsychicEventSourceClient * buddy = getClient(client); + if (buddy == NULL) { + TRACE(); + return; + } + + if (_onOpen != NULL) + _onOpen(buddy); +} + +void PsychicEventSource::closeCallback(PsychicClient * client) { + PsychicEventSourceClient * buddy = getClient(client); + if (buddy == NULL) { + TRACE(); + return; + } + + if (_onClose != NULL) + _onClose(getClient(buddy)); +} + +void PsychicEventSource::send(const char * message, const char * event, uint32_t id, uint32_t reconnect) { + String ev = generateEventMessage(message, event, id, reconnect); + for (PsychicClient * c : _clients) { + ((PsychicEventSourceClient *)c->_friend)->sendEvent(ev.c_str()); + } +} + +/*****************************************/ +// PsychicEventSourceClient +/*****************************************/ + +PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient * client) + : PsychicClient(client->server(), client->socket()) + , _lastId(0) { +} + +PsychicEventSourceClient::~PsychicEventSourceClient() { +} + +void PsychicEventSourceClient::send(const char * message, const char * event, uint32_t id, uint32_t reconnect) { + String ev = generateEventMessage(message, event, id, reconnect); + sendEvent(ev.c_str()); +} + +void PsychicEventSourceClient::sendEvent(const char * event) { + int result; + do { + result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0); + } while (result == HTTPD_SOCK_ERR_TIMEOUT); + + //if (result < 0) + //error log here +} + +/*****************************************/ +// PsychicEventSourceResponse +/*****************************************/ + +PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest * request) + : PsychicResponse(request) { +} + +esp_err_t PsychicEventSourceResponse::send() { + //build our main header + String out = String(); + out.concat("HTTP/1.1 200 OK\r\n"); + out.concat("Content-Type: text/event-stream\r\n"); + out.concat("Cache-Control: no-cache\r\n"); + out.concat("Connection: keep-alive\r\n"); + + //get our global headers out of the way first + for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) + out.concat(String(header.field) + ": " + String(header.value) + "\r\n"); + + //separator + out.concat("\r\n"); + + int result; + do { + result = httpd_send(_request->request(), out.c_str(), out.length()); + } while (result == HTTPD_SOCK_ERR_TIMEOUT); + + if (result < 0) + ESP_LOGE(PH_TAG, "EventSource send failed with %s", esp_err_to_name(result)); + + if (result > 0) + return ESP_OK; + else + return ESP_ERR_HTTPD_RESP_SEND; +} + +/*****************************************/ +// Event Message Generator +/*****************************************/ + +String generateEventMessage(const char * message, const char * event, uint32_t id, uint32_t reconnect) { + String ev = ""; + + if (reconnect) { + ev += "retry: "; + ev += String(reconnect); + ev += "\r\n"; + } + + if (id) { + ev += "id: "; + ev += String(id); + ev += "\r\n"; + } + + if (event != NULL) { + ev += "event: "; + ev += String(event); + ev += "\r\n"; + } + + if (message != NULL) { + ev += "data: "; + ev += String(message); + ev += "\r\n"; + } + ev += "\r\n"; + + return ev; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicEventSource.h b/lib/PsychicHttp/src_old/PsychicEventSource.h new file mode 100644 index 000000000..1406e5c30 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicEventSource.h @@ -0,0 +1,84 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef PsychicEventSource_H_ +#define PsychicEventSource_H_ + +#include "PsychicCore.h" +#include "PsychicClient.h" +#include "PsychicHandler.h" +#include "PsychicResponse.h" + +class PsychicEventSource; +class PsychicEventSourceResponse; +class PsychicEventSourceClient; +class PsychicResponse; + +typedef std::function PsychicEventSourceClientCallback; + +class PsychicEventSourceClient : public PsychicClient { + friend PsychicEventSource; + + protected: + uint32_t _lastId; + + public: + PsychicEventSourceClient(PsychicClient * client); + ~PsychicEventSourceClient(); + + uint32_t lastId() const { + return _lastId; + } + void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0); + void sendEvent(const char * event); +}; + +class PsychicEventSource : public PsychicHandler { + private: + PsychicEventSourceClientCallback _onOpen; + PsychicEventSourceClientCallback _onClose; + + public: + PsychicEventSource(); + ~PsychicEventSource(); + + PsychicEventSourceClient * getClient(int socket) override; + PsychicEventSourceClient * getClient(PsychicClient * client) override; + void addClient(PsychicClient * client) override; + void removeClient(PsychicClient * client) override; + void openCallback(PsychicClient * client) override; + void closeCallback(PsychicClient * client) override; + + PsychicEventSource * onOpen(PsychicEventSourceClientCallback fn); + PsychicEventSource * onClose(PsychicEventSourceClientCallback fn); + + esp_err_t handleRequest(PsychicRequest * request) override final; + + void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0); +}; + +class PsychicEventSourceResponse : public PsychicResponse { + public: + PsychicEventSourceResponse(PsychicRequest * request); + virtual esp_err_t send() override; +}; + +String generateEventMessage(const char * message, const char * event, uint32_t id, uint32_t reconnect); + +#endif /* PsychicEventSource_H_ */ \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicFileResponse.cpp b/lib/PsychicHttp/src_old/PsychicFileResponse.cpp new file mode 100644 index 000000000..51827cc49 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicFileResponse.cpp @@ -0,0 +1,169 @@ +#include "PsychicFileResponse.h" +#include "PsychicResponse.h" +#include "PsychicRequest.h" + + +PsychicFileResponse::PsychicFileResponse(PsychicRequest * request, FS & fs, const String & path, const String & contentType, bool download) + : PsychicResponse(request) { + //_code = 200; + _path = path; + + if (!download && !fs.exists(_path) && fs.exists(_path + ".gz")) { + _path = _path + ".gz"; + addHeader("Content-Encoding", "gzip"); + _sendContentLength = true; + _chunked = false; + } + + _content = fs.open(_path, "r"); + _contentLength = _content.size(); + + if (contentType == "") + _setContentType(path); + else + _contentType = contentType; + setContentType(_contentType.c_str()); + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; + + if (download) { + // set filename and force download + snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename); + } else { + // set filename and force rendering + snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); +} + +PsychicFileResponse::PsychicFileResponse(PsychicRequest * request, File content, const String & path, const String & contentType, bool download) + : PsychicResponse(request) { + _path = path; + + if (!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")) { + addHeader("Content-Encoding", "gzip"); + _sendContentLength = true; + _chunked = false; + } + + _content = content; + _contentLength = _content.size(); + setContentLength(_contentLength); + + if (contentType == "") + _setContentType(path); + else + _contentType = contentType; + setContentType(_contentType.c_str()); + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; + + if (download) { + snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename); + } else { + snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); +} + +PsychicFileResponse::~PsychicFileResponse() { + if (_content) + _content.close(); +} + +void PsychicFileResponse::_setContentType(const String & path) { + if (path.endsWith(".html")) + _contentType = "text/html"; + else if (path.endsWith(".htm")) + _contentType = "text/html"; + else if (path.endsWith(".css")) + _contentType = "text/css"; + else if (path.endsWith(".json")) + _contentType = "application/json"; + else if (path.endsWith(".js")) + _contentType = "application/javascript"; + else if (path.endsWith(".png")) + _contentType = "image/png"; + else if (path.endsWith(".gif")) + _contentType = "image/gif"; + else if (path.endsWith(".jpg")) + _contentType = "image/jpeg"; + else if (path.endsWith(".ico")) + _contentType = "image/x-icon"; + else if (path.endsWith(".svg")) + _contentType = "image/svg+xml"; + else if (path.endsWith(".eot")) + _contentType = "font/eot"; + else if (path.endsWith(".woff")) + _contentType = "font/woff"; + else if (path.endsWith(".woff2")) + _contentType = "font/woff2"; + else if (path.endsWith(".ttf")) + _contentType = "font/ttf"; + else if (path.endsWith(".xml")) + _contentType = "text/xml"; + else if (path.endsWith(".pdf")) + _contentType = "application/pdf"; + else if (path.endsWith(".zip")) + _contentType = "application/zip"; + else if (path.endsWith(".gz")) + _contentType = "application/x-gzip"; + else + _contentType = "text/plain"; +} + +esp_err_t PsychicFileResponse::send() { + esp_err_t err = ESP_OK; + + //just send small files directly + size_t size = getContentLength(); + if (size < FILE_CHUNK_SIZE) { + uint8_t * buffer = (uint8_t *)malloc(size); + int readSize = _content.readBytes((char *)buffer, size); + + this->setContent(buffer, size); + err = PsychicResponse::send(); + + free(buffer); + } else { + /* Retrieve the pointer to scratch buffer for temporary storage */ + char * chunk = (char *)malloc(FILE_CHUNK_SIZE); + if (chunk == NULL) { + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + this->sendHeaders(); + + size_t chunksize; + do { + /* Read file in chunks into the scratch buffer */ + chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE); + if (chunksize > 0) { + err = this->sendChunk((uint8_t *)chunk, chunksize); + if (err != ESP_OK) + break; + } + + /* Keep looping till the whole file is sent */ + } while (chunksize != 0); + + //keep track of our memory + free(chunk); + + if (err == ESP_OK) { + ESP_LOGI(PH_TAG, "File sending complete"); + this->finishChunking(); + } + + /* Close file after sending complete */ + _content.close(); + } + + return err; +} diff --git a/lib/PsychicHttp/src_old/PsychicFileResponse.h b/lib/PsychicHttp/src_old/PsychicFileResponse.h new file mode 100644 index 000000000..918e81bb7 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicFileResponse.h @@ -0,0 +1,28 @@ +#ifndef PsychicFileResponse_h +#define PsychicFileResponse_h + +#include "PsychicCore.h" +#include "PsychicResponse.h" + +class PsychicRequest; + +class PsychicFileResponse : public PsychicResponse { + using File = fs::File; + using FS = fs::FS; + + private: + File _content; + String _path; + bool _sendContentLength; + bool _chunked; + String _contentType; + void _setContentType(const String & path); + + public: + PsychicFileResponse(PsychicRequest * request, FS & fs, const String & path, const String & contentType = String(), bool download = false); + PsychicFileResponse(PsychicRequest * request, File content, const String & path, const String & contentType = String(), bool download = false); + ~PsychicFileResponse(); + esp_err_t send(); +}; + +#endif // PsychicFileResponse_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicHandler.cpp b/lib/PsychicHttp/src_old/PsychicHandler.cpp new file mode 100644 index 000000000..f02971fbf --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicHandler.cpp @@ -0,0 +1,103 @@ +#include "PsychicHandler.h" + +PsychicHandler::PsychicHandler() : + _filter(NULL), + _server(NULL), + _username(""), + _password(""), + _method(DIGEST_AUTH), + _realm(""), + _authFailMsg("") + {} + +PsychicHandler::~PsychicHandler() { + // actual PsychicClient deletion handled by PsychicServer + // for (PsychicClient *client : _clients) + // delete(client); + _clients.clear(); +} + +PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction fn) { + _filter = fn; + return this; +} + +bool PsychicHandler::filter(PsychicRequest *request){ + return _filter == NULL || _filter(request); +} + +PsychicHandler* PsychicHandler::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) { + _username = String(username); + _password = String(password); + _method = method; + _realm = String(realm); + _authFailMsg = String(authFailMsg); + return this; +}; + +bool PsychicHandler::needsAuthentication(PsychicRequest *request) { + return (_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()); +} + +esp_err_t PsychicHandler::authenticate(PsychicRequest *request) { + return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str()); +} + +PsychicClient * PsychicHandler::checkForNewClient(PsychicClient *client) +{ + PsychicClient *c = PsychicHandler::getClient(client); + if (c == NULL) + { + c = client; + addClient(c); + c->isNew = true; + } + else + c->isNew = false; + + return c; +} + +void PsychicHandler::checkForClosedClient(PsychicClient *client) +{ + if (hasClient(client)) + { + closeCallback(client); + removeClient(client); + } +} + +void PsychicHandler::addClient(PsychicClient *client) { + _clients.push_back(client); +} + +void PsychicHandler::removeClient(PsychicClient *client) { + _clients.remove(client); +} + +PsychicClient * PsychicHandler::getClient(int socket) +{ + //make sure the server has it too. + if (!_server->hasClient(socket)) + return NULL; + + //what about us? + for (PsychicClient *client : _clients) + if (client->socket() == socket) + return client; + + //nothing found. + return NULL; +} + +PsychicClient * PsychicHandler::getClient(PsychicClient *client) { + return PsychicHandler::getClient(client->socket()); +} + +bool PsychicHandler::hasClient(PsychicClient *socket) { + return PsychicHandler::getClient(socket) != NULL; +} + +const std::list& PsychicHandler::getClientList() { + return _clients; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicHandler.h b/lib/PsychicHttp/src_old/PsychicHandler.h new file mode 100644 index 000000000..071238124 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicHandler.h @@ -0,0 +1,67 @@ +#ifndef PsychicHandler_h +#define PsychicHandler_h + +#include "PsychicCore.h" +#include "PsychicRequest.h" + +class PsychicEndpoint; + +/* +* HANDLER :: Can be attached to any endpoint or as a generic request handler. +*/ + +class PsychicHandler { + friend PsychicEndpoint; + + protected: + PsychicRequestFilterFunction _filter; + PsychicHttpServer * _server; + + String _username; + String _password; + HTTPAuthMethod _method; + String _realm; + String _authFailMsg; + + std::list _clients; + + public: + PsychicHandler(); + ~PsychicHandler(); + + PsychicHandler * setFilter(PsychicRequestFilterFunction fn); + bool filter(PsychicRequest * request); + + PsychicHandler * + setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = ""); + bool needsAuthentication(PsychicRequest * request); + esp_err_t authenticate(PsychicRequest * request); + + virtual bool isWebSocket() { + return false; + }; + + PsychicClient * checkForNewClient(PsychicClient * client); + void checkForClosedClient(PsychicClient * client); + + virtual void addClient(PsychicClient * client); + virtual void removeClient(PsychicClient * client); + virtual PsychicClient * getClient(int socket); + virtual PsychicClient * getClient(PsychicClient * client); + virtual void openCallback(PsychicClient * client){}; + virtual void closeCallback(PsychicClient * client){}; + + bool hasClient(PsychicClient * client); + int count() { + return _clients.size(); + }; + const std::list & getClientList(); + + //derived classes must implement these functions + virtual bool canHandle(PsychicRequest * request) { + return true; + }; + virtual esp_err_t handleRequest(PsychicRequest * request) = 0; +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicHttp.h b/lib/PsychicHttp/src_old/PsychicHttp.h new file mode 100644 index 000000000..dd863162a --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicHttp.h @@ -0,0 +1,23 @@ +#ifndef PsychicHttp_h +#define PsychicHttp_h + +//#define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread + +#include +#include "PsychicHttpServer.h" +#include "PsychicRequest.h" +#include "PsychicResponse.h" +#include "PsychicEndpoint.h" +#include "PsychicHandler.h" +#include "PsychicStaticFileHandler.h" +#include "PsychicFileResponse.h" +#include "PsychicUploadHandler.h" +#include "PsychicWebSocket.h" +#include "PsychicEventSource.h" +#include "PsychicJson.h" + +#ifdef ENABLE_ASYNC +#include "async_worker.h" +#endif + +#endif /* PsychicHttp_h */ \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicHttpServer.cpp b/lib/PsychicHttp/src_old/PsychicHttpServer.cpp new file mode 100644 index 000000000..c7938ad63 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicHttpServer.cpp @@ -0,0 +1,329 @@ +#include "PsychicHttpServer.h" +#include "PsychicEndpoint.h" +#include "PsychicHandler.h" +#include "PsychicWebHandler.h" +#include "PsychicStaticFileHandler.h" +#include "PsychicWebSocket.h" +#include "WiFi.h" +#include "PsychicJson.h" // added by proddy + +PsychicHttpServer::PsychicHttpServer() + : _onOpen(NULL) + , _onClose(NULL) { + maxRequestBodySize = MAX_REQUEST_BODY_SIZE; + maxUploadSize = MAX_UPLOAD_SIZE; + + defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, ""); + onNotFound(PsychicHttpServer::defaultNotFoundHandler); + + //for a regular server + config = HTTPD_DEFAULT_CONFIG(); + config.open_fn = PsychicHttpServer::openCallback; + config.close_fn = PsychicHttpServer::closeCallback; + config.uri_match_fn = httpd_uri_match_wildcard; + config.global_user_ctx = this; + config.global_user_ctx_free_fn = destroy; + config.max_uri_handlers = 20; + +#ifdef ENABLE_ASYNC + // It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS + // Why? This leaves at least one socket still available to handle + // quick synchronous requests. Otherwise, all the sockets will + // get taken by the long async handlers, and your server will no + // longer be responsive. + config.max_open_sockets = ASYNC_WORKER_COUNT + 1; + config.lru_purge_enable = true; +#endif +} + +PsychicHttpServer::~PsychicHttpServer() { + for (auto * client : _clients) + delete (client); + _clients.clear(); + + for (auto * endpoint : _endpoints) + delete (endpoint); + _endpoints.clear(); + + for (auto * handler : _handlers) + delete (handler); + _handlers.clear(); + + delete defaultEndpoint; +} + +void PsychicHttpServer::destroy(void * ctx) { + PsychicHttpServer * temp = (PsychicHttpServer *)ctx; + delete temp; +} + +esp_err_t PsychicHttpServer::listen(uint16_t port) { + this->_use_ssl = false; + this->config.server_port = port; + + return this->_start(); +} + +esp_err_t PsychicHttpServer::_start() { + esp_err_t ret; + +#ifdef ENABLE_ASYNC + // start workers + start_async_req_workers(); +#endif + + //fire it up. + ret = _startServer(); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret)); + return ret; + } + + // Register handler + ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret)); + + return ret; +} + +esp_err_t PsychicHttpServer::_startServer() { + return httpd_start(&this->server, &this->config); +} + +void PsychicHttpServer::stop() { + httpd_stop(this->server); +} + +PsychicHandler & PsychicHttpServer::addHandler(PsychicHandler * handler) { + _handlers.push_back(handler); + return *handler; +} + +void PsychicHttpServer::removeHandler(PsychicHandler * handler) { + _handlers.remove(handler); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri) { + return on(uri, HTTP_GET); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, PsychicHttpRequestCallback fn) { + return on(uri, HTTP_GET, fn); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicHttpRequestCallback fn) { + //these basic requests need a basic web handler + PsychicWebHandler * handler = new PsychicWebHandler(); + handler->onRequest(fn); + + return on(uri, method, handler); +} + +// added by Proddy +PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicJsonRequestCallback fn) { + PsychicJsonHandler * handler = new PsychicJsonHandler(fn); + + return on(uri, method, handler); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method) { + PsychicWebHandler * handler = new PsychicWebHandler(); + + return on(uri, method, handler); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, PsychicHandler * handler) { + return on(uri, HTTP_GET, handler); +} + +PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicHandler * handler) { + //make our endpoint + PsychicEndpoint * endpoint = new PsychicEndpoint(this, method, uri); + + //set our handler + endpoint->setHandler(handler); + + // URI handler structure + httpd_uri_t my_uri{.uri = uri, .method = method, .handler = PsychicEndpoint::requestCallback, .user_ctx = endpoint, .is_websocket = handler->isWebSocket()}; + + // Register endpoint with ESP-IDF server + esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "Add endpoint %s failed (%s)", uri, esp_err_to_name(ret)); // modified by proddy + } + + //save it for later + _endpoints.push_back(endpoint); + + return endpoint; +} + +void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn) { + PsychicWebHandler * handler = new PsychicWebHandler(); + handler->onRequest(fn); + + this->defaultEndpoint->setHandler(handler); +} + +esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t * req, httpd_err_code_t err) { + PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(req->handle); + PsychicRequest request(server, req); + + //loop through our global handlers and see if anyone wants it + for (auto * handler : server->_handlers) { + //are we capable of handling this? + if (handler->filter(&request) && handler->canHandle(&request)) { + //check our credentials + if (handler->needsAuthentication(&request)) + return handler->authenticate(&request); + else + return handler->handleRequest(&request); + } + } + + //nothing found, give it to our defaultEndpoint + PsychicHandler * handler = server->defaultEndpoint->handler(); + if (handler->filter(&request) && handler->canHandle(&request)) + return handler->handleRequest(&request); + + //not sure how we got this far. + return ESP_ERR_HTTPD_INVALID_REQ; +} + +esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest * request) { + request->reply(404, "text/html", "That URI does not exist."); + + return ESP_OK; +} + +void PsychicHttpServer::onOpen(PsychicClientCallback handler) { + this->_onOpen = handler; +} + +esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd) { + ESP_LOGI(PH_TAG, "New client connected %d", sockfd); + + //get our global server reference + PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(hd); + + //lookup our client + PsychicClient * client = server->getClient(sockfd); + if (client == NULL) { + client = new PsychicClient(hd, sockfd); + server->addClient(client); + } + + //user callback + if (server->_onOpen != NULL) + server->_onOpen(client); + + return ESP_OK; +} + +void PsychicHttpServer::onClose(PsychicClientCallback handler) { + this->_onClose = handler; +} + +void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd) { + ESP_LOGI(PH_TAG, "Client disconnected %d", sockfd); + + PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(hd); + + //lookup our client + PsychicClient * client = server->getClient(sockfd); + if (client != NULL) { + //give our handlers a chance to handle a disconnect first + for (PsychicEndpoint * endpoint : server->_endpoints) { + PsychicHandler * handler = endpoint->handler(); + handler->checkForClosedClient(client); + } + + //do we have a callback attached? + if (server->_onClose != NULL) + server->_onClose(client); + + //remove it from our list + server->removeClient(client); + } else + ESP_LOGE(PH_TAG, "No client record %d", sockfd); + + //finally close it out. + close(sockfd); +} + +PsychicStaticFileHandler * PsychicHttpServer::serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control) { + PsychicStaticFileHandler * handler = new PsychicStaticFileHandler(uri, fs, path, cache_control); + this->addHandler(handler); + + return handler; +} + +void PsychicHttpServer::addClient(PsychicClient * client) { + _clients.push_back(client); +} + +void PsychicHttpServer::removeClient(PsychicClient * client) { + _clients.remove(client); + delete client; +} + +PsychicClient * PsychicHttpServer::getClient(int socket) { + for (PsychicClient * client : _clients) + if (client->socket() == socket) + return client; + + return NULL; +} + +PsychicClient * PsychicHttpServer::getClient(httpd_req_t * req) { + return getClient(httpd_req_to_sockfd(req)); +} + +bool PsychicHttpServer::hasClient(int socket) { + return getClient(socket) != NULL; +} + +const std::list & PsychicHttpServer::getClientList() { + return _clients; +} + +bool ON_STA_FILTER(PsychicRequest * request) { + return WiFi.localIP() == request->client()->localIP(); +} + +bool ON_AP_FILTER(PsychicRequest * request) { + return WiFi.softAPIP() == request->client()->localIP(); +} + +String urlDecode(const char * encoded) { + size_t length = strlen(encoded); + char * decoded = (char *)malloc(length + 1); + if (!decoded) { + return ""; + } + + size_t i, j = 0; + for (i = 0; i < length; ++i) { + if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) { + // Valid percent-encoded sequence + int hex; + sscanf(encoded + i + 1, "%2x", &hex); + decoded[j++] = (char)hex; + i += 2; // Skip the two hexadecimal characters + } else if (encoded[i] == '+') { + // Convert '+' to space + decoded[j++] = ' '; + } else { + // Copy other characters as they are + decoded[j++] = encoded[i]; + } + } + + decoded[j] = '\0'; // Null-terminate the decoded string + + String output(decoded); + free(decoded); + + return output; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicHttpServer.h b/lib/PsychicHttp/src_old/PsychicHttpServer.h new file mode 100644 index 000000000..0407ceb19 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicHttpServer.h @@ -0,0 +1,86 @@ +#ifndef PsychicHttpServer_h +#define PsychicHttpServer_h + +#include "PsychicCore.h" +#include "PsychicClient.h" +#include "PsychicHandler.h" +#include // added by proddy + +class PsychicEndpoint; +class PsychicHandler; +class PsychicStaticFileHandler; + +//callback definitions +typedef std::function PsychicHttpRequestCallback; +typedef std::function PsychicJsonRequestCallback; // added by proddy + +class PsychicHttpServer { + protected: + bool _use_ssl = false; + std::list _endpoints; + std::list _handlers; + std::list _clients; + + PsychicClientCallback _onOpen; + PsychicClientCallback _onClose; + + esp_err_t _start(); + virtual esp_err_t _startServer(); + + public: + PsychicHttpServer(); + ~PsychicHttpServer(); + + //esp-idf specific stuff + httpd_handle_t server; + httpd_config_t config; + + //some limits on what we will accept + unsigned long maxUploadSize; + unsigned long maxRequestBodySize; + + PsychicEndpoint * defaultEndpoint; + + static void destroy(void * ctx); + + esp_err_t listen(uint16_t port); + + virtual void stop(); + + PsychicHandler & addHandler(PsychicHandler * handler); + void removeHandler(PsychicHandler * handler); + + void addClient(PsychicClient * client); + void removeClient(PsychicClient * client); + PsychicClient * getClient(int socket); + PsychicClient * getClient(httpd_req_t * req); + bool hasClient(int socket); + int count() { + return _clients.size(); + }; + const std::list & getClientList(); + + PsychicEndpoint * on(const char * uri); + PsychicEndpoint * on(const char * uri, http_method method); + PsychicEndpoint * on(const char * uri, PsychicHttpRequestCallback onRequest); + PsychicEndpoint * on(const char * uri, http_method method, PsychicHttpRequestCallback onRequest); + PsychicEndpoint * on(const char * uri, PsychicHandler * handler); + PsychicEndpoint * on(const char * uri, http_method method, PsychicHandler * handler); + PsychicEndpoint * on(const char * uri, http_method method, PsychicJsonRequestCallback onRequest); // added proddy + + static esp_err_t notFoundHandler(httpd_req_t * req, httpd_err_code_t err); + static esp_err_t defaultNotFoundHandler(PsychicRequest * request); + void onNotFound(PsychicHttpRequestCallback fn); + + void onOpen(PsychicClientCallback handler); + void onClose(PsychicClientCallback handler); + static esp_err_t openCallback(httpd_handle_t hd, int sockfd); + static void closeCallback(httpd_handle_t hd, int sockfd); + + PsychicStaticFileHandler * serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control = NULL); +}; + +bool ON_STA_FILTER(PsychicRequest * request); +bool ON_AP_FILTER(PsychicRequest * request); + +#endif // PsychicHttpServer_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicHttpsServer.cpp b/lib/PsychicHttp/src_old/PsychicHttpsServer.cpp new file mode 100644 index 000000000..489beb6a3 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicHttpsServer.cpp @@ -0,0 +1,48 @@ +#include "PsychicHttpsServer.h" + +PsychicHttpsServer::PsychicHttpsServer() + : PsychicHttpServer() { + //for a SSL server + ssl_config = HTTPD_SSL_CONFIG_DEFAULT(); + ssl_config.httpd.open_fn = PsychicHttpServer::openCallback; + ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback; + ssl_config.httpd.uri_match_fn = httpd_uri_match_wildcard; + ssl_config.httpd.global_user_ctx = this; + ssl_config.httpd.global_user_ctx_free_fn = destroy; + ssl_config.httpd.max_uri_handlers = 20; + + // each SSL connection takes about 45kb of heap + // a barebones sketch with PsychicHttp has ~150kb of heap available + // if we set it higher than 2 and use all the connections, we get lots of memory errors. + // not to mention there is no heap left over for the program itself. + ssl_config.httpd.max_open_sockets = 2; +} + +PsychicHttpsServer::~PsychicHttpsServer() { +} + +esp_err_t PsychicHttpsServer::listen(uint16_t port, const char * cert, const char * private_key) { + this->_use_ssl = true; + + this->ssl_config.port_secure = port; + this->ssl_config.cacert_pem = (uint8_t *)cert; + this->ssl_config.cacert_len = strlen(cert) + 1; + this->ssl_config.prvtkey_pem = (uint8_t *)private_key; + this->ssl_config.prvtkey_len = strlen(private_key) + 1; + + return this->_start(); +} + +esp_err_t PsychicHttpsServer::_startServer() { + if (this->_use_ssl) + return httpd_ssl_start(&this->server, &this->ssl_config); + else + return httpd_start(&this->server, &this->config); +} + +void PsychicHttpsServer::stop() { + if (this->_use_ssl) + httpd_ssl_stop(this->server); + else + httpd_stop(this->server); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicHttpsServer.h b/lib/PsychicHttp/src_old/PsychicHttpsServer.h new file mode 100644 index 000000000..fa20c81e8 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicHttpsServer.h @@ -0,0 +1,31 @@ +#ifndef PsychicHttpsServer_h +#define PsychicHttpsServer_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include + +#if !CONFIG_HTTPD_WS_SUPPORT +#error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration +#endif + +#define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features + +class PsychicHttpsServer : public PsychicHttpServer { + protected: + bool _use_ssl = false; + + public: + PsychicHttpsServer(); + ~PsychicHttpsServer(); + + httpd_ssl_config_t ssl_config; + + using PsychicHttpServer::listen; //keep the regular version + esp_err_t listen(uint16_t port, const char * cert, const char * private_key); + + virtual esp_err_t _startServer() override final; + virtual void stop() override final; +}; + +#endif // PsychicHttpsServer_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicJson.h b/lib/PsychicHttp/src_old/PsychicJson.h new file mode 100644 index 000000000..5f98a404d --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicJson.h @@ -0,0 +1,247 @@ +// PsychicJson.h +/* + Async Response to use with ArduinoJson and AsyncWebServer + Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. + Ported to PsychicHttp by Zach Hoeken + +*/ +#ifndef PSYCHIC_JSON_H_ +#define PSYCHIC_JSON_H_ + +#include +#include "PsychicCore.h" +#include "PsychicResponse.h" +#include "ChunkPrinter.h" + +#if ARDUINOJSON_VERSION_MAJOR == 5 +#define ARDUINOJSON_5_COMPATIBILITY +#else +#ifndef DYNAMIC_JSON_DOCUMENT_SIZE +#define DYNAMIC_JSON_DOCUMENT_SIZE 4096 +#endif +#endif + +#ifndef JSON_BUFFER_SIZE +//#define JSON_BUFFER_SIZE 256 +#define JSON_BUFFER_SIZE 4 * 1024 +#endif + +constexpr const char * JSON_MIMETYPE = "application/json"; + +/* + * Json Response + * */ + +class PsychicJsonResponse : public PsychicResponse { + protected: +#ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer _jsonBuffer; +#else + DynamicJsonDocument _jsonBuffer; +#endif + + JsonVariant _root; + size_t _contentLength; + bool _msgPack; // added by proddy + + public: +#ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonResponse(PsychicRequest * request, bool isArray = false) + : PsychicResponse(request) { + setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.createArray(); + else + _root = _jsonBuffer.createObject(); + } +#else + PsychicJsonResponse(PsychicRequest * request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE, bool msgPack = false) // added by proddy + : PsychicResponse(request) + , _jsonBuffer(maxJsonBufferSize) + , _msgPack(msgPack) // added by proddy + { + setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.createNestedArray(); + else + _root = _jsonBuffer.createNestedObject(); + } +#endif + + ~PsychicJsonResponse() { + } + + JsonVariant & getRoot() { + return _root; + } + + size_t getLength() { +#ifdef ARDUINOJSON_5_COMPATIBILITY + return _root.measureLength(); +#else + // return measureJson(_root); + return (_msgPack) ? measureMsgPack(_root) : measureJson(_root); // added by proddy +#endif + } + + // size_t _fillBuffer(uint8_t *data, size_t len) + // { + // //TODO: fix + // //ChunkPrint dest(data, _sentLength, len); + + // #ifdef ARDUINOJSON_5_COMPATIBILITY + // _root.printTo(dest); + // #else + // //serializeJson(_root, dest); + // #endif + // return len; + // } + + virtual esp_err_t send() override { + esp_err_t err = ESP_OK; + size_t length = getLength(); + size_t buffer_size; + char * buffer; + + DUMP(length); + + //how big of a buffer do we want? + if (length < JSON_BUFFER_SIZE) + buffer_size = length + 1; + else + buffer_size = JSON_BUFFER_SIZE; + + DUMP(buffer_size); + + buffer = (char *)malloc(buffer_size); + if (buffer == NULL) { + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + //send it in one shot or no? + if (length < JSON_BUFFER_SIZE) { + TRACE(); + +#ifdef ARDUINOJSON_5_COMPATIBILITY + _root.printTo(buffer, buffer_size); +#else + // serializeJson(_root, buffer, buffer_size) + (_msgPack) ? serializeMsgPack(_root, buffer, buffer_size) : serializeJson(_root, buffer, buffer_size); // added by proddy +#endif + + this->setContent((uint8_t *)buffer, length); + this->setContentType(JSON_MIMETYPE); + + err = PsychicResponse::send(); + } else { + //helper class that acts as a stream to print chunked responses + ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size); + + //keep our headers + this->sendHeaders(); + +//these commands write to the ChunkPrinter which does the sending +#ifdef ARDUINOJSON_5_COMPATIBILITY + _root.printTo(dest); +#else + // serializeJson(_root, dest); // added by proddy + (_msgPack) ? serializeMsgPack(_root, dest) : serializeJson(_root, dest); // added by proddy + +#endif + + //send the last bits + dest.flush(); + + //done with our chunked response too + err = this->finishChunking(); + } + + //let the buffer go + free(buffer); + + return err; + } +}; + +// class PrettyPsychicJsonResponse : public PsychicJsonResponse +// { +// public: +// #ifdef ARDUINOJSON_5_COMPATIBILITY +// PrettyPsychicJsonResponse(PsychicRequest *request, bool isArray = false) : PsychicJsonResponse(request, isArray) {} +// #else +// PrettyPsychicJsonResponse( +// PsychicRequest *request, +// bool isArray = false, +// size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) +// : PsychicJsonResponse{request, isArray, maxJsonBufferSize} {} +// #endif + +// size_t setLength() +// { +// #ifdef ARDUINOJSON_5_COMPATIBILITY +// _contentLength = _root.measurePrettyLength(); +// #else +// _contentLength = measureJsonPretty(_root); +// #endif +// return _contentLength; +// } +// }; + +typedef std::function PsychicJsonRequestCallback; + +class PsychicJsonHandler : public PsychicWebHandler { + protected: + PsychicJsonRequestCallback _onRequest; +#ifndef ARDUINOJSON_5_COMPATIBILITY + const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE; +#endif + + public: +#ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonHandler() + : _onRequest(NULL){}; + + PsychicJsonHandler(PsychicJsonRequestCallback onRequest) + : _onRequest(onRequest) { + } +#else + PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) + : _onRequest(NULL) + , _maxJsonBufferSize(maxJsonBufferSize){}; + + PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) + : _onRequest(onRequest) + , _maxJsonBufferSize(maxJsonBufferSize) { + } +#endif + + void onRequest(PsychicJsonRequestCallback fn) { + _onRequest = fn; + } + + virtual esp_err_t handleRequest(PsychicRequest * request) override { + //process basic stuff + PsychicWebHandler::handleRequest(request); + + if (_onRequest) { +#ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer jsonBuffer; + JsonVariant json = jsonBuffer.parse(); + if (!json.success()) + return request->reply(400); +#else + DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize); + DeserializationError error = deserializeJson(jsonBuffer, request->body()); + if (error) + return request->reply(400); + + JsonVariant json = jsonBuffer.as(); +#endif + + return _onRequest(request, json); + } else + return request->reply(500); + } +}; +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicRequest.cpp b/lib/PsychicHttp/src_old/PsychicRequest.cpp new file mode 100644 index 000000000..50a163ca8 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicRequest.cpp @@ -0,0 +1,548 @@ +#include "PsychicRequest.h" +#include "PsychicResponse.h" +#include + +PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) : + _server(server), + _req(req), + _method(HTTP_GET), + _query(""), + _body(""), + _tempObject(NULL) +{ + //load up our client. + this->_client = server->getClient(req); + + //handle our session data + if (req->sess_ctx != NULL) + this->_session = (SessionData *)req->sess_ctx; + else + { + this->_session = new SessionData(); + req->sess_ctx = this->_session; + } + + //callback for freeing the session later + req->free_ctx = this->freeSession; + + //load up some data + this->_uri = String(this->_req->uri); +} + +PsychicRequest::~PsychicRequest() +{ + //temorary user object + if (_tempObject != NULL) + free(_tempObject); + + //our web parameters + for (auto *param : _params) + delete(param); + _params.clear(); +} + +void PsychicRequest::freeSession(void *ctx) +{ + if (ctx != NULL) + { + SessionData *session = (SessionData*)ctx; + delete session; + } +} + +PsychicHttpServer * PsychicRequest::server() { + return _server; +} + +httpd_req_t * PsychicRequest::request() { + return _req; +} + +PsychicClient * PsychicRequest::client() { + return _client; +} + +const String PsychicRequest::getFilename() +{ + //parse the content-disposition header + if (this->hasHeader("Content-Disposition")) + { + ContentDisposition cd = this->getContentDisposition(); + if (cd.filename != "") + return cd.filename; + } + + //fall back to passed in query string + PsychicWebParameter *param = getParam("_filename"); + if (param != NULL) + return param->name(); + + //fall back to parsing it from url (useful for wildcard uploads) + String uri = this->uri(); + int filenameStart = uri.lastIndexOf('/') + 1; + String filename = uri.substring(filenameStart); + if (filename != "") + return filename; + + //finally, unknown. + ESP_LOGE(PH_TAG, "Did not get a valid filename from the upload."); + return "unknown.txt"; +} + +const ContentDisposition PsychicRequest::getContentDisposition() +{ + ContentDisposition cd; + String header = this->header("Content-Disposition"); + int start; + int end; + + if (header.indexOf("form-data") == 0) + cd.disposition = FORM_DATA; + else if (header.indexOf("attachment") == 0) + cd.disposition = ATTACHMENT; + else if (header.indexOf("inline") == 0) + cd.disposition = INLINE; + else + cd.disposition = NONE; + + start = header.indexOf("filename="); + if (start) + { + end = header.indexOf('"', start+10); + cd.filename = header.substring(start+10, end-1); + } + + start = header.indexOf("name="); + if (start) + { + end = header.indexOf('"', start+6); + cd.name = header.substring(start+6, end-1); + } + + return cd; +} + +esp_err_t PsychicRequest::loadBody() +{ + esp_err_t err = ESP_OK; + + this->_body = String(); + + //Get header value string length and allocate memory for length + 1, extra byte for null termination + size_t remaining = this->_req->content_len; + char *buf = (char *)malloc(remaining+1); + + //while loop for retries + while (remaining > 0) + { + //read our data from the socket + int received = httpd_req_recv(this->_req, buf, this->_req->content_len); + + //Retry if timeout occurred + if (received == HTTPD_SOCK_ERR_TIMEOUT) + continue; + //bail if we got an error + else if (received == HTTPD_SOCK_ERR_FAIL) + { + err = ESP_FAIL; + break; + } + + //keep track of our + remaining -= received; + } + + //null terminate and make our string + buf[this->_req->content_len] = '\0'; + this->_body = String(buf); + + //keep track of that pesky memory + free(buf); + + return err; +} + +http_method PsychicRequest::method() { + return (http_method)this->_req->method; +} + +const String PsychicRequest::methodStr() { + return String(http_method_str((http_method)this->_req->method)); +} + +const String PsychicRequest::path() { + int index = _uri.indexOf("?"); + if(index == -1) + return _uri; + else + return _uri.substring(0, index); +} + +const String& PsychicRequest::uri() { + return this->_uri; +} + +const String& PsychicRequest::query() { + return this->_query; +} + +// no way to get list of headers yet.... +// int PsychicRequest::headers() +// { +// } + +const String PsychicRequest::header(const char *name) +{ + size_t header_len = httpd_req_get_hdr_value_len(this->_req, name); + + //if we've got one, allocated it and load it + if (header_len) + { + char header[header_len+1]; + httpd_req_get_hdr_value_str(this->_req, name, header, sizeof(header)); + return String(header); + } + else + return ""; +} + +bool PsychicRequest::hasHeader(const char *name) +{ + return httpd_req_get_hdr_value_len(this->_req, name) > 0; +} + +const String PsychicRequest::host() { + return this->header("Host"); +} + +const String PsychicRequest::contentType() { + return header("Content-Type"); +} + +size_t PsychicRequest::contentLength() { + return this->_req->content_len; +} + +const String& PsychicRequest::body() +{ + return this->_body; +} + +bool PsychicRequest::isMultipart() +{ + const String& type = this->contentType(); + + return (this->contentType().indexOf("multipart/form-data") >= 0); +} + +esp_err_t PsychicRequest::redirect(const char *url) +{ + PsychicResponse response(this); + response.setCode(301); + response.addHeader("Location", url); + + return response.send(); +} + +bool PsychicRequest::hasCookie(const char *key) +{ + char cookie[MAX_COOKIE_SIZE]; + size_t cookieSize = MAX_COOKIE_SIZE; + esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize); + + //did we get anything? + if (err == ESP_OK) + return true; + else if (err == ESP_ERR_HTTPD_RESULT_TRUNC) + ESP_LOGE(PH_TAG, "cookie too large (%d bytes).\n", cookieSize); + + return false; +} + +const String PsychicRequest::getCookie(const char *key) +{ + char cookie[MAX_COOKIE_SIZE]; + size_t cookieSize = MAX_COOKIE_SIZE; + esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize); + + //did we get anything? + if (err == ESP_OK) + return String(cookie); + else + return ""; +} + +void PsychicRequest::loadParams() +{ + //did we get a query string? + size_t query_len = httpd_req_get_url_query_len(_req); + if (query_len) + { + char query[query_len+1]; + httpd_req_get_url_query_str(_req, query, sizeof(query)); + _query.concat(query); + + //parse them. + _addParams(_query); + } + + //did we get form data as body? + if (this->method() == HTTP_POST && this->contentType() == "application/x-www-form-urlencoded") + { + _addParams(_body); + } +} + +void PsychicRequest::_addParams(const String& params){ + size_t start = 0; + while (start < params.length()){ + int end = params.indexOf('&', start); + if (end < 0) end = params.length(); + int equal = params.indexOf('=', start); + if (equal < 0 || equal > end) equal = end; + String name = params.substring(start, equal); + String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); + addParam(name, value); + start = end + 1; + } +} + +PsychicWebParameter * PsychicRequest::addParam(const String &name, const String &value, bool decode) +{ + if (decode) + return addParam(new PsychicWebParameter(urlDecode(name.c_str()), urlDecode(value.c_str()))); + else + return addParam(new PsychicWebParameter(name, value)); +} + +PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) { + _params.push_back(param); + return param; +} + +bool PsychicRequest::hasParam(const char *key) +{ + return getParam(key) != NULL; +} + +PsychicWebParameter * PsychicRequest::getParam(const char *key) +{ + for (auto *param : _params) + if (param->name().equals(key)) + return param; + + return NULL; +} + +bool PsychicRequest::hasSessionKey(const String& key) +{ + return this->_session->find(key) != this->_session->end(); +} + +const String PsychicRequest::getSessionKey(const String& key) +{ + auto it = this->_session->find(key); + if (it != this->_session->end()) + return it->second; + else + return ""; +} + +void PsychicRequest::setSessionKey(const String& key, const String& value) +{ + this->_session->insert(std::pair(key, value)); +} + +static const String md5str(const String &in){ + MD5Builder md5 = MD5Builder(); + md5.begin(); + md5.add(in); + md5.calculate(); + return md5.toString(); +} + +bool PsychicRequest::authenticate(const char * username, const char * password) +{ + if(hasHeader("Authorization")) + { + String authReq = header("Authorization"); + if(authReq.startsWith("Basic")){ + authReq = authReq.substring(6); + authReq.trim(); + char toencodeLen = strlen(username)+strlen(password)+1; + char *toencode = new char[toencodeLen + 1]; + if(toencode == NULL){ + authReq = ""; + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + authReq = ""; + delete[] toencode; + return false; + } + sprintf(toencode, "%s:%s", username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { + authReq = ""; + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + } + else if(authReq.startsWith(F("Digest"))) + { + authReq = authReq.substring(7); + String _username = _extractParam(authReq,F("username=\""),'\"'); + if(!_username.length() || _username != String(username)) { + authReq = ""; + return false; + } + // extracting required parameters for RFC 2069 simpler Digest + String _realm = _extractParam(authReq, F("realm=\""),'\"'); + String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); + String _uri = _extractParam(authReq, F("uri=\""),'\"'); + String _resp = _extractParam(authReq, F("response=\""),'\"'); + String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); + + if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_resp.length()) || (!_opaque.length())) { + authReq = ""; + return false; + } + if((_opaque != this->getSessionKey("opaque")) || (_nonce != this->getSessionKey("nonce")) || (_realm != this->getSessionKey("realm"))) + { + // DUMP(_opaque); + // DUMP(this->getSessionKey("opaque")); + // DUMP(_nonce); + // DUMP(this->getSessionKey("nonce")); + // DUMP(_realm); + // DUMP(this->getSessionKey("realm")); + authReq = ""; + return false; + } + // parameters for the RFC 2617 newer Digest + String _nc,_cnonce; + if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { + _nc = _extractParam(authReq, F("nc="), ','); + _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); + } + String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password)); + ESP_LOGD(PH_TAG, "Hash of user:realm:pass=%s", _H1); + String _H2 = ""; + if(_method == HTTP_GET){ + _H2 = md5str(String(F("GET:")) + _uri); + }else if(_method == HTTP_POST){ + _H2 = md5str(String(F("POST:")) + _uri); + }else if(_method == HTTP_PUT){ + _H2 = md5str(String(F("PUT:")) + _uri); + }else if(_method == HTTP_DELETE){ + _H2 = md5str(String(F("DELETE:")) + _uri); + }else{ + _H2 = md5str(String(F("GET:")) + _uri); + } + ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2); + String _responsecheck = ""; + if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); + } else { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); + } + ESP_LOGD(PH_TAG, "The Proper response=%s", _responsecheck); + if(_resp == _responsecheck){ + authReq = ""; + return true; + } + } + authReq = ""; + } + return false; +} + +const String PsychicRequest::_extractParam(const String& authReq, const String& param, const char delimit) +{ + int _begin = authReq.indexOf(param); + if (_begin == -1) + return ""; + return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); +} + +const String PsychicRequest::_getRandomHexString() +{ + char buffer[33]; // buffer to hold 32 Hex Digit + /0 + int i; + for(i = 0; i < 4; i++) { + sprintf (buffer + (i*8), "%08lx", esp_random()); + } + return String(buffer); +} + +esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg) +{ + //what is thy realm, sire? + if(!strcmp(realm, "")) + this->setSessionKey("realm", "Login Required"); + else + this->setSessionKey("realm", realm); + + PsychicResponse response(this); + String authStr; + + //what kind of auth? + if(mode == BASIC_AUTH) + { + authStr = "Basic realm=\"" + this->getSessionKey("realm") + "\""; + response.addHeader("WWW-Authenticate", authStr.c_str()); + } + else + { + //only make new ones if we havent sent them yet + if (this->getSessionKey("nonce").isEmpty()) + this->setSessionKey("nonce", _getRandomHexString()); + if (this->getSessionKey("opaque").isEmpty()) + this->setSessionKey("opaque", _getRandomHexString()); + + authStr = "Digest realm=\"" + this->getSessionKey("realm") + "\", qop=\"auth\", nonce=\"" + this->getSessionKey("nonce") + "\", opaque=\"" + this->getSessionKey("opaque") + "\""; + response.addHeader("WWW-Authenticate", authStr.c_str()); + } + + //DUMP(authStr); + + response.setCode(401); + response.setContentType("text/html"); + response.setContent(authStr.c_str()); + return response.send(); +} + +esp_err_t PsychicRequest::reply(int code) +{ + PsychicResponse response(this); + + response.setCode(code); + response.setContentType("text/plain"); + response.setContent(http_status_reason(code)); + + return response.send(); +} + +esp_err_t PsychicRequest::reply(const char *content) +{ + PsychicResponse response(this); + + response.setCode(200); + response.setContentType("text/html"); + response.setContent(content); + + return response.send(); +} + +esp_err_t PsychicRequest::reply(int code, const char *contentType, const char *content) +{ + PsychicResponse response(this); + + response.setCode(code); + response.setContentType(contentType); + response.setContent(content); + + return response.send(); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicRequest.h b/lib/PsychicHttp/src_old/PsychicRequest.h new file mode 100644 index 000000000..8ab6340ca --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicRequest.h @@ -0,0 +1,97 @@ +#ifndef PsychicRequest_h +#define PsychicRequest_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include "PsychicClient.h" +#include "PsychicWebParameter.h" + +typedef std::map SessionData; + +enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA}; + +struct ContentDisposition { + Disposition disposition; + String filename; + String name; +}; + +class PsychicRequest { + friend PsychicHttpServer; + + protected: + PsychicHttpServer *_server; + httpd_req_t *_req; + SessionData *_session; + PsychicClient *_client; + + http_method _method; + String _uri; + String _query; + String _body; + + std::list _params; + + void _addParams(const String& params); + void _parseGETParams(); + void _parsePOSTParams(); + + const String _extractParam(const String& authReq, const String& param, const char delimit); + const String _getRandomHexString(); + + public: + PsychicRequest(PsychicHttpServer *server, httpd_req_t *req); + virtual ~PsychicRequest(); + + void *_tempObject; + + PsychicHttpServer * server(); + httpd_req_t * request(); + virtual PsychicClient * client(); + + bool isMultipart(); + esp_err_t loadBody(); + + const String header(const char *name); + bool hasHeader(const char *name); + + static void freeSession(void *ctx); + bool hasSessionKey(const String& key); + const String getSessionKey(const String& key); + void setSessionKey(const String& key, const String& value); + + bool hasCookie(const char * key); + const String getCookie(const char * key); + + http_method method(); // returns the HTTP method used as enum value (eg. HTTP_GET) + const String methodStr(); // returns the HTTP method used as a string (eg. "GET") + const String path(); // returns the request path (eg /page?foo=bar returns "/page") + const String& uri(); // returns the full request uri (eg /page?foo=bar) + const String& query(); // returns the request query data (eg /page?foo=bar returns "foo=bar") + const String host(); // returns the requested host (request to http://psychic.local/foo will return "psychic.local") + const String contentType(); // returns the Content-Type header value + size_t contentLength(); // returns the Content-Length header value + const String& body(); // returns the body of the request + const ContentDisposition getContentDisposition(); + + const String& queryString() { return query(); } //compatability function. same as query() + const String& url() { return uri(); } //compatability function. same as uri() + + void loadParams(); + PsychicWebParameter * addParam(PsychicWebParameter *param); + PsychicWebParameter * addParam(const String &name, const String &value, bool decode = true); + bool hasParam(const char *key); + PsychicWebParameter * getParam(const char *name); + + const String getFilename(); + + bool authenticate(const char * username, const char * password); + esp_err_t requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg); + + esp_err_t redirect(const char *url); + esp_err_t reply(int code); + esp_err_t reply(const char *content); + esp_err_t reply(int code, const char *contentType, const char *content); +}; + +#endif // PsychicRequest_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicResponse.cpp b/lib/PsychicHttp/src_old/PsychicResponse.cpp new file mode 100644 index 000000000..0aa3ae8fc --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicResponse.cpp @@ -0,0 +1,138 @@ +#include "PsychicResponse.h" +#include "PsychicRequest.h" +#include + +PsychicResponse::PsychicResponse(PsychicRequest * request) + : _request(request) + , _code(200) + , _status("") + , _contentLength(0) + , _body("") { +} + +PsychicResponse::~PsychicResponse() { + //clean up our header variables. we have to do this since httpd_resp_send doesn't store copies + for (HTTPHeader header : _headers) { + free(header.field); + free(header.value); + } + _headers.clear(); +} + +void PsychicResponse::addHeader(const char * field, const char * value) { + //these get freed during send() + HTTPHeader header; + header.field = (char *)malloc(strlen(field) + 1); + header.value = (char *)malloc(strlen(value) + 1); + + strlcpy(header.field, field, strlen(field) + 1); + strlcpy(header.value, value, strlen(value) + 1); + + _headers.push_back(header); +} + +void PsychicResponse::setCookie(const char * name, const char * value, unsigned long secondsFromNow, const char * extras) { + time_t now = time(nullptr); + + String output; + output = urlEncode(name) + "=" + urlEncode(value); + + //if current time isn't modern, default to using max age + if (now < 1700000000) + output += "; Max-Age=" + String(secondsFromNow); + //otherwise, set an expiration date + else { + time_t expirationTimestamp = now + secondsFromNow; + + // Convert the expiration timestamp to a formatted string for the "expires" attribute + struct tm * tmInfo = gmtime(&expirationTimestamp); + char expires[30]; + strftime(expires, sizeof(expires), "%a, %d %b %Y %H:%M:%S GMT", tmInfo); + output += "; Expires=" + String(expires); + } + + //did we get any extras? + if (strlen(extras)) + output += "; " + String(extras); + + //okay, add it in. + addHeader("Set-Cookie", output.c_str()); +} + +// time_t now = time(nullptr); +// // Set the cookie with the "expires" attribute + +void PsychicResponse::setCode(int code) { + _code = code; +} + +void PsychicResponse::setContentType(const char * contentType) { + httpd_resp_set_type(_request->request(), contentType); +} + +void PsychicResponse::setContent(const char * content) { + _body = content; + setContentLength(strlen(content)); +} + +void PsychicResponse::setContent(const uint8_t * content, size_t len) { + _body = (char *)content; + setContentLength(len); +} + +const char * PsychicResponse::getContent() { + return _body; +} + +size_t PsychicResponse::getContentLength() { + return _contentLength; +} + +esp_err_t PsychicResponse::send() { + //esp-idf makes you set the whole status. + sprintf(_status, "%u %s", _code, http_status_reason(_code)); + httpd_resp_set_status(_request->request(), _status); + + //our headers too + this->sendHeaders(); + + //now send it off + esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength()); + + //did something happen? + if (err != ESP_OK) + ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err)); + + return err; +} + +void PsychicResponse::sendHeaders() { + //get our global headers out of the way first + for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) + httpd_resp_set_hdr(_request->request(), header.field, header.value); + + //now do our individual headers + for (HTTPHeader header : _headers) + httpd_resp_set_hdr(this->_request->request(), header.field, header.value); +} + +esp_err_t PsychicResponse::sendChunk(uint8_t * chunk, size_t chunksize) { + /* Send the buffer contents as HTTP response chunk */ + esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize); + if (err != ESP_OK) { + ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err)); + + /* Abort sending file */ + httpd_resp_sendstr_chunk(this->_request->request(), NULL); + + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); + } + + return err; +} + +esp_err_t PsychicResponse::finishChunking() { + /* Respond with an empty chunk to signal HTTP response completion */ + return httpd_resp_send_chunk(this->_request->request(), NULL, 0); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicResponse.h b/lib/PsychicHttp/src_old/PsychicResponse.h new file mode 100644 index 000000000..9fd11f420 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicResponse.h @@ -0,0 +1,49 @@ +#ifndef PsychicResponse_h +#define PsychicResponse_h + +#include "PsychicCore.h" +#include "time.h" + +class PsychicRequest; + +class PsychicResponse { + protected: + PsychicRequest * _request; + + int _code; + char _status[60]; + std::list _headers; + int64_t _contentLength; + const char * _body; + + public: + PsychicResponse(PsychicRequest * request); + virtual ~PsychicResponse(); + + void setCode(int code); + + void setContentType(const char * contentType); + void setContentLength(int64_t contentLength) { + _contentLength = contentLength; + } + int64_t getContentLength(int64_t contentLength) { + return _contentLength; + } + + void addHeader(const char * field, const char * value); + + void setCookie(const char * key, const char * value, unsigned long max_age = 60 * 60 * 24 * 30, const char * extras = ""); + + void setContent(const char * content); + void setContent(const uint8_t * content, size_t len); + + const char * getContent(); + size_t getContentLength(); + + virtual esp_err_t send(); + void sendHeaders(); + esp_err_t sendChunk(uint8_t * chunk, size_t chunksize); + esp_err_t finishChunking(); +}; + +#endif // PsychicResponse_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicStaticFileHander.cpp b/lib/PsychicHttp/src_old/PsychicStaticFileHander.cpp new file mode 100644 index 000000000..20d6d6b61 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicStaticFileHander.cpp @@ -0,0 +1,195 @@ +#include "PsychicStaticFileHandler.h" + +/*************************************/ +/* PsychicStaticFileHandler */ +/*************************************/ + +PsychicStaticFileHandler::PsychicStaticFileHandler(const char * uri, FS & fs, const char * path, const char * cache_control) + : _fs(fs) + , _uri(uri) + , _path(path) + , _default_file("index.html") + , _cache_control(cache_control) + , _last_modified("") { + // Ensure leading '/' + if (_uri.length() == 0 || _uri[0] != '/') + _uri = "/" + _uri; + if (_path.length() == 0 || _path[0] != '/') + _path = "/" + _path; + + // If path ends with '/' we assume a hint that this is a directory to improve performance. + // However - if it does not end with '/' we, can't assume a file, path can still be a directory. + _isDir = _path[_path.length() - 1] == '/'; + + // Remove the trailing '/' so we can handle default file + // Notice that root will be "" not "/" + if (_uri[_uri.length() - 1] == '/') + _uri = _uri.substring(0, _uri.length() - 1); + if (_path[_path.length() - 1] == '/') + _path = _path.substring(0, _path.length() - 1); + + // Reset stats + _gzipFirst = false; + _gzipStats = 0xF8; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setIsDir(bool isDir) { + _isDir = isDir; + return *this; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setDefaultFile(const char * filename) { + _default_file = String(filename); + return *this; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setCacheControl(const char * cache_control) { + _cache_control = String(cache_control); + return *this; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setLastModified(const char * last_modified) { + _last_modified = String(last_modified); + return *this; +} + +PsychicStaticFileHandler & PsychicStaticFileHandler::setLastModified(struct tm * last_modified) { + char result[30]; + strftime(result, 30, "%a, %d %b %Y %H:%M:%S %Z", last_modified); + return setLastModified((const char *)result); +} + +bool PsychicStaticFileHandler::canHandle(PsychicRequest * request) { + if (request->method() != HTTP_GET || !request->uri().startsWith(_uri)) + return false; + + if (_getFile(request)) + return true; + + return false; +} + +bool PsychicStaticFileHandler::_getFile(PsychicRequest * request) { + // Remove the found uri + String path = request->uri().substring(_uri.length()); + + // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' + bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/'); + + path = _path + path; + + // Do we have a file or .gz file + if (!canSkipFileCheck && _fileExists(path)) + return true; + + // Can't handle if not default file + if (_default_file.length() == 0) + return false; + + // Try to add default file, ensure there is a trailing '/' ot the path. + if (path.length() == 0 || path[path.length() - 1] != '/') + path += "/"; + path += _default_file; + + return _fileExists(path); +} + +#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) + +bool PsychicStaticFileHandler::_fileExists(const String & path) { + bool fileFound = false; + bool gzipFound = false; + + String gzip = path + ".gz"; + + if (_gzipFirst) { + _file = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(_file); + if (!gzipFound) { + _file = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(_file); + } + } else { + _file = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(_file); + if (!fileFound) { + _file = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(_file); + } + } + + bool found = fileFound || gzipFound; + + if (found) { + _filename = path; + + // Calculate gzip statistic + _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); + if (_gzipStats == 0x00) + _gzipFirst = false; // All files are not gzip + else if (_gzipStats == 0xFF) + _gzipFirst = true; // All files are gzip + else + _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first + } + + return found; +} + +uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const { + uint8_t w = value; + uint8_t n; + for (n = 0; w != 0; n++) + w &= w - 1; + return n; +} + +esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest * request) { + if (_file == true) { + DUMP(_filename); + + //is it not modified? + String etag = String(_file.size()); + if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { + DUMP("Last Modified Hit"); + TRACE(); + _file.close(); + request->reply(304); // Not modified + } + //does our Etag match? + else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) { + DUMP("Etag Hit"); + DUMP(etag); + DUMP(_cache_control); + + _file.close(); + + PsychicResponse response(request); + response.addHeader("Cache-Control", _cache_control.c_str()); + response.addHeader("ETag", etag.c_str()); + response.setCode(304); + response.send(); + } + //nope, send them the full file. + else { + DUMP("No cache hit"); + DUMP(_last_modified); + DUMP(_cache_control); + + PsychicFileResponse response(request, _fs, _filename); + + if (_last_modified.length()) + response.addHeader("Last-Modified", _last_modified.c_str()); + if (_cache_control.length()) { + response.addHeader("Cache-Control", _cache_control.c_str()); + response.addHeader("ETag", etag.c_str()); + } + + return response.send(); + } + } else { + return request->reply(404); + } + + return ESP_OK; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicStaticFileHandler.h b/lib/PsychicHttp/src_old/PsychicStaticFileHandler.h new file mode 100644 index 000000000..126b9beb7 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicStaticFileHandler.h @@ -0,0 +1,44 @@ +#ifndef PsychicStaticFileHandler_h +#define PsychicStaticFileHandler_h + +#include "PsychicCore.h" +#include "PsychicWebHandler.h" +#include "PsychicRequest.h" +#include "PsychicResponse.h" +#include "PsychicFileResponse.h" + +class PsychicStaticFileHandler : public PsychicWebHandler { + using File = fs::File; + using FS = fs::FS; + + private: + bool _getFile(PsychicRequest * request); + bool _fileExists(const String & path); + uint8_t _countBits(const uint8_t value) const; + + protected: + FS _fs; + File _file; + String _filename; + String _uri; + String _path; + String _default_file; + String _cache_control; + String _last_modified; + bool _isDir; + bool _gzipFirst; + uint8_t _gzipStats; + + public: + PsychicStaticFileHandler(const char * uri, FS & fs, const char * path, const char * cache_control); + bool canHandle(PsychicRequest * request) override; + esp_err_t handleRequest(PsychicRequest * request) override; + PsychicStaticFileHandler & setIsDir(bool isDir); + PsychicStaticFileHandler & setDefaultFile(const char * filename); + PsychicStaticFileHandler & setCacheControl(const char * cache_control); + PsychicStaticFileHandler & setLastModified(const char * last_modified); + PsychicStaticFileHandler & setLastModified(struct tm * last_modified); + //PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} +}; + +#endif /* PsychicHttp_h */ \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicUploadHandler.cpp b/lib/PsychicHttp/src_old/PsychicUploadHandler.cpp new file mode 100644 index 000000000..7c8d07173 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicUploadHandler.cpp @@ -0,0 +1,395 @@ +#include "PsychicUploadHandler.h" + +PsychicUploadHandler::PsychicUploadHandler() : + PsychicWebHandler() + , _temp() + , _parsedLength(0) + , _multiParseState(EXPECT_BOUNDARY) + , _boundaryPosition(0) + , _itemStartIndex(0) + , _itemSize(0) + , _itemName() + , _itemFilename() + , _itemType() + , _itemValue() + , _itemBuffer(0) + , _itemBufferIndex(0) + , _itemIsFile(false) + {} +PsychicUploadHandler::~PsychicUploadHandler() {} + +bool PsychicUploadHandler::canHandle(PsychicRequest *request) { + return true; +} + +esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest *request) +{ + esp_err_t err = ESP_OK; + + //save it for later (multipart) + _request = request; + + /* File cannot be larger than a limit */ + if (request->contentLength() > request->server()->maxUploadSize) + { + ESP_LOGE(PH_TAG, "File too large : %d bytes", request->contentLength()); + + /* Respond with 400 Bad Request */ + char error[50]; + sprintf(error, "File size must be less than %u bytes!", request->server()->maxUploadSize); + httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); + + /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ + return ESP_FAIL; + } + + //we might want to access some of these params + request->loadParams(); + + //TODO: support for the 100 header. not sure if we can do it. + // if (request->header("Expect").equals("100-continue")) + // { + // char response[] = "100 Continue"; + // httpd_socket_send(self->server, httpd_req_to_sockfd(req), response, strlen(response), 0); + // } + + //2 types of upload requests + if (request->isMultipart()) + err = _multipartUploadHandler(request); + else + err = _basicUploadHandler(request); + + //we can also call onRequest for some final processing and response + if (err == ESP_OK) + { + if (_requestCallback != NULL) + err = _requestCallback(request); + else + err = request->reply("Upload Successful."); + } + else + request->reply(500, "text/html", "Error processing upload."); + + return err; +} + +esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request) +{ + esp_err_t err = ESP_OK; + + String filename = request->getFilename(); + + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *buf = (char *)malloc(FILE_CHUNK_SIZE); + int received; + unsigned long index = 0; + + /* Content length of the request gives the size of the file being uploaded */ + int remaining = request->contentLength(); + + while (remaining > 0) + { + #ifdef ENABLE_ASYNC + httpd_sess_update_lru_counter(request->server()->server, request->client()->socket()); + #endif + + ESP_LOGI(PH_TAG, "Remaining size : %d", remaining); + + /* Receive the file part by part into a buffer */ + if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0) + { + /* Retry if timeout occurred */ + if (received == HTTPD_SOCK_ERR_TIMEOUT) + continue; + //bail if we got an error + else if (received == HTTPD_SOCK_ERR_FAIL) + { + ESP_LOGE(PH_TAG, "Socket error"); + err = ESP_FAIL; + break; + } + } + + //call our upload callback here. + if (_uploadCallback != NULL) + { + err = _uploadCallback(request, filename, index, (uint8_t *)buf, received, (remaining - received == 0)); + if (err != ESP_OK) + break; + } + else + { + ESP_LOGE(PH_TAG, "No upload callback specified!"); + err = ESP_FAIL; + break; + } + + /* Keep track of remaining size of the file left to be uploaded */ + remaining -= received; + index += received; + } + + //dont forget to free our buffer + free(buf); + + return err; +} + +esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest *request) +{ + esp_err_t err = ESP_OK; + + String value = request->header("Content-Type"); + if (value.startsWith("multipart/")){ + _boundary = value.substring(value.indexOf('=')+1); + _boundary.replace("\"",""); + } else { + ESP_LOGE(PH_TAG, "No multipart boundary found."); + return request->reply(400, "text/html", "No multipart boundary found."); + } + + char *buf = (char *)malloc(FILE_CHUNK_SIZE); + int received; + unsigned long index = 0; + + /* Content length of the request gives the size of the file being uploaded */ + int remaining = request->contentLength(); + + while (remaining > 0) + { + #ifdef ENABLE_ASYNC + httpd_sess_update_lru_counter(request->server()->server, request->client()->socket()); + #endif + + ESP_LOGI(PH_TAG, "Remaining size : %d", remaining); + + /* Receive the file part by part into a buffer */ + if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0) + { + /* Retry if timeout occurred */ + if (received == HTTPD_SOCK_ERR_TIMEOUT) + continue; + //bail if we got an error + else if (received == HTTPD_SOCK_ERR_FAIL) + { + ESP_LOGE(PH_TAG, "Socket error"); + err = ESP_FAIL; + break; + } + } + + //parse it 1 byte at a time. + for (int i=0; i 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type")){ + _itemType = _temp.substring(14); + _itemIsFile = true; + } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition")){ + _temp = _temp.substring(_temp.indexOf(';') + 2); + while(_temp.indexOf(';') > 0){ + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + _temp = _temp.substring(_temp.indexOf(';') + 2); + } + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + } + _temp = String(); + } else { + _multiParseState = WAIT_FOR_RETURN1; + //value starts from here + _itemSize = 0; + _itemStartIndex = _parsedLength; + _itemValue = String(); + if(_itemIsFile){ + if(_itemBuffer) + free(_itemBuffer); + _itemBuffer = (uint8_t*)malloc(FILE_CHUNK_SIZE); + if(_itemBuffer == NULL){ + ESP_LOGE(PH_TAG, "Multipart: Failed to allocate buffer"); + _multiParseState = PARSE_ERROR; + return; + } + _itemBufferIndex = 0; + } + } + } + } else if(_multiParseState == EXPECT_FEED1){ + if(data != '\n'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH1; + } + } else if(_multiParseState == EXPECT_DASH1){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH2; + } + } else if(_multiParseState == EXPECT_DASH2){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = BOUNDARY_OR_DATA; + _boundaryPosition = 0; + } + } else if(_multiParseState == BOUNDARY_OR_DATA){ + if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; + for(i=0; i<_boundaryPosition; i++) + itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } else if(_boundaryPosition == _boundary.length() - 1){ + _multiParseState = DASH3_OR_RETURN2; + if(!_itemIsFile){ + _request->addParam(_itemName, _itemValue); + //_addParam(new AsyncWebParameter(_itemName, _itemValue, true)); + } else { + if(_itemSize){ + if(_uploadCallback) + _uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); + _itemBufferIndex = 0; + _request->addParam(new PsychicWebParameter(_itemName, _itemFilename, true, true, _itemSize)); + } + free(_itemBuffer); + _itemBuffer = NULL; + } + + } else { + _boundaryPosition++; + } + } else if(_multiParseState == DASH3_OR_RETURN2){ + if(data == '-' && (_request->contentLength() - _parsedLength - 4) != 0){ + ESP_LOGE(PH_TAG, "ERROR: The parser got to the end of the POST but is expecting more bytes!"); + _multiParseState = PARSE_ERROR; + return; + } + if(data == '\r'){ + _multiParseState = EXPECT_FEED2; + } else if(data == '-' && _request->contentLength() == (_parsedLength + 4)){ + _multiParseState = PARSING_FINISHED; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } + } else if(_multiParseState == EXPECT_FEED2){ + if(data == '\n'){ + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicUploadHandler.h b/lib/PsychicHttp/src_old/PsychicUploadHandler.h new file mode 100644 index 000000000..df8497334 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicUploadHandler.h @@ -0,0 +1,68 @@ +#ifndef PsychicUploadHandler_h +#define PsychicUploadHandler_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include "PsychicRequest.h" +#include "PsychicWebHandler.h" +#include "PsychicWebParameter.h" + +//callback definitions +typedef std::function PsychicUploadCallback; + +/* +* HANDLER :: Can be attached to any endpoint or as a generic request handler. +*/ + +class PsychicUploadHandler : public PsychicWebHandler { + protected: + PsychicUploadCallback _uploadCallback; + + PsychicRequest * _request; + + String _temp; + size_t _parsedLength; + uint8_t _multiParseState; + String _boundary; + uint8_t _boundaryPosition; + size_t _itemStartIndex; + size_t _itemSize; + String _itemName; + String _itemFilename; + String _itemType; + String _itemValue; + uint8_t * _itemBuffer; + size_t _itemBufferIndex; + bool _itemIsFile; + + esp_err_t _basicUploadHandler(PsychicRequest * request); + esp_err_t _multipartUploadHandler(PsychicRequest * request); + + void _handleUploadByte(uint8_t data, bool last); + void _parseMultipartPostByte(uint8_t data, bool last); + + public: + PsychicUploadHandler(); + ~PsychicUploadHandler(); + + bool canHandle(PsychicRequest * request) override; + esp_err_t handleRequest(PsychicRequest * request) override; + + PsychicUploadHandler * onUpload(PsychicUploadCallback fn); +}; + +enum { + EXPECT_BOUNDARY, + PARSE_HEADERS, + WAIT_FOR_RETURN1, + EXPECT_FEED1, + EXPECT_DASH1, + EXPECT_DASH2, + BOUNDARY_OR_DATA, + DASH3_OR_RETURN2, + EXPECT_FEED2, + PARSING_FINISHED, + PARSE_ERROR +}; + +#endif // PsychicUploadHandler_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicWebHandler.cpp b/lib/PsychicHttp/src_old/PsychicWebHandler.cpp new file mode 100644 index 000000000..dbe8ee051 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicWebHandler.cpp @@ -0,0 +1,46 @@ +#include "PsychicWebHandler.h" + +PsychicWebHandler::PsychicWebHandler() + : PsychicHandler() + , _requestCallback(NULL) { +} +PsychicWebHandler::~PsychicWebHandler() { +} + +bool PsychicWebHandler::canHandle(PsychicRequest * request) { + return true; +} + +esp_err_t PsychicWebHandler::handleRequest(PsychicRequest * request) { + /* Request body cannot be larger than a limit */ + if (request->contentLength() > request->server()->maxRequestBodySize) { + ESP_LOGE(PH_TAG, "Request body too large : %d bytes", request->contentLength()); + + /* Respond with 400 Bad Request */ + char error[60]; + sprintf(error, "Request body must be less than %u bytes!", request->server()->maxRequestBodySize); + httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); + + /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ + return ESP_FAIL; + } + + //get our body loaded up. + esp_err_t err = request->loadBody(); + if (err != ESP_OK) + return err; + + //load our params in. + request->loadParams(); + + //okay, pass on to our callback. + if (this->_requestCallback != NULL) + err = this->_requestCallback(request); + + return err; +} + +PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) { + _requestCallback = fn; + return this; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicWebHandler.h b/lib/PsychicHttp/src_old/PsychicWebHandler.h new file mode 100644 index 000000000..5562dfd10 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicWebHandler.h @@ -0,0 +1,26 @@ +#ifndef PsychicWebHandler_h +#define PsychicWebHandler_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include "PsychicRequest.h" +#include "PsychicHandler.h" + +/* +* HANDLER :: Can be attached to any endpoint or as a generic request handler. +*/ + +class PsychicWebHandler : public PsychicHandler { + protected: + PsychicHttpRequestCallback _requestCallback; + + public: + PsychicWebHandler(); + ~PsychicWebHandler(); + + virtual bool canHandle(PsychicRequest * request) override; + virtual esp_err_t handleRequest(PsychicRequest * request) override; + PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn); +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicWebParameter.h b/lib/PsychicHttp/src_old/PsychicWebParameter.h new file mode 100644 index 000000000..fe6efc7e0 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicWebParameter.h @@ -0,0 +1,41 @@ +#ifndef PsychicWebParameter_h +#define PsychicWebParameter_h + +/* + * PARAMETER :: Chainable object to hold GET/POST and FILE parameters + * */ + +class PsychicWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + PsychicWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0) + : _name(name) + , _value(value) + , _size(size) + , _isForm(form) + , _isFile(file) { + } + const String & name() const { + return _name; + } + const String & value() const { + return _value; + } + size_t size() const { + return _size; + } + bool isPost() const { + return _isForm; + } + bool isFile() const { + return _isFile; + } +}; + +#endif //PsychicWebParameter_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicWebSocket.cpp b/lib/PsychicHttp/src_old/PsychicWebSocket.cpp new file mode 100644 index 000000000..24c99ca73 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicWebSocket.cpp @@ -0,0 +1,243 @@ +#include "PsychicWebSocket.h" + +/*************************************/ +/* PsychicWebSocketRequest */ +/*************************************/ + +PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest * req) + : PsychicRequest(req->server(), req->request()) + , _client(req->client()) { +} + +PsychicWebSocketRequest::~PsychicWebSocketRequest() { +} + +PsychicWebSocketClient * PsychicWebSocketRequest::client() { + return &_client; +} + +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t * ws_pkt) { + return httpd_ws_send_frame(this->_req, ws_pkt); +} + +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void * data, size_t len) { + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t *)data; + ws_pkt.len = len; + ws_pkt.type = op; + + return this->reply(&ws_pkt); +} + +esp_err_t PsychicWebSocketRequest::reply(const char * buf) { + return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +} + +/*************************************/ +/* PsychicWebSocketClient */ +/*************************************/ + +PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient * client) + : PsychicClient(client->server(), client->socket()) { +} + +PsychicWebSocketClient::~PsychicWebSocketClient() { +} + +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt) { + return httpd_ws_send_frame_async(this->server(), this->socket(), ws_pkt); +} + +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void * data, size_t len) { + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t *)data; + ws_pkt.len = len; + ws_pkt.type = op; + + return this->sendMessage(&ws_pkt); +} + +esp_err_t PsychicWebSocketClient::sendMessage(const char * buf) { + return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +} + +PsychicWebSocketHandler::PsychicWebSocketHandler() + : PsychicHandler() + , _onOpen(NULL) + , _onFrame(NULL) + , _onClose(NULL) { +} + +PsychicWebSocketHandler::~PsychicWebSocketHandler() { +} + +PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket) { + PsychicClient * client = PsychicHandler::getClient(socket); + if (client == NULL) + return NULL; + + if (client->_friend == NULL) { + DUMP(socket); + return NULL; + } + + return (PsychicWebSocketClient *)client->_friend; +} + +PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClient * client) { + return getClient(client->socket()); +} + +void PsychicWebSocketHandler::addClient(PsychicClient * client) { + client->_friend = new PsychicWebSocketClient(client); + PsychicHandler::addClient(client); +} + +void PsychicWebSocketHandler::removeClient(PsychicClient * client) { + PsychicHandler::removeClient(client); + delete (PsychicWebSocketClient *)client->_friend; + client->_friend = NULL; +} + +void PsychicWebSocketHandler::openCallback(PsychicClient * client) { + PsychicWebSocketClient * buddy = getClient(client); + if (buddy == NULL) { + TRACE(); + return; + } + + if (_onOpen != NULL) + _onOpen(getClient(buddy)); +} + +void PsychicWebSocketHandler::closeCallback(PsychicClient * client) { + PsychicWebSocketClient * buddy = getClient(client); + if (buddy == NULL) { + TRACE(); + return; + } + + if (_onClose != NULL) + _onClose(getClient(buddy)); +} + +bool PsychicWebSocketHandler::isWebSocket() { + return true; +} + +esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest * request) { + //lookup our client + PsychicClient * client = checkForNewClient(request->client()); + + // beginning of the ws URI handler and our onConnect hook + if (request->method() == HTTP_GET) { + if (client->isNew) + openCallback(client); + + return ESP_OK; + } + + //prep our request + PsychicWebSocketRequest wsRequest(request); + + //init our memory for storing the packet + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + uint8_t * buf = NULL; + + /* Set max_len = 0 to get the frame len */ + esp_err_t ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, 0); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed to get frame len with %s", esp_err_to_name(ret)); + return ret; + } + + //okay, now try to load the packet + ESP_LOGI(PH_TAG, "frame len is %d", ws_pkt.len); + if (ws_pkt.len) { + /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ + buf = (uint8_t *)calloc(1, ws_pkt.len + 1); + if (buf == NULL) { + ESP_LOGE(PH_TAG, "Failed to calloc memory for buf"); + return ESP_ERR_NO_MEM; + } + ws_pkt.payload = buf; + /* Set max_len = ws_pkt.len to get the frame payload */ + ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, ws_pkt.len); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed with %s", esp_err_to_name(ret)); + free(buf); + return ret; + } + ESP_LOGI(PH_TAG, "Got packet with message: %s", ws_pkt.payload); + } + + // Text messages are our payload. + if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) { + if (this->_onFrame != NULL) + ret = this->_onFrame(&wsRequest, &ws_pkt); + } + + //logging housekeeping + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret)); + ESP_LOGI(PH_TAG, + "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", + request->server(), + httpd_req_to_sockfd(request->request()), + httpd_ws_get_fd_info(request->server(), httpd_req_to_sockfd(request->request()))); + + //dont forget to release our buffer memory + free(buf); + + return ret; +} + +PsychicWebSocketHandler * PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn) { + _onOpen = fn; + return this; +} + +PsychicWebSocketHandler * PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn) { + _onFrame = fn; + return this; +} + +PsychicWebSocketHandler * PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn) { + _onClose = fn; + return this; +} + +void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt) { + for (PsychicClient * client : _clients) { + ESP_LOGI(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket()); + + if (client->_friend == NULL) { + TRACE(); + return; + } + + if (((PsychicWebSocketClient *)client->_friend)->sendMessage(ws_pkt) != ESP_OK) + break; + } +} + +void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void * data, size_t len) { + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t *)data; + ws_pkt.len = len; + ws_pkt.type = op; + + this->sendAll(&ws_pkt); +} + +void PsychicWebSocketHandler::sendAll(const char * buf) { + this->sendAll(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/PsychicWebSocket.h b/lib/PsychicHttp/src_old/PsychicWebSocket.h new file mode 100644 index 000000000..bea702197 --- /dev/null +++ b/lib/PsychicHttp/src_old/PsychicWebSocket.h @@ -0,0 +1,68 @@ +#ifndef PsychicWebSocket_h +#define PsychicWebSocket_h + +#include "PsychicCore.h" +#include "PsychicRequest.h" + +class PsychicWebSocketRequest; +class PsychicWebSocketClient; + +//callback function definitions +typedef std::function PsychicWebSocketClientCallback; +typedef std::function PsychicWebSocketFrameCallback; + +class PsychicWebSocketClient : public PsychicClient { + public: + PsychicWebSocketClient(PsychicClient * client); + ~PsychicWebSocketClient(); + + esp_err_t sendMessage(httpd_ws_frame_t * ws_pkt); + esp_err_t sendMessage(httpd_ws_type_t op, const void * data, size_t len); + esp_err_t sendMessage(const char * buf); +}; + +class PsychicWebSocketRequest : public PsychicRequest { + private: + PsychicWebSocketClient _client; + + public: + PsychicWebSocketRequest(PsychicRequest * req); + virtual ~PsychicWebSocketRequest(); + + PsychicWebSocketClient * client() override; + + esp_err_t reply(httpd_ws_frame_t * ws_pkt); + esp_err_t reply(httpd_ws_type_t op, const void * data, size_t len); + esp_err_t reply(const char * buf); +}; + +class PsychicWebSocketHandler : public PsychicHandler { + protected: + PsychicWebSocketClientCallback _onOpen; + PsychicWebSocketFrameCallback _onFrame; + PsychicWebSocketClientCallback _onClose; + + public: + PsychicWebSocketHandler(); + ~PsychicWebSocketHandler(); + + PsychicWebSocketClient * getClient(int socket) override; + PsychicWebSocketClient * getClient(PsychicClient * client) override; + void addClient(PsychicClient * client) override; + void removeClient(PsychicClient * client) override; + void openCallback(PsychicClient * client) override; + void closeCallback(PsychicClient * client) override; + + bool isWebSocket() override final; + esp_err_t handleRequest(PsychicRequest * request) override; + + PsychicWebSocketHandler * onOpen(PsychicWebSocketClientCallback fn); + PsychicWebSocketHandler * onFrame(PsychicWebSocketFrameCallback fn); + PsychicWebSocketHandler * onClose(PsychicWebSocketClientCallback fn); + + void sendAll(httpd_ws_frame_t * ws_pkt); + void sendAll(httpd_ws_type_t op, const void * data, size_t len); + void sendAll(const char * buf); +}; + +#endif // PsychicWebSocket_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/async_worker.cpp b/lib/PsychicHttp/src_old/async_worker.cpp new file mode 100644 index 000000000..b5b7c730a --- /dev/null +++ b/lib/PsychicHttp/src_old/async_worker.cpp @@ -0,0 +1,203 @@ +#include "async_worker.h" + +bool is_on_async_worker_thread(void) +{ + // is our handle one of the known async handles? + TaskHandle_t handle = xTaskGetCurrentTaskHandle(); + for (int i = 0; i < ASYNC_WORKER_COUNT; i++) { + if (worker_handles[i] == handle) { + return true; + } + } + return false; +} + +// Submit an HTTP req to the async worker queue +esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler) +{ + // must create a copy of the request that we own + httpd_req_t* copy = NULL; + esp_err_t err = httpd_req_async_handler_begin(req, ©); + if (err != ESP_OK) { + return err; + } + + httpd_async_req_t async_req = { + .req = copy, + .handler = handler, + }; + + // How should we handle resource exhaustion? + // In this example, we immediately respond with an + // http error if no workers are available. + int ticks = 0; + + // counting semaphore: if success, we know 1 or + // more asyncReqTaskWorkers are available. + if (xSemaphoreTake(worker_ready_count, ticks) == false) { + ESP_LOGE(PH_TAG, "No workers are available"); + httpd_req_async_handler_complete(copy); // cleanup + return ESP_FAIL; + } + + // Since worker_ready_count > 0 the queue should already have space. + // But lets wait up to 100ms just to be safe. + if (xQueueSend(async_req_queue, &async_req, pdMS_TO_TICKS(100)) == false) { + ESP_LOGE(PH_TAG, "worker queue is full"); + httpd_req_async_handler_complete(copy); // cleanup + return ESP_FAIL; + } + + return ESP_OK; +} + +void async_req_worker_task(void *p) +{ + ESP_LOGI(PH_TAG, "starting async req task worker"); + + while (true) { + + // counting semaphore - this signals that a worker + // is ready to accept work + xSemaphoreGive(worker_ready_count); + + // wait for a request + httpd_async_req_t async_req; + if (xQueueReceive(async_req_queue, &async_req, portMAX_DELAY)) { + + ESP_LOGI(PH_TAG, "invoking %s", async_req.req->uri); + + // call the handler + async_req.handler(async_req.req); + + // Inform the server that it can purge the socket used for + // this request, if needed. + if (httpd_req_async_handler_complete(async_req.req) != ESP_OK) { + ESP_LOGE(PH_TAG, "failed to complete async req"); + } + } + } + + ESP_LOGW(PH_TAG, "worker stopped"); + vTaskDelete(NULL); +} + +void start_async_req_workers(void) +{ + + // counting semaphore keeps track of available workers + worker_ready_count = xSemaphoreCreateCounting( + ASYNC_WORKER_COUNT, // Max Count + 0); // Initial Count + if (worker_ready_count == NULL) { + ESP_LOGE(PH_TAG, "Failed to create workers counting Semaphore"); + return; + } + + // create queue + async_req_queue = xQueueCreate(1, sizeof(httpd_async_req_t)); + if (async_req_queue == NULL){ + ESP_LOGE(PH_TAG, "Failed to create async_req_queue"); + vSemaphoreDelete(worker_ready_count); + return; + } + + // start worker tasks + for (int i = 0; i < ASYNC_WORKER_COUNT; i++) { + + bool success = xTaskCreate(async_req_worker_task, "async_req_worker", + ASYNC_WORKER_TASK_STACK_SIZE, // stack size + (void *)0, // argument + ASYNC_WORKER_TASK_PRIORITY, // priority + &worker_handles[i]); + + if (!success) { + ESP_LOGE(PH_TAG, "Failed to start asyncReqWorker"); + continue; + } + } +} + +/**** + * + * This code is backported from the 5.1.x branch + * +****/ + +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +/* Calculate the maximum size needed for the scratch buffer */ +#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN) + +/** + * @brief Auxiliary data structure for use during reception and processing + * of requests and temporarily keeping responses + */ +struct httpd_req_aux { + struct sock_db *sd; /*!< Pointer to socket database */ + char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */ + size_t remaining_len; /*!< Amount of data remaining to be fetched */ + char *status; /*!< HTTP response's status code */ + char *content_type; /*!< HTTP response's content type */ + bool first_chunk_sent; /*!< Used to indicate if first chunk sent */ + unsigned req_hdrs_count; /*!< Count of total headers in request packet */ + unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */ + struct resp_hdr { + const char *field; + const char *value; + } *resp_hdrs; /*!< Additional headers in response packet */ + struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */ +#ifdef CONFIG_HTTPD_WS_SUPPORT + bool ws_handshake_detect; /*!< WebSocket handshake detection flag */ + httpd_ws_type_t ws_type; /*!< WebSocket frame type */ + bool ws_final; /*!< WebSocket FIN bit (final frame or not) */ + uint8_t mask_key[4]; /*!< WebSocket mask key for this payload */ +#endif +}; + +esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out) +{ + if (r == NULL || out == NULL) { + return ESP_ERR_INVALID_ARG; + } + + // alloc async req + httpd_req_t *async = (httpd_req_t *)malloc(sizeof(httpd_req_t)); + if (async == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy(async, r, sizeof(httpd_req_t)); + + // alloc async aux + async->aux = (httpd_req_aux *)malloc(sizeof(struct httpd_req_aux)); + if (async->aux == NULL) { + free(async); + return ESP_ERR_NO_MEM; + } + memcpy(async->aux, r->aux, sizeof(struct httpd_req_aux)); + + // not available in 4.4.x + // mark socket as "in use" + // struct httpd_req_aux *ra = r->aux; + //ra->sd->for_async_req = true; + + *out = async; + + return ESP_OK; +} + +esp_err_t httpd_req_async_handler_complete(httpd_req_t *r) +{ + if (r == NULL) { + return ESP_ERR_INVALID_ARG; + } + + // not available in 4.4.x + // struct httpd_req_aux *ra = (httpd_req_aux *)r->aux; + // ra->sd->for_async_req = false; + + free(r->aux); + free(r); + + return ESP_OK; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/async_worker.h b/lib/PsychicHttp/src_old/async_worker.h new file mode 100644 index 000000000..e77c4a6a1 --- /dev/null +++ b/lib/PsychicHttp/src_old/async_worker.h @@ -0,0 +1,36 @@ +#ifndef async_worker_h +#define async_worker_h + +#include "PsychicCore.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#define ASYNC_WORKER_TASK_PRIORITY 5 +#define ASYNC_WORKER_TASK_STACK_SIZE (4*1024) +#define ASYNC_WORKER_COUNT 8 + +// Async requests are queued here while they wait to be processed by the workers +static QueueHandle_t async_req_queue; + +// Track the number of free workers at any given time +static SemaphoreHandle_t worker_ready_count; + +// Each worker has its own thread +static TaskHandle_t worker_handles[ASYNC_WORKER_COUNT]; + +typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t *req); + +typedef struct { + httpd_req_t* req; + httpd_req_handler_t handler; +} httpd_async_req_t; + +bool is_on_async_worker_thread(void); +esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler); +void async_req_worker_task(void *p); +void start_async_req_workers(void); + +esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out); +esp_err_t httpd_req_async_handler_complete(httpd_req_t *r); + +#endif //async_worker_h \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/http_status.cpp b/lib/PsychicHttp/src_old/http_status.cpp new file mode 100644 index 000000000..ae4b0ee5a --- /dev/null +++ b/lib/PsychicHttp/src_old/http_status.cpp @@ -0,0 +1,185 @@ +#include "http_status.h" + +bool http_informational(int code) { + return code >= 100 && code < 200; +} + +bool http_success(int code) { + return code >= 200 && code < 300; +} + +bool http_redirection(int code) { + return code >= 300 && code < 400; +} + +bool http_client_error(int code) { + return code >= 400 && code < 500; +} + +bool http_server_error(int code) { + return code >= 500 && code < 600; +} + +bool http_failure(int code) { + return code >= 400 && code < 600; +} + +const char * http_status_group(int code) { + if (http_informational(code)) + return "Informational"; + + if (http_success(code)) + return "Success"; + + if (http_redirection(code)) + return "Redirection"; + + if (http_client_error(code)) + return "Client Error"; + + if (http_server_error(code)) + return "Server Error"; + + return "Unknown"; +} + +const char * http_status_reason(int code) { + switch (code) { + /*####### 1xx - Informational #######*/ + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 103: + return "Early Hints"; + + /*####### 2xx - Successful #######*/ + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 208: + return "Already Reported"; + case 226: + return "IM Used"; + + /*####### 3xx - Redirection #######*/ + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + + /*####### 4xx - Client Error #######*/ + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Content Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 418: + return "I'm a teapot"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Content"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 425: + return "Too Early"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + + /*####### 5xx - Server Error #######*/ + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 510: + return "Not Extended"; + case 511: + return "Network Authentication Required"; + + default: + return "Unknown"; + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/src_old/http_status.h b/lib/PsychicHttp/src_old/http_status.h new file mode 100644 index 000000000..12ad33281 --- /dev/null +++ b/lib/PsychicHttp/src_old/http_status.h @@ -0,0 +1,15 @@ +#ifndef MICRO_HTTP_STATUS_H +#define MICRO_HTTP_STATUS_H + +#include + +bool http_informational(int code); +bool http_success(int code); +bool http_redirection(int code); +bool http_client_error(int code); +bool http_server_error(int code); +bool http_failure(int code); +const char * http_status_group(int code); +const char * http_status_reason(int code); + +#endif // MICRO_HTTP_STATUS_H \ No newline at end of file