🛸 Moves PhychicHTTP lib from /libs
This commit is contained in:
@@ -1,16 +0,0 @@
|
|||||||
# v1.1
|
|
||||||
|
|
||||||
* Changed the internal structure to support request handlers on endpoints and generic requests that do not match an endpoint
|
|
||||||
* websockets, uploads, etc should now create an appropriate handler and attach to an endpoint with the server.on() syntax
|
|
||||||
* Added PsychicClient to abstract away some of the internals of ESP-IDF sockets + add convenience
|
|
||||||
* onOpen and onClose callbacks have changed as a result
|
|
||||||
* Added support for EventSource / SSE
|
|
||||||
* Added support for multipart file uploads
|
|
||||||
* changed getParam() to return a PsychicWebParameter in line with ESPAsyncWebserver
|
|
||||||
* Renamed various classes / files:
|
|
||||||
* PsychicHttpFileResponse -> PsychicFileResponse
|
|
||||||
* PsychicHttpServerEndpoint -> PsychicEndpoint
|
|
||||||
* PsychicHttpServerRequest -> PsychicRequest
|
|
||||||
* PsychicHttpServerResponse -> PsychicResponse
|
|
||||||
* PsychicHttpWebsocket.h -> PsychicWebSocket.h
|
|
||||||
* Websocket => WebSocket
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
GNU LESSER GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
|
|
||||||
This version of the GNU Lesser General Public License incorporates
|
|
||||||
the terms and conditions of version 3 of the GNU General Public
|
|
||||||
License, supplemented by the additional permissions listed below.
|
|
||||||
|
|
||||||
0. Additional Definitions.
|
|
||||||
|
|
||||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
|
||||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
|
||||||
General Public License.
|
|
||||||
|
|
||||||
"The Library" refers to a covered work governed by this License,
|
|
||||||
other than an Application or a Combined Work as defined below.
|
|
||||||
|
|
||||||
An "Application" is any work that makes use of an interface provided
|
|
||||||
by the Library, but which is not otherwise based on the Library.
|
|
||||||
Defining a subclass of a class defined by the Library is deemed a mode
|
|
||||||
of using an interface provided by the Library.
|
|
||||||
|
|
||||||
A "Combined Work" is a work produced by combining or linking an
|
|
||||||
Application with the Library. The particular version of the Library
|
|
||||||
with which the Combined Work was made is also called the "Linked
|
|
||||||
Version".
|
|
||||||
|
|
||||||
The "Minimal Corresponding Source" for a Combined Work means the
|
|
||||||
Corresponding Source for the Combined Work, excluding any source code
|
|
||||||
for portions of the Combined Work that, considered in isolation, are
|
|
||||||
based on the Application, and not on the Linked Version.
|
|
||||||
|
|
||||||
The "Corresponding Application Code" for a Combined Work means the
|
|
||||||
object code and/or source code for the Application, including any data
|
|
||||||
and utility programs needed for reproducing the Combined Work from the
|
|
||||||
Application, but excluding the System Libraries of the Combined Work.
|
|
||||||
|
|
||||||
1. Exception to Section 3 of the GNU GPL.
|
|
||||||
|
|
||||||
You may convey a covered work under sections 3 and 4 of this License
|
|
||||||
without being bound by section 3 of the GNU GPL.
|
|
||||||
|
|
||||||
2. Conveying Modified Versions.
|
|
||||||
|
|
||||||
If you modify a copy of the Library, and, in your modifications, a
|
|
||||||
facility refers to a function or data to be supplied by an Application
|
|
||||||
that uses the facility (other than as an argument passed when the
|
|
||||||
facility is invoked), then you may convey a copy of the modified
|
|
||||||
version:
|
|
||||||
|
|
||||||
a) under this License, provided that you make a good faith effort to
|
|
||||||
ensure that, in the event an Application does not supply the
|
|
||||||
function or data, the facility still operates, and performs
|
|
||||||
whatever part of its purpose remains meaningful, or
|
|
||||||
|
|
||||||
b) under the GNU GPL, with none of the additional permissions of
|
|
||||||
this License applicable to that copy.
|
|
||||||
|
|
||||||
3. Object Code Incorporating Material from Library Header Files.
|
|
||||||
|
|
||||||
The object code form of an Application may incorporate material from
|
|
||||||
a header file that is part of the Library. You may convey such object
|
|
||||||
code under terms of your choice, provided that, if the incorporated
|
|
||||||
material is not limited to numerical parameters, data structure
|
|
||||||
layouts and accessors, or small macros, inline functions and templates
|
|
||||||
(ten or fewer lines in length), you do both of the following:
|
|
||||||
|
|
||||||
a) Give prominent notice with each copy of the object code that the
|
|
||||||
Library is used in it and that the Library and its use are
|
|
||||||
covered by this License.
|
|
||||||
|
|
||||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
|
||||||
document.
|
|
||||||
|
|
||||||
4. Combined Works.
|
|
||||||
|
|
||||||
You may convey a Combined Work under terms of your choice that,
|
|
||||||
taken together, effectively do not restrict modification of the
|
|
||||||
portions of the Library contained in the Combined Work and reverse
|
|
||||||
engineering for debugging such modifications, if you also do each of
|
|
||||||
the following:
|
|
||||||
|
|
||||||
a) Give prominent notice with each copy of the Combined Work that
|
|
||||||
the Library is used in it and that the Library and its use are
|
|
||||||
covered by this License.
|
|
||||||
|
|
||||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
|
||||||
document.
|
|
||||||
|
|
||||||
c) For a Combined Work that displays copyright notices during
|
|
||||||
execution, include the copyright notice for the Library among
|
|
||||||
these notices, as well as a reference directing the user to the
|
|
||||||
copies of the GNU GPL and this license document.
|
|
||||||
|
|
||||||
d) Do one of the following:
|
|
||||||
|
|
||||||
0) Convey the Minimal Corresponding Source under the terms of this
|
|
||||||
License, and the Corresponding Application Code in a form
|
|
||||||
suitable for, and under terms that permit, the user to
|
|
||||||
recombine or relink the Application with a modified version of
|
|
||||||
the Linked Version to produce a modified Combined Work, in the
|
|
||||||
manner specified by section 6 of the GNU GPL for conveying
|
|
||||||
Corresponding Source.
|
|
||||||
|
|
||||||
1) Use a suitable shared library mechanism for linking with the
|
|
||||||
Library. A suitable mechanism is one that (a) uses at run time
|
|
||||||
a copy of the Library already present on the user's computer
|
|
||||||
system, and (b) will operate properly with a modified version
|
|
||||||
of the Library that is interface-compatible with the Linked
|
|
||||||
Version.
|
|
||||||
|
|
||||||
e) Provide Installation Information, but only if you would otherwise
|
|
||||||
be required to provide such information under section 6 of the
|
|
||||||
GNU GPL, and only to the extent that such information is
|
|
||||||
necessary to install and execute a modified version of the
|
|
||||||
Combined Work produced by recombining or relinking the
|
|
||||||
Application with a modified version of the Linked Version. (If
|
|
||||||
you use option 4d0, the Installation Information must accompany
|
|
||||||
the Minimal Corresponding Source and Corresponding Application
|
|
||||||
Code. If you use option 4d1, you must provide the Installation
|
|
||||||
Information in the manner specified by section 6 of the GNU GPL
|
|
||||||
for conveying Corresponding Source.)
|
|
||||||
|
|
||||||
5. Combined Libraries.
|
|
||||||
|
|
||||||
You may place library facilities that are a work based on the
|
|
||||||
Library side by side in a single library together with other library
|
|
||||||
facilities that are not Applications and are not covered by this
|
|
||||||
License, and convey such a combined library under terms of your
|
|
||||||
choice, if you do both of the following:
|
|
||||||
|
|
||||||
a) Accompany the combined library with a copy of the same work based
|
|
||||||
on the Library, uncombined with any other library facilities,
|
|
||||||
conveyed under the terms of this License.
|
|
||||||
|
|
||||||
b) Give prominent notice with the combined library that part of it
|
|
||||||
is a work based on the Library, and explaining where to find the
|
|
||||||
accompanying uncombined form of the same work.
|
|
||||||
|
|
||||||
6. Revised Versions of the GNU Lesser General Public License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions
|
|
||||||
of the GNU Lesser General Public License from time to time. Such new
|
|
||||||
versions will be similar in spirit to the present version, but may
|
|
||||||
differ in detail to address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Library as you received it specifies that a certain numbered version
|
|
||||||
of the GNU Lesser General Public License "or any later version"
|
|
||||||
applies to it, you have the option of following the terms and
|
|
||||||
conditions either of that published version or of any later version
|
|
||||||
published by the Free Software Foundation. If the Library as you
|
|
||||||
received it does not specify a version number of the GNU Lesser
|
|
||||||
General Public License, you may choose any version of the GNU Lesser
|
|
||||||
General Public License ever published by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Library as you received it specifies that a proxy can decide
|
|
||||||
whether future versions of the GNU Lesser General Public License shall
|
|
||||||
apply, that proxy's public statement of acceptance of any version is
|
|
||||||
permanent authorization for you to choose that version for the
|
|
||||||
Library.
|
|
||||||
@@ -1,619 +0,0 @@
|
|||||||
# PsychicHttp - HTTP on your ESP 🧙🔮
|
|
||||||
|
|
||||||
PsychicHttp is a webserver library for ESP32 + Arduino framework which uses the [ESP-IDF HTTP Server](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_http_server.html) library under the hood. It is written in a similar style to the [Arduino WebServer](https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer), [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer), and [ArduinoMongoose](https://github.com/jeremypoulter/ArduinoMongoose) libraries to make writing code simple and porting from those other libraries straightforward.
|
|
||||||
|
|
||||||
# Features
|
|
||||||
|
|
||||||
* Asynchronous approach (server runs in its own FreeRTOS thread)
|
|
||||||
* Handles all HTTP methods with lots of convenience functions:
|
|
||||||
* GET/POST parameters
|
|
||||||
* get/set headers
|
|
||||||
* get/set cookies
|
|
||||||
* basic key/value session data storage
|
|
||||||
* authentication (basic and digest mode)
|
|
||||||
* HTTPS / SSL support
|
|
||||||
* Static fileserving (SPIFFS, LittleFS, etc.)
|
|
||||||
* Chunked response serving for large files
|
|
||||||
* File uploads (Basic + Multipart)
|
|
||||||
* Websocket support with onOpen, onFrame, and onClose callbacks
|
|
||||||
* EventSource / SSE support with onOpen, and onClose callbacks
|
|
||||||
* Request filters, including Client vs AP mode (ON_STA_FILTER / ON_AP_FILTER)
|
|
||||||
|
|
||||||
## Differences from ESPAsyncWebserver
|
|
||||||
|
|
||||||
* No templating system (anyone actually use this?)
|
|
||||||
* No url rewriting (but you can use request->redirect)
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Platformio
|
|
||||||
|
|
||||||
[PlatformIO](http://platformio.org) is an open source ecosystem for IoT development.
|
|
||||||
|
|
||||||
Add "PsychicHttp" to project using [Project Configuration File `platformio.ini`](http://docs.platformio.org/page/projectconf.html) and [lib_deps](http://docs.platformio.org/page/projectconf/section_env_library.html#lib-deps) option:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[env:myboard]
|
|
||||||
platform = espressif...
|
|
||||||
board = ...
|
|
||||||
framework = arduino
|
|
||||||
|
|
||||||
# using the latest stable version
|
|
||||||
lib_deps = hoeken/PsychicHttp
|
|
||||||
|
|
||||||
# or using GIT Url (the latest development version)
|
|
||||||
lib_deps = https://github.com/hoeken/PsychicHttp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Installation - Arduino
|
|
||||||
|
|
||||||
Open *Tools -> Manage Libraries...* and search for PsychicHttp.
|
|
||||||
|
|
||||||
# Principles of Operation
|
|
||||||
|
|
||||||
## Things to Note
|
|
||||||
|
|
||||||
* PsychicHttp is a fully asynchronous server and as such does not run on the loop thread.
|
|
||||||
* You should not use yield or delay or any function that uses them inside the callbacks.
|
|
||||||
* The server is smart enough to know when to close the connection and free resources.
|
|
||||||
* You can not send more than one response to a single request.
|
|
||||||
|
|
||||||
## PsychicHttp
|
|
||||||
|
|
||||||
* Listens for connections.
|
|
||||||
* Wraps the incoming request into PsychicRequest.
|
|
||||||
* Keeps track of clients + calls optional callbacks on client open and close.
|
|
||||||
* Find the appropriate handler (if any) for a request and pass it on.
|
|
||||||
|
|
||||||
## Request Life Cycle
|
|
||||||
|
|
||||||
* TCP connection is received by the server.
|
|
||||||
* HTTP request is wrapped inside ```PsychicRequest``` object + TCP Connection wrapped inside PsychicConnection object.
|
|
||||||
* When the request head is received, the server goes through all ```PsychicEndpoints``` and finds one that matches the url + method.
|
|
||||||
* ```handler->filter()``` and ```handler->canHandle()``` are called on the handler to verify the handler should process the request.
|
|
||||||
* ```handler->needsAuthentication()``` is called and sends an authorization response if required.
|
|
||||||
* ```handler->handleRequest()``` is called to actually process the HTTP request.
|
|
||||||
* If the handler cannot process the request, the server will loop through any global handlers and call that handler if it passes filter(), canHandle(), and needsAuthentication().
|
|
||||||
* If no global handlers are called, the server.defaultEndpoint handler will be called.
|
|
||||||
* Each handler is responsible for processing the request and sending a response.
|
|
||||||
* When the response is sent, the client is closed and freed from the memory.
|
|
||||||
* Unless its a special handler like websockets or eventsource.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Handlers
|
|
||||||
|
|
||||||
* ```PsychicHandler``` is used for processing and responding to specific HTTP requests.
|
|
||||||
* ```PsychicHandler``` instances can be attached to any endpoint or as global handlers.
|
|
||||||
* Setting a ```Filter``` to the ```PsychicHandler``` controls when to apply the handler, decision can be based on
|
|
||||||
request method, url, request host/port/target host, the request client's localIP or remoteIP.
|
|
||||||
* Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface,
|
|
||||||
```ON_STA_FILTER``` to execute the rewrite when request is made to the STA interface.
|
|
||||||
* The ```canHandle``` method is used for handler specific control on whether the requests can be handled. Decision can be based on request method, request url, request host/port/target host.
|
|
||||||
* Depending on how the handler is implemented, it may provide callbacks for adding your own custom processing code to the handler.
|
|
||||||
* Global ```Handlers``` are evaluated in the order they are attached to the server. The ```canHandle``` is called only
|
|
||||||
if the ```Filter``` that was set to the ```Handler``` return true.
|
|
||||||
* The first global ```Handler``` that can handle the request is selected, no further processing of handlers is called.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Responses and how do they work
|
|
||||||
|
|
||||||
* The ```PsychicResponse``` objects are used to send the response data back to the client.
|
|
||||||
* Typically the response should be fully generated and sent from the callback.
|
|
||||||
* It may be possible to generate the response outside the callback, but it will be difficult.
|
|
||||||
* The exceptions are websockets + eventsource where the response is sent, but the connection is maintained and new data can be sent/received outside the handler.
|
|
||||||
|
|
||||||
# Porting From ESPAsyncWebserver
|
|
||||||
|
|
||||||
If you have existing code using ESPAsyncWebserver, you will feel right at home with PsychicHttp. Even if internally it is much different, the external interface is very similar. Some things are mostly cosmetic, like different class names and callback definitions. A few things might require a bit more in-depth approach. If you're porting your code and run into issues that aren't covered here, please post and issue.
|
|
||||||
|
|
||||||
## Globals Stuff
|
|
||||||
|
|
||||||
* Change your #include to ```#include <PsychicHttp.h>```
|
|
||||||
* Change your server instance: ```PsychicHttpServer server;```
|
|
||||||
* Define websocket handler if you have one: ```PsychicWebSocketHandler websocketHandler;```
|
|
||||||
* Define eventsource if you have one: ```PsychicEventSource eventSource;```
|
|
||||||
|
|
||||||
## setup() Stuff
|
|
||||||
|
|
||||||
* no more server.begin(), call server.listen(80), before you add your handlers
|
|
||||||
* server has a configurable limit on .on() endpoints. change it with ```server.config.max_uri_handlers = 20;``` as needed.
|
|
||||||
* check your callback function definitions:
|
|
||||||
* AsyncWebServerRequest -> PsychicRequest
|
|
||||||
* no more onBody() event
|
|
||||||
* for small bodies (server.maxRequestBodySize, default 16k) it will be automatically loaded and accessed by request->body()
|
|
||||||
* for large bodies, use an upload handler and onUpload()
|
|
||||||
* websocket callbacks are much different (and simpler!)
|
|
||||||
* websocket / eventsource handlers get attached to url in server.on("/url", &handler) instead of passing url to handler constructor.
|
|
||||||
* eventsource callbacks are onOpen and onClose now.
|
|
||||||
* HTTP_ANY is not supported by ESP-IDF, so we can't use it either.
|
|
||||||
* NO server.onFileUpload(onUpload); (you could attach an UploadHandler to the default endpoint i guess?)
|
|
||||||
* NO server.onRequestBody(onBody); (same)
|
|
||||||
|
|
||||||
## Requests / Responses
|
|
||||||
|
|
||||||
* request->send is now request->reply()
|
|
||||||
* if you create a response, call response->send() directly, not request->send(reply)
|
|
||||||
* request->headers() is not supported by ESP-IDF, you have to just check for the header you need.
|
|
||||||
* No AsyncCallbackJsonWebHandler (for now... can add if needed)
|
|
||||||
* No request->beginResponse(). Instanciate a PsychicResponse instead: ```PsychicResponse response(request);```
|
|
||||||
* No PROGMEM suppport (its not relevant to ESP32: https://esp32.com/viewtopic.php?t=20595)
|
|
||||||
* No Stream response support just yet
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
## Create the Server
|
|
||||||
|
|
||||||
Here is an example of the typical server setup:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <PsychicHttp.h>
|
|
||||||
PsychicHttpServer server;
|
|
||||||
|
|
||||||
void setup()
|
|
||||||
{
|
|
||||||
//optional low level setup server config stuff here.
|
|
||||||
//server.config is an ESP-IDF httpd_config struct
|
|
||||||
//see: https://docs.espressif.com/projects/esp-idf/en/v4.4.6/esp32/api-reference/protocols/esp_http_server.html#_CPPv412httpd_config
|
|
||||||
//increase maximum number of uri endpoint handlers (.on() calls)
|
|
||||||
server.config.max_uri_handlers = 20;
|
|
||||||
|
|
||||||
//connect to wifi
|
|
||||||
|
|
||||||
//start the server listening on port 80 (standard HTTP port)
|
|
||||||
server.listen(80);
|
|
||||||
|
|
||||||
//call server methods to attach endpoints and handlers
|
|
||||||
server.on(...);
|
|
||||||
server.serveStatic(...);
|
|
||||||
server.attachHandler(...);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Add Handlers
|
|
||||||
|
|
||||||
One major difference from ESPAsyncWebserver is that handlers can be attached to a specific url (endpoint) or as a global handler. The reason for this, is that attaching to a specific URL is more efficient and makes for cleaner code.
|
|
||||||
|
|
||||||
### Endpoint Handlers
|
|
||||||
|
|
||||||
An endpoint is basically just the URL path (eg. /path/to/file) without any query string. The ```server.on(...)``` function is a convenience function for creating endpoints and attaching a handler to them. There are two main styles: attaching a basic ```WebRequest``` handler and attaching an external handler.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//creates a basic PsychicWebHandler that calls the request_callback callback
|
|
||||||
server.on("/url", HTTP_GET, request_callback);
|
|
||||||
|
|
||||||
//same as above, but defaults to HTTP_GET
|
|
||||||
server.on("/url", request_callback);
|
|
||||||
|
|
||||||
//attaches a websocket handler to /ws
|
|
||||||
PsychicWebSocketHandler websocketHandler;
|
|
||||||
server.on("/ws", &websocketHandler);
|
|
||||||
```
|
|
||||||
|
|
||||||
The ```server.on(...)``` returns a pointer to the endpoint, which can be used to call various functions like ```setHandler()```, ```setFilter()```, and ```setAuthentication()```.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//respond to /url only from requests to the AP
|
|
||||||
server.on("/url", HTTP_GET, request_callback)->setFilter(ON_AP_FILTER);
|
|
||||||
|
|
||||||
//require authentication on /url
|
|
||||||
server.on("/url", HTTP_GET, request_callback)->setAuthentication("user", "pass");
|
|
||||||
|
|
||||||
//attach websocket handler to /ws
|
|
||||||
PsychicWebSocketHandler websocketHandler;
|
|
||||||
server.on("/ws")->attachHandler(&websocketHandler);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Basic Requests
|
|
||||||
|
|
||||||
The ```PsychicWebHandler``` class is for handling standard web requests. It provides a single callback: ```onRequest()```. This callback is called when the handler receives a valid HTTP request.
|
|
||||||
|
|
||||||
One major difference from ESPAsyncWebserver is that this callback needs to return an esp_err_t variable to let the server know the result of processing the request. The ```response->reply()``` and ```request->send()``` functions will return this. It is a good habit to return the result of these functions as sending the response will close the connection.
|
|
||||||
|
|
||||||
The function definition for the onRequest callback is:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
esp_err_t function_name(PsychicRequest *request);
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is a simple example that sends back the client's IP on the URL /ip
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
server.on("/ip", [](PsychicRequest *request)
|
|
||||||
{
|
|
||||||
String output = "Your IP is: " + request->client()->remoteIP().toString();
|
|
||||||
return request->reply(output.c_str());
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Uploads
|
|
||||||
|
|
||||||
The ```PsychicUploadHandler``` class is for handling uploads, both large POST bodies and multipart encoded forms. It provides two callbacks: ```onUpload()``` and ```onRequest()```.
|
|
||||||
|
|
||||||
```onUpload(...)``` is called when there is new data. This function may be called multiple times so that you can process the data in chunks. The function definition for the onUpload callback is:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
esp_err_t function_name(PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final);
|
|
||||||
```
|
|
||||||
|
|
||||||
* request is a pointer to the Request object
|
|
||||||
* filename is the name of the uploaded file
|
|
||||||
* index is the overall byte position of the current data
|
|
||||||
* data is a pointer to the data buffer
|
|
||||||
* len is the length of the data buffer
|
|
||||||
* final is a flag to tell if its the last chunk of data
|
|
||||||
|
|
||||||
```onRequest(...)``` is called after the successful handling of the upload. Its definition and usage is the same as the basic request example as above.
|
|
||||||
|
|
||||||
#### Basic Upload (file is the entire POST body)
|
|
||||||
|
|
||||||
It's worth noting that there is no standard way of passing in a filename for this method, so the handler attempts to guess the filename with the following methods:
|
|
||||||
|
|
||||||
* Checking the Content-Disposition header
|
|
||||||
* Checking the _filename query parameter (eg. /upload?filename=filename.txt becomes filename.txt)
|
|
||||||
* Checking the url and taking the last part as filename (eg. /upload/filename.txt becomes filename.txt). You must set a wildcard url for this to work as in the example below.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//handle a very basic upload as post body
|
|
||||||
PsychicUploadHandler *uploadHandler = new PsychicUploadHandler();
|
|
||||||
uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
|
|
||||||
File file;
|
|
||||||
String path = "/www/" + filename;
|
|
||||||
|
|
||||||
Serial.printf("Writing %d/%d bytes to: %s\n", (int)index+(int)len, request->contentLength(), path.c_str());
|
|
||||||
|
|
||||||
if (last)
|
|
||||||
Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len);
|
|
||||||
|
|
||||||
//our first call?
|
|
||||||
if (!index)
|
|
||||||
file = LittleFS.open(path, FILE_WRITE);
|
|
||||||
else
|
|
||||||
file = LittleFS.open(path, FILE_APPEND);
|
|
||||||
|
|
||||||
if(!file) {
|
|
||||||
Serial.println("Failed to open file");
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!file.write(data, len)) {
|
|
||||||
Serial.println("Write failed");
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
});
|
|
||||||
|
|
||||||
//gets called after upload has been handled
|
|
||||||
uploadHandler->onRequest([](PsychicRequest *request)
|
|
||||||
{
|
|
||||||
String url = "/" + request->getFilename();
|
|
||||||
String output = "<a href=\"" + url + "\">" + url + "</a>";
|
|
||||||
|
|
||||||
return request->reply(output.c_str());
|
|
||||||
});
|
|
||||||
|
|
||||||
//wildcard basic file upload - POST to /upload/filename.ext
|
|
||||||
server.on("/upload/*", HTTP_POST, uploadHandler);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Multipart Upload
|
|
||||||
|
|
||||||
Very similar to the basic upload, with 2 key differences:
|
|
||||||
|
|
||||||
* multipart requests don't know the total size of the file until after it has been fully processed. You can get a rough idea with request->contentLength(), but that is the length of the entire multipart encoded request.
|
|
||||||
* you can access form variables, including multipart file infor (name + size) in the onRequest handler using request->getParam()
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//a little bit more complicated multipart form
|
|
||||||
PsychicUploadHandler *multipartHandler = new PsychicUploadHandler();
|
|
||||||
multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
|
|
||||||
File file;
|
|
||||||
String path = "/www/" + filename;
|
|
||||||
|
|
||||||
//some progress over serial.
|
|
||||||
Serial.printf("Writing %d bytes to: %s\n", (int)len, path.c_str());
|
|
||||||
if (last)
|
|
||||||
Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len);
|
|
||||||
|
|
||||||
//our first call?
|
|
||||||
if (!index)
|
|
||||||
file = LittleFS.open(path, FILE_WRITE);
|
|
||||||
else
|
|
||||||
file = LittleFS.open(path, FILE_APPEND);
|
|
||||||
|
|
||||||
if(!file) {
|
|
||||||
Serial.println("Failed to open file");
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!file.write(data, len)) {
|
|
||||||
Serial.println("Write failed");
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
});
|
|
||||||
|
|
||||||
//gets called after upload has been handled
|
|
||||||
multipartHandler->onRequest([](PsychicRequest *request)
|
|
||||||
{
|
|
||||||
PsychicWebParameter *file = request->getParam("file_upload");
|
|
||||||
|
|
||||||
String url = "/" + file->value();
|
|
||||||
String output;
|
|
||||||
|
|
||||||
output += "<a href=\"" + url + "\">" + url + "</a><br/>\n";
|
|
||||||
output += "Bytes: " + String(file->size()) + "<br/>\n";
|
|
||||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
|
||||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
|
||||||
|
|
||||||
return request->reply(output.c_str());
|
|
||||||
});
|
|
||||||
|
|
||||||
//upload to /multipart url
|
|
||||||
server.on("/multipart", HTTP_POST, multipartHandler);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Static File Serving
|
|
||||||
|
|
||||||
The ```PsychicStaticFileHandler``` is a special handler that does not provide any callbacks. It is used to serve a file or files from a specific directory in a filesystem to a directory on the webserver. The syntax is exactly the same as ESPAsyncWebserver. Anything that is derived from the ```FS``` class should work (eg. SPIFFS, LittleFS, SD, etc)
|
|
||||||
|
|
||||||
A couple important notes:
|
|
||||||
|
|
||||||
* If it finds a file with an extra .gz extension, it will serve it as gzip encoded (eg: /targetfile.ext -> {targetfile.ext}.gz)
|
|
||||||
* If the file is larger than FILE_CHUNK_SIZE (default 8kb) then it will send it as a chunked response.
|
|
||||||
* It will detect most basic filetypes and automatically set the appropriate Content-Type
|
|
||||||
|
|
||||||
The ```server.serveStatic()``` function handles creating the handler and assigning it to the server:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//serve static files from LittleFS/www on / only to clients on same wifi network
|
|
||||||
//this is where our /index.html file lives
|
|
||||||
server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER);
|
|
||||||
|
|
||||||
//serve static files from LittleFS/www-ap on / only to clients on SoftAP
|
|
||||||
//this is where our /index.html file lives
|
|
||||||
server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER);
|
|
||||||
|
|
||||||
//serve static files from LittleFS/img on /img
|
|
||||||
//it's more efficient to serve everything from a single www directory, but this is also possible.
|
|
||||||
server.serveStatic("/img", LittleFS, "/img/");
|
|
||||||
|
|
||||||
//you can also serve single files
|
|
||||||
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
|
|
||||||
```
|
|
||||||
|
|
||||||
You could also theoretically use the file response directly:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
server.on("/ip", [](PsychicRequest *request)
|
|
||||||
{
|
|
||||||
String filename = "/path/to/file";
|
|
||||||
PsychicFileResponse response(request, LittleFS, filename);
|
|
||||||
|
|
||||||
return response.send();
|
|
||||||
});
|
|
||||||
PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Websockets
|
|
||||||
|
|
||||||
The ```PsychicWebSocketHandler``` class is for handling WebSocket connections. It provides 3 callbacks:
|
|
||||||
|
|
||||||
```onOpen(...)``` is called when a new WebSocket client connects.
|
|
||||||
```onFrame(...)``` is called when a new WebSocket frame has arrived.
|
|
||||||
```onClose(...)``` is called when a new WebSocket client disconnects.
|
|
||||||
|
|
||||||
Here are the callback definitions:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
void open_function(PsychicWebSocketClient *client);
|
|
||||||
esp_err_t frame_function(PsychicWebSocketRequest *request, httpd_ws_frame *frame);
|
|
||||||
void close_function(PsychicWebSocketClient *client);
|
|
||||||
```
|
|
||||||
|
|
||||||
WebSockets were the main reason for starting PsychicHttp, so they are well tested. They are also much simplified from the ESPAsyncWebserver style. You do not need to worry about error handling, partial frame assembly, PONG messages, etc. The onFrame() function is called when a complete frame has been received, and can handle frames up to the entire available heap size.
|
|
||||||
|
|
||||||
Here is a basic example of using WebSockets:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//create our handler... note this should be located as a global or somewhere it wont go out of scope and be destroyed.
|
|
||||||
PsychicWebSocketHandler websocketHandler();
|
|
||||||
|
|
||||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
|
||||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
client->sendMessage("Hello!");
|
|
||||||
});
|
|
||||||
|
|
||||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
|
||||||
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
|
|
||||||
return request->reply(frame);
|
|
||||||
});
|
|
||||||
|
|
||||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
|
||||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
//attach the handler to /ws. You can then connect to ws://ip.address/ws
|
|
||||||
server.on("/ws", &websocketHandler);
|
|
||||||
```
|
|
||||||
|
|
||||||
The onFrame() callback has 2 parameters:
|
|
||||||
|
|
||||||
* ```PsychicWebSocketRequest *request``` a special request with helper functions for replying in websocket format.
|
|
||||||
* ```httpd_ws_frame *frame``` ESP-IDF websocket struct. The important struct members we care about are:
|
|
||||||
* ```uint8_t *payload; /*!< Pre-allocated data buffer */```
|
|
||||||
* ```size_t len; /*!< Length of the WebSocket data */```
|
|
||||||
|
|
||||||
For sending data on the websocket connection, there are 3 methods:
|
|
||||||
|
|
||||||
* ```request->reply()``` - only available in the onFrame() callback context.
|
|
||||||
* ```webSocketHandler.sendAll()``` - can be used anywhere to send websocket messages to all connected clients.
|
|
||||||
* ```client->send()``` - can be used anywhere* to send a websocket message to a specific client
|
|
||||||
|
|
||||||
All of the above functions either accept simple ```char *``` string of you can construct your own httpd_ws_frame.
|
|
||||||
|
|
||||||
*Special Note:* Do not hold on to the ```PsychicWebSocketClient``` for sending messages to clients outside the callbacks. That pointer is destroyed when a client disconnects. Instead, store the ```int client->socket()```. Then when you want to send a message, use this code:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//make sure our client is still connected.
|
|
||||||
PsychicWebSocketClient *client = websocketHandler.getClient(socket);
|
|
||||||
if (client != NULL)
|
|
||||||
client->send("Your Message")
|
|
||||||
```
|
|
||||||
|
|
||||||
### EventSource / SSE
|
|
||||||
|
|
||||||
The ```PsychicEventSource``` class is for handling EventSource / SSE connections. It provides 2 callbacks:
|
|
||||||
|
|
||||||
```onOpen(...)``` is called when a new EventSource client connects.
|
|
||||||
```onClose(...)``` is called when a new EventSource client disconnects.
|
|
||||||
|
|
||||||
Here are the callback definitions:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
void open_function(PsychicEventSourceClient *client);
|
|
||||||
void close_function(PsychicEventSourceClient *client);
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is a basic example of using PsychicEventSource:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//create our handler... note this should be located as a global or somewhere it wont go out of scope and be destroyed.
|
|
||||||
PsychicEventSource eventSource;
|
|
||||||
|
|
||||||
eventSource.onOpen([](PsychicEventSourceClient *client) {
|
|
||||||
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
client->send("Hello user!", NULL, millis(), 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
eventSource.onClose([](PsychicEventSourceClient *client) {
|
|
||||||
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
//attach the handler to /events
|
|
||||||
server.on("/events", &eventSource);
|
|
||||||
```
|
|
||||||
|
|
||||||
For sending data on the EventSource connection, there are 2 methods:
|
|
||||||
|
|
||||||
* ```eventSource.send()``` - can be used anywhere to send events to all connected clients.
|
|
||||||
* ```client->send()``` - can be used anywhere* to send events to a specific client
|
|
||||||
|
|
||||||
All of the above functions accept a simple ```char *``` message, and optionally: ```char *``` event name, id, and reconnect time.
|
|
||||||
|
|
||||||
*Special Note:* Do not hold on to the ```PsychicEventSourceClient``` for sending messages to clients outside the callbacks. That pointer is destroyed when a client disconnects. Instead, store the ```int client->socket()```. Then when you want to send a message, use this code:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//make sure our client is still connected.
|
|
||||||
PsychicEventSourceClient *client = eventSource.getClient(socket);
|
|
||||||
if (client != NULL)
|
|
||||||
client->send("Your Event")
|
|
||||||
```
|
|
||||||
|
|
||||||
### HTTPS / SSL
|
|
||||||
|
|
||||||
PsychicHttp supports HTTPS / SSL out of the box, however there are some limitations (see performance below). Enabling it also increases the code size by about 100kb. To use HTTPS, you need to modify your setup like so:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <PsychicHttp.h>
|
|
||||||
#include <PsychicHttpsServer.h>
|
|
||||||
PsychicHttpsServer server;
|
|
||||||
server.listen(443, server_cert, server_key);
|
|
||||||
```
|
|
||||||
|
|
||||||
```server_cert``` and ```server_key``` are both ```const char *``` parameters which contain the server certificate and private key, respectively.
|
|
||||||
|
|
||||||
To generate your own key and self signed certificate, you can use the command below:
|
|
||||||
|
|
||||||
```
|
|
||||||
openssl req -x509 -newkey rsa:4096 -nodes -keyout server.key -out server.crt -sha256 -days 365
|
|
||||||
```
|
|
||||||
|
|
||||||
Including the ```PsychicHttpsServer.h``` also defines ```PSY_ENABLE_SSL``` which you can use in your code to allow enabling / disabling calls in your code based on if the HTTPS server is available:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//our main server object
|
|
||||||
#ifdef PSY_ENABLE_SSL
|
|
||||||
PsychicHttpsServer server;
|
|
||||||
#else
|
|
||||||
PsychicHttpServer server;
|
|
||||||
#endif
|
|
||||||
```
|
|
||||||
|
|
||||||
Last, but not least, you can create a separate HTTP server on port 80 that redirects all requests to the HTTPS server:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
|
||||||
PsychicHttpServer *redirectServer = new PsychicHttpServer();
|
|
||||||
redirectServer->config.ctrl_port = 20420; // just a random port different from the default one
|
|
||||||
redirectServer->listen(80);
|
|
||||||
redirectServer->onNotFound([](PsychicRequest *request) {
|
|
||||||
String url = "https://" + request->host() + request->url();
|
|
||||||
return request->redirect(url.c_str());
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
# Performance
|
|
||||||
|
|
||||||
In order to really see the differences between libraries, I created some basic benchmark firmwares for PsychicHttp, ESPAsyncWebserver, and ArduinoMongoose. I then ran the loadtest-http.sh and loadtest-websocket.sh scripts against each firmware to get some real numbers on the performance of each server library. All of the code and results are available in the /benchmark folder. If you want to see the collated data and graphs, there is a [LibreOffice spreadsheet](/benchmark/comparison.ods).
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
## HTTPS / SSL
|
|
||||||
|
|
||||||
Yes, PsychicHttp supports SSL out of the box, but there are a few caveats:
|
|
||||||
|
|
||||||
* Due to memory limitations, it can only handle 2 connections at a time. Each SSL connection takes about 45k ram, and a blank PsychicHttp sketch has about 150k ram free.
|
|
||||||
* Speed and latency are still pretty good (see graph above) but the SSH handshake seems to take 1500ms. With websockets or browser its not an issue since the connection is kept alive, but if you are loading requests in another way it will be a bit slow
|
|
||||||
* Unless you want to expose your ESP to the internet, you are limited to self signed keys and the annoying browser security warnings that come with them.
|
|
||||||
|
|
||||||
## Analysis
|
|
||||||
|
|
||||||
The results clearly show some of the reasons for writing PsychicHttp: ESPAsyncWebserver crashes under heavy load on each test, across the board in a 60s test. That means in normal usage, you're just rolling the dice with how long it will go until it crashes. Every other number is moot, IMHO.
|
|
||||||
|
|
||||||
ArduinoMongoose doesn't crash under heavy load, but it does bog down with extremely high latency (15s) for web requests and appears to not even respond at the highest loadings as the loadtest script crashes instead. The code itself doesnt crash, so bonus points there. After the high load, it does go back to serving normally. One area ArduinoMongoose does shine, is in websockets where its performance is almost 2x the performance of PsychicHttp. Both in requests per second and latency. Clearly an area of improvement for PsychicHttp.
|
|
||||||
|
|
||||||
PsychicHttp has good performance across the board. No crashes and continously responds during each test. It is a clear winner in requests per second when serving files from memory, dynamic JSON, and has consistent performance when serving files from LittleFS. The only real downside is the lower performance of the websockets with a single connection handling 38rps, and maxing out at 120rps across multiple connections.
|
|
||||||
|
|
||||||
## Takeaways
|
|
||||||
|
|
||||||
With all due respect to @me-no-dev who has done some amazing work in the open source community, I cannot recommend anyone use the ESPAsyncWebserver for anything other than simple projects that don't need to be reliable. Even then, PsychicHttp has taken the arcane api of the ESP-IDF web server library and made it nice and friendly to use with a very similar API to ESPAsyncWebserver. Also, ESPAsyncWebserver is more or less abandoned, with 150 open issues, 77 pending pull requests, and the last commit in over 2 years.
|
|
||||||
|
|
||||||
ArduinoMongoose is a good alternative, although the latency issues when it gets fully loaded can be very annoying. I believe it is also cross platform to other microcontrollers as well, but I haven't tested that. The other issue here is that it is based on an old version of a modified Mongoose library that will be difficult to update as it is a major revision behind and several security updates behind as well. Big thanks to @jeremypoulter though as PsychicHttp is a fork of ArduinoMongoose so it's built on strong bones.
|
|
||||||
|
|
||||||
# Roadmap
|
|
||||||
|
|
||||||
## v1.1: Event Source + Handlers
|
|
||||||
|
|
||||||
* Fix all outstanding issues on Github
|
|
||||||
* Another pass over the docs
|
|
||||||
* DefaultHeaders
|
|
||||||
|
|
||||||
|
|
||||||
## v1.2: ESPAsyncWebserver Parity
|
|
||||||
|
|
||||||
* HTTP_ANY support (by abusing httpd_req_handle_err)
|
|
||||||
* Issue: it would log a warning on every request (httpd_uri.c:298)
|
|
||||||
* Issue: req->user_ctx is not passed in. (httpd_uri.c:309)
|
|
||||||
* Websocket support assumes an endpoint with matching url / method (httpd_uri.c:312)
|
|
||||||
|
|
||||||
* templating system
|
|
||||||
* rewrite
|
|
||||||
* regex url matching
|
|
||||||
* What else are we missing?
|
|
||||||
|
|
||||||
## Longterm Wants
|
|
||||||
|
|
||||||
* investigate websocket performance gap
|
|
||||||
* support for esp-idf framework
|
|
||||||
* support for arduino 3.0 framework
|
|
||||||
* Enable worker based multithreading with esp-idf v5.x
|
|
||||||
* 100-continue support?
|
|
||||||
|
|
||||||
If anyone wants to take a crack at implementing any of the above features I am more than happy to accept pull requests.
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 20 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 31 KiB |
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "PsychicHttp",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"description": "Arduino style wrapper around ESP-IDF HTTP library. HTTP server with SSL + websockets. Works on esp32 and probably esp8266",
|
|
||||||
"keywords": "network,http,https,tcp,ssl,tls,websocket,espasyncwebserver",
|
|
||||||
"repository":
|
|
||||||
{
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/hoeken/PsychicHttp"
|
|
||||||
},
|
|
||||||
"authors":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "Zach Hoeken",
|
|
||||||
"email": "hoeken@gmail.com",
|
|
||||||
"maintainer": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license" : "LGPL-3.0-or-later",
|
|
||||||
"examples": [
|
|
||||||
{
|
|
||||||
"name": "platformio",
|
|
||||||
"base": "examples/platformio",
|
|
||||||
"files": [
|
|
||||||
"src/main.cpp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frameworks": "arduino",
|
|
||||||
"platforms": "espressif32",
|
|
||||||
"dependencies": [
|
|
||||||
{
|
|
||||||
"owner": "bblanchon",
|
|
||||||
"name": "ArduinoJson",
|
|
||||||
"version": "^6.21.4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"owner": "bblanchon",
|
|
||||||
"name": "ArduinoTrace",
|
|
||||||
"version": "^1.2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"owner": "plageoj",
|
|
||||||
"name" : "UrlEncode",
|
|
||||||
"version" : "^1.0.1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
name=PsychicHttp
|
|
||||||
version=1.0.1
|
|
||||||
author=Zach Hoeken <hoeken@gmail.com>
|
|
||||||
maintainer=Zach Hoeken <hoeken@gmail.com>
|
|
||||||
sentence=PsychicHttp is a robust webserver that supports http/https + websockets.
|
|
||||||
paragraph=This library is based on the ESP-IDF HTTP Server library which is asynchronous, does http / https+ssl and supports websockets.
|
|
||||||
category=Communication
|
|
||||||
architectures=esp32
|
|
||||||
url=https://github.com/hoeken/PsychicHttp
|
|
||||||
includes=PsychicHttp.h
|
|
||||||
depends=ArduinoJson,ArduinoTrace,UrlEncode
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<mxfile host="Electron" modified="2023-12-09T15:02:09.427Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.8.10 Chrome/106.0.5249.199 Electron/21.3.5 Safari/537.36" etag="cOtONeHkqmkM2fyBsENB" version="20.8.10" type="device" pages="2"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Request Flow">7Vxbd5s4EP41Pmf3wTkgAcaPza3Zbq/bbNM+yiAbNhi5ICd2f/0KIxmQFIypsZ1jpw+1hBCS5pv5ZgaJHryaLt4maBZ8ID6OesDwFz143QPANIDD/stqlnnNwIJ5xSQJfd6oqPga/sLiTl47D32cVhpSQiIazqqVHolj7NFKHUoS8lxtNiZR9akzNMFKxVcPRWrtQ+jTIK91waCov8PhJBBPNp1hfmWKRGM+kzRAPnkuVcGbHrxKCKH5r+niCkfZ4ol1efhr+RC9f3TevvuS/kT/Xv59//FbP+/sdptb1lNIcExbd+0/B97S/uJ9MrxvX76P0/f9+Gff5MvwhKI5XzA+WboUK5iQeezjrBezBy+fg5DirzPkZVefGWZYXUCnEb+8XiSDFcYkphwRbL3ZRXZbGE9Y0c6uhlF0RSKSrB4Dx3b2j99Vqs//srtpQh5x6Yqz+mNXGq4PX8cnnFC8KKGDr9dbTKaYJkvWRFwVIObYN21efi6QZA95XVBCkevwSsTRO1n3XUiI/eBC0gvsevkufbie3t/Yd9blDfz0bvHQ70NzG4EZVeH8hyldcoGgOSWsiiQ0IBMSo+g9ITPeriQ3Myvj2H+TqSIrjyLiPeZVt2E29NUzWIm3d9eCEtqmoCJCIxxdIu9xshqoEGhMYpx15TP15XMpBndT1LKHsYX8nnV2YYvij/VAWOF6USktRWkR0tJtrPSDDy/7XdyUFcQ9KUUJFXPnI9wObSmZJx6uUUEuP/agCaZ1kueQyhaoFrwJjhANn6r2TwdEfutnErKJrEEPbFgBPRyCahf5jPhdZYOzoSPbkDrKp6x0tNKL9XwaqYpWAODkNaU9UrXtoIpUbTtn10BtajHrRl2Cwd39/WdW8w/+Occp/T2+KwOAzfFyEqE0XRuPWulK/Ocj7I49Lc95Lh6Nu+Q5t6qpQGhqiedMoOE5awc0p52CpQjtB3MiT0x92xLdjtXeaaj2ptlQ7znq+sYFgIZbQV7f5N53WxITTch4nOJ6foKGdVB+shWMfyQKxDej5FUrQTdQ3YhA4c8b+wObVTWxljHYK9gcBWw3sT/jY/yAqBdkELpVTWxApqN52iLwu0XTMMpme4ejJ0xDD2noEkXhJGYFj8kfJ3o0rYNGpyjdr9DLaEINI8dj4Glp1HdGjt1puDiUPGdnoNKooQsXu6JRU40WP6dLLwi9OxT7EVvxs/MjOz+2uUfnRxvpaaR2it5Pi3B9/x6TXoCamH5XLpMNoC25TPtjMcuQWAxILNY2N2ANm9EhwydalpqtGCzVTErQrmSSLRfUj0um6Wp79iMfQVturoVLSd9XjJyxHnAiBqDLEbPUzoSuoOqgaWaH41Ga/ScM+Zm7f4sFZP9sr9ytR4WauLjGYzSPMrgWztvJ87cle12udWE3ZHC7K9mp+YvTi+0KMq5QccHMOyHjjXlzoUdHGBRam4LCpnQKZTo1O4su9Wus5jLOHuur8lib5vZbeKyOpST5Duix2m1VTNJVW3Z9d+Sx2rIqH6XHqqaTPuJnVnGV7+IISax1SA/jnBwyJSS/WXEbJhdAZ26luuMjxQmb0QWJP81wnAkRRdGImcFjkZ9vY9e3dPJzwQh2ugMEwqrpAoODJ4eGivxUx7KgRS9b9dCriqbKw4K2zBJpFRS2iba69O0205bm3VQt6jfSVnlnj0as9m8y1xpWktEWsNqalVw5ISN11LHjB9QgtUQBzJREJH3hxcLJc4Fra1IMe+UCoIapQZ5OYmSwEp2af1qTxQvXBXnUef8nwx6Sb+ZCt2lqQnZTdydzNVQ7Xf4AmrCnVlOOlD9Mw2ob1khGadgwEb8rAhG7GktwvA3jMA2OxXyMXQ/rCWPk2pa9PSjbE8YQNCSMzpxPCGoIg0cPPaC8o0hn2AvHzIoAwzvTw4v0MBCm+HDyVd25kyAH7cZstyE3CKU4Fm4wq6hybPNiWP5rSRTWVt3ujja0smmA0y6Tzes3HK9ub2ibHHV7vapNIZcVq+5kROcHHuTMrt0yGJc7GkCwV6VQXSnGdRQnf/yZ0fI5Bte4VPY+87H6E3iK0DwU55s6znJ7UW4OPLTcVE+YcUnAph16iJ5FVyO6g6ucmvVK8qNAbwoJhiTOZHgc0sMmi1IGOukNnQFE+4xRtNKzzD1K70TzV03PjtYh/kgjlIHV0uFSAmi5o44dLleBYp4N4ScLzwZEY/4HB2du9QVqgtMZiVN8kTLLcZaaRmpu00MR3fnJanRzULu/r+1adfZ8o90fHrfdbxtoy9sdlY52tRdLvPYRzg//FMJL45LbD7bcuzXY8d4tvSKpkcvpKpKhKlLtEY1j1ST56HJTTbLEkQaxed6W7PeONMmxqs8RX815aVxyewjr28vnfKT2HWnSkb0tOagmNd6Of9ya5LTXJAmBg272B8uaIcqvW5PUlIw4p0zprFf6VotxGzFlkrWMebW0qldVl5m/Vin717xKOcSW+cihh6I3/MI09P3VGxed21/VXuVzZ8q30PiIre10r7nbDiTh6T7kAsUOj7I2we3ddlYsvn2XA6H4giC8+R8=</diagram><diagram name="Standard Handlers" id="NiY0WBS-fA4Nieu9lBZ8">7Vtbc5s4FP41ntk+tGMQAvyYOLdNp9NOvTttn3ZkkDGNQA7It/z6FUayBcK3xNhMA34I5+iCdL5P5xwJ0gH9aHGfoMn4C/Ux6Zhdf9EBNx3TNLqmzf9kmmWucSyQK4Ik9EWljWIQvmDZUminoY/TQkVGKWHhpKj0aBxjjxV0KEnovFhtREnxqRMUYE0x8BDRtT9Cn41zrWs6G/0DDoOxfLJh9/KSCMnKYibpGPl0rqjAbQf0E0pZfhct+phkxpN2ue69/J493t0Yzy/92Xf/3v3nKviYd3Z3TJP1FBIcs9N2beZdzxCZCnuJubKlNGBCp7GPs06MDriej0OGBxPkZaVzThmuG7OIiOK1jbpcGNGYCUJwc/NC3iyMAy7CrDQkpE8JTVaPASOY/UQrRZ9fWWuW0CeslNiri5dEdIaGq+FmQ0hwGr6oMmWIKTInOFZl7IeqKHioaA60vkBphhOGFwr3BBr3mEaYJUteRZTyueVNxMoCUBBtvuEpsIVurHDUcoQSibURrPve4M9vBAWOoENPo8MPPHxAsU9w8jZiKFwwuE2uA4LSVPAkR1auTp1GRaL4CLsjr5IQnouHozoRc7sFxCwANcQMswqxugCTA1AQE3Clq2HxUZndPiJkiLynVIOQz5uVFnDBqDGNcQkBoUIkDGIuetzEnBvgOrNiyN3ulSiIQt/PHlNJjA11yn4CVjoRMWKrNmANWFyKbsVS7Dk6sOAEwBqfH8jEnz7+9yv4m4HH5+gruhe+XMWVxt/x8xSn7K8PjVmKELu+VbUUXXMIVr65LsQsGcDlUrQOXIpmXYgZGmIaTDj2r7KUJls3mdFDr4hMcVlwiyXLn6rwKxM+QSneLNTCm6WUFiH7KYMbv1dacWnTKBNkm+NgSuk08fD+OMJQEmC2w2YCC+wXcjgddAVUWIGp1CWYIBbOiplfFdDiCd9oyKercKrk3iEsdpHPW7RSU61yR3axI2Bbn3rqVew2N5PW7YqFayO8nph6jseD+oB6T5i1oX1baLfNM4b2SthARQT4OsFx6/4r3L/dvbT7tyrguktQhFu8qvA6NHOuDS89caZxn9C0xasSL/vSeNnvM73aFdH3pldWs9IroxhiofHa9Ao4uzuqOaFymkLF42i1ly6gWXTpljIy60R0sctp/Ra6cADRUqk2ySqkh/NbDnjruMr1jUJ9fpOP4KTcdf9Q7sJ3wl2zodx1zsBd/XT63wmhyG93sdt2sevIeLFdrORJe5B5YKYNL3+Q+U5PMnf5nL3xR/K8KQGodJQJwYmOMi3rNUeZbw1HcviHhiMIzxCODLPCteURqfVsVZ7t4md0hn5IdyHPdpzN93sfs1nep3sq71Pe6dvnSX+P9jfn2LoZ+pHl7YzzZ5DHsKY4nOZkv845P8+ohkw/tWxf4mwNEM6h79zqCxD68Uj7VmAHYBd/K2Dq28s/JKLbjY7ozqs/jQBbKFRzRHfgcRHdAWeI6GZjdtqnZq/7TthbTmybwt63HcdycfNZe159888B4PZ/</diagram></mxfile>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
|
|
||||||
#include "ChunkPrinter.h"
|
|
||||||
|
|
||||||
ChunkPrinter::ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) :
|
|
||||||
_response(response),
|
|
||||||
_buffer(buffer),
|
|
||||||
_length(len),
|
|
||||||
_pos(0)
|
|
||||||
{}
|
|
||||||
|
|
||||||
ChunkPrinter::~ChunkPrinter()
|
|
||||||
{
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ChunkPrinter::write(uint8_t c)
|
|
||||||
{
|
|
||||||
esp_err_t err;
|
|
||||||
|
|
||||||
//if we're full, send a chunk
|
|
||||||
if (_pos == _length)
|
|
||||||
{
|
|
||||||
_pos = 0;
|
|
||||||
err = _response->sendChunk(_buffer, _length);
|
|
||||||
|
|
||||||
if (err != ESP_OK)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_buffer[_pos] = c;
|
|
||||||
_pos++;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ChunkPrinter::write(const uint8_t *buffer, size_t size)
|
|
||||||
{
|
|
||||||
size_t written = 0;
|
|
||||||
|
|
||||||
while (written < size)
|
|
||||||
{
|
|
||||||
size_t space = _length - _pos;
|
|
||||||
size_t blockSize = std::min(space, size - written);
|
|
||||||
|
|
||||||
memcpy(_buffer + _pos, buffer + written, blockSize);
|
|
||||||
_pos += blockSize;
|
|
||||||
|
|
||||||
if (_pos == _length)
|
|
||||||
{
|
|
||||||
_pos = 0;
|
|
||||||
|
|
||||||
if (_response->sendChunk(_buffer, _length) != ESP_OK)
|
|
||||||
return written;
|
|
||||||
}
|
|
||||||
written += blockSize; //Update if sent correctly.
|
|
||||||
}
|
|
||||||
return written;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChunkPrinter::flush()
|
|
||||||
{
|
|
||||||
if (_pos)
|
|
||||||
{
|
|
||||||
_response->sendChunk(_buffer, _pos);
|
|
||||||
_pos = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ChunkPrinter::copyFrom(Stream &stream)
|
|
||||||
{
|
|
||||||
size_t count = 0;
|
|
||||||
|
|
||||||
while (stream.available()){
|
|
||||||
|
|
||||||
if (_pos == _length)
|
|
||||||
{
|
|
||||||
_response->sendChunk(_buffer, _length);
|
|
||||||
_pos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t readBytes = stream.readBytes(_buffer + _pos, _length - _pos);
|
|
||||||
_pos += readBytes;
|
|
||||||
count += readBytes;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#ifndef ChunkPrinter_h
|
|
||||||
#define ChunkPrinter_h
|
|
||||||
|
|
||||||
#include "PsychicResponse.h"
|
|
||||||
#include <Print.h>
|
|
||||||
|
|
||||||
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);
|
|
||||||
~ChunkPrinter();
|
|
||||||
|
|
||||||
size_t write(uint8_t c) override;
|
|
||||||
size_t write(const uint8_t *buffer, size_t size) override;
|
|
||||||
|
|
||||||
size_t copyFrom(Stream &stream);
|
|
||||||
|
|
||||||
void flush() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#include "PsychicClient.h"
|
|
||||||
#include "PsychicHttpServer.h"
|
|
||||||
#include <lwip/sockets.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_LOGD(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_LOGD(PH_TAG, "Client Remote IP => %s", ipstr);
|
|
||||||
address.fromString(ipstr);
|
|
||||||
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#ifndef PsychicClient_h
|
|
||||||
#define PsychicClient_h
|
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
#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 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
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ARDUINO
|
|
||||||
#include <Arduino.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <esp_http_server.h>
|
|
||||||
#include <map>
|
|
||||||
#include <list>
|
|
||||||
#include <libb64/cencode.h>
|
|
||||||
#include "esp_random.h"
|
|
||||||
#include "MD5Builder.h"
|
|
||||||
#include <UrlEncode.h>
|
|
||||||
#include "FS.h"
|
|
||||||
#include <ArduinoJson.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<bool(PsychicRequest *request)> PsychicRequestFilterFunction;
|
|
||||||
|
|
||||||
//client connect callback
|
|
||||||
typedef std::function<void(PsychicClient *client)> PsychicClientCallback;
|
|
||||||
|
|
||||||
//callback definitions
|
|
||||||
typedef std::function<esp_err_t(PsychicRequest *request)> PsychicHttpRequestCallback;
|
|
||||||
typedef std::function<esp_err_t(PsychicRequest *request, JsonVariant &json)> PsychicJsonRequestCallback;
|
|
||||||
|
|
||||||
struct HTTPHeader {
|
|
||||||
char * field;
|
|
||||||
char * value;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DefaultHeaders {
|
|
||||||
std::list<HTTPHeader> _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<HTTPHeader>& 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
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#include "PsychicEndpoint.h"
|
|
||||||
#include "PsychicHttpServer.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.</div>");
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#ifndef PsychicEndpoint_h
|
|
||||||
#define PsychicEndpoint_h
|
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
|
|
||||||
class PsychicHandler;
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
/*
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_onOpen != NULL)
|
|
||||||
_onOpen(buddy);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PsychicEventSource::closeCallback(PsychicClient *client) {
|
|
||||||
PsychicEventSourceClient *buddy = getClient(client);
|
|
||||||
if (buddy == NULL)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
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 "PsychicHandler.h"
|
|
||||||
#include "PsychicClient.h"
|
|
||||||
#include "PsychicResponse.h"
|
|
||||||
|
|
||||||
class PsychicEventSource;
|
|
||||||
class PsychicEventSourceResponse;
|
|
||||||
class PsychicEventSourceClient;
|
|
||||||
class PsychicResponse;
|
|
||||||
|
|
||||||
typedef std::function<void(PsychicEventSourceClient *client)> 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_ */
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
#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;
|
|
||||||
String _path(path);
|
|
||||||
|
|
||||||
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
|
|
||||||
_path = _path+".gz";
|
|
||||||
addHeader("Content-Encoding", "gzip");
|
|
||||||
}
|
|
||||||
|
|
||||||
_content = fs.open(_path, "r");
|
|
||||||
_contentLength = _content.size();
|
|
||||||
|
|
||||||
if(contentType == "")
|
|
||||||
_setContentType(path);
|
|
||||||
else
|
|
||||||
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) {
|
|
||||||
String _path(path);
|
|
||||||
|
|
||||||
if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){
|
|
||||||
addHeader("Content-Encoding", "gzip");
|
|
||||||
}
|
|
||||||
|
|
||||||
_content = content;
|
|
||||||
_contentLength = _content.size();
|
|
||||||
|
|
||||||
if(contentType == "")
|
|
||||||
_setContentType(path);
|
|
||||||
else
|
|
||||||
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){
|
|
||||||
const char *_contentType;
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
setContentType(_contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
if (buffer == 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 readSize = _content.readBytes((char *)buffer, size);
|
|
||||||
|
|
||||||
this->setContent(buffer, readSize);
|
|
||||||
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_LOGD(PH_TAG, "File sending complete");
|
|
||||||
this->finishChunking();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#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;
|
|
||||||
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
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
#include "PsychicHandler.h"
|
|
||||||
|
|
||||||
PsychicHandler::PsychicHandler() :
|
|
||||||
_filter(NULL),
|
|
||||||
_server(NULL),
|
|
||||||
_username(""),
|
|
||||||
_password(""),
|
|
||||||
_method(DIGEST_AUTH),
|
|
||||||
_realm(""),
|
|
||||||
_authFailMsg(""),
|
|
||||||
_subprotocol("")
|
|
||||||
{}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PsychicHandler::setSubprotocol(const String& subprotocol) {
|
|
||||||
this->_subprotocol = subprotocol;
|
|
||||||
}
|
|
||||||
const char* PsychicHandler::getSubprotocol() const {
|
|
||||||
return _subprotocol.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
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<PsychicClient*>& PsychicHandler::getClientList() {
|
|
||||||
return _clients;
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#ifndef PsychicHandler_h
|
|
||||||
#define PsychicHandler_h
|
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
#include "PsychicRequest.h"
|
|
||||||
|
|
||||||
class PsychicEndpoint;
|
|
||||||
class PsychicHttpServer;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
String _subprotocol;
|
|
||||||
|
|
||||||
std::list<PsychicClient*> _clients;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PsychicHandler();
|
|
||||||
virtual ~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; };
|
|
||||||
|
|
||||||
void setSubprotocol(const String& subprotocol);
|
|
||||||
const char* getSubprotocol() const;
|
|
||||||
|
|
||||||
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<PsychicClient*>& getClientList();
|
|
||||||
|
|
||||||
//derived classes must implement these functions
|
|
||||||
virtual bool canHandle(PsychicRequest *request) { return true; };
|
|
||||||
virtual esp_err_t handleRequest(PsychicRequest *request) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#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 <http_status.h>
|
|
||||||
#include "PsychicHttpServer.h"
|
|
||||||
#include "PsychicRequest.h"
|
|
||||||
#include "PsychicResponse.h"
|
|
||||||
#include "PsychicEndpoint.h"
|
|
||||||
#include "PsychicHandler.h"
|
|
||||||
#include "PsychicStaticFileHandler.h"
|
|
||||||
#include "PsychicFileResponse.h"
|
|
||||||
#include "PsychicStreamResponse.h"
|
|
||||||
#include "PsychicUploadHandler.h"
|
|
||||||
#include "PsychicWebSocket.h"
|
|
||||||
#include "PsychicEventSource.h"
|
|
||||||
#include "PsychicJson.h"
|
|
||||||
|
|
||||||
#ifdef ENABLE_ASYNC
|
|
||||||
#include "async_worker.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* PsychicHttp_h */
|
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
#include "PsychicHttpServer.h"
|
|
||||||
#include "PsychicEndpoint.h"
|
|
||||||
#include "PsychicHandler.h"
|
|
||||||
#include "PsychicWebHandler.h"
|
|
||||||
#include "PsychicStaticFileHandler.h"
|
|
||||||
#include "PsychicWebSocket.h"
|
|
||||||
#include "PsychicJson.h"
|
|
||||||
#include "WiFi.h"
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// do not release any resource for PsychicHttpServer in order to be able to restart it after stopping
|
|
||||||
}
|
|
||||||
|
|
||||||
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, 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(),
|
|
||||||
.supported_subprotocol = handler->getSubprotocol()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonRequestCallback fn)
|
|
||||||
{
|
|
||||||
return on(uri, HTTP_GET, 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);
|
|
||||||
|
|
||||||
return on(uri, method, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn)
|
|
||||||
{
|
|
||||||
PsychicWebHandler *handler = new PsychicWebHandler();
|
|
||||||
handler->onRequest(fn == nullptr ? PsychicHttpServer::defaultNotFoundHandler : 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_LOGD(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_LOGD(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<PsychicClient*>& 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;
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#ifndef PsychicHttpServer_h
|
|
||||||
#define PsychicHttpServer_h
|
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
#include "PsychicClient.h"
|
|
||||||
#include "PsychicHandler.h"
|
|
||||||
|
|
||||||
class PsychicEndpoint;
|
|
||||||
class PsychicHandler;
|
|
||||||
class PsychicStaticFileHandler;
|
|
||||||
|
|
||||||
class PsychicHttpServer
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
bool _use_ssl = false;
|
|
||||||
std::list<PsychicEndpoint*> _endpoints;
|
|
||||||
std::list<PsychicHandler*> _handlers;
|
|
||||||
std::list<PsychicClient*> _clients;
|
|
||||||
|
|
||||||
PsychicClientCallback _onOpen;
|
|
||||||
PsychicClientCallback _onClose;
|
|
||||||
|
|
||||||
esp_err_t _start();
|
|
||||||
virtual esp_err_t _startServer();
|
|
||||||
|
|
||||||
public:
|
|
||||||
PsychicHttpServer();
|
|
||||||
virtual ~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<PsychicClient*>& getClientList();
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#include "PsychicHttpsServer.h"
|
|
||||||
|
|
||||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
|
|
||||||
this->ssl_config.servercert = (uint8_t *)cert;
|
|
||||||
this->ssl_config.servercert_len = strlen(cert)+1;
|
|
||||||
#else
|
|
||||||
this->ssl_config.cacert_pem = (uint8_t *)cert;
|
|
||||||
this->ssl_config.cacert_len = strlen(cert)+1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#ifndef PsychicHttpsServer_h
|
|
||||||
#define PsychicHttpsServer_h
|
|
||||||
|
|
||||||
#include <sdkconfig.h>
|
|
||||||
|
|
||||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
#include "PsychicHttpServer.h"
|
|
||||||
#include <esp_https_server.h>
|
|
||||||
#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
|
|
||||||
|
|
||||||
#else
|
|
||||||
#error ESP-IDF https server support not enabled.
|
|
||||||
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
#include "PsychicJson.h"
|
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray) : PsychicResponse(request)
|
|
||||||
{
|
|
||||||
setContentType(JSON_MIMETYPE);
|
|
||||||
if (isArray)
|
|
||||||
_root = _jsonBuffer.add<JsonArray>();
|
|
||||||
else
|
|
||||||
_root = _jsonBuffer.add<JsonObject>();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
JsonVariant &PsychicJsonResponse::getRoot() { return _root; }
|
|
||||||
|
|
||||||
size_t PsychicJsonResponse::getLength()
|
|
||||||
{
|
|
||||||
return measureJson(_root);
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t PsychicJsonResponse::send()
|
|
||||||
{
|
|
||||||
esp_err_t err = ESP_OK;
|
|
||||||
size_t length = getLength();
|
|
||||||
size_t buffer_size;
|
|
||||||
char *buffer;
|
|
||||||
|
|
||||||
//how big of a buffer do we want?
|
|
||||||
if (length < JSON_BUFFER_SIZE)
|
|
||||||
buffer_size = length+1;
|
|
||||||
else
|
|
||||||
buffer_size = JSON_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)
|
|
||||||
{
|
|
||||||
serializeJson(_root, buffer, buffer_size);
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
serializeJson(_root, dest);
|
|
||||||
|
|
||||||
//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_6_COMPATIBILITY
|
|
||||||
PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) :
|
|
||||||
_onRequest(NULL),
|
|
||||||
_maxJsonBufferSize(maxJsonBufferSize)
|
|
||||||
{};
|
|
||||||
|
|
||||||
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) :
|
|
||||||
_onRequest(onRequest),
|
|
||||||
_maxJsonBufferSize(maxJsonBufferSize)
|
|
||||||
{}
|
|
||||||
#else
|
|
||||||
PsychicJsonHandler::PsychicJsonHandler() :
|
|
||||||
_onRequest(NULL)
|
|
||||||
{};
|
|
||||||
|
|
||||||
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) :
|
|
||||||
_onRequest(onRequest)
|
|
||||||
{}
|
|
||||||
#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_6_COMPATIBILITY
|
|
||||||
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
|
|
||||||
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
|
||||||
if (error)
|
|
||||||
return request->reply(400);
|
|
||||||
|
|
||||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
|
||||||
#else
|
|
||||||
JsonDocument jsonBuffer;
|
|
||||||
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
|
||||||
if (error)
|
|
||||||
return request->reply(400);
|
|
||||||
|
|
||||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return _onRequest(request, json);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return request->reply(500);
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
// 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 "PsychicRequest.h"
|
|
||||||
#include "PsychicWebHandler.h"
|
|
||||||
#include "ChunkPrinter.h"
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
|
||||||
#define ARDUINOJSON_6_COMPATIBILITY
|
|
||||||
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
|
||||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 4096
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef JSON_BUFFER_SIZE
|
|
||||||
#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;
|
|
||||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
|
||||||
DynamicJsonDocument _jsonBuffer;
|
|
||||||
#else
|
|
||||||
JsonDocument _jsonBuffer;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
JsonVariant _root;
|
|
||||||
size_t _contentLength;
|
|
||||||
|
|
||||||
public:
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
|
||||||
PsychicJsonResponse(PsychicRequest *request, bool isArray = false);
|
|
||||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
|
||||||
PsychicJsonResponse(PsychicRequest *request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
|
||||||
#else
|
|
||||||
PsychicJsonResponse(PsychicRequest *request, bool isArray = false);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
~PsychicJsonResponse() {}
|
|
||||||
|
|
||||||
JsonVariant &getRoot();
|
|
||||||
size_t getLength();
|
|
||||||
|
|
||||||
virtual esp_err_t send() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PsychicJsonHandler : public PsychicWebHandler
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
PsychicJsonRequestCallback _onRequest;
|
|
||||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
|
||||||
const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public:
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
|
||||||
PsychicJsonHandler();
|
|
||||||
PsychicJsonHandler(PsychicJsonRequestCallback onRequest);
|
|
||||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
|
||||||
PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
|
||||||
PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
|
||||||
#else
|
|
||||||
PsychicJsonHandler();
|
|
||||||
PsychicJsonHandler(PsychicJsonRequestCallback onRequest);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void onRequest(PsychicJsonRequestCallback fn);
|
|
||||||
virtual esp_err_t handleRequest(PsychicRequest *request) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,542 +0,0 @@
|
|||||||
#include "PsychicRequest.h"
|
|
||||||
#include "http_status.h"
|
|
||||||
#include "PsychicHttpServer.h"
|
|
||||||
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
size_t remaining = this->_req->content_len;
|
|
||||||
size_t actuallyReceived = 0;
|
|
||||||
char *buf = (char *)malloc(remaining + 1);
|
|
||||||
if (buf == NULL) {
|
|
||||||
ESP_LOGE(PH_TAG, "Failed to allocate memory for body");
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (remaining > 0) {
|
|
||||||
int received = httpd_req_recv(this->_req, buf + actuallyReceived, remaining);
|
|
||||||
|
|
||||||
if (received == HTTPD_SOCK_ERR_TIMEOUT) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (received == HTTPD_SOCK_ERR_FAIL) {
|
|
||||||
ESP_LOGE(PH_TAG, "Failed to receive data.");
|
|
||||||
err = ESP_FAIL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining -= received;
|
|
||||||
actuallyReceived += received;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf[actuallyReceived] = '\0';
|
|
||||||
this->_body = String(buf);
|
|
||||||
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, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//did we get form data as body?
|
|
||||||
if (this->method() == HTTP_POST && this->contentType().startsWith("application/x-www-form-urlencoded"))
|
|
||||||
{
|
|
||||||
_addParams(_body, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PsychicRequest::_addParams(const String& params, bool post){
|
|
||||||
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, true, post);
|
|
||||||
start = end + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PsychicWebParameter * PsychicRequest::addParam(const String &name, const String &value, bool decode, bool post)
|
|
||||||
{
|
|
||||||
if (decode)
|
|
||||||
return addParam(new PsychicWebParameter(urlDecode(name.c_str()), urlDecode(value.c_str()), post));
|
|
||||||
else
|
|
||||||
return addParam(new PsychicWebParameter(name, value, post));
|
|
||||||
}
|
|
||||||
|
|
||||||
PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) {
|
|
||||||
// ESP_LOGD(PH_TAG, "Adding param: '%s' = '%s'", param->name().c_str(), param->value().c_str());
|
|
||||||
_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<String, String>(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")))
|
|
||||||
{
|
|
||||||
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.c_str());
|
|
||||||
|
|
||||||
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.c_str());
|
|
||||||
|
|
||||||
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.c_str());
|
|
||||||
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", (unsigned long int)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());
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
#ifndef PsychicRequest_h
|
|
||||||
#define PsychicRequest_h
|
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
#include "PsychicHttpServer.h"
|
|
||||||
#include "PsychicClient.h"
|
|
||||||
#include "PsychicWebParameter.h"
|
|
||||||
#include "PsychicResponse.h"
|
|
||||||
|
|
||||||
typedef std::map<String, String> 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<PsychicWebParameter*> _params;
|
|
||||||
|
|
||||||
void _addParams(const String& params, bool post);
|
|
||||||
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 post = false);
|
|
||||||
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
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
#include "PsychicResponse.h"
|
|
||||||
#include "PsychicRequest.h"
|
|
||||||
#include <http_status.h>
|
|
||||||
|
|
||||||
PsychicResponse::PsychicResponse(PsychicRequest *request) :
|
|
||||||
_request(request),
|
|
||||||
_code(200),
|
|
||||||
_status(""),
|
|
||||||
_contentLength(0),
|
|
||||||
_body("")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PsychicResponse::~PsychicResponse()
|
|
||||||
{
|
|
||||||
//clean up our header variables. we have to do this on desctruct 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 after send by the destructor
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// DO NOT RELEASE HEADERS HERE... released in the PsychicResponse destructor after they have been sent.
|
|
||||||
// httpd_resp_set_hdr just passes on the pointer, but its needed after this call.
|
|
||||||
// clean up our header variables after send
|
|
||||||
// for (HTTPHeader header : _headers)
|
|
||||||
// {
|
|
||||||
// free(header.field);
|
|
||||||
// free(header.value);
|
|
||||||
// }
|
|
||||||
// _headers.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#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<HTTPHeader> _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
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
#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)
|
|
||||||
{
|
|
||||||
//is it not modified?
|
|
||||||
String etag = String(_file.size());
|
|
||||||
if (_last_modified.length() && _last_modified == request->header("If-Modified-Since"))
|
|
||||||
{
|
|
||||||
_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))
|
|
||||||
{
|
|
||||||
_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
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#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 */
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
#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.c_str());
|
|
||||||
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
|
|
||||||
{
|
|
||||||
_printer->~ChunkPrinter(); //flushed on destruct
|
|
||||||
err = finishChunking();
|
|
||||||
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::write(const uint8_t *buffer, size_t size)
|
|
||||||
{
|
|
||||||
return _buffer ? _printer->write(buffer, size) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t PsychicStreamResponse::copyFrom(Stream &stream)
|
|
||||||
{
|
|
||||||
if(_buffer)
|
|
||||||
return _printer->copyFrom(stream);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#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();
|
|
||||||
|
|
||||||
void flush() override;
|
|
||||||
|
|
||||||
size_t write(uint8_t data) override;
|
|
||||||
size_t write(const uint8_t *buffer, size_t size) override;
|
|
||||||
|
|
||||||
size_t copyFrom(Stream &stream);
|
|
||||||
|
|
||||||
using Print::write;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // PsychicStreamResponse_h
|
|
||||||
@@ -1,395 +0,0 @@
|
|||||||
#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;
|
|
||||||
_parsedLength = 0;
|
|
||||||
/* 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 %lu 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_LOGD(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_LOGD(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<received; i++)
|
|
||||||
{
|
|
||||||
/* Keep track of remaining size of the file left to be uploaded */
|
|
||||||
remaining--;
|
|
||||||
index++;
|
|
||||||
|
|
||||||
//send it to our parser
|
|
||||||
_parseMultipartPostByte(buf[i], !remaining);
|
|
||||||
_parsedLength++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//dont forget to free our buffer
|
|
||||||
free(buf);
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
PsychicUploadHandler * PsychicUploadHandler::onUpload(PsychicUploadCallback fn) {
|
|
||||||
_uploadCallback = fn;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PsychicUploadHandler::_handleUploadByte(uint8_t data, bool last)
|
|
||||||
{
|
|
||||||
_itemBuffer[_itemBufferIndex++] = data;
|
|
||||||
|
|
||||||
if(last || _itemBufferIndex == FILE_CHUNK_SIZE)
|
|
||||||
{
|
|
||||||
if(_uploadCallback)
|
|
||||||
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, last);
|
|
||||||
_itemBufferIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0)
|
|
||||||
|
|
||||||
void PsychicUploadHandler::_parseMultipartPostByte(uint8_t data, bool last)
|
|
||||||
{
|
|
||||||
if (_multiParseState == PARSE_ERROR)
|
|
||||||
{
|
|
||||||
// not sure we can end up with an error during buffer fill, but jsut to be safe
|
|
||||||
if (_itemBuffer != NULL)
|
|
||||||
{
|
|
||||||
free(_itemBuffer);
|
|
||||||
_itemBuffer = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!_parsedLength){
|
|
||||||
_multiParseState = EXPECT_BOUNDARY;
|
|
||||||
_temp = String();
|
|
||||||
_itemName = String();
|
|
||||||
_itemFilename = String();
|
|
||||||
_itemType = String();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_multiParseState == WAIT_FOR_RETURN1){
|
|
||||||
if(data != '\r'){
|
|
||||||
itemWriteByte(data);
|
|
||||||
} else {
|
|
||||||
_multiParseState = EXPECT_FEED1;
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == EXPECT_BOUNDARY){
|
|
||||||
if(_parsedLength < 2 && data != '-'){
|
|
||||||
ESP_LOGE(PH_TAG, "Multipart: No boundary");
|
|
||||||
_multiParseState = PARSE_ERROR;
|
|
||||||
return;
|
|
||||||
} else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){
|
|
||||||
ESP_LOGE(PH_TAG, "Multipart: Multipart malformed");
|
|
||||||
_multiParseState = PARSE_ERROR;
|
|
||||||
return;
|
|
||||||
} else if(_parsedLength - 2 == _boundary.length() && data != '\r'){
|
|
||||||
ESP_LOGE(PH_TAG, "Multipart: Multipart missing carriage return");
|
|
||||||
_multiParseState = PARSE_ERROR;
|
|
||||||
return;
|
|
||||||
} else if(_parsedLength - 3 == _boundary.length()){
|
|
||||||
if(data != '\n'){
|
|
||||||
ESP_LOGE(PH_TAG, "Multipart: Multipart missing newline");
|
|
||||||
_multiParseState = PARSE_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_multiParseState = PARSE_HEADERS;
|
|
||||||
_itemIsFile = false;
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == PARSE_HEADERS){
|
|
||||||
if((char)data != '\r' && (char)data != '\n')
|
|
||||||
_temp += (char)data;
|
|
||||||
if((char)data == '\n'){
|
|
||||||
if(_temp.length()){
|
|
||||||
if(_temp.length() > 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
#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<esp_err_t(PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final)> 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
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
#include "PsychicWebHandler.h"
|
|
||||||
|
|
||||||
PsychicWebHandler::PsychicWebHandler() :
|
|
||||||
PsychicHandler(),
|
|
||||||
_requestCallback(NULL),
|
|
||||||
_onOpen(NULL),
|
|
||||||
_onClose(NULL)
|
|
||||||
{}
|
|
||||||
PsychicWebHandler::~PsychicWebHandler() {}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
/* 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 %lu 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#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;
|
|
||||||
PsychicClientCallback _onOpen;
|
|
||||||
PsychicClientCallback _onClose;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PsychicWebHandler();
|
|
||||||
~PsychicWebHandler();
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
#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)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_onOpen != NULL)
|
|
||||||
_onOpen(getClient(buddy));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PsychicWebSocketHandler::closeCallback(PsychicClient *client) {
|
|
||||||
PsychicWebSocketClient *buddy = getClient(client);
|
|
||||||
if (buddy == NULL)
|
|
||||||
{
|
|
||||||
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_LOGD(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_LOGD(PH_TAG, "Got packet with message: %s", ws_pkt.payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text messages are our payload.
|
|
||||||
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_BINARY)
|
|
||||||
{
|
|
||||||
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_LOGD(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()->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_LOGD(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket());
|
|
||||||
|
|
||||||
if (client->_friend == NULL)
|
|
||||||
{
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#ifndef PsychicWebSocket_h
|
|
||||||
#define PsychicWebSocket_h
|
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
#include "PsychicRequest.h"
|
|
||||||
|
|
||||||
class PsychicWebSocketRequest;
|
|
||||||
class PsychicWebSocketClient;
|
|
||||||
|
|
||||||
//callback function definitions
|
|
||||||
typedef std::function<void(PsychicWebSocketClient *client)> PsychicWebSocketClientCallback;
|
|
||||||
typedef std::function<esp_err_t(PsychicWebSocketRequest *request, httpd_ws_frame *frame)> 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
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
/************************************************************
|
|
||||||
|
|
||||||
TemplatePrinter Class
|
|
||||||
|
|
||||||
A basic templating engine for a stream of text.
|
|
||||||
This wraps the Arduino Print interface and writes to any
|
|
||||||
Print interface.
|
|
||||||
|
|
||||||
Written by Christopher Andrews (https://github.com/Chris--A)
|
|
||||||
|
|
||||||
************************************************************/
|
|
||||||
|
|
||||||
#include "TemplatePrinter.h"
|
|
||||||
|
|
||||||
void TemplatePrinter::resetParam(bool flush){
|
|
||||||
if(flush && _inParam){
|
|
||||||
_stream.write(_delimiter);
|
|
||||||
|
|
||||||
if(_paramPos)
|
|
||||||
_stream.print(_paramBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(_paramBuffer, 0, sizeof(_paramBuffer));
|
|
||||||
_paramPos = 0;
|
|
||||||
_inParam = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void TemplatePrinter::flush(){
|
|
||||||
resetParam(true);
|
|
||||||
_stream.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t TemplatePrinter::write(uint8_t data){
|
|
||||||
|
|
||||||
if(data == _delimiter){
|
|
||||||
|
|
||||||
// End of parameter, send to callback
|
|
||||||
if(_inParam){
|
|
||||||
|
|
||||||
// On false, return the parameter place holder as is: not a parameter
|
|
||||||
// Bug fix: ignore parameters that are zero length.
|
|
||||||
if(!_paramPos || !_cb(_stream, _paramBuffer)){
|
|
||||||
resetParam(true);
|
|
||||||
_stream.write(data);
|
|
||||||
}else{
|
|
||||||
resetParam(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start collecting parameter
|
|
||||||
}else{
|
|
||||||
_inParam = true;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
|
|
||||||
// Are we collecting
|
|
||||||
if(_inParam){
|
|
||||||
|
|
||||||
// Is param still valid
|
|
||||||
if(isalnum(data) || data == '_'){
|
|
||||||
|
|
||||||
// Total param len must be 63, 1 for null.
|
|
||||||
if(_paramPos < sizeof(_paramBuffer) - 1){
|
|
||||||
_paramBuffer[_paramPos++] = data;
|
|
||||||
|
|
||||||
// Not a valid param
|
|
||||||
}else{
|
|
||||||
resetParam(true);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
resetParam(true);
|
|
||||||
_stream.write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just output
|
|
||||||
}else{
|
|
||||||
_stream.write(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t TemplatePrinter::copyFrom(Stream &stream){
|
|
||||||
size_t count = 0;
|
|
||||||
|
|
||||||
while(stream.available())
|
|
||||||
count += this->write(stream.read());
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#ifndef TemplatePrinter_h
|
|
||||||
#define TemplatePrinter_h
|
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
#include <Print.h>
|
|
||||||
|
|
||||||
/************************************************************
|
|
||||||
|
|
||||||
TemplatePrinter Class
|
|
||||||
|
|
||||||
A basic templating engine for a stream of text.
|
|
||||||
This wraps the Arduino Print interface and writes to any
|
|
||||||
Print interface.
|
|
||||||
|
|
||||||
Written by Christopher Andrews (https://github.com/Chris--A)
|
|
||||||
|
|
||||||
************************************************************/
|
|
||||||
|
|
||||||
class TemplatePrinter;
|
|
||||||
|
|
||||||
typedef std::function<bool(Print &output, const char *parameter)> TemplateCallback;
|
|
||||||
typedef std::function<void(TemplatePrinter &printer)> TemplateSourceCallback;
|
|
||||||
|
|
||||||
class TemplatePrinter : public Print{
|
|
||||||
private:
|
|
||||||
bool _inParam;
|
|
||||||
char _paramBuffer[64];
|
|
||||||
uint8_t _paramPos;
|
|
||||||
Print &_stream;
|
|
||||||
TemplateCallback _cb;
|
|
||||||
char _delimiter;
|
|
||||||
|
|
||||||
void resetParam(bool flush);
|
|
||||||
|
|
||||||
public:
|
|
||||||
using Print::write;
|
|
||||||
|
|
||||||
static void start(Print &stream, TemplateCallback cb, TemplateSourceCallback entry){
|
|
||||||
TemplatePrinter printer(stream, cb);
|
|
||||||
entry(printer);
|
|
||||||
}
|
|
||||||
|
|
||||||
TemplatePrinter(Print &stream, TemplateCallback cb, const char delimeter = '%') : _stream(stream), _cb(cb), _delimiter(delimeter) { resetParam(false); }
|
|
||||||
~TemplatePrinter(){ flush(); }
|
|
||||||
|
|
||||||
void flush() override;
|
|
||||||
size_t write(uint8_t data) override;
|
|
||||||
size_t copyFrom(Stream &stream);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
#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((void *)async, (void *)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;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
#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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#ifndef MICRO_HTTP_STATUS_H
|
|
||||||
#define MICRO_HTTP_STATUS_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -84,6 +84,7 @@ build_src_flags =
|
|||||||
test_ignore = test_embedded
|
test_ignore = test_embedded
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
hoeken/PsychicHttp@^1.2.1
|
||||||
ArduinoJson@>=7.0.0
|
ArduinoJson@>=7.0.0
|
||||||
teckel12/NewPing@^1.9.7
|
teckel12/NewPing@^1.9.7
|
||||||
jrowberg/I2Cdevlib-MPU6050@^1.0.0
|
jrowberg/I2Cdevlib-MPU6050@^1.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user