PsychichHTTP v2-dev

This commit is contained in:
iranl
2024-12-30 14:37:09 +01:00
parent 2cf5201285
commit 78459c2d08
118 changed files with 5453 additions and 4972 deletions

View File

@@ -0,0 +1,25 @@
Language: Cpp
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignConsecutiveMacros: true
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortIfStatementsOnASingleLine: false
BinPackArguments: false
ColumnLimit: 0
ContinuationIndentWidth: 2
FixNamespaceComments: false
IndentAccessModifiers: true
IndentCaseLabels: true
IndentPPDirectives: BeforeHash
IndentWidth: 2
NamespaceIndentation: All
PointerAlignment: Left
ReferenceAlignment: Left
TabWidth: 2
UseTab: Never
BreakBeforeBraces: Linux
AllowShortLambdasOnASingleLine: All
AlignAfterOpenBracket: DontAlign

55
lib/PsychicHttp/.gitignore vendored Normal file
View File

@@ -0,0 +1,55 @@
_secret.h
.$request flow.drawio.bkp
.$request flow.drawio.dtmp
.clang_complete
.gcc-flags.json
.pio
.pioenvs
.pioenvs
.piolibdeps
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/ipch
.vscode/launch.json
.vscode/settings.json
*.a
*.app
*.dll
*.dylib
*.exe
*.gch
*.la
*.lai
*.lib
*.lo
*.mod
*.o
*.obj
*.opensdf
*.out
*.pch
*.sdf
*.slo
*.so
*.suo
**.DS_Store
**.pio
**.vscode
/build
/psychic-http-loadtest.log
/psychic-websocket-loadtest.log
benchmark/_psychic-http-loadtest.json
benchmark/.~lock.comparison.ods#
benchmark/http-loadtest-results.csv
benchmark/node_modules
benchmark/package-lock.json
benchmark/psychic-http-loadtest.log
benchmark/psychic-websocket-loadtest.json
benchmark/psychic-websocket-loadtest.log
benchmark/websocket-loadtest-results.csv
examples/arduino/
examples/platformio/lib/PsychicHttp
examples/websockets/lib/PsychicHttp
src/cookie.txt
src/secret.h
Visual\ Micro

14
lib/PsychicHttp/.gitmodules vendored Normal file
View File

@@ -0,0 +1,14 @@
[submodule "examples/esp-idf/components/UrlEncode"]
path = examples/esp-idf/components/UrlEncode
url = https://github.com/dzungpv/UrlEncode
[submodule "examples/esp-idf/components/ArduinoJson"]
path = examples/esp-idf/components/ArduinoJson
url = https://github.com/bblanchon/ArduinoJson
branch = 7.x
[submodule "examples/esp-idf/components/arduino-esp32"]
path = examples/esp-idf/components/arduino-esp32
url = https://github.com/espressif/arduino-esp32
branch = idf-release/v4.4
[submodule "examples/esp-idf/components/esp_littlefs"]
path = examples/esp-idf/components/esp_littlefs
url = https://github.com/joltwallet/esp_littlefs.git

View File

@@ -1,3 +1,23 @@
# v2.0
* Modified the request handling to bring initial url matching and filtering into PsychicHttpServer itself.
* Fixed a bug with filter() where endpoint is matched, but filter fails and it doesn't continue matching further endpoints on same uri (checks were in different codebases)
* HTTP_ANY support
* unlimited endpoints (no more need to manually set config.max_uri_handlers)
* much more flexibility for future
* Endpoint Matching Updates
* Endpoint matching functions can be set on server level (```server.setURIMatchFunction()```) or endpoint level (```endpoint.setURIMatchFunction()```)
* Added convenience macros MATCH_SIMPLE, MATCH_WILDCARD, and MATCH_REGEX
* Added regex matching of URIs, enable it with define PSY_ENABLE_REGEX
* On regex matched requests, you can get match data with request->getRegexMatches()
* Ported URL rewrite functionality from ESPAsyncWS
## Changes required from v1.x to v2.0:
* add a ```server.begin()``` or ```server.start()``` after all your ```server.on()``` calls
* remove any calls to ```config.max_uri_handlers```
* if you are using a custom ```server.config.uri_match_fn``` to match uris, change it to ```server.setURIMatchFunction()```
# v1.2.1
* Fix bug with missing include preventing the HTTPS server from compiling.

View File

@@ -23,7 +23,7 @@ PsychicHttp is a webserver library for ESP32 + Arduino framework which uses the
## Differences from ESPAsyncWebserver
* No templating system (anyone actually use this?)
* No url rewriting (but you can use request->redirect)
* No url rewriting (but you can use response->redirect)
# Usage
@@ -120,7 +120,7 @@ If you have existing code using ESPAsyncWebserver, you will feel right at home w
## setup() Stuff
* no more server.begin(), call server.listen(80), before you add your handlers
* add your handlers and call server.begin()
* 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
@@ -136,7 +136,7 @@ If you have existing code using ESPAsyncWebserver, you will feel right at home w
## Requests / Responses
* request->send is now request->reply()
* request->send is now response->send()
* 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)
@@ -164,9 +164,6 @@ void setup()
//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(...);
@@ -198,7 +195,7 @@ The ```server.on(...)``` returns a pointer to the endpoint, which can be used to
```cpp
//respond to /url only from requests to the AP
server.on("/url", HTTP_GET, request_callback)->setFilter(ON_AP_FILTER);
server.on("/url", HTTP_GET, request_callback)->addFilter(ON_AP_FILTER);
//require authentication on /url
server.on("/url", HTTP_GET, request_callback)->setAuthentication("user", "pass");
@@ -212,7 +209,7 @@ server.on("/ws")->attachHandler(&websocketHandler);
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.
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->send()``` 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:
@@ -226,7 +223,7 @@ Here is a simple example that sends back the client's IP on the URL /ip
server.on("/ip", [](PsychicRequest *request)
{
String output = "Your IP is: " + request->client()->remoteIP().toString();
return request->reply(output.c_str());
return response->send(output.c_str());
});
```
@@ -294,7 +291,7 @@ It's worth noting that there is no standard way of passing in a filename for thi
String url = "/" + request->getFilename();
String output = "<a href=\"" + url + "\">" + url + "</a>";
return request->reply(output.c_str());
return response->send(output.c_str());
});
//wildcard basic file upload - POST to /upload/filename.ext
@@ -352,7 +349,7 @@ Very similar to the basic upload, with 2 key differences:
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
return response->send(output.c_str());
});
//upload to /multipart url
@@ -374,11 +371,11 @@ The ```server.serveStatic()``` function handles creating the handler and assigni
```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);
server.serveStatic("/", LittleFS, "/www/")->addFilter(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);
server.serveStatic("/", LittleFS, "/www-ap/")->addFilter(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.
@@ -426,17 +423,17 @@ Here is a basic example of using WebSockets:
PsychicWebSocketHandler websocketHandler();
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str());
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);
return response->send(frame);
});
websocketHandler.onClose([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString().c_str());
});
//attach the handler to /ws. You can then connect to ws://ip.address/ws
@@ -452,7 +449,7 @@ The onFrame() callback has 2 parameters:
For sending data on the websocket connection, there are 3 methods:
* ```request->reply()``` - only available in the onFrame() callback context.
* ```response->send()``` - 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
@@ -488,12 +485,12 @@ Here is a basic example of using PsychicEventSource:
PsychicEventSource eventSource;
eventSource.onOpen([](PsychicEventSourceClient *client) {
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str());
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());
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString().c_str());
});
//attach the handler to /events
@@ -524,7 +521,7 @@ PsychicHttp supports HTTPS / SSL out of the box, however there are some limitati
#include <PsychicHttp.h>
#include <PsychicHttpsServer.h>
PsychicHttpsServer server;
server.listen(443, server_cert, server_key);
server.setCertificate(server_cert, server_key);
```
```server_cert``` and ```server_key``` are both ```const char *``` parameters which contain the server certificate and private key, respectively.
@@ -552,10 +549,9 @@ Last, but not least, you can create a separate HTTP server on port 80 that redir
//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());
return response->redirect(url.c_str());
});
```
@@ -775,51 +771,22 @@ With all due respect to @me-no-dev who has done some amazing work in the open so
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.
# Community / Support
The best way to get support is probably with Github issues. There is also a [Discord chat](https://discord.gg/CM5abjGG) that is pretty active.
# Roadmap
## v1.2: ESPAsyncWebserver Parity
Change:
Modify the request handling to bring initail url matching and filtering into PsychicHttpServer itself.
Benefits:
* Fix a bug with filter() where endpoint is matched, but filter fails and it doesn't continue matching further endpoints (checks are in different codebases)
* HTTP_ANY support
* unlimited endpoints
* we would use a List to store endpoints
* dont have to pre-declare config.max_uri_handlers;
* much more flexibility for future
Issues
* it would log a warning on every request as if its a 404. (httpd_uri.c:298)
* req->user_ctx is not passed in. (httpd_uri.c:309)
* but... user_ctx is something we could store in the psychicendpoint data
* Websocket support assumes an endpoint with matching url / method (httpd_uri.c:312)
* we could copy and bring this code into our own internal request processor
* would need to manually maintain more code (~100 lines?) and be more prone to esp-idf http_server updates causing problems.
How to implement
* set config.max_uri_handlers = 1;
* possibly do not register any uri_handlers (looks like it would be fastest way to exit httpd_find_uri_handler (httpd_uri.c:94))
* looks like 404 is set by default, so should work.
* modify PsychicEndpoint to store the stuff we would pass to http_server
* create a new function handleRequest() before PsychicHttpServer::defaultNotFoundHandler to process incoming requests.
* bring in code from PsychicHttpServer::notFoundHandler
* add new code to loop over endpoints to call match and filter
* bring code from esp-idf library
* templating system
* regex url matching
* rewrite urls?
* What else are we missing?
## v2.0: ESPAsyncWebserver Parity
* As much ESPAsyncWebServer compatibility as possible
* Update benchmarks and get new data
* we should also track program size and memory usage
## 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?

View File

@@ -1,6 +1,8 @@
* Update CHANGELOG
* Bump version in src/PsychicVersion.h
* Bump version in library.json
* Bump version in library.properties
* Make new release + tag
* this will get pulled in automatically by Arduino Library Indexer
* run ```pio pkg publish``` to publish to Platform.io
* ~~run ```pio pkg publish``` to publish to Platform.io~~
* automatically publishes on release via .github hook

View File

@@ -8,18 +8,18 @@
*/
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoJSON.h>
#include <LittleFS.h>
#include <MongooseCore.h>
#include <MongooseHttpServer.h>
#include <LittleFS.h>
#include <ArduinoJSON.h>
#include <WiFi.h>
const char *ssid = "";
const char *password = "";
const char* ssid = "";
const char* password = "";
MongooseHttpServer server;
const char *htmlContent = R"(
const char* htmlContent = R"(
<!DOCTYPE html>
<html>
<head>
@@ -154,25 +154,23 @@ void setup()
// To debug, please enable Core Debug Level to Verbose
if (connectToWifi())
{
if(!LittleFS.begin())
if (!LittleFS.begin())
{
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
return;
}
//start our server
// start our server
Mongoose.begin();
server.begin(80);
//index file
server.on("/", HTTP_GET, [](MongooseHttpServerRequest *request)
{
request->send(200, "text/html", htmlContent);
});
// index file
server.on("/", HTTP_GET, [](MongooseHttpServerRequest* request)
{ request->send(200, "text/html", htmlContent); });
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](MongooseHttpServerRequest *request)
{
// api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](MongooseHttpServerRequest* request)
{
//create a response object
StaticJsonDocument<128> output;
output["msg"] = "status";
@@ -189,19 +187,18 @@ void setup()
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
request->send(200, "application/json", jsonBuffer.c_str());
});
request->send(200, "application/json", jsonBuffer.c_str()); });
//websocket
server.on("/ws$")->
onFrame([](MongooseHttpWebSocketConnection *connection, int flags, uint8_t *data, size_t len) {
connection->send(WEBSOCKET_OP_TEXT, data, len);
//server.sendAll(connection, (char *)data);
});
// websocket
server.on("/ws$")->onFrame([](MongooseHttpWebSocketConnection* connection, int flags, uint8_t* data, size_t len)
{
connection->send(WEBSOCKET_OP_TEXT, data, len);
// server.sendAll(connection, (char *)data);
});
//hack - no servestatic
server.on("/alien.png", HTTP_GET, [](MongooseHttpServerRequest *request)
{
// hack - no servestatic
server.on("/alien.png", HTTP_GET, [](MongooseHttpServerRequest* request)
{
//open our file
File fp = LittleFS.open("/www/alien.png");
size_t length = fp.size();
@@ -223,8 +220,7 @@ void setup()
free(data);
}
else
request->send(503);
});
request->send(503); });
}
}

View File

@@ -11,12 +11,31 @@
[env]
platform = espressif32
framework = arduino
board = esp32dev
; board = esp32dev
board = esp32-s3-devkitc-1
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM1
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
lib_deps =
https://github.com/me-no-dev/ESPAsyncWebServer
bblanchon/ArduinoJson
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
; mathieucarbou/AsyncTCP @ 3.2.10
https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
mathieucarbou/ESPAsyncWebServer @ 3.3.16
bblanchon/ArduinoJson
lib_ignore =
AsyncTCP
mathieucarbou/AsyncTCP
board_build.filesystem = littlefs
build_flags =
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000
-D CONFIG_ASYNC_TCP_PRIORITY=10
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=128
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096
-D WS_MAX_QUEUED_MESSAGES=128
[env:default]

View File

@@ -7,19 +7,29 @@
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "_secret.h"
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <ESPmDNS.h>
#include <LittleFS.h>
#include <ArduinoJSON.h>
#include <WiFi.h>
const char *ssid = "";
const char *password = "";
#ifndef WIFI_SSID
#error "You need to enter your wifi credentials. Copy secret.h to _secret.h and enter your credentials there."
#endif
// Enter your WIFI credentials in secret.h
const char* ssid = WIFI_SSID;
const char* password = WIFI_PASS;
// hostname for mdns (psychic.local)
const char* local_hostname = "psychic";
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
const char *htmlContent = R"(
const char* htmlContent = R"(
<!DOCTYPE html>
<html>
<head>
@@ -79,14 +89,16 @@ const char *htmlContent = R"(
</html>
)";
const size_t htmlContentLen = strlen(htmlContent);
bool connectToWifi()
{
Serial.println();
Serial.print("[WiFi] Connecting to ");
Serial.println(ssid);
WiFi.setSleep(false);
WiFi.useStaticBuffers(true);
// WiFi.setSleep(false);
// WiFi.useStaticBuffers(true);
WiFi.begin(ssid, password);
@@ -95,10 +107,8 @@ bool connectToWifi()
int numberOfTries = 20;
// Wait for the WiFi event
while (true)
{
switch (WiFi.status())
{
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL:
Serial.println("[WiFi] SSID not found");
break;
@@ -128,15 +138,12 @@ bool connectToWifi()
}
delay(tryDelay);
if (numberOfTries <= 0)
{
if (numberOfTries <= 0) {
Serial.print("[WiFi] Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return false;
}
else
{
} else {
numberOfTries--;
}
}
@@ -144,28 +151,29 @@ bool connectToWifi()
return false;
}
void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if(type == WS_EVT_CONNECT){
//client connected
// Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
// client->printf("Hello Client %u :)", client->id());
// client->ping();
} else if(type == WS_EVT_DISCONNECT){
//client disconnected
// Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
} else if(type == WS_EVT_ERROR){
//error was received from the other end
// Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
} else if(type == WS_EVT_PONG){
//pong message was received (in response to a ping request maybe)
// Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
} else if(type == WS_EVT_DATA){
//data packet
AwsFrameInfo * info = (AwsFrameInfo*)arg;
if(info->final && info->index == 0 && info->len == len){
//the whole message is in a single frame and we got all of it's data
// Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
if(info->opcode == WS_TEXT){
void onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
{
if (type == WS_EVT_CONNECT) {
// client connected
// Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
// client->printf("Hello Client %u :)", client->id());
// client->ping();
} else if (type == WS_EVT_DISCONNECT) {
// client disconnected
// Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
} else if (type == WS_EVT_ERROR) {
// error was received from the other end
// Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
} else if (type == WS_EVT_PONG) {
// pong message was received (in response to a ping request maybe)
// Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
} else if (type == WS_EVT_DATA) {
// data packet
AwsFrameInfo* info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len) {
// the whole message is in a single frame and we got all of it's data
// Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
if (info->opcode == WS_TEXT) {
data[len] = 0;
// Serial.printf("%s\n", (char*)data);
} else {
@@ -174,22 +182,21 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
// }
// Serial.printf("\n");
}
if(info->opcode == WS_TEXT)
{
client->text((char *)data, len);
if (info->opcode == WS_TEXT) {
client->text((char*)data, len);
}
// else
// client->binary("I got your binary message");
} else {
//message is comprised of multiple frames or the frame is split into multiple packets
if(info->index == 0){
// message is comprised of multiple frames or the frame is split into multiple packets
if (info->index == 0) {
// if(info->num == 0)
// Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
// Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
}
Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
if(info->message_opcode == WS_TEXT){
Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT) ? "text" : "binary", info->index, info->index + len);
if (info->message_opcode == WS_TEXT) {
data[len] = 0;
// Serial.printf("%s\n", (char*)data);
} else {
@@ -199,13 +206,12 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
// Serial.printf("\n");
}
if((info->index + len) == info->len){
if ((info->index + len) == info->len) {
// Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
if(info->final){
if (info->final) {
// Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
if(info->message_opcode == WS_TEXT)
{
client->text((char *)data, info->len);
if (info->message_opcode == WS_TEXT) {
client->text((char*)data, info->len);
}
// else
// client->binary("I got your binary message");
@@ -223,40 +229,41 @@ void setup()
// We start by connecting to a WiFi network
// To debug, please enable Core Debug Level to Verbose
if (connectToWifi())
{
if(!LittleFS.begin())
{
if (connectToWifi()) {
// set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
Serial.println("Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if (!LittleFS.begin()) {
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
return;
}
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{
request->send(200, "text/html", htmlContent);
// api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
// ESPAsyncWebServer, sending a char* does a buffer copy, unlike Psychic.
// Sending flash data is done with the uint8_t* overload.
request->send(200, "text/html", (uint8_t*)htmlContent, htmlContentLen);
});
//serve static files from LittleFS/www on /
server.serveStatic("/", LittleFS, "/www/");
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](AsyncWebServerRequest *request)
{
//create a response object
StaticJsonDocument<128> output;
// api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](AsyncWebServerRequest* request) {
// create a response object
JsonDocument output;
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
//work with some params
if (request->hasParam("foo"))
{
AsyncWebParameter* foo = request->getParam("foo");
// work with some params
if (request->hasParam("foo")) {
const AsyncWebParameter* foo = request->getParam("foo");
output["foo"] = foo->value();
}
//serialize and return
// serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
request->send(200, "application/json", jsonBuffer.c_str());
@@ -265,6 +272,10 @@ void setup()
ws.onEvent(onEvent);
server.addHandler(&ws);
// put this last, otherwise it clogs the other requests
// serve static files from LittleFS/www on /
server.serveStatic("/", LittleFS, "/www/");
server.begin();
}
}
@@ -272,5 +283,6 @@ void setup()
void loop()
{
ws.cleanupClients();
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
delay(1000);
}

View File

@@ -0,0 +1,2 @@
#define WIFI_SSID "Your_SSID"
#define WIFI_PASS "Your_PASS"

View File

@@ -1,7 +1,8 @@
#!/usr/bin/env node
//stress test the client opening/closing code
const EventSource = require('eventsource');
const url = 'http://192.168.2.131/events';
const url = 'http://psychic.local/events';
async function eventSourceClient() {
console.log(`Starting test`);

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env node
//stress test the http request code
const axios = require('axios');
const url = 'http://192.168.2.131/api';
const url = 'http://psychic.local/api';
const queryParams = {
foo: 'bar',
foo1: 'bar',

View File

@@ -1,37 +1,42 @@
#!/usr/bin/env bash
#Command to install the testers:
# npm install -g autocannon
# npm install
TEST_IP="192.168.2.131"
TEST_TIME=60
LOG_FILE=psychic-http-loadtest.log
TEST_IP="psychic.local"
TEST_TIME=10
#LOG_FILE=psychic-http-loadtest.log
LOG_FILE=_psychic-http-loadtest.json
RESULTS_FILE=http-loadtest-results.csv
TIMEOUT=10000
WORKERS=1
PROTOCOL=http
#PROTOCOL=https
if test -f "$LOG_FILE"; then
rm $LOG_FILE
fi
echo "url,connections,rps,latency,errors" > $RESULTS_FILE
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
#for CONCURRENCY in 20
do
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/"
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/" --quiet >> $LOG_FILE
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/" >> $LOG_FILE 2>&1
printf "\n\n----------------\n\n" >> $LOG_FILE
sleep 1
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/" > $LOG_FILE
node parse-http-test.js $LOG_FILE $RESULTS_FILE
sleep 5
done
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
do
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/api"
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/api?foo=bar" --quiet >> $LOG_FILE
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/api?foo=bar" >> $LOG_FILE 2>&1
printf "\n\n----------------\n\n" >> $LOG_FILE
sleep 1
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/api?foo=bar" > $LOG_FILE
node parse-http-test.js $LOG_FILE $RESULTS_FILE
sleep 5
done
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
do
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/alien.png"
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/alien.png" --quiet >> $LOG_FILE
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/alien.png" >> $LOG_FILE 2>&1
printf "\n\n----------------\n\n" >> $LOG_FILE
sleep 1
done
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/alien.png" > $LOG_FILE
node parse-http-test.js $LOG_FILE $RESULTS_FILE
sleep 5
done
rm $LOG_FILE

View File

@@ -1,10 +1,11 @@
#!/usr/bin/env bash
#Command to install the testers:
# npm install -g loadtest
# npm install
TEST_IP="192.168.2.131"
TEST_IP="psychic.local"
TEST_TIME=60
LOG_FILE=psychic-websocket-loadtest.log
LOG_FILE=psychic-websocket-loadtest.json
RESULTS_FILE=websocket-loadtest-results.csv
PROTOCOL=ws
#PROTOCOL=wss
@@ -12,20 +13,33 @@ if test -f "$LOG_FILE"; then
rm $LOG_FILE
fi
for CONCURRENCY in 1 2 3 4 5 6 7
echo "url,clients,rps,latency,errors" > $RESULTS_FILE
CORES=1
for CONCURRENCY in 1 2 3 4 5
do
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/ws"
loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
sleep 1
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
sleep 2
done
for CONNECTIONS in 8 10 16 20
#for CONNECTIONS in 20
CORES=2
for CONNECTIONS in 6 8 10 12 14
do
CONCURRENCY=$((CONNECTIONS / 2))
printf "\n\nCLIENTS: *** $CONNECTIONS ***\n\n" >> $LOG_FILE
echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws"
loadtest -c $CONCURRENCY --cores 2 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
sleep 1
done
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
sleep 2
done
CORES=4
for CONNECTIONS in 16 20 24 28 32
do
CONCURRENCY=$((CONNECTIONS / CORES))
echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws"
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
sleep 2
done

View File

@@ -1,7 +1,10 @@
{
"dependencies": {
"autocannon": "^7.15.0",
"axios": "^1.6.2",
"csv-writer": "^1.6.0",
"eventsource": "^2.0.2",
"loadtest": "^8.0.9",
"ws": "^8.14.2"
}
}

View File

@@ -0,0 +1,55 @@
const fs = require('fs');
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
// Get the input and output file paths from the command line arguments
const inputFilePath = process.argv[2];
const outputFilePath = process.argv[3];
if (!inputFilePath || !outputFilePath) {
console.error('Usage: node script.js <inputFilePath> <outputFilePath>');
process.exit(1);
}
// Read and parse the JSON file
fs.readFile(inputFilePath, 'utf8', (err, data) => {
if (err) {
console.error('Error reading the input file:', err);
return;
}
// Parse the JSON data
const jsonData = JSON.parse(data);
// Extract the desired fields
const { url, connections, latency, requests, errors } = jsonData;
const latencyMean = latency.mean;
const requestsMean = requests.mean;
// Set up the CSV writer
const csvWriter = createCsvWriter({
path: outputFilePath,
header: [
{id: 'url', title: 'URL'},
{id: 'connections', title: 'Connections'},
{id: 'requestsMean', title: 'Requests Mean'},
{id: 'latencyMean', title: 'Latency Mean'},
{id: 'errors', title: 'Errors'},
],
append: true // this will append to the existing file
});
// Prepare the data to be written
const records = [
{ url: url, connections: connections, latencyMean: latencyMean, requestsMean: requestsMean, errors: errors }
];
// Write the data to the CSV file
csvWriter.writeRecords(records)
.then(() => {
console.log('Data successfully appended to CSV file.');
})
.catch(err => {
console.error('Error writing to the CSV file:', err);
});
});

View File

@@ -0,0 +1,64 @@
const fs = require('fs');
const readline = require('readline');
if (process.argv.length !== 4) {
console.error('Usage: node parse-websocket-test.js <input_file> <output_file>');
process.exit(1);
}
const inputFile = process.argv[2];
const outputFile = process.argv[3];
async function parseFile() {
const fileStream = fs.createReadStream(inputFile);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
let targetUrl = null;
let totalErrors = null;
let meanLatency = null;
let effectiveRps = null;
let concurrentClients = null;
for await (const line of rl) {
if (line.startsWith('Target URL:')) {
targetUrl = line.split(':').slice(1).join(':').trim();
}
if (line.startsWith('Total errors:')) {
totalErrors = parseInt(line.split(':')[1].trim(), 10);
}
if (line.startsWith('Mean latency:')) {
meanLatency = parseFloat(line.split(':')[1].trim());
}
if (line.startsWith('Effective rps:')) {
effectiveRps = parseInt(line.split(':')[1].trim(), 10);
}
if (line.startsWith('Concurrent clients:')) {
concurrentClients = parseInt(line.split(':')[1].trim(), 10);
}
}
if (targetUrl === null || totalErrors === null || meanLatency === null || effectiveRps === null || concurrentClients === null) {
console.error('Failed to extract necessary data from the input file');
process.exit(1);
}
const csvLine = `${targetUrl},${concurrentClients},${effectiveRps},${meanLatency},${totalErrors}\n`;
fs.appendFile(outputFile, csvLine, (err) => {
if (err) {
console.error('Failed to append to CSV file:', err);
process.exit(1);
}
console.log('Data successfully appended to CSV file.');
});
}
parseFile().catch(err => {
console.error('Error reading file:', err);
process.exit(1);
});

View File

@@ -15,8 +15,14 @@ board = esp32dev
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
lib_deps =
https://github.com/hoeken/PsychicHttp
bblanchon/ArduinoJson
board_build.filesystem = littlefs
[env:default]
lib_deps = https://github.com/hoeken/PsychicHttp
[env:v2-dev]
lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev
board = esp32-s3-devkitc-1
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM1

View File

@@ -7,26 +7,30 @@
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <Arduino.h>
#include <WiFi.h>
#include <PsychicHttp.h>
#include <LittleFS.h>
#include <ArduinoJSON.h>
#include "_secret.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESPmDNS.h>
#include <LittleFS.h>
#include <PsychicHttp.h>
#include <WiFi.h>
#ifndef WIFI_SSID
#error "You need to enter your wifi credentials. Copy secret.h to _secret.h and enter your credentials there."
#endif
//Enter your WIFI credentials in secret.h
const char *ssid = WIFI_SSID;
const char *password = WIFI_PASS;
// Enter your WIFI credentials in secret.h
const char* ssid = WIFI_SSID;
const char* password = WIFI_PASS;
// hostname for mdns (psychic.local)
const char* local_hostname = "psychic";
PsychicHttpServer server;
PsychicWebSocketHandler websocketHandler;
PsychicEventSource eventSource;
const char *htmlContent = R"(
const char* htmlContent = R"(
<!DOCTYPE html>
<html>
<head>
@@ -92,8 +96,8 @@ bool connectToWifi()
Serial.print("[WiFi] Connecting to ");
Serial.println(ssid);
WiFi.setSleep(false);
WiFi.useStaticBuffers(true);
// WiFi.setSleep(false);
// WiFi.useStaticBuffers(true);
WiFi.begin(ssid, password);
@@ -102,10 +106,8 @@ bool connectToWifi()
int numberOfTries = 20;
// Wait for the WiFi event
while (true)
{
switch (WiFi.status())
{
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL:
Serial.println("[WiFi] SSID not found");
break;
@@ -135,15 +137,12 @@ bool connectToWifi()
}
delay(tryDelay);
if (numberOfTries <= 0)
{
if (numberOfTries <= 0) {
Serial.print("[WiFi] Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return false;
}
else
{
} else {
numberOfTries--;
}
}
@@ -157,47 +156,42 @@ void setup()
delay(10);
Serial.println("PsychicHTTP Benchmark");
if (connectToWifi())
{
if(!LittleFS.begin())
{
if (connectToWifi()) {
// set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
Serial.println("Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if (!LittleFS.begin()) {
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
return;
}
//start our server
server.listen(80);
// our index
server.on("/", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send(200, "text/html", htmlContent); });
//our index
server.on("/", HTTP_GET, [](PsychicRequest *request)
{
return request->reply(200, "text/html", htmlContent);
});
//serve static files from LittleFS/www on /
// serve static files from LittleFS/www on /
server.serveStatic("/", LittleFS, "/www/");
//a websocket echo server
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
client->sendMessage("Hello!");
});
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
request->reply(frame);
return ESP_OK;
// a websocket echo server
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
// client->sendMessage("Hello!");
});
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
response->send(frame);
return ESP_OK; });
server.on("/ws", &websocketHandler);
//EventSource server
eventSource.onOpen([](PsychicEventSourceClient *client) {
client->send("Hello", NULL, millis(), 1000);
});
// EventSource server
eventSource.onOpen([](PsychicEventSourceClient* client) { client->send("Hello", NULL, millis(), 1000); });
server.on("/events", &eventSource);
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](PsychicRequest *request)
{
// api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
//create a response object
StaticJsonDocument<128> output;
JsonDocument output;
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
@@ -212,16 +206,16 @@ void setup()
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
return request->reply(200, "application/json", jsonBuffer.c_str());
});
return response->send(200, "application/json", jsonBuffer.c_str()); });
server.begin();
}
}
unsigned long last;
void loop()
{
if (millis() - last > 1000)
{
if (millis() - last > 1000) {
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
last = millis();
}

View File

@@ -7,22 +7,21 @@
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <Arduino.h>
#include <WiFi.h>
#include <PsychicHttp.h>
#include <LittleFS.h>
#include <ArduinoJSON.h>
#include "_secret.h"
#include <Arduino.h>
#include <ArduinoJSON.h>
#include <LittleFS.h>
#include <PsychicHttp.h>
#include <PsychicHttpsServer.h>
#include <WiFi.h>
#ifndef WIFI_SSID
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
#endif
//Enter your WIFI credentials in secret.h
const char *ssid = WIFI_SSID;
const char *password = WIFI_PASS;
// Enter your WIFI credentials in secret.h
const char* ssid = WIFI_SSID;
const char* password = WIFI_PASS;
PsychicHttpsServer server;
PsychicWebSocketHandler websocketHandler;
@@ -30,7 +29,7 @@ PsychicWebSocketHandler websocketHandler;
String server_cert;
String server_key;
const char *htmlContent = R"(
const char* htmlContent = R"(
<!DOCTYPE html>
<html>
<head>
@@ -162,52 +161,56 @@ void setup()
if (connectToWifi())
{
if(!LittleFS.begin())
if (!LittleFS.begin())
{
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
return;
}
File fp = LittleFS.open("/server.crt");
if (fp) {
if (fp)
{
server_cert = fp.readString();
} else {
}
else
{
Serial.println("server.pem not found, SSL not available");
return;
}
fp.close();
File fp2 = LittleFS.open("/server.key");
if (fp2) {
if (fp2)
{
server_key = fp2.readString();
} else {
}
else
{
Serial.println("server.key not found, SSL not available");
return;
}
fp2.close();
//start our server
server.listen(443, server_cert.c_str(), server_key.c_str());
// start our server
server.setCertificate(server_cert.c_str(), server_key.c_str());
//our index
server.on("/", HTTP_GET, [](PsychicRequest *request)
{
return request->reply(200, "text/html", htmlContent);
});
// our index
server.on("/", HTTP_GET, [](PsychicRequest* request)
{ return response->send(200, "text/html", htmlContent); });
//serve static files from LittleFS/www on /
// serve static files from LittleFS/www on /
server.serveStatic("/", LittleFS, "/www/");
//a websocket echo server
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
request->reply(frame);
return ESP_OK;
});
// a websocket echo server
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame)
{
response->send(frame);
return ESP_OK; });
server.on("/ws", &websocketHandler);
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](PsychicRequest *request)
{
// api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](PsychicRequest* request)
{
//create a response object
StaticJsonDocument<128> output;
output["msg"] = "status";
@@ -224,8 +227,7 @@ void setup()
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
return request->reply(200, "application/json", jsonBuffer.c_str());
});
return response->send(200, "application/json", jsonBuffer.c_str()); });
}
}

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env node
//stress test the client open/close for websockets
const WebSocket = require('ws');
const uri = 'ws://192.168.2.131/ws';
const uri = 'ws://psychic.local/ws';
async function websocketClient() {
console.log(`Starting test`);

View File

@@ -206,30 +206,25 @@ void setup()
//do we want secure or not?
if (app_enable_ssl)
{
server.listen(443, server_cert.c_str(), server_key.c_str());
server.setCertificate(server_cert.c_str(), server_key.c_str());
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
PsychicHttpServer *redirectServer = new PsychicHttpServer();
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
redirectServer->listen(80);
redirectServer->onNotFound([](PsychicRequest *request) {
redirectServer->onNotFound([](PsychicRequest *request, PsychicResponse *response) {
String url = "https://" + request->host() + request->url();
return request->redirect(url.c_str());
return response->redirect(url.c_str());
});
}
else
server.listen(80);
#else
server.listen(80);
#endif
//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);
server.serveStatic("/", LittleFS, "/www/")->addFilter(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);
server.serveStatic("/", LittleFS, "/www-ap/")->addFilter(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.
@@ -240,16 +235,16 @@ void setup()
//example callback everytime a connection is opened
server.onOpen([](PsychicClient *client) {
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString());
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
});
//example callback everytime a connection is closed
server.onClose([](PsychicClient *client) {
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString());
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
});
//api - json message passed in as post body
server.on("/api", HTTP_POST, [](PsychicRequest *request)
server.on("/api", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response)
{
//load our JSON request
StaticJsonDocument<1024> json;
@@ -272,18 +267,18 @@ void setup()
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
return request->reply(200, "application/json", jsonBuffer.c_str());
return response->send(200, "application/json", jsonBuffer.c_str());
});
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/ip", HTTP_GET, [](PsychicRequest *request)
server.on("/ip", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
{
String output = "Your IP is: " + request->client()->remoteIP().toString();
return request->reply(output.c_str());
return response->send(output.c_str());
});
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](PsychicRequest *request)
server.on("/api", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
{
//create a response object
StaticJsonDocument<128> output;
@@ -301,65 +296,64 @@ void setup()
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
return request->reply(200, "application/json", jsonBuffer.c_str());
return response->send(200, "application/json", jsonBuffer.c_str());
});
//how to redirect a request
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
server.on("/redirect", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
{
return request->redirect("/alien.png");
return response->redirect("/alien.png");
});
//how to do basic auth
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
return request->reply("Auth Basic Success!");
return response->send("Auth Basic Success!");
});
//how to do digest auth
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
return request->reply("Auth Digest Success!");
return response->send("Auth Digest Success!");
});
//example of getting / setting cookies
server.on("/cookies", HTTP_GET, [](PsychicRequest *request)
server.on("/cookies", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
{
PsychicResponse response(request);
int counter = 0;
if (request->hasCookie("counter"))
char cookie[14];
size_t size = 14;
if (request->getCookie("counter", cookie, &size) == ESP_OK)
{
counter = std::stoi(request->getCookie("counter").c_str());
// value is null-terminated.
counter = std::stoi(cookie);
counter++;
}
sprintf(cookie, "%d", counter);
char cookie[10];
sprintf(cookie, "%i", counter);
response.setCookie("counter", cookie);
response.setContent(cookie);
return response.send();
response->setCookie("counter", cookie);
response->setContent(cookie);
return response->send();
});
//example of getting POST variables
server.on("/post", HTTP_POST, [](PsychicRequest *request)
server.on("/post", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response)
{
String output;
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
return response->send(output.c_str());
});
//you can set up a custom 404 handler.
server.onNotFound([](PsychicRequest *request)
server.onNotFound([](PsychicRequest *request, PsychicResponse *response)
{
return request->reply(404, "text/html", "Custom 404 Handler");
return response->send(404, "text/html", "Custom 404 Handler");
});
//handle a very basic upload as post body
@@ -393,12 +387,12 @@ void setup()
});
//gets called after upload has been handled
uploadHandler->onRequest([](PsychicRequest *request)
uploadHandler->onRequest([](PsychicRequest *request, PsychicResponse *response)
{
String url = "/" + request->getFilename();
String output = "<a href=\"" + url + "\">" + url + "</a>";
return request->reply(output.c_str());
return response->send(output.c_str());
});
//wildcard basic file upload - POST to /upload/filename.ext
@@ -435,7 +429,7 @@ void setup()
});
//gets called after upload has been handled
multipartHandler->onRequest([](PsychicRequest *request)
multipartHandler->onRequest([](PsychicRequest *request, PsychicResponse *response)
{
PsychicWebParameter *file = request->getParam("file_upload");
@@ -447,7 +441,7 @@ void setup()
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
return response->send(output.c_str());
});
//wildcard basic file upload - POST to /upload/filename.ext
@@ -455,7 +449,7 @@ void setup()
//a websocket echo server
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString());
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
client->sendMessage("Hello!");
});
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
@@ -463,17 +457,17 @@ void setup()
return request->reply(frame);
});
websocketHandler.onClose([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString());
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
});
server.on("/ws", &websocketHandler);
//EventSource server
eventSource.onOpen([](PsychicEventSourceClient *client) {
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString());
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
client->send("Hello user!", NULL, millis(), 1000);
});
eventSource.onClose([](PsychicEventSourceClient *client) {
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString());
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
});
server.on("/events", &eventSource);
}
@@ -486,10 +480,10 @@ void loop()
{
if (millis() - lastUpdate > 2000)
{
sprintf(output, "Millis: %d\n", millis());
sprintf(output, "Millis: %lu\n", millis());
websocketHandler.sendAll(output);
sprintf(output, "%d", millis());
sprintf(output, "%lu", millis());
eventSource.send(output, "millis", millis(), 0);
lastUpdate = millis();

View File

@@ -28,7 +28,7 @@ public:
esp_err_t handleRequest(PsychicRequest *request) {
//PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html"
//return response.send(); // uncomment : return captive portal page
return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
return response->send(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
}
};
CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal

View File

@@ -14,7 +14,7 @@
#include <ESPmDNS.h>
#include <PsychicHttp.h>
char* TAG = "CAPTPORT";
#define TAG "CAPTPORT"
// captiveportal
// credits https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino
@@ -29,10 +29,10 @@ public:
// ... if needed some tests ... return(false);
return true; // activate captive portal
}
esp_err_t handleRequest(PsychicRequest *request) {
esp_err_t handleRequest(PsychicRequest *request, PsychicResponse *response) {
//PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html"
//return response.send(); // uncomment : return captive portal page
return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
return response->send(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
}
};
CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal
@@ -128,7 +128,6 @@ void setup() {
//setup server config stuff here
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
server.listen(80);
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");

View File

@@ -8,7 +8,7 @@
CONDITIONS OF ANY KIND, either express or implied.
*/
char *TAG = "OTA"; // ESP_LOG tag
#define TAG "OTA" // ESP_LOG tag
// PsychicHttp
#include <Arduino.h>
@@ -100,7 +100,6 @@ void setup()
//setup server config stuff here
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
server.listen(80);
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
@@ -109,8 +108,8 @@ void setup()
//you can set up a custom 404 handler.
// curl -i http://psychic.local/404
server.onNotFound([](PsychicRequest *request) {
return request->reply(404, "text/html", "Custom 404 Handler");
server.onNotFound([](PsychicRequest *request, PsychicResponse *response) {
return response->send(404, "text/html", "Custom 404 Handler");
});
// OTA
@@ -177,34 +176,34 @@ void setup()
}
}); // end onUpload
updateHandler->onRequest([](PsychicRequest *request) { // triggered when update is completed (either OK or KO) and returns request's response (important)
updateHandler->onRequest([](PsychicRequest *request, PsychicResponse *response) { // triggered when update is completed (either OK or KO) and returns request's response (important)
String result; // request result
// code below is executed when update is finished
if (!Update.hasError()) { // update is OK
ESP_LOGI(TAG,"Update code or data OK Update.errorString() %s", Update.errorString());
result = "<b style='color:green'>Update done for file.</b>";
return request->reply(200,"text/html",result.c_str());
return response->send(200,"text/html",result.c_str());
// ESP.restart(); // restart ESP if needed
} // end update is OK
else { // update is KO, send request with pretty print error
result = " Update.errorString() " + String(Update.errorString());
ESP_LOGE(TAG,"ERROR : error %s",result.c_str());
return request->reply(500, "text/html", result.c_str());
return response->send(500, "text/html", result.c_str());
} // end update is KO
});
server.on("/update", HTTP_GET, [](PsychicRequest*request){
PsychicFileResponse response(request, LittleFS, "/update.html");
server.on("/update", HTTP_GET, [](PsychicRequest*request, PsychicResponse *res){
PsychicFileResponse response(res, LittleFS, "/update.html");
return response.send();
});
server.on("/update", HTTP_POST, updateHandler);
server.on("/restart", HTTP_POST, [](PsychicRequest *request) {
server.on("/restart", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response) {
String output = "<b style='color:green'>Restarting ...</b>";
ESP_LOGI(TAG,"%s",output.c_str());
esprestart=true;
return request->reply(output.c_str());
return response->send(output.c_str());
});
} // end onRequest

View File

@@ -9,66 +9,69 @@
*/
/**********************************************************************************************
* Note: this demo relies on the following libraries (Install via Library Manager)
* ArduinoJson UrlEncode
**********************************************************************************************/
* Note: this demo relies on the following libraries (Install via Library Manager)
* ArduinoJson UrlEncode
**********************************************************************************************/
/**********************************************************************************************
* Note: this demo relies on various files to be uploaded on the LittleFS partition
* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/
**********************************************************************************************/
* Note: this demo relies on various files to be uploaded on the LittleFS partition
* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/
**********************************************************************************************/
#include "secret.h"
#include <Arduino.h>
#include <WiFi.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <ESPmDNS.h>
#include "secret.h"
#include <LittleFS.h>
#include <PsychicHttp.h>
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE //set this to y in menuconfig to enable SSL
#include <PsychicHttpsServer.h>
#include <WiFi.h>
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE // set this to y in menuconfig to enable SSL
#include <PsychicHttpsServer.h>
#endif
#ifndef WIFI_SSID
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
#endif
//Enter your WIFI credentials in secret.h
const char *ssid = WIFI_SSID;
const char *password = WIFI_PASS;
// Enter your WIFI credentials in secret.h
const char* ssid = WIFI_SSID;
const char* password = WIFI_PASS;
// Set your SoftAP credentials
const char *softap_ssid = "PsychicHttp";
const char *softap_password = "";
IPAddress softap_ip(10, 0, 0, 1);
const char* softap_ssid = "PsychicHttp";
const char* softap_password = "";
IPAddress softap_ip(10, 0, 0, 1);
//credentials for the /auth-basic and /auth-digest examples
const char *app_user = "admin";
const char *app_pass = "admin";
const char *app_name = "Your App";
// credentials for the /auth-basic and /auth-digest examples
const char* app_user = "admin";
const char* app_pass = "admin";
const char* app_name = "Your App";
//hostname for mdns (psychic.local)
const char *local_hostname = "psychic";
AuthenticationMiddleware basicAuth;
AuthenticationMiddleware digestAuth;
//#define CONFIG_ESP_HTTPS_SERVER_ENABLE to enable ssl
// hostname for mdns (psychic.local)
const char* local_hostname = "psychic";
// #define CONFIG_ESP_HTTPS_SERVER_ENABLE to enable ssl
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
bool app_enable_ssl = true;
String server_cert;
String server_key;
bool app_enable_ssl = true;
String server_cert;
String server_key;
#endif
//our main server object
// our main server object
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
PsychicHttpsServer server;
PsychicHttpsServer server;
#else
PsychicHttpServer server;
PsychicHttpServer server;
#endif
PsychicWebSocketHandler websocketHandler;
PsychicEventSource eventSource;
bool connectToWifi()
{
//dual client and AP mode
// dual client and AP mode
WiFi.mode(WIFI_AP_STA);
// Configure SoftAP
@@ -92,10 +95,8 @@ bool connectToWifi()
int numberOfTries = 20;
// Wait for the WiFi event
while (true)
{
switch (WiFi.status())
{
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL:
Serial.println("[WiFi] SSID not found");
break;
@@ -125,15 +126,12 @@ bool connectToWifi()
}
delay(tryDelay);
if (numberOfTries <= 0)
{
if (numberOfTries <= 0) {
Serial.print("[WiFi] Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return false;
}
else
{
} else {
numberOfTries--;
}
}
@@ -148,111 +146,102 @@ void setup()
// We start by connecting to a WiFi network
// To debug, please enable Core Debug Level to Verbose
if (connectToWifi())
{
//set up our esp32 to listen on the local_hostname.local domain
if (connectToWifi()) {
// set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
Serial.println("Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if(!LittleFS.begin())
{
if (!LittleFS.begin()) {
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
return;
}
//look up our keys?
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
if (app_enable_ssl)
{
File fp = LittleFS.open("/server.crt");
if (fp)
{
server_cert = fp.readString();
// look up our keys?
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
if (app_enable_ssl) {
File fp = LittleFS.open("/server.crt");
if (fp) {
server_cert = fp.readString();
// Serial.println("Server Cert:");
// Serial.println(server_cert);
}
else
{
Serial.println("server.pem not found, SSL not available");
app_enable_ssl = false;
}
fp.close();
File fp2 = LittleFS.open("/server.key");
if (fp2)
{
server_key = fp2.readString();
// Serial.println("Server Key:");
// Serial.println(server_key);
}
else
{
Serial.println("server.key not found, SSL not available");
app_enable_ssl = false;
}
fp2.close();
// Serial.println("Server Cert:");
// Serial.println(server_cert);
} else {
Serial.println("server.pem not found, SSL not available");
app_enable_ssl = false;
}
#endif
fp.close();
//setup server config stuff here
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
File fp2 = LittleFS.open("/server.key");
if (fp2) {
server_key = fp2.readString();
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
// Serial.println("Server Key:");
// Serial.println(server_key);
} else {
Serial.println("server.key not found, SSL not available");
app_enable_ssl = false;
}
fp2.close();
}
#endif
//do we want secure or not?
if (app_enable_ssl)
{
server.listen(443, server_cert.c_str(), server_key.c_str());
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
PsychicHttpServer *redirectServer = new PsychicHttpServer();
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
redirectServer->listen(80);
redirectServer->onNotFound([](PsychicRequest *request) {
// setup server config stuff here
server.config.max_uri_handlers = 20; // maximum number of uri handlers (.on() calls)
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
server.ssl_config.httpd.max_uri_handlers = 20; // maximum number of uri handlers (.on() calls)
// do we want secure or not?
if (app_enable_ssl) {
server.setCertificate(server_cert.c_str(), server_key.c_str());
// this creates a 2nd server listening on port 80 and redirects all requests HTTPS
PsychicHttpServer* redirectServer = new PsychicHttpServer();
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) {
String url = "https://" + request->host() + request->url();
return request->redirect(url.c_str());
});
}
else
server.listen(80);
#else
server.listen(80);
#endif
return response->redirect(url.c_str()); });
}
#endif
//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);
basicAuth.setUsername(app_user);
basicAuth.setPassword(app_pass);
basicAuth.setRealm(app_name);
basicAuth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH);
basicAuth.setAuthFailureMessage("You must log in.");
//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);
digestAuth.setUsername(app_user);
digestAuth.setPassword(app_pass);
digestAuth.setRealm(app_name);
digestAuth.setAuthMethod(HTTPAuthMethod::DIGEST_AUTH);
digestAuth.setAuthFailureMessage("You must log in.");
//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.
// 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/")->addFilter(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/")->addFilter(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
// you can also serve single files
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
//example callback everytime a connection is opened
server.onOpen([](PsychicClient *client) {
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
});
// example callback everytime a connection is opened
server.onOpen([](PsychicClient* client) { Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); });
//example callback everytime a connection is closed
server.onClose([](PsychicClient *client) {
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
});
// example callback everytime a connection is closed
server.onClose([](PsychicClient* client) { Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
//api - json message passed in as post body
server.on("/api", HTTP_POST, [](PsychicRequest *request)
{
// api - json message passed in as post body
server.on("/api", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
//load our JSON request
JsonDocument json;
String body = request->body();
@@ -284,19 +273,15 @@ void setup()
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
return request->reply(200, "application/json", jsonBuffer.c_str());
});
return response->send(200, "application/json", jsonBuffer.c_str()); });
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/ip", HTTP_GET, [](PsychicRequest *request)
{
// api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/ip", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
String output = "Your IP is: " + request->client()->remoteIP().toString();
return request->reply(output.c_str());
});
return response->send(output.c_str()); });
//api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](PsychicRequest *request)
{
// api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
//create a response object
JsonDocument output;
output["msg"] = "status";
@@ -313,70 +298,48 @@ void setup()
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
return request->reply(200, "application/json", jsonBuffer.c_str());
});
return response->send(200, "application/json", jsonBuffer.c_str()); });
//how to redirect a request
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
{
return request->redirect("/alien.png");
});
// how to redirect a request
server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); });
//how to do basic auth
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
return request->reply("Auth Basic Success!");
});
// how to do basic auth
server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send("Auth Basic Success!"); })->addMiddleware(&basicAuth);
//how to do digest auth
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
return request->reply("Auth Digest Success!");
});
//example of getting / setting cookies
server.on("/cookies", HTTP_GET, [](PsychicRequest *request)
{
PsychicResponse response(request);
// how to do digest auth
server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send("Auth Digest Success!"); })->addMiddleware(&digestAuth);
// example of getting / setting cookies
server.on("/cookies", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
int counter = 0;
if (request->hasCookie("counter"))
char cookie[14];
size_t size = 14;
if (request->getCookie("counter", cookie, &size) == ESP_OK)
{
counter = std::stoi(request->getCookie("counter").c_str());
// value is null-terminated.
counter = std::stoi(cookie);
counter++;
}
sprintf(cookie, "%d", counter);
char cookie[12];
sprintf(cookie, "%i", counter);
response->setCookie("counter", cookie);
response->setContent(cookie);
return response->send(); });
response.setCookie("counter", cookie);
response.setContent(cookie);
return response.send();
});
//example of getting POST variables
server.on("/post", HTTP_POST, [](PsychicRequest *request)
{
// example of getting POST variables
server.on("/post", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
String output;
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
});
return response->send(output.c_str()); });
//you can set up a custom 404 handler.
server.onNotFound([](PsychicRequest *request)
{
return request->reply(404, "text/html", "Custom 404 Handler");
});
// you can set up a custom 404 handler.
server.onNotFound([](PsychicRequest* request, PsychicResponse* response) { return response->send(404, "text/html", "Custom 404 Handler"); });
//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) {
// 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;
@@ -401,24 +364,21 @@ void setup()
return ESP_FAIL;
}
return ESP_OK;
});
return ESP_OK; });
//gets called after upload has been handled
uploadHandler->onRequest([](PsychicRequest *request)
{
// gets called after upload has been handled
uploadHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
String url = "/" + request->getFilename();
String output = "<a href=\"" + url + "\">" + url + "</a>";
return request->reply(output.c_str());
});
return response->send(output.c_str()); });
//wildcard basic file upload - POST to /upload/filename.ext
// wildcard basic file upload - POST to /upload/filename.ext
server.on("/upload/*", HTTP_POST, uploadHandler);
//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) {
// 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;
@@ -443,12 +403,10 @@ void setup()
return ESP_FAIL;
}
return ESP_OK;
});
return ESP_OK; });
//gets called after upload has been handled
multipartHandler->onRequest([](PsychicRequest *request)
{
// gets called after upload has been handled
multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
PsychicWebParameter *file = request->getParam("file_upload");
String url = "/" + file->value();
@@ -459,34 +417,26 @@ void setup()
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
});
return response->send(output.c_str()); });
//wildcard basic file upload - POST to /upload/filename.ext
// wildcard basic file upload - POST to /upload/filename.ext
server.on("/multipart", HTTP_POST, multipartHandler);
//a websocket echo server
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
// a websocket echo server
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
client->sendMessage("Hello!");
});
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
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->localIP().toString().c_str());
});
return request->reply(frame); });
websocketHandler.onClose([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
server.on("/ws", &websocketHandler);
//EventSource server
eventSource.onOpen([](PsychicEventSourceClient *client) {
// EventSource server
eventSource.onOpen([](PsychicEventSourceClient* client) {
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
client->send("Hello user!", NULL, millis(), 1000);
});
eventSource.onClose([](PsychicEventSourceClient *client) {
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
});
client->send("Hello user!", NULL, millis(), 1000); });
eventSource.onClose([](PsychicEventSourceClient* client) { Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
server.on("/events", &eventSource);
}
}
@@ -496,8 +446,7 @@ char output[60];
void loop()
{
if (millis() - lastUpdate > 2000)
{
if (millis() - lastUpdate > 2000) {
sprintf(output, "Millis: %lu\n", millis());
websocketHandler.sendAll(output);

View File

@@ -1,39 +0,0 @@
.pioenvs
.clang_complete
.gcc-flags.json
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Visual Studio/VisualMicro stuff
Visual\ Micro
*.sdf
*.opensdf
*.suo
.pioenvs
.piolibdeps
.pio
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/settings.json
.vscode/.browse.c_cpp.db*
.vscode/ipch

View File

@@ -1,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -1,74 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[common]
lib_deps = ArduinoMongoose
monitor_speed = 115200
monitor_port = /dev/ttyUSB1
build_flags =
-DENABLE_DEBUG
# -DCS_ENABLE_STDIO
-DMG_ENABLE_HTTP_STREAMING_MULTIPART=1
build_flags_secure =
-DSIMPLE_SERVER_SECURE
-DMG_ENABLE_SSL=1
# -DMG_SSL_IF=MG_SSL_IF_OPENSSL
# -DKR_VERSION
-DMG_SSL_MBED_DUMMY_RANDOM=1
-DMG_SSL_IF=MG_SSL_IF_MBEDTLS
-DMG_SSL_IF_MBEDTLS_FREE_CERTS=1
-DMG_SSL_IF_MBEDTLS_MAX_FRAG_LEN=2048
build_flags_auth =
-DADMIN_USER='"admin"'
-DADMIN_PASS='"admin"'
-DADMIN_REALM='"esp_ota_http_server"'
#[env:huzzah]
#platform = espressif8266
#board = huzzah
#framework = arduino
#monitor_speed = ${common.monitor_speed}
#monitor_port = ${common.monitor_port}
#lib_deps = ${common.lib_deps}
#build_flags = ${common.build_flags}
[env:esp-wrover-kit]
platform = espressif32
framework = arduino
board = esp-wrover-kit
monitor_speed = ${common.monitor_speed}
monitor_port = ${common.monitor_port}
lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags}
[env:esp-wrover-kit-secure]
platform = espressif32
framework = arduino
board = esp-wrover-kit
monitor_speed = ${common.monitor_speed}
monitor_port = ${common.monitor_port}
lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags} ${common.build_flags_secure}
[env:esp-wrover-kit-auth]
extends = env:esp-wrover-kit
build_flags = ${common.build_flags} ${common.build_flags_auth} -ggdb
#[env:linux_x86_64]
#platform = linux_x86_64
#framework = arduino
#board = generic
#lib_deps = ${common.lib_deps}
#build_flags = ${common.build_flags}
#build_flags = -DSERIAL_TO_CONSOLE

View File

@@ -1,229 +0,0 @@
//
// A simple server implementation showing how to:
// * serve static messages
// * read GET and POST parameters
// * handle missing pages / 404s
//
#include <Arduino.h>
#include <MongooseCore.h>
#include <MongooseHttpServer.h>
#include <Update.h>
#ifdef ESP32
#include <WiFi.h>
#define START_ESP_WIFI
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#define START_ESP_WIFI
#else
#error Platform not supported
#endif
MongooseHttpServer server;
const char *ssid = "wifi";
const char *password = "password";
const char *server_pem =
"-----BEGIN CERTIFICATE-----\r\n"
"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n"
"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n"
"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n"
"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n"
"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n"
"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n"
"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n"
"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n"
"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n"
"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n"
"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n"
"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n"
"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n"
"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n"
"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n"
"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n"
"gJ2sWjUlokrbHswSBLLbUJIF\r\n"
"-----END CERTIFICATE-----\r\n";
const char *server_key =
"-----BEGIN PRIVATE KEY-----\r\n"
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n"
"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n"
"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n"
"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n"
"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n"
"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n"
"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n"
"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n"
"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n"
"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n"
"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n"
"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n"
"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n"
"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n"
"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n"
"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n"
"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n"
"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n"
"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n"
"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n"
"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n"
"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n"
"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n"
"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n"
"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n"
"7G/qUI9iE5iUU7MkIcLIbTf/\r\n"
"-----END PRIVATE KEY-----\r\n";
const char* server_index =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update'>"
"<input type='submit' value='Update'>"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
"$.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>";
#include <Arduino.h>
bool updateCompleted = false;
static void updateError(MongooseHttpServerRequest *request)
{
MongooseHttpServerResponseStream *resp = request->beginResponseStream();
resp->setCode(500);
resp->setContentType("text/plain");
resp->printf("Error: %d", Update.getError());
request->send(resp);
// Anoyingly this uses Stream rather than Print...
Update.printError(Serial);
}
void setup()
{
Serial.begin(115200);
#ifdef START_ESP_WIFI
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED)
{
Serial.printf("WiFi Failed!\n");
return;
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Hostname: ");
#ifdef ESP32
Serial.println(WiFi.getHostname());
#elif defined(ESP8266)
Serial.println(WiFi.hostname());
#endif
#endif
Mongoose.begin();
#ifdef SIMPLE_SERVER_SECURE
if(false == server.begin(443, server_pem, server_key)) {
Serial.print("Failed to start server");
return;
}
#else
server.begin(80);
#endif
server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) {
#if defined(ADMIN_USER) && defined(ADMIN_PASS) && defined(ADMIN_REALM)
if(false == request->authenticate(ADMIN_USER, ADMIN_PASS)) {
request->requestAuthentication(ADMIN_REALM);
return;
}
#endif
request->send(200, "text/html", server_index);
});
server.on("/update$", HTTP_POST)->
onRequest([](MongooseHttpServerRequest *request) {
#if defined(ADMIN_USER) && defined(ADMIN_PASS) && defined(ADMIN_REALM)
if(false == request->authenticate(ADMIN_USER, ADMIN_PASS)) {
request->requestAuthentication(ADMIN_REALM);
return;
}
#endif
updateCompleted = false;
})->
onUpload([](MongooseHttpServerRequest *request, int ev, MongooseString filename, uint64_t index, uint8_t *data, size_t len)
{
if(MG_EV_HTTP_PART_BEGIN == ev) {
Serial.printf("Update Start: %s\n", filename.c_str());
if (!Update.begin()) { //start with max available size
updateError(request);
}
}
if(!Update.hasError())
{
Serial.printf("Update Writing %llu\n", index);
if(Update.write(data, len) != len) {
updateError(request);
}
}
if(MG_EV_HTTP_PART_END == ev) {
Serial.println("Data finished");
if(Update.end(true)) {
Serial.printf("Update Success: %lluB\n", index+len);
request->send(200, "text/plain", "OK");
updateCompleted = true;
} else {
updateError(request);
}
}
return len;
})->
onClose([](MongooseHttpServerRequest *request)
{
if(updateCompleted) {
ESP.restart();
}
});
}
void loop()
{
Mongoose.poll(1000);
}

View File

@@ -1,11 +0,0 @@
This directory is intended for PIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

View File

@@ -1,39 +0,0 @@
.pioenvs
.clang_complete
.gcc-flags.json
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Visual Studio/VisualMicro stuff
Visual\ Micro
*.sdf
*.opensdf
*.suo
.pioenvs
.piolibdeps
.pio
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/settings.json
.vscode/.browse.c_cpp.db*
.vscode/ipch

View File

@@ -1,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -1,64 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[common]
lib_deps = ArduinoMongoose
monitor_speed = 115200
monitor_port = /dev/ttyUSB1
build_flags =
-DENABLE_DEBUG
# -DCS_ENABLE_STDIO
build_flags_secure =
-DSIMPLE_SERVER_SECURE
-DMG_ENABLE_SSL=1
# -DMG_SSL_IF=MG_SSL_IF_OPENSSL
# -DKR_VERSION
-DMG_SSL_MBED_DUMMY_RANDOM=1
-DMG_SSL_IF=MG_SSL_IF_MBEDTLS
-DMG_SSL_IF_MBEDTLS_FREE_CERTS=1
-DMG_SSL_IF_MBEDTLS_MAX_FRAG_LEN=2048
[env:huzzah]
platform = espressif8266
board = huzzah
framework = arduino
monitor_speed = ${common.monitor_speed}
monitor_port = ${common.monitor_port}
lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags}
[env:esp-wrover-kit]
platform = espressif32
framework = arduino
board = esp-wrover-kit
monitor_speed = ${common.monitor_speed}
monitor_port = ${common.monitor_port}
lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags}
[env:esp-wrover-kit-secure]
platform = espressif32
framework = arduino
board = esp-wrover-kit
monitor_speed = ${common.monitor_speed}
monitor_port = ${common.monitor_port}
lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags} ${common.build_flags_secure}
#[env:linux_x86_64]
#platform = linux_x86_64
#framework = arduino
#board = generic
#lib_deps = ${common.lib_deps}
#build_flags = ${common.build_flags}
#build_flags = -DSERIAL_TO_CONSOLE

View File

@@ -1,211 +0,0 @@
//
// A simple server implementation showing how to:
// * serve static messages
// * read GET and POST parameters
// * handle missing pages / 404s
//
#include <Arduino.h>
#include <MongooseCore.h>
#include <MongooseHttpServer.h>
#ifdef ESP32
#include <WiFi.h>
#define START_ESP_WIFI
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#define START_ESP_WIFI
#else
#error Platform not supported
#endif
MongooseHttpServer server;
const char *ssid = "wifi";
const char *password = "password";
const char *PARAM_MESSAGE = "message";
const char *server_pem =
"-----BEGIN CERTIFICATE-----\r\n"
"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n"
"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n"
"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n"
"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n"
"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n"
"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n"
"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n"
"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n"
"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n"
"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n"
"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n"
"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n"
"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n"
"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n"
"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n"
"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n"
"gJ2sWjUlokrbHswSBLLbUJIF\r\n"
"-----END CERTIFICATE-----\r\n";
const char *server_key =
"-----BEGIN PRIVATE KEY-----\r\n"
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n"
"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n"
"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n"
"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n"
"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n"
"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n"
"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n"
"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n"
"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n"
"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n"
"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n"
"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n"
"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n"
"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n"
"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n"
"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n"
"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n"
"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n"
"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n"
"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n"
"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n"
"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n"
"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n"
"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n"
"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n"
"7G/qUI9iE5iUU7MkIcLIbTf/\r\n"
"-----END PRIVATE KEY-----\r\n";
static void notFound(MongooseHttpServerRequest *request);
#include <Arduino.h>
void setup()
{
Serial.begin(115200);
#ifdef START_ESP_WIFI
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED)
{
Serial.printf("WiFi Failed!\n");
return;
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Hostname: ");
#ifdef ESP32
Serial.println(WiFi.getHostname());
#elif defined(ESP8266)
Serial.println(WiFi.hostname());
#endif
#endif
Mongoose.begin();
#ifdef SIMPLE_SERVER_SECURE
if(false == server.begin(443, server_pem, server_key)) {
Serial.print("Failed to start server");
return;
}
#else
server.begin(80);
#endif
server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) {
request->send(200, "text/plain", "Hello world");
});
// Send a GET request to <IP>/get?message=<message>
server.on("/get$", HTTP_GET, [](MongooseHttpServerRequest *request) {
String message;
if (request->hasParam(PARAM_MESSAGE))
{
message = request->getParam(PARAM_MESSAGE);
}
else
{
message = "No message sent";
}
request->send(200, "text/plain", "Hello, GET: " + message);
});
// Send a POST request to <IP>/post with a form field message set to <message>
server.on("/post$", HTTP_POST, [](MongooseHttpServerRequest *request) {
String message;
if (request->hasParam(PARAM_MESSAGE))
{
message = request->getParam(PARAM_MESSAGE);
}
else
{
message = "No message sent";
}
request->send(200, "text/plain", "Hello, POST: " + message);
});
// Test the basic response class
server.on("/basic$", HTTP_GET, [](MongooseHttpServerRequest *request) {
MongooseHttpServerResponseBasic *resp = request->beginResponse();
resp->setCode(200);
resp->setContentType("text/html");
resp->addHeader("Cache-Control", "max-age=300");
resp->addHeader("X-hello", "world");
resp->setContent(
"<html>\n"
"<head>\n"
"<title>Basic Page</title>\n"
"</head>\n"
"<body>\n"
"<h1>Basic Page</h1>\n"
"<p>\n"
"This page has been sent using the MongooseHttpServerResponseBasic class\n"
"</p>\n"
"</body>\n"
"</html>\n");
request->send(resp);
});
// Test the stream response class
server.on("/stream$", HTTP_GET, [](MongooseHttpServerRequest *request) {
MongooseHttpServerResponseStream *resp = request->beginResponseStream();
resp->setCode(200);
resp->setContentType("text/html");
resp->addHeader("Cache-Control", "max-age=300");
resp->addHeader("X-hello", "world");
resp->println("<html>");
resp->println("<head>");
resp->println("<title>Stream Page</title>");
resp->println("</head>");
resp->println("<body>");
resp->println("<h1>Stream Page</h1>");
resp->println("<p>");
resp->println("This page has been sent using the MongooseHttpServerResponseStream class");
resp->println("</p>");
resp->println("<p>");
resp->printf("micros = %lu<br/>", micros());
resp->printf("free = %u<br/>", ESP.getFreeHeap());
resp->println("</p>");
resp->println("</body>");
resp->println("</html>");
request->send(resp);
});
server.onNotFound(notFound);
}
void loop()
{
Mongoose.poll(1000);
Serial.printf("Free memory %u\n", ESP.getFreeHeap());
}
static void notFound(MongooseHttpServerRequest *request)
{
request->send(404, "text/plain", "Not found");
}

View File

@@ -1,11 +0,0 @@
This directory is intended for PIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

View File

@@ -1,35 +0,0 @@
# Name: REST Client
# Id: humao.rest-client
# Description: REST Client for Visual Studio Code
# Version: 0.21.3
# Publisher: Huachao Mao
# VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=humao.rest-client
@baseUrl = http://172.16.0.87
###
GET {{baseUrl}}/ HTTP/1.1
###
GET {{baseUrl}}/get?message=Hello+World HTTP/1.1
###
POST {{baseUrl}}/post HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
message=Hello+World
###
GET {{baseUrl}}/someRandomFile HTTP/1.1
###
GET {{baseUrl}}/basic HTTP/1.1
###
GET {{baseUrl}}/stream HTTP/1.1

View File

@@ -1,39 +0,0 @@
.pioenvs
.clang_complete
.gcc-flags.json
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Visual Studio/VisualMicro stuff
Visual\ Micro
*.sdf
*.opensdf
*.suo
.pioenvs
.piolibdeps
.pio
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/settings.json
.vscode/.browse.c_cpp.db*
.vscode/ipch

View File

@@ -1,67 +0,0 @@
# Continuous Integration (CI) is the practice, in software
# engineering, of merging all developer working copies with a shared mainline
# several times a day < https://docs.platformio.org/page/ci/index.html >
#
# Documentation:
#
# * Travis CI Embedded Builds with PlatformIO
# < https://docs.travis-ci.com/user/integration/platformio/ >
#
# * PlatformIO integration with Travis CI
# < https://docs.platformio.org/page/ci/travis.html >
#
# * User Guide for `platformio ci` command
# < https://docs.platformio.org/page/userguide/cmd_ci.html >
#
#
# Please choose one of the following templates (proposed below) and uncomment
# it (remove "# " before each line) or use own configuration according to the
# Travis CI documentation (see above).
#
#
# Template #1: General project. Test it using existing `platformio.ini`.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio run
#
# Template #2: The project is intended to be used as a library with examples.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# env:
# - PLATFORMIO_CI_SRC=path/to/test/file.c
# - PLATFORMIO_CI_SRC=examples/file.ino
# - PLATFORMIO_CI_SRC=path/to/test/directory
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N

View File

@@ -1,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -1,40 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[common]
lib_deps = ArduinoMongoose
monitor_speed = 115200
build_flags =
[espressif8266]
build_flags = -DMG_ESP8266
[espressif32]
build_flags =
[env:huzzah]
platform = espressif8266
framework = arduino
board = huzzah
monitor_speed = ${common.monitor_speed}
lib_deps = ${common.lib_deps}
build_flags =
${espressif8266.build_flags}
${common.build_flags}
[env:espwroverkit]
platform = espressif32
framework = arduino
board = esp-wrover-kit
monitor_speed = ${common.monitor_speed}
lib_deps = ${common.lib_deps}
build_flags =
${espressif32.build_flags}
${common.build_flags}

View File

@@ -1,97 +0,0 @@
// Copyright (c) 2015 Cesanta Software Limited
// All rights reserved
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#else
#error Platform not supported
#endif
#include "mongoose.h"
const char* ssid = "my-ssid";
const char* password = "my-password";
static const char *s_http_port = "80";
//static struct mg_serve_http_opts s_http_server_opts;
static void ev_handler(struct mg_connection *nc, int ev, void *p, void *d) {
static const char *reply_fmt =
"HTTP/1.0 200 OK\r\n"
"Connection: close\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"Hello %s\n";
switch (ev) {
case MG_EV_ACCEPT: {
char addr[32];
mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
Serial.printf("Connection %p from %s\n", nc, addr);
break;
}
case MG_EV_HTTP_REQUEST: {
char addr[32];
struct http_message *hm = (struct http_message *) p;
mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
Serial.printf("HTTP request from %s: %.*s %.*s\n", addr, (int) hm->method.len,
hm->method.p, (int) hm->uri.len, hm->uri.p);
mg_printf(nc, reply_fmt, addr);
nc->flags |= MG_F_SEND_AND_CLOSE;
break;
}
case MG_EV_CLOSE: {
Serial.printf("Connection %p closed\n", nc);
break;
}
}
}
struct mg_mgr mgr;
struct mg_connection *nc;
void setup()
{
Serial.begin(115200);
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
mg_mgr_init(&mgr, NULL);
Serial.printf("Starting web server on port %s\n", s_http_port);
nc = mg_bind(&mgr, s_http_port, ev_handler, NULL);
if (nc == NULL) {
Serial.printf("Failed to create listener\n");
return;
}
// Set up HTTP server parameters
mg_set_protocol_http_websocket(nc);
// s_http_server_opts.document_root = "."; // Serve current directory
// s_http_server_opts.enable_directory_listing = "yes";
}
static uint32_t count = 0;
void loop()
{
mg_mgr_poll(&mgr, 1000);
//Serial.println(count++);
}

View File

@@ -1,11 +0,0 @@
This directory is intended for PIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

View File

@@ -1,39 +0,0 @@
.pioenvs
.clang_complete
.gcc-flags.json
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Visual Studio/VisualMicro stuff
Visual\ Micro
*.sdf
*.opensdf
*.suo
.pioenvs
.piolibdeps
.pio
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/settings.json
.vscode/.browse.c_cpp.db*
.vscode/ipch

View File

@@ -1,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -1,64 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[common]
lib_deps = ArduinoMongoose
monitor_speed = 115200
monitor_port = /dev/ttyUSB2
build_flags =
-DENABLE_DEBUG
# -DCS_ENABLE_STDIO
build_flags_secure =
-DSIMPLE_SERVER_SECURE
-DMG_ENABLE_SSL=1
# -DMG_SSL_IF=MG_SSL_IF_OPENSSL
# -DKR_VERSION
-DMG_SSL_MBED_DUMMY_RANDOM=1
-DMG_SSL_IF=MG_SSL_IF_MBEDTLS
-DMG_SSL_IF_MBEDTLS_FREE_CERTS=1
-DMG_SSL_IF_MBEDTLS_MAX_FRAG_LEN=2048
[env:huzzah]
platform = espressif8266
board = huzzah
framework = arduino
monitor_speed = ${common.monitor_speed}
monitor_port = ${common.monitor_port}
lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags}
[env:esp-wrover-kit]
platform = espressif32
framework = arduino
board = esp-wrover-kit
monitor_speed = ${common.monitor_speed}
monitor_port = ${common.monitor_port}
lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags}
[env:esp-wrover-kit-secure]
platform = espressif32
framework = arduino
board = esp-wrover-kit
monitor_speed = ${common.monitor_speed}
monitor_port = ${common.monitor_port}
lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags} ${common.build_flags_secure}
#[env:linux_x86_64]
#platform = linux_x86_64
#framework = arduino
#board = generic
#lib_deps = ${common.lib_deps}
#build_flags = ${common.build_flags}
#build_flags = -DSERIAL_TO_CONSOLE

View File

@@ -1,235 +0,0 @@
//
// A simple server implementation showing how to:
// * serve static messages
// * read GET and POST parameters
// * handle missing pages / 404s
//
#include <Arduino.h>
#include <MongooseCore.h>
#include <MongooseHttpServer.h>
#ifdef ESP32
#include <WiFi.h>
#define START_ESP_WIFI
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#define START_ESP_WIFI
#else
#error Platform not supported
#endif
MongooseHttpServer server;
const char *ssid = "wifi";
const char *password = "password";
const char *server_pem =
"-----BEGIN CERTIFICATE-----\r\n"
"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n"
"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n"
"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n"
"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n"
"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n"
"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n"
"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n"
"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n"
"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n"
"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n"
"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n"
"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n"
"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n"
"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n"
"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n"
"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n"
"gJ2sWjUlokrbHswSBLLbUJIF\r\n"
"-----END CERTIFICATE-----\r\n";
const char *server_key =
"-----BEGIN PRIVATE KEY-----\r\n"
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n"
"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n"
"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n"
"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n"
"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n"
"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n"
"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n"
"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n"
"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n"
"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n"
"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n"
"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n"
"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n"
"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n"
"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n"
"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n"
"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n"
"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n"
"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n"
"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n"
"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n"
"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n"
"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n"
"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n"
"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n"
"7G/qUI9iE5iUU7MkIcLIbTf/\r\n"
"-----END PRIVATE KEY-----\r\n";
const char *index_page =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"utf-8\" />\n"
" <title>WebSocket Test</title>\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
" <style type=\"text/css\">\n"
" body {\n"
" background-color: #789; margin: 0;\n"
" padding: 0; font: 14px Helvetica, Arial, sans-serif;\n"
" }\n"
" div.content {\n"
" width: 800px; margin: 2em auto; padding: 20px 50px;\n"
" background-color: #fff; border-radius: 1em;\n"
" }\n"
" #messages {\n"
" border: 2px solid #fec; border-radius: 1em;\n"
" height: 10em; overflow: scroll; padding: 0.5em 1em;\n"
" }\n"
" a:link, a:visited { color: #69c; text-decoration: none; }\n"
" @media (max-width: 700px) {\n"
" body { background-color: #fff; }\n"
" div.content {\n"
" width: auto; margin: 0 auto; border-radius: 0;\n"
" padding: 1em;\n"
" }\n"
" }\n"
"</style>\n"
"\n"
"<script language=\"javascript\" type=\"text/javascript\">\n"
"\n"
" var rooms = [];\n"
" var ws = new WebSocket(\'ws://\' + location.host + \'/ws\');\n"
"\n"
" if (!window.console) { window.console = { log: function() {} } };\n"
"\n"
" ws.onopen = function(ev) { console.log(ev); };\n"
" ws.onerror = function(ev) { console.log(ev); };\n"
" ws.onclose = function(ev) { console.log(ev); };\n"
" ws.onmessage = function(ev) {\n"
" console.log(ev);\n"
" var div = document.createElement(\'div\');\n"
" div.innerHTML = ev.data;\n"
" document.getElementById(\'messages\').appendChild(div);\n"
"\n"
" };\n"
"\n"
" window.onload = function() {\n"
" document.getElementById(\'send_button\').onclick = function(ev) {\n"
" var msg = document.getElementById(\'send_input\').value;\n"
" document.getElementById(\'send_input\').value = \'\';\n"
" ws.send(msg);\n"
" };\n"
" document.getElementById(\'send_input\').onkeypress = function(ev) {\n"
" if (ev.keyCode == 13 || ev.which == 13) {\n"
" document.getElementById(\'send_button\').click();\n"
" }\n"
" };\n"
" };\n"
"</script>\n"
"</head>\n"
"<body>\n"
" <div class=\"content\">\n"
" <h1>Websocket PubSub Demonstration</h1>\n"
"\n"
" <p>\n"
" This page demonstrates how Mongoose could be used to implement\n"
" <a href=\"http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern\">\n"
" publishsubscribe pattern</a>. Open this page in several browser\n"
" windows. Each window initiates persistent\n"
" <a href=\"http://en.wikipedia.org/wiki/WebSocket\">WebSocket</a>\n"
" connection with the server, making each browser window a websocket client.\n"
" Send messages, and see messages sent by other clients.\n"
" </p>\n"
"\n"
" <div id=\"messages\">\n"
" </div>\n"
"\n"
" <p>\n"
" <input type=\"text\" id=\"send_input\" />\n"
" <button id=\"send_button\">Send Message</button>\n"
" </p>\n"
" </div>\n"
"</body>\n"
"</html>\n";
#include <Arduino.h>
void broadcast(MongooseHttpWebSocketConnection *from, MongooseString msg)
{
char buf[500];
char addr[32];
mg_sock_addr_to_str(from->getRemoteAddress(), addr, sizeof(addr),
MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
snprintf(buf, sizeof(buf), "%s %.*s", addr, (int) msg.length(), msg.c_str());
printf("%s\n", buf);
server.sendAll(from, buf);
}
void setup()
{
Serial.begin(115200);
#ifdef START_ESP_WIFI
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED)
{
Serial.printf("WiFi Failed!\n");
return;
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Hostname: ");
#ifdef ESP32
Serial.println(WiFi.getHostname());
#elif defined(ESP8266)
Serial.println(WiFi.hostname());
#endif
#endif
Mongoose.begin();
#ifdef SIMPLE_SERVER_SECURE
if(false == server.begin(443, server_pem, server_key)) {
Serial.print("Failed to start server");
return;
}
#else
server.begin(80);
#endif
server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) {
request->send(200, "text/html", index_page);
});
// Test the stream response class
server.on("/ws$")->
onConnect([](MongooseHttpWebSocketConnection *connection) {
broadcast(connection, MongooseString("++ joined"));
})->
onClose([](MongooseHttpServerRequest *c) {
MongooseHttpWebSocketConnection *connection = static_cast<MongooseHttpWebSocketConnection *>(c);
broadcast(connection, MongooseString("++ left"));
})->
onFrame([](MongooseHttpWebSocketConnection *connection, int flags, uint8_t *data, size_t len) {
broadcast(connection, MongooseString((const char *)data, len));
});
}
void loop()
{
Mongoose.poll(1000);
Serial.printf("Free memory %u\n", ESP.getFreeHeap());
}

View File

@@ -1,11 +0,0 @@
This directory is intended for PIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

View File

@@ -1,35 +0,0 @@
# Name: REST Client
# Id: humao.rest-client
# Description: REST Client for Visual Studio Code
# Version: 0.21.3
# Publisher: Huachao Mao
# VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=humao.rest-client
@baseUrl = http://172.16.0.87
###
GET {{baseUrl}}/ HTTP/1.1
###
GET {{baseUrl}}/get?message=Hello+World HTTP/1.1
###
POST {{baseUrl}}/post HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
message=Hello+World
###
GET {{baseUrl}}/someRandomFile HTTP/1.1
###
GET {{baseUrl}}/basic HTTP/1.1
###
GET {{baseUrl}}/stream HTTP/1.1

View File

@@ -1,66 +1,73 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PsychicHTTP Demo</title>
<link rel="icon" href="./favicon.ico" type="image/x-icon">
</head>
<body>
<main>
<h1>Basic Request Examples</h1>
<ul>
<li><a href="/api?foo=bar">API Call</a></li>
<li><a href="/redirect">Redirection</a></li>
<li><a href="/auth-digest">Authentication (Digest)</a></li>
<li><a href="/auth-basic">Authentication (Basic)</a></li>
<li><a href="/cookies">Cookies Demo</a></li>
<li><a href="/404">404</a></li>
</ul>
<h1>Static Serving</h1>
<p>
<a href="/alien.png"><img width="60" src="/alien.png"></a>
<a href="/img/request_flow.png"><img width="60" src="/img/request_flow.png"></a>
</p>
<p><a href="/myfile.txt">Text File</a></p>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PsychicHTTP Demo</title>
<link rel="icon" href="./favicon.ico" type="image/x-icon">
</head>
<h1>Simple POST Form</h1>
<form action="/post" method="post">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<input type="submit" value="Submit">
</form>
<body>
<main>
<h1>Basic Request Examples</h1>
<ul>
<li><a href="/api?foo=bar">API Call</a></li>
<li><a href="/redirect">Redirection</a></li>
<li><a href="/auth-digest">Authentication (Digest)</a></li>
<li><a href="/auth-basic">Authentication (Basic)</a></li>
<li><a href="/cookies">Cookies Demo</a></li>
<li><a href="/404">404</a></li>
</ul>
<h1>Basic File Upload</h1>
<table border="0">
<tr>
<td>
<label for="newfile">Upload a file</label>
</td>
<td colspan="2">
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
</td>
</tr>
<tr>
<td>
<label for="filepath">Set path on server</label>
</td>
<td>
<input id="filepath" type="text" style="width:100%;">
</td>
</tr>
<tr>
<td>
<button id="upload" type="button" onclick="upload()">Upload</button>
</td>
</tr>
</table>
<script>
<h1>Utilities</h1>
<ul>
<li><a href="/websocket-test.html">WebSocket Tester</a></li>
</ul>
<h1>Static Serving</h1>
<p>
<a href="/alien.png"><img width="60" src="/alien.png"></a>
<a href="/img/request_flow.png"><img width="60" src="/img/request_flow.png"></a>
</p>
<p><a href="/myfile.txt">Text File</a></p>
<h1>Simple POST Form</h1>
<form action="/post" method="post">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<input type="submit" value="Submit">
</form>
<h1>Basic File Upload</h1>
<table border="0">
<tr>
<td>
<label for="newfile">Upload a file</label>
</td>
<td colspan="2">
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
</td>
</tr>
<tr>
<td>
<label for="filepath">Set path on server</label>
</td>
<td>
<input id="filepath" type="text" style="width:100%;">
</td>
</tr>
<tr>
<td>
<button id="upload" type="button" onclick="upload()">Upload</button>
</td>
</tr>
</table>
<script>
function setpath() {
var default_path = document.getElementById("newfile").files[0].name;
document.getElementById("filepath").value = default_path;
@@ -72,7 +79,7 @@
/* Max size of an individual file. Make sure this
* value is same as that set in file_server.c */
var MAX_FILE_SIZE = 2048*1024;
var MAX_FILE_SIZE = 2048 * 1024;
var MAX_FILE_SIZE_STR = "2MB";
if (fileInput.length == 0) {
@@ -81,9 +88,9 @@
alert("File path on server is not set!");
} else if (filePath.indexOf(' ') >= 0) {
alert("File path on server cannot have spaces!");
} else if (filePath[filePath.length-1] == '/') {
} else if (filePath[filePath.length - 1] == '/') {
alert("File name not specified after path!");
} else if (fileInput[0].size > 200*1024) {
} else if (fileInput[0].size > 200 * 1024) {
alert("File size must be less than 200KB!");
} else {
document.getElementById("newfile").disabled = true;
@@ -92,7 +99,7 @@
var file = fileInput[0];
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
xhttp.onreadystatechange = function () {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
document.open();
@@ -111,126 +118,125 @@
xhttp.send(file);
}
}
</script>
</script>
<h1>Multipart POST Form</h1>
<form action="/multipart" method="post" enctype="multipart/form-data">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<h1>Multipart POST Form</h1>
<form action="/multipart" method="post" enctype="multipart/form-data">
<label for="param1">Parameter 1:</label>
<input type="text" id="param1" name="param1" value="Parameter 1">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<label for="param2">Parameter 2:</label>
<input type="text" id="param2" name="param2" value="Parameter 2">
<br>
<label for="file-upload">File Upload:</label>
<input type="file" id="file-upload" name="file_upload" accept=".txt, .html, .pdf, .png, .jpg, .gif" required>
<br>
<input type="submit" value="Upload File">
</form>
<label for="file-upload">File Upload:</label>
<input type="file" id="file-upload" name="file_upload" accept=".txt, .html, .pdf, .png, .jpg, .gif"
required>
<br>
<h1>Websocket Demo</h1>
<input type="text" id="message_input" placeholder="Type your message">
<button onclick="sendMessage()">Send Message</button>
<button onclick="websocketConnect()">Connect</button>
<div>
<textarea id="websocket_output" style="width: 50%; height: 250px;"></textarea>
</div>
<input type="submit" value="Upload File">
</form>
<script>
let socket;
const outputText = document.getElementById('websocket_output');
const messageInput = document.getElementById('message_input');
<h1>Websocket Demo</h1>
<input type="text" id="message_input" placeholder="Type your message">
<button onclick="sendMessage()">Send Message</button>
<button onclick="websocketConnect()">Connect</button>
<div>
<textarea id="websocket_output" style="width: 50%; height: 250px;"></textarea>
</div>
function websocketConnect()
{
// Create a WebSocket connection
socket = new WebSocket((location.protocol === "https:" ? "wss://" : "ws://") + window.location.host + "/ws");
// Event handler for when the WebSocket connection is open
socket.addEventListener('open', (event) => {
outputText.value += `[socket] connection opened!\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when a message is received from the WebSocket server
socket.addEventListener('message', (event) => {
// Echo the received message into the output div
let data = event.data.trim();
outputText.value += `[received] ${data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when an error occurs with the WebSocket connection
socket.addEventListener('error', (event) => {
let data = event.data.trim();
outputText.value += `[error] ${event.data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when the WebSocket connection is closed
socket.addEventListener('close', (event) => {
outputText.value += `[socket] connection closed!\n`;
});
}
// Function to send a message to the WebSocket server
function sendMessage() {
if (socket.readyState == WebSocket.OPEN) {
const message = messageInput.value.trim();
if (message) {
socket.send(message);
messageInput.value = ''; // Clear the input field after sending the message
outputText.value += `[sent] ${message}\n`;
outputText.scrollTop = outputText.scrollHeight;
}
}
else
{
outputText.value += `[error] Not Connected\n`;
<script>
let socket;
const outputText = document.getElementById('websocket_output');
const messageInput = document.getElementById('message_input');
function websocketConnect() {
// Create a WebSocket connection
socket = new WebSocket((location.protocol === "https:" ? "wss://" : "ws://") + window.location.host + "/ws");
// Event handler for when the WebSocket connection is open
socket.addEventListener('open', (event) => {
outputText.value += `[socket] connection opened!\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when a message is received from the WebSocket server
socket.addEventListener('message', (event) => {
// Echo the received message into the output div
let data = event.data.trim();
outputText.value += `[received] ${data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when an error occurs with the WebSocket connection
socket.addEventListener('error', (event) => {
let data = event.data.trim();
outputText.value += `[error] ${event.data}\n`;
outputText.scrollTop = outputText.scrollHeight;
});
// Event handler for when the WebSocket connection is closed
socket.addEventListener('close', (event) => {
outputText.value += `[socket] connection closed!\n`;
});
}
// Function to send a message to the WebSocket server
function sendMessage() {
if (socket.readyState == WebSocket.OPEN) {
const message = messageInput.value.trim();
if (message) {
socket.send(message);
messageInput.value = ''; // Clear the input field after sending the message
outputText.value += `[sent] ${message}\n`;
outputText.scrollTop = outputText.scrollHeight;
}
}
</script>
<h1>EventSource Demo</h1>
<button onclick="eventSourceConnect()">Connect</button>
<div>
<textarea id="eventsource_output" style="width: 50%; height: 250px;"></textarea>
</div>
<script>
const dataElement = document.getElementById('eventsource_output');
function eventSourceConnect()
{
const eventSource = new EventSource('/events');
eventSource.onopen = () => {
dataElement.value += `[connected]\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.addEventListener('millis', (event) => {
let data = event.data.trim()
dataElement.value += `[millis] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
});
eventSource.onmessage = (event) => {
let data = event.data.trim()
dataElement.value += `[message] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.onerror = (error) => {
dataElement.value += `[error] ${error}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
eventSource.close();
};
else {
outputText.value += `[error] Not Connected\n`;
outputText.scrollTop = outputText.scrollHeight;
}
</script>
</main>
</body>
}
</script>
<h1>EventSource Demo</h1>
<button onclick="eventSourceConnect()">Connect</button>
<div>
<textarea id="eventsource_output" style="width: 50%; height: 250px;"></textarea>
</div>
<script>
const dataElement = document.getElementById('eventsource_output');
function eventSourceConnect() {
const eventSource = new EventSource('/events');
eventSource.onopen = () => {
dataElement.value += `[connected]\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.addEventListener('millis', (event) => {
let data = event.data.trim()
dataElement.value += `[millis] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
});
eventSource.onmessage = (event) => {
let data = event.data.trim()
dataElement.value += `[message] ${data}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
};
eventSource.onerror = (error) => {
dataElement.value += `[error] ${error}\n`;
dataElement.scrollTop = dataElement.scrollHeight;
eventSource.close();
};
}
</script>
</main>
</body>
</html>

View File

@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Message Rate Test</title>
</head>
<body>
<h1>WebSocket Message Rate Test</h1>
<p>Time Remaining: <span id="time_remaining">0</span></p>
<p>Messages Count: <span id="message_count">0</span></p>
<p>Messages per second: <span id="rate">0</span></p>
<label for="duration">Test Duration (seconds):</label>
<input type="number" id="duration" value="30" min="1">
<p>
<button id="startTestSmall">Start Test (256b json)</button>
<button id="startTestBig">Start Test (2k json)</button>
</p>
<p id="status"></p>
<script>
let ws;
let messageCount = 0;
let startTime;
let endTime;
let testRunning = false;
let smallPayload = { "user": { "id": 123456789, "name": "JohnDoe", "email": "johndoe@example.com", "preferences": { "theme": "dark", "notifications": { "email": true, "sms": false }, "language": "en" } } };
let bigPayload = { "user": { "id": 123456789, "name": "JohnDoe", "email": "johndoe@example.com", "preferences": { "theme": "dark", "notifications": { "email": true, "sms": false }, "language": "en", "options": { "option1": "value1", "option2": "value2", "option3": "value3", "option4": "value4", "option5": "value5", "option6": "value6", "option7": "value7", "option8": "value8", "option9": "value9", "option10": "value10", "option11": "value11", "option12": "value12", "option13": "value13", "option14": "value14", "option15": "value15", "option16": "value16", "option17": "value17", "option18": "value18", "option19": "value19", "option20": "value20", "option21": "value21", "option22": "value22", "option23": "value23", "option24": "value24", "option25": "value25", "option26": "value26", "option27": "value27", "option28": "value28", "option29": "value29", "option30": "value30", "option31": "value31", "option32": "value32", "option33": "value33", "option34": "value34", "option35": "value35", "option36": "value36", "option37": "value37", "option38": "value38", "option39": "value39", "option40": "value40", "option41": "value41", "option42": "value42", "option43": "value43", "option44": "value44", "option45": "value45", "option46": "value46", "option47": "value47", "option48": "value48", "option49": "value49", "option50": "value50", "option51": "value51", "option52": "value52", "option53": "value53", "option54": "value54", "option55": "value55", "option56": "value56", "option57": "value57", "option58": "value58", "option59": "value59", "option60": "value60", "option61": "value61", "option62": "value62", "option63": "value63", "option64": "value64", "option65": "value65", "option66": "value66", "option67": "value67", "option68": "value68", "option69": "value69", "option70": "value70", "option71": "value71", "option72": "value72", "option73": "value73", "option74": "value74", "option75": "value75", "option76": "value76", "option77": "value77", "option78": "value78", "option79": "value79", "option80": "value80", "option81": "value81", "option82": "value82", "option83": "value83", "option84": "value84", "option85": "value85", "option86": "value86", "option87": "value87", "option88": "value88", "option89": "value89", "option90": "value90", "option91": "value91", "option92": "value92", "option93": "value93", "option94": "value94", "option95": "value95", "option96": "value96", "option97": "value97", "option98": "value98", "option99": "value99", "option100": "value100", "option101": "value101", "option102": "value102", "option103": "value103", "option104": "value104", "option105": "value105", "option106": "value106", "option107": "value107", "option108": "value108", "option109": "value109", "option110": "value110", "option111": "value111", "option112": "value112", "option113": "value113", "option114": "value114", "option115": "value115", "option116": "value116", "option117": "value117", "option118": "value118", "option119": "value119", "option120": "value120" } } } };
let payload;
// Function to update the message rate
function updateRate(force = false) {
const currentTime = Date.now();
const elapsedTime = (currentTime - startTime) / 1000; // in seconds
let remainingTime = (endTime - currentTime) / 1000; // in seconds
remainingTime = Math.max(0, remainingTime);
const rate = messageCount / elapsedTime;
if (rate) {
document.getElementById('rate').innerText = rate.toFixed(2);
document.getElementById('message_count').innerText = messageCount;
document.getElementById('time_remaining').innerText = remainingTime.toFixed(2);
}
if (testRunning)
setTimeout(updateRate, 25);
}
function startTestSmall() {
payload = smallPayload;
startTest();
}
function startTestBig() {
payload = bigPayload;
startTest();
}
// Function to start the WebSocket connection and the test
function startTest() {
if (testRunning) return;
console.log("Payload length: " + JSON.stringify(payload).length);
document.getElementById('startTestSmall').disabled = true;
document.getElementById('startTestBig').disabled = true;
document.getElementById('status').innerText = 'Connecting';
const durationInput = document.getElementById('duration').value;
const testDuration = parseInt(durationInput) * 1000 || 60000; // default to 60 seconds if invalid
// Determine the WebSocket protocol based on the current protocol
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
ws = new WebSocket(`${protocol}${window.location.host}/ws`);
ws.onopen = function () {
document.getElementById('status').innerText = 'Connected';
startTime = Date.now();
endTime = startTime + testDuration;
messageCount = 0;
testRunning = true;
sendAndReceiveMessage();
updateRate();
};
ws.onmessage = function (event) {
try {
const parsedData = JSON.parse(event.data);
if (parsedData.user) {
messageCount++;
if (testRunning) {
sendAndReceiveMessage();
}
}
} catch (err) { }
};
ws.onerror = function (error) {
document.getElementById('status').innerText = 'Error: ' + error.message;
};
ws.onclose = function () {
//document.getElementById('status').innerText = 'Connection Closed';
};
}
// Function to send a message and wait for the response
function sendAndReceiveMessage() {
if (Date.now() >= endTime) {
testRunning = false;
document.getElementById('startTestSmall').disabled = false;
document.getElementById('startTestBig').disabled = false;
if (ws.readyState === WebSocket.OPEN) {
ws.close();
}
document.getElementById('status').innerText = 'Test Finished';
updateRate(true);
} else {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
}
document.getElementById('startTestSmall').addEventListener('click', startTestSmall);
document.getElementById('startTestBig').addEventListener('click', startTestBig);
</script>
</body>
</html>

View File

@@ -1,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -12,19 +12,44 @@
platform = espressif32
framework = arduino
board = esp32-s3-devkitc-1
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM1
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
lib_deps =
; devmode: with this disabled make a symlink from platformio/lib to the PsychicHttp directory
;hoeken/PsychicHttp
bblanchon/ArduinoJson
; hoeken/PsychicHttp
; PIO is not able to consider installed project in CI
;../..
board_build.filesystem = littlefs
[env:default]
build_flags =
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN
;-D ENABLE_ASYNC
-Wall
-Wextra
; [env:arduino3]
; platform = https://github.com/platformio/platform-espressif32.git
; platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#master
[env:arduino2]
platform = espressif32@6.8.1
[env:arduino2-ssl]
platform = espressif32@6.8.1
build_flags = -D PSY_ENABLE_SSL
[env:arduino2-regex]
platform = espressif32@6.8.1
build_flags = -D PSY_ENABLE_REGEX
[env:arduino3]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
[env:arduino3-ssl]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
build_flags = -D PSY_ENABLE_SSL
[env:arduino3-regex]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
build_flags = -D PSY_ENABLE_REGEX
[env:waveshare-4-3-touchscreen]
lib_deps = ${env.lib_deps}
https://github.com/esp-arduino-libs/ESP32_IO_Expander
build_flags =
-D PSY_ENABLE_SDCARD
-D WAVESHARE_43_TOUCH

View File

@@ -9,66 +9,113 @@
*/
/**********************************************************************************************
* Note: this demo relies on various files to be uploaded on the LittleFS partition
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
**********************************************************************************************/
* Note: this demo relies on various files to be uploaded on the LittleFS partition
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
**********************************************************************************************/
#include "_secret.h"
#include <Arduino.h>
#include <WiFi.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <ESPmDNS.h>
#include <esp_sntp.h>
#include "_secret.h"
#include <LittleFS.h>
#include <PsychicHttp.h>
//#include <PsychicHttpsServer.h> //uncomment this to enable HTTPS / SSL
#include <WiFi.h>
#include <esp_sntp.h>
// #define this to enable SD card support
#ifdef PSY_ENABLE_SDCARD
#ifdef WAVESHARE_43_TOUCH
#include <ESP_IOExpander_Library.h>
// Extend IO Pin define
#define TP_RST 1
#define LCD_BL 2
#define LCD_RST 3
#define SD_CS 4
#define USB_SEL 5
// I2C Pin define
#define I2C_MASTER_NUM I2C_NUM_0
#define I2C_MASTER_SDA_IO 8
#define I2C_MASTER_SCL_IO 9
#define SD_MOSI 11
#define SD_CLK 12
#define SD_MISO 13
#define SD_SS -1
#else
#define SD_MOSI 11
#define SD_CLK 12
#define SD_MISO 13
#define SD_SS 5
#endif
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#endif
// #define this to enable SSL at build (or switch to the 'ssl' build target in vscode)
#ifdef PSY_ENABLE_SSL
#include <PsychicHttpsServer.h>
#endif
// debugging library
#ifdef PSY_DEVMODE
#include <ArduinoTrace.h>
#endif
#ifndef WIFI_SSID
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
#endif
//Enter your WIFI credentials in secret.h
const char *ssid = WIFI_SSID;
const char *password = WIFI_PASS;
// Enter your WIFI credentials in secret.h
const char* ssid = WIFI_SSID;
const char* password = WIFI_PASS;
// Set your SoftAP credentials
const char *softap_ssid = "PsychicHttp";
const char *softap_password = "";
IPAddress softap_ip(10, 0, 0, 1);
const char* softap_ssid = "PsychicHttp";
const char* softap_password = "";
IPAddress softap_ip(10, 0, 0, 1);
//credentials for the /auth-basic and /auth-digest examples
const char *app_user = "admin";
const char *app_pass = "admin";
const char *app_name = "Your App";
// credentials for the /auth-basic and /auth-digest examples
const char* app_user = "admin";
const char* app_pass = "admin";
const char* app_name = "Your App";
//hostname for mdns (psychic.local)
const char *local_hostname = "psychic";
LoggingMiddleware loggingMiddleware;
AuthenticationMiddleware basicAuth;
AuthenticationMiddleware digestAuth;
//#define PSY_ENABLE_SSL to enable ssl
// hostname for mdns (psychic.local)
const char* local_hostname = "psychic";
// #define PSY_ENABLE_SSL to enable ssl
#ifdef PSY_ENABLE_SSL
bool app_enable_ssl = true;
String server_cert;
String server_key;
bool app_enable_ssl = true;
String server_cert;
String server_key;
#endif
//our main server object
// our main server object
#ifdef PSY_ENABLE_SSL
PsychicHttpsServer server;
PsychicHttpsServer server;
#else
PsychicHttpServer server;
PsychicHttpServer server;
#endif
PsychicWebSocketHandler websocketHandler;
PsychicEventSource eventSource;
CorsMiddleware corsMiddleware;
//NTP server stuff
const char *ntpServer1 = "pool.ntp.org";
const char *ntpServer2 = "time.nist.gov";
// NTP server stuff
const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;
struct tm timeinfo;
// Callback function (gets called when time adjusts via NTP)
void timeAvailable(struct timeval *t)
void timeAvailable(struct timeval* t)
{
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time");
@@ -83,10 +130,12 @@ void timeAvailable(struct timeval *t)
bool connectToWifi()
{
//dual client and AP mode
WiFi.mode(WIFI_AP_STA);
// WiFi.mode(WIFI_AP); // ap only mode
// WiFi.mode(WIFI_STA); // client only mode
WiFi.mode(WIFI_AP_STA); // ap and client
// Configure SoftAP
// dual client and AP mode
WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00
WiFi.softAP(softap_ssid, softap_password);
IPAddress myIP = WiFi.softAPIP();
@@ -107,10 +156,8 @@ bool connectToWifi()
int numberOfTries = 20;
// Wait for the WiFi event
while (true)
{
switch (WiFi.status())
{
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL:
Serial.println("[WiFi] SSID not found");
break;
@@ -140,15 +187,12 @@ bool connectToWifi()
}
delay(tryDelay);
if (numberOfTries <= 0)
{
if (numberOfTries <= 0) {
Serial.print("[WiFi] Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return false;
}
else
{
} else {
numberOfTries--;
}
}
@@ -156,198 +200,247 @@ bool connectToWifi()
return false;
}
#ifdef PSY_ENABLE_SDCARD
bool setupSDCard()
{
#ifdef WAVESHARE_43_TOUCH
ESP_IOExpander* expander = new ESP_IOExpander_CH422G((i2c_port_t)I2C_MASTER_NUM, ESP_IO_EXPANDER_I2C_CH422G_ADDRESS_000, I2C_MASTER_SCL_IO, I2C_MASTER_SDA_IO);
expander->init();
expander->begin();
expander->multiPinMode(TP_RST | LCD_BL | LCD_RST | SD_CS | USB_SEL, OUTPUT);
expander->multiDigitalWrite(TP_RST | LCD_BL | LCD_RST, HIGH);
// use extend GPIO for SD card
expander->digitalWrite(SD_CS, LOW);
SPI.setHwCs(false);
#endif
SPI.begin(SD_CLK, SD_MISO, SD_MOSI, SD_SS);
if (!SD.begin()) {
Serial.println("SD Card Mount Failed");
return false;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return false;
}
Serial.print("SD Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
return true;
}
#endif
void setup()
{
esp_log_level_set(PH_TAG, ESP_LOG_DEBUG);
esp_log_level_set("httpd_uri", ESP_LOG_DEBUG);
Serial.begin(115200);
delay(10);
Serial.printf("ESP-IDF Version: %s\n", esp_get_idf_version());
#ifdef ESP_ARDUINO_VERSION_STR
Serial.printf("Arduino Version: %s\n", ESP_ARDUINO_VERSION_STR);
#else
Serial.printf("Arduino Version: %d.%d.%d\n", ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH);
#endif
Serial.printf("PsychicHttp Version: %s\n", PSYCHIC_VERSION_STR);
// We start by connecting to a WiFi network
// To debug, please enable Core Debug Level to Verbose
if (connectToWifi())
{
//Setup our NTP to get the current time.
if (connectToWifi()) {
// Setup our NTP to get the current time.
sntp_set_time_sync_notification_cb(timeAvailable);
sntp_servermode_dhcp(1); // (optional)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
esp_sntp_servermode_dhcp(1); // (optional)
#else
sntp_servermode_dhcp(1); // (optional)
#endif
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2);
//set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
// set up our esp32 to listen on the psychic.local domain
if (MDNS.begin(local_hostname))
MDNS.addService("http", "tcp", 80);
else
Serial.println("Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if(!LittleFS.begin())
{
if (!LittleFS.begin()) {
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
return;
}
//look up our keys?
#ifdef PSY_ENABLE_SSL
if (app_enable_ssl)
{
File fp = LittleFS.open("/server.crt");
if (fp)
{
server_cert = fp.readString();
#ifdef PSY_ENABLE_SSL
// look up our keys?
if (app_enable_ssl) {
File fp = LittleFS.open("/server.crt");
if (fp) {
server_cert = fp.readString();
// Serial.println("Server Cert:");
// Serial.println(server_cert);
}
else
{
Serial.println("server.pem not found, SSL not available");
app_enable_ssl = false;
}
fp.close();
File fp2 = LittleFS.open("/server.key");
if (fp2)
{
server_key = fp2.readString();
// Serial.println("Server Key:");
// Serial.println(server_key);
}
else
{
Serial.println("server.key not found, SSL not available");
app_enable_ssl = false;
}
fp2.close();
// Serial.println("Server Cert:");
// Serial.println(server_cert);
} else {
Serial.println("server.pem not found, SSL not available");
app_enable_ssl = false;
}
#endif
fp.close();
//setup server config stuff here
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
File fp2 = LittleFS.open("/server.key");
if (fp2) {
server_key = fp2.readString();
#ifdef PSY_ENABLE_SSL
server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
//do we want secure or not?
if (app_enable_ssl)
{
server.listen(443, server_cert.c_str(), server_key.c_str());
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
PsychicHttpServer *redirectServer = new PsychicHttpServer();
redirectServer->config.ctrl_port = 20424; // 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());
});
// Serial.println("Server Key:");
// Serial.println(server_key);
} else {
Serial.println("server.key not found, SSL not available");
app_enable_ssl = false;
}
else
server.listen(80);
#else
server.listen(80);
#endif
fp2.close();
}
// do we want secure or not?
if (app_enable_ssl) {
server.setCertificate(server_cert.c_str(), server_key.c_str());
// this creates a 2nd server listening on port 80 and redirects all requests HTTPS
PsychicHttpServer* redirectServer = new PsychicHttpServer();
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) {
String url = "https://" + request->host() + request->url();
return response->redirect(url.c_str()); });
}
#endif
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
//serve static files from LittleFS/www on / only to clients on same wifi network
//this is where our /index.html file lives
// curl -i http://psychic.local/
PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/");
handler->setFilter(ON_STA_FILTER);
handler->setCacheControl("max-age=60");
loggingMiddleware.setOutput(Serial);
//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);
basicAuth.setUsername(app_user);
basicAuth.setPassword(app_pass);
basicAuth.setRealm(app_name);
basicAuth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH);
basicAuth.setAuthFailureMessage("You must log in.");
//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.
// curl -i http://psychic.local/img/request_flow.png
digestAuth.setUsername(app_user);
digestAuth.setPassword(app_pass);
digestAuth.setRealm(app_name);
digestAuth.setAuthMethod(HTTPAuthMethod::DIGEST_AUTH);
digestAuth.setAuthFailureMessage("You must log in.");
// corsMiddleware.setAllowCredentials(true);
// corsMiddleware.setOrigin("http://www.example.com,https://www.example.com,http://api.example.com,https://api.example.com");
// corsMiddleware.setHeaders("Origin,X-Requested-With,Content-Type,Accept,Content-Type,Authorization,X-Access-Token");
server.addMiddleware(&loggingMiddleware);
// this will send CORS headers on every HTTP_OPTIONS request that contains the Origin: header
server.addMiddleware(&corsMiddleware);
// rewrites!
server.rewrite("/rewrite", "/api?foo=rewrite");
// serve static files from LittleFS/www on / only to clients on same wifi network
// this is where our /index.html file lives
// curl -i http://psychic.local/
server.serveStatic("/", LittleFS, "/www/")
->setCacheControl("max-age=60")
->addFilter(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/")->addFilter(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.
// curl -i http://psychic.local/img/request_flow.png
server.serveStatic("/img", LittleFS, "/img/");
//you can also serve single files
// curl -i http://psychic.local/myfile.txt
#ifdef PSY_ENABLE_SDCARD
// if we detect an SD card, serve all files from sd:/ on http://psychic.local/sd
if (setupSDCard())
server.serveStatic("/sd", SD, "/");
#endif
// you can also serve single files
// curl -i http://psychic.local/myfile.txt
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
//example callback everytime a connection is opened
server.onOpen([](PsychicClient *client) {
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
});
// example callback everytime a connection is opened
server.onOpen([](PsychicClient* client) { Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
//example callback everytime a connection is closed
server.onClose([](PsychicClient *client) {
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
// example callback everytime a connection is closed
server.onClose([](PsychicClient* client) { Serial.printf("[http] connection #%u closed\n", client->socket()); });
//api - json message passed in as post body
// curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api
server.on("/api", HTTP_POST, [](PsychicRequest *request, JsonVariant &json)
{
// api - json message passed in as post body
// curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api
server.on("/api", HTTP_POST, [](PsychicRequest* request, PsychicResponse* resp, JsonVariant& json) {
JsonObject input = json.as<JsonObject>();
//create our response json
PsychicJsonResponse response = PsychicJsonResponse(request);
// create our response json
PsychicJsonResponse response(resp);
JsonObject output = response.getRoot();
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
output["method"] = request->methodStr();
//work with some params
if (input.containsKey("foo"))
{
// work with some params
if (input.containsKey("foo")) {
String foo = input["foo"];
output["foo"] = foo;
}
return response.send();
});
//ip - get info about the client
// curl -i http://psychic.local/ip
server.on("/ip", HTTP_GET, [](PsychicRequest *request)
{
// ip - get info about the client
// curl -i http://psychic.local/ip
server.on("/ip", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
String output = "Your IP is: " + request->client()->remoteIP().toString();
return request->reply(output.c_str());
return response->send(output.c_str());
});
//client connect/disconnect to a url
// curl -i http://psychic.local/handler
PsychicWebHandler *connectionHandler = new PsychicWebHandler();
connectionHandler->onRequest([](PsychicRequest *request)
{
return request->reply("OK");
});
connectionHandler->onOpen([](PsychicClient *client) {
Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
});
connectionHandler->onClose([](PsychicClient *client) {
Serial.printf("[handler] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
// client connect/disconnect to a url
// curl -i http://psychic.local/handler
PsychicWebHandler* connectionHandler = new PsychicWebHandler();
connectionHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { return response->send("OK"); });
connectionHandler->onOpen([](PsychicClient* client) { Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
connectionHandler->onClose([](PsychicClient* client) { Serial.printf("[handler] connection #%u closed\n", client->socket()); });
//add it to our server
// add it to our server
server.on("/handler", connectionHandler);
//api - parameters passed in via query eg. /api?foo=bar
// curl -i 'http://psychic.local/api?foo=bar'
server.on("/api", HTTP_GET, [](PsychicRequest *request)
{
//showcase some of the variables
Serial.println(request->host());
Serial.println(request->uri());
Serial.println(request->path());
Serial.println(request->queryString());
//create a response object
//create our response json
PsychicJsonResponse response = PsychicJsonResponse(request);
// api - parameters passed in via query eg. /api?foo=bar
// curl -i 'http://psychic.local/api?foo=bar'
server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* resp) {
// create our response json
PsychicJsonResponse response = PsychicJsonResponse(resp);
JsonObject output = response.getRoot();
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
output["method"] = request->methodStr();
//work with some params
if (request->hasParam("foo"))
{
// work with some params
if (request->hasParam("foo")) {
String foo = request->getParam("foo")->value();
output["foo"] = foo;
}
@@ -355,112 +448,135 @@ void setup()
return response.send();
});
//JsonResponse example
// curl -i http://psychic.local/json
server.on("/json", HTTP_GET, [](PsychicRequest *request)
{
PsychicJsonResponse response = PsychicJsonResponse(request);
// curl -i -X GET 'http://psychic.local/any'
// curl -i -X POST 'http://psychic.local/any'
server.on("/any", HTTP_ANY, [](PsychicRequest* request, PsychicResponse* resp) {
// create our response json
PsychicJsonResponse response = PsychicJsonResponse(resp);
JsonObject output = response.getRoot();
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
output["method"] = request->methodStr();
return response.send();
});
// curl -i 'http://psychic.local/simple'
server.on("/simple", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
return response->send("Simple");
})
->setURIMatchFunction(MATCH_SIMPLE);
#ifdef PSY_ENABLE_REGEX
// curl -i 'http://psychic.local/regex/23'
// curl -i 'http://psychic.local/regex/4223'
server.on("^/regex/([\\d]+)/?$", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
// look up our regex matches
std::smatch matches;
if (request->getRegexMatches(matches)) {
String output;
output += "Matches: " + String(matches.size()) + "<br/>\n";
output += "Matched URI: " + String(matches.str(0).c_str()) + "<br/>\n";
output += "Match 1: " + String(matches.str(1).c_str()) + "<br/>\n";
return response->send(output.c_str());
} else
return response->send("No regex match.");
})
->setURIMatchFunction(MATCH_REGEX);
#endif
// JsonResponse example
// curl -i http://psychic.local/json
server.on("/json", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
PsychicJsonResponse jsonResponse = PsychicJsonResponse(response);
char key[16];
char value[32];
JsonObject root = response.getRoot();
for (int i=0; i<100; i++)
{
JsonObject root = jsonResponse.getRoot();
for (int i = 0; i < 100; i++) {
sprintf(key, "key%d", i);
sprintf(value, "value is %d", i);
root[key] = value;
}
return response.send();
});
//how to redirect a request
// curl -i http://psychic.local/redirect
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
{
return request->redirect("/alien.png");
return jsonResponse.send();
});
//how to do basic auth
// curl -i --user admin:admin http://psychic.local/auth-basic
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
return request->reply("Auth Basic Success!");
});
// how to redirect a request
// curl -i http://psychic.local/redirect
server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); });
//how to do digest auth
// curl -i --user admin:admin http://psychic.local/auth-digest
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
{
if (!request->authenticate(app_user, app_pass))
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
return request->reply("Auth Digest Success!");
});
// how to do basic auth
// curl -i --user admin:admin http://psychic.local/auth-basic
server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
return response->send("Auth Basic Success!");
})->addMiddleware(&basicAuth);
//example of getting / setting cookies
// curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies
server.on("/cookies", HTTP_GET, [](PsychicRequest *request)
{
PsychicResponse response(request);
// how to do digest auth
// curl -i --user admin:admin http://psychic.local/auth-digest
server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
return response->send("Auth Digest Success!");
})->addMiddleware(&digestAuth);
// example of getting / setting cookies
// curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies
server.on("/cookies", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
int counter = 0;
if (request->hasCookie("counter"))
{
counter = std::stoi(request->getCookie("counter").c_str());
char cookie[14];
size_t size = sizeof(cookie);
if (request->getCookie("counter", cookie, &size) == ESP_OK) {
// value is null-terminated.
counter = std::stoi(cookie);
counter++;
}
sprintf(cookie, "%d", counter);
char cookie[10];
sprintf(cookie, "%i", counter);
response.setCookie("counter", cookie);
response.setContent(cookie);
return response.send();
response->setCookie("counter", cookie);
response->setContent(cookie);
return response->send();
});
//example of getting POST variables
// curl -i -d "param1=value1&param2=value2" -X POST http://psychic.local/post
server.on("/post", HTTP_POST, [](PsychicRequest *request)
{
// example of getting POST variables
// curl -i -d "param1=value1&param2=value2" -X POST http://psychic.local/post
// curl -F "param1=value1" -F "param2=value2" -X POST http://psychic.local/post
server.on("/post", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
String output;
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return request->reply(output.c_str());
return response->send(output.c_str());
});
//you can set up a custom 404 handler.
// curl -i http://psychic.local/404
server.onNotFound([](PsychicRequest *request)
{
return request->reply(404, "text/html", "Custom 404 Handler");
});
// you can set up a custom 404 handler.
// curl -i http://psychic.local/404
server.onNotFound([](PsychicRequest* request, PsychicResponse* response) { return response->send(404, "text/html", "Custom 404 Handler"); });
//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) {
// 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());
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);
Serial.printf("%s is finished. Total bytes: %llu\n", path.c_str(), (uint64_t)index + (uint64_t)len);
//our first call?
// our first call?
if (!index)
file = LittleFS.open(path, FILE_WRITE);
else
file = LittleFS.open(path, FILE_APPEND);
if(!file) {
if (!file) {
Serial.println("Failed to open file");
return ESP_FAIL;
}
if(!file.write(data, len)) {
if (!file.write(data, len)) {
Serial.println("Write failed");
return ESP_FAIL;
}
@@ -468,42 +584,41 @@ void setup()
return ESP_OK;
});
//gets called after upload has been handled
uploadHandler->onRequest([](PsychicRequest *request)
{
// gets called after upload has been handled
uploadHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
String url = "/" + request->getFilename();
String output = "<a href=\"" + url + "\">" + url + "</a>";
return request->reply(output.c_str());
return response->send(output.c_str());
});
//wildcard basic file upload - POST to /upload/filename.ext
// use http://psychic.local/ to test
// wildcard basic file upload - POST to /upload/filename.ext
// use http://psychic.local/ to test
server.on("/upload/*", HTTP_POST, uploadHandler);
//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) {
// 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());
// some progress over serial.
Serial.printf("Writing %d bytes to: %s @ index %llu\n", (int)len, path.c_str(), index);
if (last)
Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len);
Serial.printf("%s is finished. Total bytes: %llu\n", path.c_str(), (uint64_t)index + (uint64_t)len);
//our first call?
// our first call?
if (!index)
file = LittleFS.open(path, FILE_WRITE);
else
file = LittleFS.open(path, FILE_APPEND);
if(!file) {
if (!file) {
Serial.println("Failed to open file");
return ESP_FAIL;
}
if(!file.write(data, len)) {
if (!file.write(data, len)) {
Serial.println("Write failed");
return ESP_FAIL;
}
@@ -511,57 +626,83 @@ void setup()
return ESP_OK;
});
//gets called after upload has been handled
multipartHandler->onRequest([](PsychicRequest *request)
{
if (request->hasParam("file_upload"))
{
PsychicWebParameter *file = request->getParam("file_upload");
// gets called after upload has been handled
multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
String output;
if (request->hasParam("file_upload")) {
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());
}
else
return request->reply("No upload.");
if (request->hasParam("param1"))
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
if (request->hasParam("param2"))
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return response->send(output.c_str());
});
//wildcard basic file upload - POST to /upload/filename.ext
// use http://psychic.local/ to test
// wildcard basic file upload - POST to /upload/filename.ext
// use http://psychic.local/ to test
// just multipart data: curl -F "param1=multi" -F "param2=part" http://psychic.local/multipart
server.on("/multipart", HTTP_POST, multipartHandler);
//a websocket echo server
// npm install -g wscat
// wscat -c ws://psychic.local/ws
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
// form only multipart handler
// curl -F "param1=multi" -F "param2=part" http://psychic.local/multipart-data
PsychicUploadHandler* multipartFormHandler = new PsychicUploadHandler();
multipartFormHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
String output;
if (request->hasParam("param1"))
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
if (request->hasParam("param2"))
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return response->send(output.c_str());
});
server.on("/multipart-data", HTTP_POST, multipartFormHandler);
// a websocket echo server
// npm install -g wscat
// Plaintext: wscat -c ws://psychic.local/ws
// SSL: wscat -n -c wss://psychic.local/ws
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str());
client->sendMessage("Hello!");
});
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
// Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), String((char*)frame->payload, frame->len).c_str());
return request->reply(frame);
});
websocketHandler.onClose([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
websocketHandler.onClose([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u closed\n", client->socket()); });
server.on("/ws", &websocketHandler);
//EventSource server
// curl -i -N http://psychic.local/events
eventSource.onOpen([](PsychicEventSourceClient *client) {
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
// EventSource server
// curl -i -N http://psychic.local/events
eventSource.onOpen([](PsychicEventSourceClient* client) {
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str());
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());
});
eventSource.onClose([](PsychicEventSourceClient* client) { Serial.printf("[eventsource] connection #%u closed\n", client->socket()); });
server.on("/events", &eventSource);
// example of using POST data inside the filter
// works: curl -F "secret=password" http://psychic.local/post-filter
// 404: curl -F "foo=bar" http://psychic.local/post-filter
server.on("/post-filter", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
String output;
output += "Secret: " + request->getParam("secret")->value() + "<br/>\n";
return response->send(output.c_str());
})
->addFilter([](PsychicRequest* request) {
request->loadParams();
return request->hasParam("secret");
});
server.begin();
}
}
@@ -570,14 +711,21 @@ char output[60];
void loop()
{
if (millis() - lastUpdate > 2000)
{
sprintf(output, "Millis: %d\n", millis());
if (millis() - lastUpdate > 1000) {
sprintf(output, "Millis: %lu\n", millis());
websocketHandler.sendAll(output);
sprintf(output, "%d", millis());
sprintf(output, "%lu", millis());
eventSource.send(output, "millis", millis(), 0);
lastUpdate = millis();
}
// just some dev code to test that starting / stopping the server works okay.
// delay(5000);
// Serial.println("Stopping Server");
// server.stop();
// delay(5000);
// Serial.println("Starting Server");
// server.start();
}

View File

@@ -1,11 +0,0 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html

View File

@@ -9,38 +9,39 @@
*/
/**********************************************************************************************
* Note: this demo relies on various files to be uploaded on the LittleFS partition
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
**********************************************************************************************/
* Note: this demo relies on various files to be uploaded on the LittleFS partition
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
**********************************************************************************************/
#include "_secret.h"
#include <Arduino.h>
#include <WiFi.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <ESPmDNS.h>
#include <esp_sntp.h>
#include "_secret.h"
#include <LittleFS.h>
#include <PsychicHttp.h>
#include <WiFi.h>
#include <esp_sntp.h>
#include <freertos/queue.h>
#ifndef WIFI_SSID
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
#endif
//Enter your WIFI credentials in secret.h
const char *ssid = WIFI_SSID;
const char *password = WIFI_PASS;
// Enter your WIFI credentials in secret.h
const char* ssid = WIFI_SSID;
const char* password = WIFI_PASS;
//hostname for mdns (psychic.local)
const char *local_hostname = "psychic";
// hostname for mdns (psychic.local)
const char* local_hostname = "psychic";
PsychicHttpServer server;
PsychicWebSocketHandler websocketHandler;
typedef struct {
int socket;
char *buffer;
size_t len;
typedef struct
{
int socket;
char* buffer;
size_t len;
} WebsocketMessage;
QueueHandle_t wsMessages;
@@ -50,7 +51,7 @@ bool connectToWifi()
Serial.print("[WiFi] Connecting to ");
Serial.println(ssid);
//setup our wifi
// setup our wifi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
@@ -113,7 +114,7 @@ void setup()
Serial.begin(115200);
delay(10);
//prepare our message queue of 10 messages
// prepare our message queue of 10 messages
wsMessages = xQueueCreate(10, sizeof(WebsocketMessage));
if (wsMessages == 0)
Serial.printf("Failed to create queue= %p\n", wsMessages);
@@ -122,34 +123,33 @@ void setup()
// To debug, please enable Core Debug Level to Verbose
if (connectToWifi())
{
//set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname)) {
// set up our esp32 to listen on the local_hostname.local domain
if (!MDNS.begin(local_hostname))
{
Serial.println("Error starting mDNS");
return;
}
MDNS.addService("http", "tcp", 80);
if(!LittleFS.begin())
if (!LittleFS.begin())
{
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
return;
}
server.listen(80);
// this is where our /index.html file lives
// curl -i http://psychic.local/
server.serveStatic("/", LittleFS, "/www/");
//this is where our /index.html file lives
// curl -i http://psychic.local/
PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/");
//a websocket echo server
// npm install -g wscat
// wscat -c ws://psychic.local/ws
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)
{
// a websocket echo server
// npm install -g wscat
// wscat -c ws://psychic.local/ws
websocketHandler.onOpen([](PsychicWebSocketClient* client)
{
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str());
client->sendMessage("Hello!"); });
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame)
{
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
//we are allocating memory here, and the worker will free it
@@ -181,11 +181,9 @@ void setup()
if (!uxQueueSpacesAvailable(wsMessages))
return request->reply("Queue Full");
return ESP_OK;
});
websocketHandler.onClose([](PsychicWebSocketClient *client) {
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
return ESP_OK; });
websocketHandler.onClose([](PsychicWebSocketClient* client)
{ Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
server.on("/ws", &websocketHandler);
}
}
@@ -195,29 +193,30 @@ char output[60];
void loop()
{
//process our websockets outside the callback.
// process our websockets outside the callback.
WebsocketMessage message;
while (xQueueReceive(wsMessages, &message, 0) == pdTRUE)
{
//make sure our client is still good.
PsychicWebSocketClient *client = websocketHandler.getClient(message.socket);
if (client == NULL) {
// make sure our client is still good.
PsychicWebSocketClient* client = websocketHandler.getClient(message.socket);
if (client == NULL)
{
Serial.printf("[socket] client #%d bad, bailing\n", message.socket);
return;
}
//echo it back to the client.
//alternatively, this is where you would deserialize a json message, parse it, and generate a response if needed
// echo it back to the client.
// alternatively, this is where you would deserialize a json message, parse it, and generate a response if needed
client->sendMessage(HTTPD_WS_TYPE_TEXT, message.buffer, message.len);
//make sure to release our memory!
// make sure to release our memory!
free(message.buffer);
}
//send a periodic update to all clients
// send a periodic update to all clients
if (millis() - lastUpdate > 2000)
{
sprintf(output, "Millis: %d\n", millis());
sprintf(output, "Millis: %lu\n", millis());
websocketHandler.sendAll(output);
lastUpdate = millis();

View File

@@ -1,15 +1,13 @@
{
"name": "PsychicHttp",
"version": "1.2.1",
"version": "2.0.0",
"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":
{
"repository": {
"type": "git",
"url": "https://github.com/hoeken/PsychicHttp"
},
"authors":
[
"authors": [
{
"name": "Zach Hoeken",
"email": "hoeken@gmail.com",
@@ -36,8 +34,8 @@
},
{
"owner": "plageoj",
"name" : "UrlEncode",
"version" : "^1.0.1"
"name": "UrlEncode",
"version": "^1.0.1"
}
],
"export": {
@@ -50,4 +48,4 @@
"README.md"
]
}
}
}

View File

@@ -1,5 +1,5 @@
name=PsychicHttp
version=1.2.1
version=2.0.0
author=Zach Hoeken <hoeken@gmail.com>
maintainer=Zach Hoeken <hoeken@gmail.com>
sentence=PsychicHttp is a robust webserver that supports http/https + websockets.

View File

@@ -0,0 +1,30 @@
# PsychicHandler
- [x] create addMiddleware()
- [x] create runMiddleware()
- [ ] move all the handler::canHandle() stuff into filter();
- [ ] canHandle should be declared static
# PsychicEndpoint
- [ ] convert setAuthentication() to add AuthMiddleware instead.
## PsychicHttpServer
- [ ] add _chain
- [ ] create addMiddleware()
- [ ] create runMiddleware()
- [ ] create removeMiddleware(name)
- [ ] _filters -> _middleware
- [ ] destructor / cleanup
# PsychicRequest
- [ ] add _response pointer to PsychicRequest, created in constructor
- [ ] request->beginReply() should return existing _response pointer
- [ ] requestAuthentication() -> should move to response?
# PsychicResponse
- how do we have extended classes when we have a pre-declared base PsychicResponse object?
- the delegation style is really ugly and causes problems with inheritance

View File

@@ -0,0 +1,7 @@
# Name ,Type ,SubType ,Offset ,Size ,Flags
nvs ,data ,nvs ,36K ,20K ,
otadata ,data ,ota ,56K ,8K ,
app0 ,app ,ota_0 ,64K ,1856K ,
app1 ,app ,ota_1 ,1920K ,1856K ,
spiffs ,data ,spiffs ,3776K ,256K ,
coredump ,data ,coredump ,4032K ,64K ,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 36K 20K
3 otadata data ota 56K 8K
4 app0 app ota_0 64K 1856K
5 app1 app ota_1 1920K 1856K
6 spiffs data spiffs 3776K 256K
7 coredump data coredump 4032K 64K

View File

@@ -0,0 +1,108 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
lib_dir = .
src_dir = examples/platformio/src
data_dir = examples/platformio/data
; src_dir = examples/websockets/src
; data_dir = examples/websockets/data
; src_dir = examples/arduino
; data_dir = examples/arduino/data
; src_dir = examples/arduino/arduino_captive_portal
; data_dir = examples/arduino/arduino_captive_portal/data
; src_dir = examples/arduino/arduino_ota
; data_dir = examples/arduino/arduino_ota/data
[env]
platform = espressif32
framework = arduino
board = esp32-s3-devkitc-1
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
board_build.partitions = partitions-4MB.csv
board_build.filesystem = littlefs
lib_deps =
bblanchon/ArduinoJson
plageoj/UrlEncode
lib_ignore =
examples
build_flags =
-Wall
-Wextra
-Og
-D CONFIG_ARDUHAL_LOG_COLORS
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
[env:arduino2]
platform = espressif32@6.8.1
[env:arduino2-ssl]
platform = espressif32@6.8.1
build_flags = -DPSY_ENABLE_SSL
[env:arduino2-regex]
platform = espressif32@6.8.1
build_flags = -DPSY_ENABLE_REGEX
[env:arduino3]
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
[env:arduino3-ssl]
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
build_flags = -DPSY_ENABLE_SSL
[env:arduino3-regex]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
build_flags = -D PSY_ENABLE_REGEX
; [env:waveshare-4-3-touchscreen]
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
; lib_deps = ${env.lib_deps}
; https://github.com/esp-arduino-libs/ESP32_IO_Expander
; build_flags =
; -D PSY_ENABLE_SDCARD
; -D WAVESHARE_43_TOUCH
[env:pioarduino-c6]
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
board = esp32-c6-devkitc-1
[env:mathieu]
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
board = esp32dev
lib_deps = ${env.lib_deps}
; bblanchon/ArduinoTrace@^1.2.0
build_flags = ${env.build_flags}
; -D PSY_DEVMODE
; -D PSY_ENABLE_REGEX
-D PSY_ENABLE_SSL
[env:hoeken]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
board = esp32-s3-devkitc-1
lib_deps = ${env.lib_deps}
bblanchon/ArduinoTrace@^1.2.0
build_flags = ${env.build_flags}
-D PSY_DEVMODE
[env:ci]
platform = ${sysenv.PIO_PLATFORM}
board = ${sysenv.PIO_BOARD}

View File

@@ -1,12 +1,12 @@
#include "ChunkPrinter.h"
ChunkPrinter::ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) :
_response(response),
_buffer(buffer),
_length(len),
_pos(0)
{}
ChunkPrinter::ChunkPrinter(PsychicResponse* response, uint8_t* buffer, size_t len) : _response(response),
_buffer(buffer),
_length(len),
_pos(0)
{
}
ChunkPrinter::~ChunkPrinter()
{
@@ -16,34 +16,34 @@ ChunkPrinter::~ChunkPrinter()
size_t ChunkPrinter::write(uint8_t c)
{
esp_err_t err;
//if we're full, send a chunk
// 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 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;
@@ -51,7 +51,7 @@ size_t ChunkPrinter::write(const uint8_t *buffer, size_t size)
if (_response->sendChunk(_buffer, _length) != ESP_OK)
return written;
}
written += blockSize; //Update if sent correctly.
written += blockSize; // Update if sent correctly.
}
return written;
}
@@ -65,18 +65,19 @@ void ChunkPrinter::flush()
}
}
size_t ChunkPrinter::copyFrom(Stream &stream)
size_t ChunkPrinter::copyFrom(Stream& stream)
{
size_t count = 0;
while (stream.available()){
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;

View File

@@ -7,19 +7,19 @@
class ChunkPrinter : public Print
{
private:
PsychicResponse *_response;
uint8_t *_buffer;
PsychicResponse* _response;
uint8_t* _buffer;
size_t _length;
size_t _pos;
public:
ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len);
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);
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;
};

View File

@@ -0,0 +1,426 @@
#include "MultipartProcessor.h"
#include "PsychicRequest.h"
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
};
MultipartProcessor::MultipartProcessor(PsychicRequest* request, PsychicUploadCallback uploadCallback) : _request(request),
_uploadCallback(uploadCallback),
_temp(),
_parsedLength(0),
_multiParseState(EXPECT_BOUNDARY),
_boundaryPosition(0),
_itemStartIndex(0),
_itemSize(0),
_itemName(),
_itemFilename(),
_itemType(),
_itemValue(),
_itemBuffer(0),
_itemBufferIndex(0),
_itemIsFile(false)
{
}
MultipartProcessor::~MultipartProcessor() {}
esp_err_t MultipartProcessor::process()
{
esp_err_t err = ESP_OK;
_parsedLength = 0;
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 ESP_ERR_HTTPD_INVALID_REQ;
}
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
/* 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;
}
esp_err_t MultipartProcessor::process(const char* body)
{
esp_err_t err = ESP_OK;
_parsedLength = 0;
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 ESP_ERR_HTTPD_INVALID_REQ;
}
// loop over the whole string
unsigned int size = strlen(body);
for (unsigned i = 0; i < size; i++)
{
// send it to our parser
_parseMultipartPostByte(body[i], i == size - 1);
_parsedLength++;
}
return err;
}
void MultipartProcessor::_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 MultipartProcessor::_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)
{
// External - Add parameter!
_request->addParam(_itemName, _itemValue);
}
else
{
if (_itemSize)
{
if (_uploadCallback)
{
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
}
_itemBufferIndex = 0;
// External - Add parameter!
_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);
}
}
}

View File

@@ -0,0 +1,42 @@
#ifndef MULTIPART_PROCESSOR_H
#define MULTIPART_PROCESSOR_H
#include "PsychicCore.h"
/*
* MultipartProcessor - handle parsing and processing a multipart form.
* */
class MultipartProcessor
{
protected:
PsychicRequest* _request;
PsychicUploadCallback _uploadCallback;
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;
void _handleUploadByte(uint8_t data, bool last);
void _parseMultipartPostByte(uint8_t data, bool last);
public:
MultipartProcessor(PsychicRequest* request, PsychicUploadCallback uploadCallback = nullptr);
~MultipartProcessor();
esp_err_t process();
esp_err_t process(const char* body);
};
#endif

View File

@@ -2,21 +2,24 @@
#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() {
PsychicClient::PsychicClient(httpd_handle_t server, int socket) : _server(server),
_socket(socket),
_friend(NULL),
isNew(false)
{
}
httpd_handle_t PsychicClient::server() {
PsychicClient::~PsychicClient()
{
}
httpd_handle_t PsychicClient::server()
{
return _server;
}
int PsychicClient::socket() {
int PsychicClient::socket()
{
return _socket;
}
@@ -24,49 +27,67 @@ int PsychicClient::socket() {
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.
// 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);
IPAddress address(0, 0, 0, 0);
char ipstr[INET6_ADDRSTRLEN];
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
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) {
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);
// ESP_LOGD(PH_TAG, "Client Local IP => %s", ipstr);
address.fromString(ipstr);
return address;
}
uint16_t PsychicClient::localPort() const
{
struct sockaddr_storage addr;
socklen_t len = sizeof addr;
getsockname(_socket, (struct sockaddr*)&addr, &len);
struct sockaddr_in* s = (struct sockaddr_in*)&addr;
return ntohs(s->sin_port);
}
IPAddress PsychicClient::remoteIP()
{
IPAddress address(0,0,0,0);
IPAddress address(0, 0, 0, 0);
char ipstr[INET6_ADDRSTRLEN];
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
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) {
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);
// ESP_LOGD(PH_TAG, "Client Remote IP => %s", ipstr);
address.fromString(ipstr);
return address;
}
}
uint16_t PsychicClient::remotePort() const
{
struct sockaddr_storage addr;
socklen_t len = sizeof addr;
getpeername(_socket, (struct sockaddr*)&addr, &len);
struct sockaddr_in* s = (struct sockaddr_in*)&addr;
return ntohs(s->sin_port);
}

View File

@@ -4,10 +4,12 @@
#include "PsychicCore.h"
/*
* PsychicClient :: Generic wrapper around the ESP-IDF socket
*/
* PsychicClient :: Generic wrapper around the ESP-IDF socket
*/
class PsychicClient
{
class PsychicClient {
protected:
httpd_handle_t _server;
int _socket;
@@ -16,9 +18,9 @@ class PsychicClient {
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;
// 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;
@@ -29,7 +31,9 @@ class PsychicClient {
esp_err_t close();
IPAddress localIP();
uint16_t localPort() const;
IPAddress remoteIP();
uint16_t remotePort() const;
};
#endif

View File

@@ -3,17 +3,8 @@
#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
#define FILE_CHUNK_SIZE 8 * 1024
#endif
#ifndef STREAM_CHUNK_SIZE
@@ -21,87 +12,93 @@
#endif
#ifndef MAX_UPLOAD_SIZE
#define MAX_UPLOAD_SIZE (2048*1024) // 2MB
#define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB
#endif
#ifndef MAX_REQUEST_BODY_SIZE
#define MAX_REQUEST_BODY_SIZE (16*1024) //16K
#define MAX_REQUEST_BODY_SIZE (16 * 1024) // 16K
#endif
#ifdef ARDUINO
#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 "MD5Builder.h"
#include "esp_random.h"
#include <ArduinoJson.h>
#include <UrlEncode.h>
#include <esp_http_server.h>
#include <libb64/cencode.h>
#include <list>
#include <map>
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
#ifdef PSY_DEVMODE
#include "ArduinoTrace.h"
#endif
enum HTTPAuthMethod {
BASIC_AUTH,
DIGEST_AUTH
};
String urlDecode(const char* encoded);
class PsychicHttpServer;
class PsychicRequest;
class PsychicResponse;
class PsychicWebSocketRequest;
class PsychicClient;
//filter function definition
typedef std::function<bool(PsychicRequest *request)> PsychicRequestFilterFunction;
// filter function definition
typedef std::function<bool(PsychicRequest* request)> PsychicRequestFilterFunction;
//client connect callback
typedef std::function<void(PsychicClient *client)> PsychicClientCallback;
// middleware function definition
typedef std::function<esp_err_t()> PsychicMiddlewareNext;
typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)> PsychicMiddlewareCallback;
//callback definitions
typedef std::function<esp_err_t(PsychicRequest *request)> PsychicHttpRequestCallback;
typedef std::function<esp_err_t(PsychicRequest *request, JsonVariant &json)> PsychicJsonRequestCallback;
// client connect callback
typedef std::function<void(PsychicClient* client)> PsychicClientCallback;
// callback definitions
typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse* response)> PsychicHttpRequestCallback;
typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse* response, JsonVariant& json)> PsychicJsonRequestCallback;
typedef std::function<esp_err_t(PsychicRequest* request, const String& filename, uint64_t index, uint8_t* data, size_t len, bool final)> PsychicUploadCallback;
struct HTTPHeader {
char * field;
char * value;
String field;
String value;
};
class DefaultHeaders {
std::list<HTTPHeader> _headers;
class DefaultHeaders
{
std::list<HTTPHeader> _headers;
public:
DefaultHeaders() {}
public:
DefaultHeaders() {}
void addHeader(const String& field, const String& value)
{
addHeader(field.c_str(), value.c_str());
}
void addHeader(const String& field, const String& value)
{
_headers.push_back({field, value});
}
void addHeader(const char * field, const char * value)
{
HTTPHeader header;
void addHeader(const char* field, const char* value)
{
_headers.push_back({field, value});
}
//these are just going to stick around forever.
header.field =(char *)malloc(strlen(field)+1);
header.value = (char *)malloc(strlen(value)+1);
const std::list<HTTPHeader>& getHeaders() { return _headers; }
strlcpy(header.field, field, strlen(field)+1);
strlcpy(header.value, value, strlen(value)+1);
// delete the copy constructor, singleton class
DefaultHeaders(DefaultHeaders const&) = delete;
DefaultHeaders& operator=(DefaultHeaders const&) = delete;
_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;
}
// single static class interface
static DefaultHeaders& Instance()
{
static DefaultHeaders instance;
return instance;
}
};
#endif //PsychicCore_h
#endif // PsychicCore_h

View File

@@ -1,90 +1,140 @@
#include "PsychicEndpoint.h"
#include "PsychicHttpServer.h"
PsychicEndpoint::PsychicEndpoint() :
_server(NULL),
_uri(""),
_method(HTTP_GET),
_handler(NULL)
PsychicEndpoint::PsychicEndpoint() : _server(NULL),
_uri(""),
_method(HTTP_GET),
_handler(NULL)
{
}
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) :
_server(server),
_uri(uri),
_method(method),
_handler(NULL)
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer* server, int method, const char* uri) : _server(server),
_uri(uri),
_method(method),
_handler(NULL)
{
}
PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler)
PsychicEndpoint* PsychicEndpoint::setHandler(PsychicHandler* handler)
{
//clean up old / default handler
// clean up old / default handler
if (_handler != NULL)
delete _handler;
//get our new pointer
// get our new pointer
_handler = handler;
//keep a pointer to the server
// keep a pointer to the server
_handler->_server = _server;
return this;
}
PsychicHandler * PsychicEndpoint::handler()
PsychicHandler* PsychicEndpoint::handler()
{
return _handler;
}
String PsychicEndpoint::uri() {
String PsychicEndpoint::uri()
{
return _uri;
}
esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req)
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;
}
#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
}
#endif
PsychicEndpoint *self = (PsychicEndpoint *)req->user_ctx;
PsychicHandler *handler = self->handler();
PsychicEndpoint* self = (PsychicEndpoint*)req->user_ctx;
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);
esp_err_t err = self->process(&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);
}
if (err == HTTPD_404_NOT_FOUND)
return PsychicHttpServer::requestHandler(req);
if (err == ESP_ERR_HTTPD_INVALID_REQ)
return request.response()->error(HTTPD_500_INTERNAL_SERVER_ERROR, "No handler registered.");
return err;
}
bool PsychicEndpoint::matches(const char* uri)
{
// we only want to match the path, no GET strings
char* ptr;
size_t position = 0;
// look for a ? and set our path length to that,
ptr = strchr(uri, '?');
if (ptr != NULL)
position = (size_t)(int)(ptr - uri);
// or use the whole uri if not found
else
return request.reply(500, "text/html", "No handler registered.");
position = strlen(uri);
// do we have a per-endpoint match function
if (this->getURIMatchFunction() != NULL) {
// ESP_LOGD(PH_TAG, "Match? %s == %s (%d)", _uri.c_str(), uri, position);
return this->getURIMatchFunction()(_uri.c_str(), uri, (size_t)position);
}
// do we have a global match function
if (_server->getURIMatchFunction() != NULL) {
// ESP_LOGD(PH_TAG, "Match? %s == %s (%d)", _uri.c_str(), uri, position);
return _server->getURIMatchFunction()(_uri.c_str(), uri, (size_t)position);
} else {
ESP_LOGE(PH_TAG, "No uri matching function set");
return false;
}
}
PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) {
_handler->setFilter(fn);
httpd_uri_match_func_t PsychicEndpoint::getURIMatchFunction()
{
return _uri_match_fn;
}
void PsychicEndpoint::setURIMatchFunction(httpd_uri_match_func_t match_fn)
{
_uri_match_fn = match_fn;
}
PsychicEndpoint* PsychicEndpoint::addFilter(PsychicRequestFilterFunction fn)
{
_handler->addFilter(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);
PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddleware* middleware)
{
_handler->addMiddleware(middleware);
return this;
};
}
PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddlewareCallback fn)
{
_handler->addMiddleware(fn);
return this;
}
void PsychicEndpoint::removeMiddleware(PsychicMiddleware* middleware)
{
_handler->removeMiddleware(middleware);
}
esp_err_t PsychicEndpoint::process(PsychicRequest* request)
{
esp_err_t ret = ESP_ERR_HTTPD_INVALID_REQ;
if (_handler != NULL)
ret = _handler->process(request);
ESP_LOGD(PH_TAG, "Endpoint %s processed %s: %s", _uri.c_str(), request->uri().c_str(), esp_err_to_name(ret));
return ret;
}

View File

@@ -4,6 +4,7 @@
#include "PsychicCore.h"
class PsychicHandler;
class PsychicMiddleware;
#ifdef ENABLE_ASYNC
#include "async_worker.h"
@@ -11,27 +12,39 @@ class PsychicHandler;
class PsychicEndpoint
{
friend PsychicHttpServer;
friend PsychicHttpServer;
private:
PsychicHttpServer *_server;
PsychicHttpServer* _server;
String _uri;
http_method _method;
PsychicHandler *_handler;
int _method;
PsychicHandler* _handler;
httpd_uri_match_func_t _uri_match_fn = nullptr; // use this change the endpoint matching function.
public:
PsychicEndpoint();
PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri);
PsychicEndpoint(PsychicHttpServer* server, int method, const char* uri);
PsychicEndpoint *setHandler(PsychicHandler *handler);
PsychicHandler *handler();
PsychicEndpoint* setHandler(PsychicHandler* handler);
PsychicHandler* handler();
PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn);
PsychicEndpoint* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = "");
httpd_uri_match_func_t getURIMatchFunction();
void setURIMatchFunction(httpd_uri_match_func_t match_fn);
bool matches(const char* uri);
// called to process this endpoint with its middleware chain
esp_err_t process(PsychicRequest* request);
PsychicEndpoint* addFilter(PsychicRequestFilterFunction fn);
PsychicEndpoint* addMiddleware(PsychicMiddleware* middleware);
PsychicEndpoint* addMiddleware(PsychicMiddlewareCallback fn);
void removeMiddleware(PsychicMiddleware* middleware);
String uri();
static esp_err_t requestCallback(httpd_req_t *req);
static esp_err_t requestCallback(httpd_req_t* req);
};
#endif // PsychicEndpoint_h

View File

@@ -19,83 +19,88 @@
*/
#include "PsychicEventSource.h"
#include <string.h>
/*****************************************/
// PsychicEventSource - Handler
/*****************************************/
PsychicEventSource::PsychicEventSource() :
PsychicHandler(),
_onOpen(NULL),
_onClose(NULL)
{}
PsychicEventSource::~PsychicEventSource() {
PsychicEventSource::PsychicEventSource() : PsychicHandler(),
_onOpen(NULL),
_onClose(NULL)
{
}
PsychicEventSourceClient * PsychicEventSource::getClient(int socket)
PsychicEventSource::~PsychicEventSource()
{
PsychicClient *client = PsychicHandler::getClient(socket);
}
PsychicEventSourceClient* PsychicEventSource::getClient(int socket)
{
PsychicClient* client = PsychicHandler::getClient(socket);
if (client == NULL)
return NULL;
return (PsychicEventSourceClient *)client->_friend;
return (PsychicEventSourceClient*)client->_friend;
}
PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient *client) {
PsychicEventSourceClient* PsychicEventSource::getClient(PsychicClient* client)
{
return getClient(client->socket());
}
esp_err_t PsychicEventSource::handleRequest(PsychicRequest *request)
esp_err_t PsychicEventSource::handleRequest(PsychicRequest* request, PsychicResponse* resp)
{
//start our open ended HTTP response
PsychicEventSourceResponse response(request);
// start our open ended HTTP response
PsychicEventSourceResponse response(resp);
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);
// 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.
// let our handler know.
openCallback(client);
}
return err;
}
PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) {
PsychicEventSource* PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn)
{
_onOpen = fn;
return this;
}
PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) {
PsychicEventSource* PsychicEventSource::onClose(PsychicEventSourceClientCallback fn)
{
_onClose = fn;
return this;
}
void PsychicEventSource::addClient(PsychicClient *client) {
void PsychicEventSource::addClient(PsychicClient* client)
{
client->_friend = new PsychicEventSourceClient(client);
PsychicHandler::addClient(client);
}
void PsychicEventSource::removeClient(PsychicClient *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)
{
void PsychicEventSource::openCallback(PsychicClient* client)
{
PsychicEventSourceClient* buddy = getClient(client);
if (buddy == NULL) {
return;
}
@@ -103,10 +108,10 @@ void PsychicEventSource::openCallback(PsychicClient *client) {
_onOpen(buddy);
}
void PsychicEventSource::closeCallback(PsychicClient *client) {
PsychicEventSourceClient *buddy = getClient(client);
if (buddy == NULL)
{
void PsychicEventSource::closeCallback(PsychicClient* client)
{
PsychicEventSourceClient* buddy = getClient(client);
if (buddy == NULL) {
return;
}
@@ -114,11 +119,13 @@ void PsychicEventSource::closeCallback(PsychicClient *client) {
_onClose(getClient(buddy));
}
void PsychicEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect)
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());
if (c && c->_friend) {
((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str());
}
}
}
@@ -126,58 +133,135 @@ void PsychicEventSource::send(const char *message, const char *event, uint32_t i
// PsychicEventSourceClient
/*****************************************/
PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient *client) :
PsychicClient(client->server(), client->socket()),
_lastId(0)
PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient* client) : PsychicClient(client->server(), client->socket()),
_lastId(0)
{
}
PsychicEventSourceClient::~PsychicEventSourceClient(){
PsychicEventSourceClient::~PsychicEventSourceClient()
{
}
void PsychicEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
void PsychicEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect)
{
String ev = generateEventMessage(message, event, id, reconnect);
sendEvent(ev.c_str());
}
void PsychicEventSourceClient::sendEvent(const char *event) {
int result;
do {
result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0);
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
void PsychicEventSourceClient::sendEvent(const char* event)
{
_sendEventAsync(this->server(), this->socket(), event, strlen(event));
}
//if (result < 0)
//error log here
esp_err_t PsychicEventSourceClient::_sendEventAsync(httpd_handle_t handle, int socket, const char* event, size_t len)
{
// create the transfer object
async_event_transfer_t* transfer = (async_event_transfer_t*)calloc(1, sizeof(async_event_transfer_t));
if (transfer == NULL) {
return ESP_ERR_NO_MEM;
}
// populate it
transfer->arg = this;
transfer->callback = _sendEventSentCallback;
transfer->handle = handle;
transfer->socket = socket;
transfer->len = len;
// allocate for event text
transfer->event = (char*)malloc(len);
if (transfer->event == NULL) {
free(transfer);
return ESP_ERR_NO_MEM;
}
// copy over the event data
memcpy(transfer->event, event, len);
// queue it.
esp_err_t err = httpd_queue_work(handle, _sendEventWorkCallback, transfer);
// cleanup
if (err) {
free(transfer->event);
free(transfer);
return err;
}
return ESP_OK;
}
void PsychicEventSourceClient::_sendEventWorkCallback(void* arg)
{
async_event_transfer_t* trans = (async_event_transfer_t*)arg;
// omg the error is overloaded with the number of bytes sent!
esp_err_t err = httpd_socket_send(trans->handle, trans->socket, trans->event, trans->len, 0);
if (err == trans->len)
err = ESP_OK;
if (trans->callback)
trans->callback(err, trans->socket, trans->arg);
// free our memory
free(trans->event);
free(trans);
}
void PsychicEventSourceClient::_sendEventSentCallback(esp_err_t err, int socket, void* arg)
{
// PsychicEventSourceClient* client = (PsychicEventSourceClient*)arg;
if (err == ESP_OK)
return;
else if (err == ESP_FAIL)
ESP_LOGE(PH_TAG, "EventSource: send - socket error (#%d)", socket);
else if (err == ESP_ERR_INVALID_STATE)
ESP_LOGE(PH_TAG, "EventSource: Handshake was already done beforehand (#%d)", socket);
else if (err == ESP_ERR_INVALID_ARG)
ESP_LOGE(PH_TAG, "EventSource: Argument is invalid (#%d)", socket);
else if (err == HTTPD_SOCK_ERR_TIMEOUT)
ESP_LOGE(PH_TAG, "EventSource: Socket timeout (#%d)", socket);
else if (err == HTTPD_SOCK_ERR_INVALID)
ESP_LOGE(PH_TAG, "EventSource: Invalid socket (#%d)", socket);
else if (err == HTTPD_SOCK_ERR_FAIL)
ESP_LOGE(PH_TAG, "EventSource: Socket fail (#%d)", socket);
else
ESP_LOGE(PH_TAG, "EventSource: %#06x %s (#%d)", (int)err, esp_err_to_name(err), socket);
}
/*****************************************/
// PsychicEventSourceResponse
/*****************************************/
PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest *request)
: PsychicResponse(request)
PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicResponse* response) : PsychicResponseDelegate(response)
{
}
esp_err_t PsychicEventSourceResponse::send() {
esp_err_t PsychicEventSourceResponse::send()
{
_response->addHeader("Content-Type", "text/event-stream");
_response->addHeader("Cache-Control", "no-cache");
_response->addHeader("Connection", "keep-alive");
//build our main header
// 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");
// get our global headers out of the way first
for (auto& header : DefaultHeaders::Instance().getHeaders())
out.concat(header.field + ": " + header.value + "\r\n");
//separator
// now do our individual headers
for (auto& header : _response->headers())
out.concat(header.field + ": " + header.value + "\r\n");
// separator
out.concat("\r\n");
int result;
do {
result = httpd_send(_request->request(), out.c_str(), out.length());
result = httpd_send(request(), out.c_str(), out.length());
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
if (result < 0)
@@ -193,28 +277,29 @@ esp_err_t PsychicEventSourceResponse::send() {
// Event Message Generator
/*****************************************/
String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect)
{
String ev = "";
if(reconnect){
if (reconnect) {
ev += "retry: ";
ev += String(reconnect);
ev += "\r\n";
}
if(id){
if (id) {
ev += "id: ";
ev += String(id);
ev += "\r\n";
}
if(event != NULL){
if (event != NULL) {
ev += "event: ";
ev += String(event);
ev += "\r\n";
}
if(message != NULL){
if (message != NULL) {
ev += "data: ";
ev += String(message);
ev += "\r\n";

View File

@@ -20,9 +20,9 @@
#ifndef PsychicEventSource_H_
#define PsychicEventSource_H_
#include "PsychicClient.h"
#include "PsychicCore.h"
#include "PsychicHandler.h"
#include "PsychicClient.h"
#include "PsychicResponse.h"
class PsychicEventSource;
@@ -30,24 +30,38 @@ class PsychicEventSourceResponse;
class PsychicEventSourceClient;
class PsychicResponse;
typedef std::function<void(PsychicEventSourceClient *client)> PsychicEventSourceClientCallback;
typedef std::function<void(PsychicEventSourceClient* client)> PsychicEventSourceClientCallback;
class PsychicEventSourceClient : public PsychicClient {
friend PsychicEventSource;
typedef struct {
httpd_handle_t handle;
int socket;
char* event;
size_t len;
transfer_complete_cb callback;
void* arg;
} async_event_transfer_t;
class PsychicEventSourceClient : public PsychicClient
{
friend PsychicEventSource;
protected:
uint32_t _lastId;
esp_err_t _sendEventAsync(httpd_handle_t handle, int socket, const char* event, size_t len);
static void _sendEventWorkCallback(void* arg);
static void _sendEventSentCallback(esp_err_t err, int socket, void* arg);
public:
PsychicEventSourceClient(PsychicClient *client);
PsychicEventSourceClient(PsychicClient* client);
~PsychicEventSourceClient();
uint32_t lastId() const { return _lastId; }
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
void sendEvent(const char *event);
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 {
class PsychicEventSource : public PsychicHandler
{
private:
PsychicEventSourceClientCallback _onOpen;
PsychicEventSourceClientCallback _onClose;
@@ -56,27 +70,28 @@ class PsychicEventSource : public PsychicHandler {
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;
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);
PsychicEventSource* onOpen(PsychicEventSourceClientCallback fn);
PsychicEventSource* onClose(PsychicEventSourceClientCallback fn);
esp_err_t handleRequest(PsychicRequest *request) override final;
esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override final;
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
};
class PsychicEventSourceResponse: public PsychicResponse {
class PsychicEventSourceResponse : public PsychicResponseDelegate
{
public:
PsychicEventSourceResponse(PsychicRequest *request);
virtual esp_err_t send() override;
PsychicEventSourceResponse(PsychicResponse* response);
esp_err_t send();
};
String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect);
String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect);
#endif /* PsychicEventSource_H_ */

View File

@@ -1,97 +1,116 @@
#include "PsychicFileResponse.h"
#include "PsychicResponse.h"
#include "PsychicRequest.h"
#include "PsychicResponse.h"
PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType, bool download)
: PsychicResponse(request) {
PsychicFileResponse::PsychicFileResponse(PsychicResponse* response, FS& fs, const String& path, const String& contentType, bool download) : PsychicResponseDelegate(response)
{
//_code = 200;
String _path(path);
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
_path = _path+".gz";
if (!download && !fs.exists(_path) && fs.exists(_path + ".gz")) {
_path = _path + ".gz";
addHeader("Content-Encoding", "gzip");
}
_content = fs.open(_path, "r");
_contentLength = _content.size();
setContentLength(_content.size());
if(contentType == "")
_setContentType(path);
if (contentType == "")
_setContentTypeFromPath(path);
else
setContentType(contentType.c_str());
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
if (download) {
// set filename and force download
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename);
} else {
// set filename and force rendering
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
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) {
PsychicFileResponse::PsychicFileResponse(PsychicResponse* response, File content, const String& path, const String& contentType, bool download) : PsychicResponseDelegate(response)
{
String _path(path);
if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){
if (!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")) {
addHeader("Content-Encoding", "gzip");
}
_content = content;
_contentLength = _content.size();
setContentLength(_content.size());
if(contentType == "")
_setContentType(path);
if (contentType == "")
_setContentTypeFromPath(path);
else
setContentType(contentType.c_str());
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
if (download) {
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename);
} else {
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename);
}
addHeader("Content-Disposition", buf);
}
PsychicFileResponse::~PsychicFileResponse()
{
if(_content)
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";
void PsychicFileResponse::_setContentTypeFromPath(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);
}
@@ -99,59 +118,52 @@ esp_err_t PsychicFileResponse::send()
{
esp_err_t err = ESP_OK;
//just send small files directly
// 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.");
if (size < FILE_CHUNK_SIZE) {
uint8_t* buffer = (uint8_t*)malloc(size);
if (buffer == NULL) {
ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", size);
httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
return ESP_FAIL;
}
size_t readSize = _content.readBytes((char *)buffer, size);
size_t readSize = _content.readBytes((char*)buffer, size);
setContent(buffer, readSize);
err = _response->send();
this->setContent(buffer, readSize);
err = PsychicResponse::send();
free(buffer);
}
else
{
} 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.");
char* chunk = (char*)malloc(FILE_CHUNK_SIZE);
if (chunk == NULL) {
ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", FILE_CHUNK_SIZE);
httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
return ESP_FAIL;
}
this->sendHeaders();
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;
}
/* Read file in chunks into the scratch buffer */
chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE);
if (chunksize > 0) {
err = sendChunk((uint8_t*)chunk, chunksize);
if (err != ESP_OK)
break;
}
/* Keep looping till the whole file is sent */
/* Keep looping till the whole file is sent */
} while (chunksize != 0);
//keep track of our memory
// keep track of our memory
free(chunk);
if (err == ESP_OK)
{
if (err == ESP_OK) {
ESP_LOGD(PH_TAG, "File sending complete");
this->finishChunking();
finishChunking();
}
}

View File

@@ -6,16 +6,18 @@
class PsychicRequest;
class PsychicFileResponse: public PsychicResponse
class PsychicFileResponse : public PsychicResponseDelegate
{
using File = fs::File;
using FS = fs::FS;
private:
using File = fs::File;
using FS = fs::FS;
protected:
File _content;
void _setContentType(const String& path);
void _setContentTypeFromPath(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(PsychicResponse* response, FS& fs, const String& path, const String& contentType = String(), bool download = false);
PsychicFileResponse(PsychicResponse* response, File content, const String& path, const String& contentType = String(), bool download = false);
~PsychicFileResponse();
esp_err_t send();
};

View File

@@ -1,111 +1,148 @@
#include "PsychicHandler.h"
PsychicHandler::PsychicHandler() :
_filter(NULL),
_server(NULL),
_username(""),
_password(""),
_method(DIGEST_AUTH),
_realm(""),
_authFailMsg(""),
_subprotocol("")
{}
PsychicHandler::PsychicHandler()
{
}
PsychicHandler::~PsychicHandler() {
PsychicHandler::~PsychicHandler()
{
delete _chain;
// 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)
PsychicHandler* PsychicHandler::addFilter(PsychicRequestFilterFunction fn)
{
PsychicClient *c = PsychicHandler::getClient(client);
if (c == NULL)
{
_filters.push_back(fn);
return this;
}
bool PsychicHandler::filter(PsychicRequest* request)
{
// run through our filter chain.
for (auto& filter : _filters) {
if (!filter(request)) {
ESP_LOGD(PH_TAG, "Request %s refused by filter from handler", request->uri().c_str());
return false;
}
}
return true;
}
void PsychicHandler::setSubprotocol(const String& subprotocol)
{
this->_subprotocol = subprotocol;
}
const char* PsychicHandler::getSubprotocol() const
{
return _subprotocol.c_str();
}
PsychicClient* PsychicHandler::checkForNewClient(PsychicClient* client)
{
PsychicClient* c = PsychicHandler::getClient(client);
if (c == NULL) {
c = client;
addClient(c);
c->isNew = true;
}
else
} else
c->isNew = false;
return c;
}
void PsychicHandler::checkForClosedClient(PsychicClient *client)
void PsychicHandler::checkForClosedClient(PsychicClient* client)
{
if (hasClient(client))
{
if (hasClient(client)) {
closeCallback(client);
removeClient(client);
}
}
void PsychicHandler::addClient(PsychicClient *client) {
void PsychicHandler::addClient(PsychicClient* client)
{
_clients.push_back(client);
}
void PsychicHandler::removeClient(PsychicClient *client) {
void PsychicHandler::removeClient(PsychicClient* client)
{
_clients.remove(client);
}
PsychicClient * PsychicHandler::getClient(int socket)
PsychicClient* PsychicHandler::getClient(int socket)
{
//make sure the server has it too.
// make sure the server has it too.
if (!_server->hasClient(socket))
return NULL;
//what about us?
for (PsychicClient *client : _clients)
// what about us?
for (PsychicClient* client : _clients)
if (client->socket() == socket)
return client;
//nothing found.
// nothing found.
return NULL;
}
PsychicClient * PsychicHandler::getClient(PsychicClient *client) {
PsychicClient* PsychicHandler::getClient(PsychicClient* client)
{
return PsychicHandler::getClient(client->socket());
}
bool PsychicHandler::hasClient(PsychicClient *socket) {
bool PsychicHandler::hasClient(PsychicClient* socket)
{
return PsychicHandler::getClient(socket) != NULL;
}
const std::list<PsychicClient*>& PsychicHandler::getClientList() {
const std::list<PsychicClient*>& PsychicHandler::getClientList()
{
return _clients;
}
PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddleware* middleware)
{
if (!_chain) {
_chain = new PsychicMiddlewareChain();
}
_chain->addMiddleware(middleware);
return this;
}
PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddlewareCallback fn)
{
if (!_chain) {
_chain = new PsychicMiddlewareChain();
}
_chain->addMiddleware(fn);
return this;
}
void PsychicHandler::removeMiddleware(PsychicMiddleware* middleware)
{
if (_chain) {
_chain->removeMiddleware(middleware);
}
}
esp_err_t PsychicHandler::process(PsychicRequest* request)
{
if (!filter(request)) {
return HTTPD_404_NOT_FOUND;
}
if (!canHandle(request)) {
ESP_LOGD(PH_TAG, "Request %s refused by handler", request->uri().c_str());
return HTTPD_404_NOT_FOUND;
}
if (_chain) {
return _chain->runChain(request, [this, request]() {
return handleRequest(request, request->response());
});
} else {
return handleRequest(request, request->response());
}
}

View File

@@ -6,23 +6,21 @@
class PsychicEndpoint;
class PsychicHttpServer;
class PsychicMiddleware;
class PsychicMiddlewareChain;
/*
* HANDLER :: Can be attached to any endpoint or as a generic request handler.
*/
* HANDLER :: Can be attached to any endpoint or as a generic request handler.
*/
class PsychicHandler {
friend PsychicEndpoint;
class PsychicHandler
{
friend PsychicEndpoint;
protected:
PsychicRequestFilterFunction _filter;
PsychicHttpServer *_server;
String _username;
String _password;
HTTPAuthMethod _method;
String _realm;
String _authFailMsg;
PsychicHttpServer* _server = nullptr;
PsychicMiddlewareChain* _chain = nullptr;
std::list<PsychicRequestFilterFunction> _filters;
String _subprotocol;
@@ -32,35 +30,39 @@ class PsychicHandler {
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);
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) {};
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);
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;
// called to process this handler with its middleware chain and filers
esp_err_t process(PsychicRequest* request);
//bool filter(PsychicRequest* request);
PsychicHandler* addFilter(PsychicRequestFilterFunction fn);
bool filter(PsychicRequest* request);
PsychicHandler* addMiddleware(PsychicMiddleware* middleware);
PsychicHandler* addMiddleware(PsychicMiddlewareCallback fn);
void removeMiddleware(PsychicMiddleware *middleware);
// derived classes must implement these functions
virtual bool canHandle(PsychicRequest* request) { return true; };
virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) { return HTTPD_404_NOT_FOUND; };
};
#endif

View File

@@ -1,24 +1,33 @@
#ifndef PsychicHttp_h
#define PsychicHttp_h
//#define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread
// #define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread
#include <http_status.h>
#include "PsychicEndpoint.h"
#include "PsychicEventSource.h"
#include "PsychicFileResponse.h"
#include "PsychicHandler.h"
#include "PsychicHttpServer.h"
#include "PsychicJson.h"
#include "PsychicMiddleware.h"
#include "PsychicMiddlewareChain.h"
#include "PsychicMiddlewares.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 "PsychicVersion.h"
#include "PsychicWebSocket.h"
#include "PsychicEventSource.h"
#include "PsychicJson.h"
#include <http_status.h>
#ifdef ENABLE_ASYNC
#include "async_worker.h"
#endif
// debugging library
#ifdef PSY_USE_ARDUINO_TRACE
#include <ArduinoTrace.h>
#endif
#endif /* PsychicHttp_h */

View File

@@ -1,155 +1,328 @@
#include "PsychicHttpServer.h"
#include "PsychicEndpoint.h"
#include "PsychicHandler.h"
#include "PsychicWebHandler.h"
#include "PsychicStaticFileHandler.h"
#include "PsychicWebSocket.h"
#include "PsychicJson.h"
#include "PsychicStaticFileHandler.h"
#include "PsychicWebHandler.h"
#include "PsychicWebSocket.h"
#include "WiFi.h"
#ifdef PSY_ENABLE_ETHERNET
#include "ETH.h"
#endif
PsychicHttpServer::PsychicHttpServer() :
_onOpen(NULL),
_onClose(NULL)
PsychicHttpServer::PsychicHttpServer(uint16_t port)
{
maxRequestBodySize = MAX_REQUEST_BODY_SIZE;
maxUploadSize = MAX_UPLOAD_SIZE;
defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, "");
onNotFound(PsychicHttpServer::defaultNotFoundHandler);
//for a regular server
// 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;
config.global_user_ctx_free_fn = PsychicHttpServer::destroy;
config.uri_match_fn = MATCH_WILDCARD; // new internal endpoint matching - do not change this!!!
config.stack_size = 4608; // default stack is just a little bit too small.
#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
// our internal matching function for endpoints
_uri_match_fn = MATCH_WILDCARD; // use this change the endpoint matching function.
#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
setPort(port);
}
PsychicHttpServer::~PsychicHttpServer()
{
for (auto *client : _clients)
delete(client);
_esp_idf_endpoints.clear();
for (auto* client : _clients)
delete (client);
_clients.clear();
for (auto *endpoint : _endpoints)
delete(endpoint);
for (auto* endpoint : _endpoints)
delete (endpoint);
_endpoints.clear();
for (auto *handler : _handlers)
delete(handler);
for (auto* handler : _handlers)
delete (handler);
_handlers.clear();
for (auto* rewrite : _rewrites)
delete (rewrite);
_rewrites.clear();
delete defaultEndpoint;
delete _chain;
}
void PsychicHttpServer::destroy(void *ctx)
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)
void PsychicHttpServer::setPort(uint16_t port)
{
this->_use_ssl = false;
this->config.server_port = port;
return this->_start();
}
esp_err_t PsychicHttpServer::_start()
uint16_t PsychicHttpServer::getPort()
{
return this->config.server_port;
}
bool PsychicHttpServer::isConnected()
{
if (WiFi.softAPIP())
return true;
if (WiFi.localIP())
return true;
#ifdef PSY_ENABLE_ETHERNET
if (ETH.localIP())
return true;
#endif
return false;
}
esp_err_t PsychicHttpServer::start()
{
if (_running)
return ESP_OK;
// starting without network will crash us.
if (!isConnected()) {
ESP_LOGE(PH_TAG, "Server start failed - no network.");
return ESP_FAIL;
}
esp_err_t ret;
#ifdef ENABLE_ASYNC
// start workers
start_async_req_workers();
#endif
#ifdef ENABLE_ASYNC
// start workers
start_async_req_workers();
#endif
//fire it up.
// one URI handler for each http_method
config.max_uri_handlers = supported_methods.size() + _esp_idf_endpoints.size();
// fire it up.
ret = _startServer();
if (ret != ESP_OK)
{
if (ret != ESP_OK) {
ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret));
return ret;
}
// some handlers (aka websockets) need actual endpoints in esp-idf http_server
for (auto& endpoint : _esp_idf_endpoints) {
ESP_LOGD(PH_TAG, "Adding endpoint %s | %s", endpoint.uri, http_method_str((http_method)endpoint.method));
// Register endpoint with ESP-IDF server
esp_err_t ret = httpd_register_uri_handler(this->server, &endpoint);
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret));
}
// Register a handler for each http_method method - it will match all requests with that URI/method
for (auto& method : supported_methods) {
ESP_LOGD(PH_TAG, "Adding %s meta endpoint", http_method_str((http_method)method));
httpd_uri_t my_uri;
my_uri.uri = "*";
my_uri.method = method;
my_uri.handler = PsychicHttpServer::requestHandler;
my_uri.is_websocket = false;
my_uri.supported_subprotocol = "";
// 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));
}
// 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));
ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret));
ESP_LOGI(PH_TAG, "Server started on port %" PRIu16, getPort());
_running = true;
return ret;
}
esp_err_t PsychicHttpServer::_startServer() {
esp_err_t PsychicHttpServer::_startServer()
{
return httpd_start(&this->server, &this->config);
}
void PsychicHttpServer::stop()
esp_err_t PsychicHttpServer::stop()
{
httpd_stop(this->server);
if (!_running)
return ESP_OK;
// some handlers (aka websockets) need actual endpoints in esp-idf http_server
for (auto& endpoint : _esp_idf_endpoints) {
ESP_LOGD(PH_TAG, "Removing endpoint %s | %s", endpoint.uri, http_method_str((http_method)endpoint.method));
// Unregister endpoint with ESP-IDF server
esp_err_t ret = httpd_unregister_uri_handler(this->server, endpoint.uri, endpoint.method);
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "Removal of endpoint failed (%s)", esp_err_to_name(ret));
}
// Unregister a handler for each http_method method - it will match all requests with that URI/method
for (auto& method : supported_methods) {
ESP_LOGD(PH_TAG, "Removing %s meta endpoint", http_method_str((http_method)method));
// Unregister endpoint with ESP-IDF server
esp_err_t ret = httpd_unregister_uri_handler(this->server, "*", method);
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "Removal of endpoint failed (%s)", esp_err_to_name(ret));
}
esp_err_t ret = _stopServer();
if (ret != ESP_OK) {
ESP_LOGE(PH_TAG, "Server stop failed (%s)", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(PH_TAG, "Server stopped");
_running = false;
return ret;
}
PsychicHandler& PsychicHttpServer::addHandler(PsychicHandler* handler){
esp_err_t PsychicHttpServer::_stopServer()
{
return httpd_stop(this->server);
}
void PsychicHttpServer::reset()
{
if (_running)
stop();
for (auto* client : _clients)
delete (client);
_clients.clear();
for (auto* endpoint : _endpoints)
delete (endpoint);
_endpoints.clear();
for (auto* handler : _handlers)
delete (handler);
_handlers.clear();
for (auto* rewrite : _rewrites)
delete (rewrite);
_rewrites.clear();
_esp_idf_endpoints.clear();
onNotFound(PsychicHttpServer::defaultNotFoundHandler);
_onOpen = nullptr;
_onClose = nullptr;
}
httpd_uri_match_func_t PsychicHttpServer::getURIMatchFunction()
{
return _uri_match_fn;
}
void PsychicHttpServer::setURIMatchFunction(httpd_uri_match_func_t match_fn)
{
_uri_match_fn = match_fn;
}
PsychicHandler* PsychicHttpServer::addHandler(PsychicHandler* handler)
{
_handlers.push_back(handler);
return *handler;
return handler;
}
void PsychicHttpServer::removeHandler(PsychicHandler *handler){
void PsychicHttpServer::removeHandler(PsychicHandler* handler)
{
_handlers.remove(handler);
}
PsychicEndpoint* PsychicHttpServer::on(const char* uri) {
PsychicRewrite* PsychicHttpServer::addRewrite(PsychicRewrite* rewrite)
{
_rewrites.push_back(rewrite);
return rewrite;
}
void PsychicHttpServer::removeRewrite(PsychicRewrite* rewrite)
{
_rewrites.remove(rewrite);
}
PsychicRewrite* PsychicHttpServer::rewrite(const char* from, const char* to)
{
return addRewrite(new PsychicRewrite(from, to));
}
PsychicEndpoint* PsychicHttpServer::on(const char* uri)
{
return on(uri, HTTP_GET);
}
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method)
PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method)
{
PsychicWebHandler *handler = new PsychicWebHandler();
PsychicWebHandler* handler = new PsychicWebHandler();
return on(uri, method, handler);
}
PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler *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)
PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, PsychicHandler* handler)
{
//make our endpoint
PsychicEndpoint *endpoint = new PsychicEndpoint(this, method, uri);
// make our endpoint
PsychicEndpoint* endpoint = new PsychicEndpoint(this, method, uri);
//set our handler
// set our handler
endpoint->setHandler(handler);
// URI handler structure
httpd_uri_t my_uri {
.uri = uri,
.method = method,
.handler = PsychicEndpoint::requestCallback,
.user_ctx = endpoint,
.is_websocket = handler->isWebSocket(),
.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));
// websockets need a real endpoint in esp-idf
if (handler->isWebSocket()) {
// URI handler structure
httpd_uri_t my_uri;
my_uri.uri = uri;
my_uri.method = HTTP_GET;
my_uri.handler = PsychicEndpoint::requestCallback;
my_uri.user_ctx = endpoint;
my_uri.is_websocket = handler->isWebSocket();
my_uri.supported_subprotocol = handler->getSubprotocol();
//save it for later
// save it to our 'real' handlers for later.
_esp_idf_endpoints.push_back(my_uri);
}
// if this is a method we haven't added yet, do it.
if (method != HTTP_ANY) {
if (!(std::find(supported_methods.begin(), supported_methods.end(), (http_method)method) != supported_methods.end())) {
ESP_LOGD(PH_TAG, "Adding %s to server.supported_methods", http_method_str((http_method)method));
supported_methods.push_back((http_method)method);
}
}
// add it to our meta endpoints
_endpoints.push_back(endpoint);
return endpoint;
@@ -160,10 +333,10 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpRequestCallba
return on(uri, HTTP_GET, fn);
}
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHttpRequestCallback fn)
PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, PsychicHttpRequestCallback fn)
{
//these basic requests need a basic web handler
PsychicWebHandler *handler = new PsychicWebHandler();
// these basic requests need a basic web handler
PsychicWebHandler* handler = new PsychicWebHandler();
handler->onRequest(fn);
return on(uri, method, handler);
@@ -174,59 +347,187 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonRequestCallba
return on(uri, HTTP_GET, fn);
}
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicJsonRequestCallback fn)
PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, PsychicJsonRequestCallback fn)
{
//these basic requests need a basic web handler
PsychicJsonHandler *handler = new PsychicJsonHandler();
// these basic requests need a basic web handler
PsychicJsonHandler* handler = new PsychicJsonHandler();
handler->onRequest(fn);
return on(uri, method, handler);
}
bool PsychicHttpServer::removeEndpoint(const char* uri, int method)
{
// some handlers (aka websockets) need actual endpoints in esp-idf http_server
// don't return from here, because its added to the _endpoints list too.
for (auto& endpoint : _esp_idf_endpoints) {
if (!strcmp(endpoint.uri, uri) && method == endpoint.method) {
ESP_LOGD(PH_TAG, "Unregistering endpoint %s | %s", endpoint.uri, http_method_str((http_method)endpoint.method));
// Register endpoint with ESP-IDF server
esp_err_t ret = httpd_register_uri_handler(this->server, &endpoint);
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret));
}
}
// loop through our endpoints and see if anyone matches
for (auto* endpoint : _endpoints) {
if (endpoint->uri().equals(uri) && method == endpoint->_method)
return removeEndpoint(endpoint);
}
return false;
}
bool PsychicHttpServer::removeEndpoint(PsychicEndpoint* endpoint)
{
_endpoints.remove(endpoint);
return true;
}
PsychicHttpServer* PsychicHttpServer::addFilter(PsychicRequestFilterFunction fn)
{
_filters.push_back(fn);
return this;
}
bool PsychicHttpServer::_filter(PsychicRequest* request)
{
// run through our filter chain.
for (auto& filter : _filters) {
if (!filter(request))
return false;
}
return true;
}
PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddleware* middleware)
{
if (!_chain) {
_chain = new PsychicMiddlewareChain();
}
_chain->addMiddleware(middleware);
return this;
}
PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddlewareCallback fn)
{
if (!_chain) {
_chain = new PsychicMiddlewareChain();
}
_chain->addMiddleware(fn);
return this;
}
void PsychicHttpServer::removeMiddleware(PsychicMiddleware* middleware)
{
if (_chain) {
_chain->removeMiddleware(middleware);
}
}
void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn)
{
PsychicWebHandler *handler = new PsychicWebHandler();
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)
bool PsychicHttpServer::_rewriteRequest(PsychicRequest* request)
{
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);
for (auto* r : _rewrites) {
if (r->match(request)) {
request->_setUri(r->toUrl().c_str());
return true;
}
}
//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;
return false;
}
esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest *request)
esp_err_t PsychicHttpServer::requestHandler(httpd_req_t* req)
{
request->reply(404, "text/html", "That URI does not exist.");
PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle);
PsychicRequest request(server, req);
return ESP_OK;
// process any URL rewrites
server->_rewriteRequest(&request);
// run it through our global server filter list
if (!server->_filter(&request)) {
ESP_LOGD(PH_TAG, "Request %s refused by global filter", request.uri().c_str());
return request.response()->send(400);
}
// then runs the request through the filter chain
esp_err_t ret;
if (server->_chain) {
ret = server->_chain->runChain(&request, [server, &request]() {
return server->_process(&request);
});
} else {
ret = server->_process(&request);
}
ESP_LOGD(PH_TAG, "Request %s processed by global middleware: %s", request.uri().c_str(), esp_err_to_name(ret));
if (ret == HTTPD_404_NOT_FOUND) {
return PsychicHttpServer::notFoundHandler(req, HTTPD_404_NOT_FOUND);
}
return ret;
}
void PsychicHttpServer::onOpen(PsychicClientCallback handler) {
esp_err_t PsychicHttpServer::_process(PsychicRequest* request)
{
// loop through our endpoints and see if anyone wants it.
for (auto* endpoint : _endpoints) {
if (endpoint->matches(request->uri().c_str())) {
if (endpoint->_method == request->method() || endpoint->_method == HTTP_ANY) {
request->setEndpoint(endpoint);
return endpoint->process(request);
}
}
}
// loop through our global handlers and see if anyone wants it
for (auto* handler : _handlers) {
esp_err_t ret = handler->process(request);
if (ret != HTTPD_404_NOT_FOUND)
return ret;
}
return HTTPD_404_NOT_FOUND;
}
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);
// pull up our default handler / endpoint
PsychicHandler* handler = server->defaultEndpoint->handler();
if (!handler)
return request.response()->send(404);
esp_err_t ret = handler->process(&request);
if (ret != HTTPD_404_NOT_FOUND)
return ret;
// not sure how we got this far.
return request.response()->send(404);
}
esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest* request, PsychicResponse* response)
{
return response->send(404, "text/html", "That URI does not exist.");
}
void PsychicHttpServer::onOpen(PsychicClientCallback handler)
{
this->_onOpen = handler;
}
@@ -234,25 +535,25 @@ 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);
// 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)
{
// lookup our client
PsychicClient* client = server->getClient(sockfd);
if (client == NULL) {
client = new PsychicClient(hd, sockfd);
server->addClient(client);
}
//user callback
// user callback
if (server->_onOpen != NULL)
server->_onOpen(client);
return ESP_OK;
}
void PsychicHttpServer::onClose(PsychicClientCallback handler) {
void PsychicHttpServer::onClose(PsychicClientCallback handler)
{
this->_onClose = handler;
}
@@ -260,30 +561,27 @@ 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);
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();
// 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?
// do we have a callback attached?
if (server->_onClose != NULL)
server->_onClose(client);
//remove it from our list
// remove it from our list
server->removeClient(client);
}
else
} else
ESP_LOGE(PH_TAG, "No client record %d", sockfd);
//finally close it out.
// finally close it out.
close(sockfd);
}
@@ -295,49 +593,49 @@ PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri, fs::FS
return handler;
}
void PsychicHttpServer::addClient(PsychicClient *client) {
void PsychicHttpServer::addClient(PsychicClient* client)
{
_clients.push_back(client);
}
void PsychicHttpServer::removeClient(PsychicClient *client) {
void PsychicHttpServer::removeClient(PsychicClient* client)
{
_clients.remove(client);
delete client;
}
PsychicClient * PsychicHttpServer::getClient(int socket) {
for (PsychicClient * client : _clients)
PsychicClient* PsychicHttpServer::getClient(int socket)
{
for (PsychicClient* client : _clients)
if (client->socket() == socket)
return client;
return NULL;
}
PsychicClient * PsychicHttpServer::getClient(httpd_req_t *req) {
PsychicClient* PsychicHttpServer::getClient(httpd_req_t* req)
{
return getClient(httpd_req_to_sockfd(req));
}
bool PsychicHttpServer::hasClient(int socket) {
bool PsychicHttpServer::hasClient(int socket)
{
return getClient(socket) != NULL;
}
const std::list<PsychicClient*>& PsychicHttpServer::getClientList() {
const std::list<PsychicClient*>& PsychicHttpServer::getClientList()
{
return _clients;
}
bool ON_STA_FILTER(PsychicRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2
bool ON_STA_FILTER(PsychicRequest* request)
{
return WiFi.localIP() == request->client()->localIP();
#else
return false;
#endif
}
bool ON_AP_FILTER(PsychicRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2
bool ON_AP_FILTER(PsychicRequest* request)
{
return WiFi.softAPIP() == request->client()->localIP();
#else
return false;
#endif
}
String urlDecode(const char* encoded)
@@ -350,25 +648,46 @@ String urlDecode(const char* encoded)
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];
}
if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) {
// Valid percent-encoded sequence
int hex;
sscanf(encoded + i + 1, "%2x", &hex);
decoded[j++] = (char)hex;
i += 2; // Skip the two hexadecimal characters
} else if (encoded[i] == '+') {
// Convert '+' to space
decoded[j++] = ' ';
} else {
// Copy other characters as they are
decoded[j++] = encoded[i];
}
}
decoded[j] = '\0'; // Null-terminate the decoded string
decoded[j] = '\0'; // Null-terminate the decoded string
String output(decoded);
free(decoded);
return output;
}
}
bool psychic_uri_match_simple(const char* uri1, const char* uri2, size_t len2)
{
return strlen(uri1) == len2 && // First match lengths
(strncmp(uri1, uri2, len2) == 0); // Then match actual URIs
}
#ifdef PSY_ENABLE_REGEX
bool psychic_uri_match_regex(const char* uri1, const char* uri2, size_t len2)
{
std::regex pattern(uri1);
std::smatch matches;
std::string s(uri2);
// len2 is passed in to tell us to match up to a point.
if (s.length() > len2)
s = s.substr(0, len2);
return std::regex_search(s, matches, pattern);
}
#endif

View File

@@ -1,9 +1,20 @@
#ifndef PsychicHttpServer_h
#define PsychicHttpServer_h
#include "PsychicCore.h"
#include "PsychicClient.h"
#include "PsychicCore.h"
#include "PsychicHandler.h"
#include "PsychicMiddleware.h"
#include "PsychicMiddlewareChain.h"
#include "PsychicRewrite.h"
#ifdef PSY_ENABLE_REGEX
#include <regex>
#endif
#ifndef HTTP_ANY
#define HTTP_ANY INT_MAX
#endif
class PsychicEndpoint;
class PsychicHandler;
@@ -12,70 +23,124 @@ class PsychicStaticFileHandler;
class PsychicHttpServer
{
protected:
bool _use_ssl = false;
std::list<httpd_uri_t> _esp_idf_endpoints;
std::list<PsychicEndpoint*> _endpoints;
std::list<PsychicHandler*> _handlers;
std::list<PsychicClient*> _clients;
std::list<PsychicRewrite*> _rewrites;
std::list<PsychicRequestFilterFunction> _filters;
PsychicClientCallback _onOpen;
PsychicClientCallback _onClose;
PsychicClientCallback _onOpen = nullptr;
PsychicClientCallback _onClose = nullptr;
PsychicMiddlewareChain* _chain = nullptr;
esp_err_t _start();
virtual esp_err_t _startServer();
virtual esp_err_t _stopServer();
bool _running = false;
httpd_uri_match_func_t _uri_match_fn = nullptr;
bool _rewriteRequest(PsychicRequest* request);
esp_err_t _process(PsychicRequest* request);
bool _filter(PsychicRequest* request);
public:
PsychicHttpServer();
PsychicHttpServer(uint16_t port = 80);
virtual ~PsychicHttpServer();
//esp-idf specific stuff
// what methods to support
std::list<http_method> supported_methods = {
HTTP_GET,
HTTP_POST,
HTTP_DELETE,
HTTP_HEAD,
HTTP_PUT,
HTTP_OPTIONS
};
// esp-idf specific stuff
httpd_handle_t server;
httpd_config_t config;
//some limits on what we will accept
// some limits on what we will accept
unsigned long maxUploadSize;
unsigned long maxRequestBodySize;
PsychicEndpoint *defaultEndpoint;
PsychicEndpoint* defaultEndpoint;
static void destroy(void *ctx);
static void destroy(void* ctx);
esp_err_t listen(uint16_t port);
virtual void setPort(uint16_t port);
virtual uint16_t getPort();
virtual void stop();
bool isConnected();
bool isRunning() { return _running; }
esp_err_t begin() { return start(); }
esp_err_t end() { return stop(); }
esp_err_t start();
esp_err_t stop();
void reset();
PsychicHandler& addHandler(PsychicHandler* handler);
httpd_uri_match_func_t getURIMatchFunction();
void setURIMatchFunction(httpd_uri_match_func_t match_fn);
PsychicRewrite* addRewrite(PsychicRewrite* rewrite);
void removeRewrite(PsychicRewrite* rewrite);
PsychicRewrite* rewrite(const char* from, const char* to);
PsychicHandler* addHandler(PsychicHandler* handler);
void removeHandler(PsychicHandler* handler);
void addClient(PsychicClient *client);
void removeClient(PsychicClient *client);
void addClient(PsychicClient* client);
void removeClient(PsychicClient* client);
PsychicClient* getClient(int socket);
PsychicClient* getClient(httpd_req_t *req);
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, int method);
PsychicEndpoint* on(const char* uri, PsychicHandler* handler);
PsychicEndpoint* on(const char* uri, int 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, int method, PsychicHttpRequestCallback onRequest);
PsychicEndpoint* on(const char* uri, PsychicJsonRequestCallback onRequest);
PsychicEndpoint* on(const char* uri, http_method method, PsychicJsonRequestCallback onRequest);
PsychicEndpoint* on(const char* uri, int 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);
bool removeEndpoint(const char* uri, int method);
bool removeEndpoint(PsychicEndpoint* endpoint);
void onOpen(PsychicClientCallback handler);
void onClose(PsychicClientCallback handler);
PsychicHttpServer* addFilter(PsychicRequestFilterFunction fn);
PsychicHttpServer* addMiddleware(PsychicMiddleware* middleware);
PsychicHttpServer* addMiddleware(PsychicMiddlewareCallback fn);
void removeMiddleware(PsychicMiddleware *middleware);
static esp_err_t requestHandler(httpd_req_t* req);
static esp_err_t notFoundHandler(httpd_req_t* req, httpd_err_code_t err);
static esp_err_t defaultNotFoundHandler(PsychicRequest* request, PsychicResponse* response);
static esp_err_t openCallback(httpd_handle_t hd, int sockfd);
static void closeCallback(httpd_handle_t hd, int sockfd);
void onNotFound(PsychicHttpRequestCallback fn);
void onOpen(PsychicClientCallback handler);
void onClose(PsychicClientCallback handler);
PsychicStaticFileHandler* serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
};
bool ON_STA_FILTER(PsychicRequest *request);
bool ON_AP_FILTER(PsychicRequest *request);
bool ON_STA_FILTER(PsychicRequest* request);
bool ON_AP_FILTER(PsychicRequest* request);
// URI matching functions
bool psychic_uri_match_simple(const char* uri1, const char* uri2, size_t len2);
#define MATCH_SIMPLE psychic_uri_match_simple
#define MATCH_WILDCARD httpd_uri_match_wildcard
#ifdef PSY_ENABLE_REGEX
bool psychic_uri_match_regex(const char* uri1, const char* uri2, size_t len2);
#define MATCH_REGEX psychic_uri_match_regex
#endif
#endif // PsychicHttpServer_h

View File

@@ -2,9 +2,9 @@
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer()
PsychicHttpsServer::PsychicHttpsServer(uint16_t port) : PsychicHttpServer(port)
{
//for a SSL server
// for a SSL server
ssl_config = HTTPD_SSL_CONFIG_DEFAULT();
ssl_config.httpd.open_fn = PsychicHttpServer::openCallback;
ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback;
@@ -18,44 +18,53 @@ PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer()
// 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;
setPort(port);
}
PsychicHttpsServer::~PsychicHttpsServer() {}
esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, const char *private_key)
void PsychicHttpsServer::setPort(uint16_t port)
{
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
uint16_t PsychicHttpsServer::getPort()
{
return this->ssl_config.port_secure;
}
this->ssl_config.prvtkey_pem = (uint8_t *)private_key;
this->ssl_config.prvtkey_len = strlen(private_key)+1;
void PsychicHttpsServer::setCertificate(const uint8_t* cert, size_t cert_size, const uint8_t* private_key, size_t private_key_size)
{
if (cert) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
this->ssl_config.servercert = cert;
this->ssl_config.servercert_len = cert_size;
#else
this->ssl_config.cacert_pem = cert;
this->ssl_config.cacert_len = cert_size;
#endif
}
return this->_start();
if (private_key) {
this->ssl_config.prvtkey_pem = private_key;
this->ssl_config.prvtkey_len = private_key_size;
}
}
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);
return httpd_ssl_start(&this->server, &this->ssl_config);
}
void PsychicHttpsServer::stop()
esp_err_t PsychicHttpsServer::_stopServer()
{
if (this->_use_ssl)
httpd_ssl_stop(this->server);
else
httpd_stop(this->server);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
return httpd_ssl_stop(this->server);
#else
httpd_ssl_stop(this->server);
return ESP_OK;
#endif
}
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE

View File

@@ -1,38 +1,44 @@
#ifndef PsychicHttpsServer_h
#define PsychicHttpsServer_h
#define PsychicHttpsServer_h
#include <sdkconfig.h>
#include <sdkconfig.h>
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
#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
#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
#ifndef PSY_ENABLE_SSL
#define PSY_ENABLE_SSL // you can use this define in your code to enable/disable these features
#endif
class PsychicHttpsServer : public PsychicHttpServer
{
protected:
bool _use_ssl = false;
virtual esp_err_t _startServer() override final;
virtual esp_err_t _stopServer() override final;
public:
PsychicHttpsServer();
PsychicHttpsServer(uint16_t port = 443);
~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;
// using PsychicHttpServer::listen; // keep the regular version
virtual void setPort(uint16_t port) override final;
virtual uint16_t getPort() override final;
// Pointer to certificate data in PEM format
void setCertificate(const char* cert, const char* private_key) { setCertificate((const uint8_t*)cert, strlen(cert) + 1, (const uint8_t*)private_key, private_key ? strlen(private_key) + 1 : 0); }
// Pointer to certificate data in PEM or DER format. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in certSize and keySize.
void setCertificate(const uint8_t* cert, size_t cert_size, const uint8_t* private_key, size_t private_key_size);
};
#endif // PsychicHttpsServer_h
#else
#warning ESP-IDF https server support not enabled.
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
#endif // PsychicHttpsServer_h
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE

View File

@@ -1,28 +1,30 @@
#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();
}
PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray, size_t maxJsonBufferSize) : __response(response),
_jsonBuffer(maxJsonBufferSize)
{
response->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>();
}
PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray) : PsychicResponseDelegate(response)
{
setContentType(JSON_MIMETYPE);
if (isArray)
_root = _jsonBuffer.add<JsonArray>();
else
_root = _jsonBuffer.add<JsonObject>();
}
#endif
JsonVariant &PsychicJsonResponse::getRoot() { return _root; }
JsonVariant& PsychicJsonResponse::getRoot()
{
return _root;
}
size_t PsychicJsonResponse::getLength()
{
@@ -34,100 +36,93 @@ esp_err_t PsychicJsonResponse::send()
esp_err_t err = ESP_OK;
size_t length = getLength();
size_t buffer_size;
char *buffer;
char* buffer;
//how big of a buffer do we want?
// how big of a buffer do we want?
if (length < JSON_BUFFER_SIZE)
buffer_size = length+1;
buffer_size = length + 1;
else
buffer_size = JSON_BUFFER_SIZE;
buffer = (char *)malloc(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;
return error(HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
}
//send it in one shot or no?
if (length < JSON_BUFFER_SIZE)
{
// 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);
setContent((uint8_t*)buffer, length);
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);
err = send();
} else {
// helper class that acts as a stream to print chunked responses
ChunkPrinter dest(_response, (uint8_t*)buffer, buffer_size);
//keep our headers
this->sendHeaders();
// keep our headers
sendHeaders();
serializeJson(_root, dest);
//send the last bits
// send the last bits
dest.flush();
//done with our chunked response too
err = this->finishChunking();
// done with our chunked response too
err = finishChunking();
}
//let the buffer go
// let the buffer go
free(buffer);
return err;
}
#ifdef ARDUINOJSON_6_COMPATIBILITY
PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) :
_onRequest(NULL),
_maxJsonBufferSize(maxJsonBufferSize)
{};
PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) : _onRequest(NULL),
_maxJsonBufferSize(maxJsonBufferSize) {};
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) :
_onRequest(onRequest),
_maxJsonBufferSize(maxJsonBufferSize)
{}
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) : _onRequest(onRequest),
_maxJsonBufferSize(maxJsonBufferSize)
{
}
#else
PsychicJsonHandler::PsychicJsonHandler() :
_onRequest(NULL)
{};
PsychicJsonHandler::PsychicJsonHandler() : _onRequest(NULL) {};
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) :
_onRequest(onRequest)
{}
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) : _onRequest(onRequest)
{
}
#endif
void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) { _onRequest = fn; }
esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest *request)
void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn)
{
//process basic stuff
PsychicWebHandler::handleRequest(request);
_onRequest = fn;
}
if (_onRequest)
{
#ifdef ARDUINOJSON_6_COMPATIBILITY
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, request->body());
if (error)
return request->reply(400);
esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest* request, PsychicResponse* response)
{
// process basic stuff
PsychicWebHandler::handleRequest(request, response);
JsonVariant json = jsonBuffer.as<JsonVariant>();
#else
JsonDocument jsonBuffer;
DeserializationError error = deserializeJson(jsonBuffer, request->body());
if (error)
return request->reply(400);
if (_onRequest) {
#ifdef ARDUINOJSON_6_COMPATIBILITY
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, request->body());
if (error)
return response->send(400);
JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif
JsonVariant json = jsonBuffer.as<JsonVariant>();
#else
JsonDocument jsonBuffer;
DeserializationError error = deserializeJson(jsonBuffer, request->body());
if (error)
return response->send(400);
return _onRequest(request, json);
}
else
return request->reply(500);
JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif
return _onRequest(request, response, json);
} else
return response->send(500);
}

View File

@@ -8,9 +8,9 @@
#ifndef PSYCHIC_JSON_H_
#define PSYCHIC_JSON_H_
#include "ChunkPrinter.h"
#include "PsychicRequest.h"
#include "PsychicWebHandler.h"
#include "ChunkPrinter.h"
#include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR == 6
@@ -20,70 +20,71 @@
#endif
#endif
#ifndef JSON_BUFFER_SIZE
#define JSON_BUFFER_SIZE 4*1024
#define JSON_BUFFER_SIZE 4 * 1024
#endif
constexpr const char *JSON_MIMETYPE = "application/json";
constexpr const char* JSON_MIMETYPE = "application/json";
/*
* Json Response
* */
class PsychicJsonResponse : public PsychicResponse
class PsychicJsonResponse : public PsychicResponseDelegate
{
protected:
#ifdef ARDUINOJSON_5_COMPATIBILITY
DynamicJsonBuffer _jsonBuffer;
#elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer;
#else
JsonDocument _jsonBuffer;
#endif
#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
#ifdef ARDUINOJSON_5_COMPATIBILITY
PsychicJsonResponse(PsychicResponse* response, bool isArray = false);
#elif ARDUINOJSON_VERSION_MAJOR == 6
PsychicJsonResponse(PsychicResponse* response, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
PsychicJsonResponse(PsychicResponse* response, bool isArray = false);
#endif
~PsychicJsonResponse() {}
~PsychicJsonResponse()
{
}
JsonVariant &getRoot();
JsonVariant& getRoot();
size_t getLength();
virtual esp_err_t send() override;
esp_err_t send();
};
class PsychicJsonHandler : public PsychicWebHandler
{
protected:
PsychicJsonRequestCallback _onRequest;
#if ARDUINOJSON_VERSION_MAJOR == 6
const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE;
#endif
#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
#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;
virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override;
};
#endif

View File

@@ -0,0 +1,6 @@
#include "PsychicMiddleware.h"
esp_err_t PsychicMiddlewareFunction::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
{
return _fn(request, request->response(), next);
}

View File

@@ -0,0 +1,37 @@
#ifndef PsychicMiddleware_h
#define PsychicMiddleware_h
#include "PsychicCore.h"
#include "PsychicRequest.h"
#include "PsychicResponse.h"
class PsychicMiddlewareChain;
/*
* PsychicMiddleware :: fancy callback wrapper for handling requests and responses.
* */
class PsychicMiddleware
{
public:
virtual ~PsychicMiddleware() {}
virtual esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
{
return next();
}
private:
friend PsychicMiddlewareChain;
bool _freeOnRemoval = false;
};
class PsychicMiddlewareFunction : public PsychicMiddleware
{
public:
PsychicMiddlewareFunction(PsychicMiddlewareCallback fn) : _fn(fn) { assert(_fn); }
esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next) override;
protected:
PsychicMiddlewareCallback _fn;
};
#endif

View File

@@ -0,0 +1,47 @@
#include "PsychicMiddlewareChain.h"
PsychicMiddlewareChain::~PsychicMiddlewareChain()
{
for (auto middleware : _middleware)
if (middleware->_freeOnRemoval)
delete middleware;
_middleware.clear();
}
void PsychicMiddlewareChain::addMiddleware(PsychicMiddleware* middleware)
{
_middleware.push_back(middleware);
}
void PsychicMiddlewareChain::addMiddleware(PsychicMiddlewareCallback fn)
{
PsychicMiddlewareFunction* closure = new PsychicMiddlewareFunction(fn);
closure->_freeOnRemoval = true;
_middleware.push_back(closure);
}
void PsychicMiddlewareChain::removeMiddleware(PsychicMiddleware* middleware)
{
_middleware.remove(middleware);
if (middleware->_freeOnRemoval)
delete middleware;
}
esp_err_t PsychicMiddlewareChain::runChain(PsychicRequest* request, PsychicMiddlewareNext finalizer)
{
if (_middleware.size() == 0)
return finalizer();
PsychicMiddlewareNext next;
std::list<PsychicMiddleware*>::iterator it = _middleware.begin();
next = [this, &next, &it, request, finalizer]() {
if (it == _middleware.end())
return finalizer();
PsychicMiddleware* m = *it;
it++;
return m->run(request, request->response(), next);
};
return next();
}

View File

@@ -0,0 +1,28 @@
#ifndef PsychicMiddlewareChain_h
#define PsychicMiddlewareChain_h
#include "PsychicCore.h"
#include "PsychicMiddleware.h"
#include "PsychicRequest.h"
#include "PsychicResponse.h"
/*
* PsychicMiddlewareChain - handle tracking and executing our chain of middleware objects
* */
class PsychicMiddlewareChain
{
public:
virtual ~PsychicMiddlewareChain();
void addMiddleware(PsychicMiddleware* middleware);
void addMiddleware(PsychicMiddlewareCallback fn);
void removeMiddleware(PsychicMiddleware* middleware);
esp_err_t runChain(PsychicRequest* request, PsychicMiddlewareNext finalizer);
protected:
std::list<PsychicMiddleware*> _middleware;
};
#endif

View File

@@ -0,0 +1,170 @@
#include "PsychicMiddlewares.h"
void LoggingMiddleware::setOutput(Print &output) {
_out = &output;
}
esp_err_t LoggingMiddleware::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
{
_out->print("* Connection from ");
_out->print(request->client()->remoteIP().toString());
_out->print(":");
_out->println(request->client()->remotePort());
_out->print("> ");
_out->print(request->methodStr());
_out->print(" ");
_out->print(request->uri());
_out->print(" ");
_out->println(request->version());
// TODO: find a way to collect all headers
// int n = request->headerCount();
// for (int i = 0; i < n; i++) {
// String v = server.header(i);
// if (!v.isEmpty()) {
// // because these 2 are always there, eventually empty: "Authorization", "If-None-Match"
// _out->print("< ");
// _out->print(server.headerName(i));
// _out->print(": ");
// _out->println(server.header(i));
// }
// }
_out->println(">");
esp_err_t ret = next();
if (ret != HTTPD_404_NOT_FOUND) {
_out->println("* Processed!");
_out->print("< ");
_out->print(response->version());
_out->print(" ");
_out->print(response->getCode());
_out->print(" ");
_out->println(http_status_reason(response->getCode()));
// iterate over response->headers()
std::list<HTTPHeader>::iterator it = response->headers().begin();
while (it != response->headers().end()) {
HTTPHeader h = *it;
_out->print("< ");
_out->print(h.field);
_out->print(": ");
_out->println(h.value);
it++;
}
_out->println("<");
} else {
_out->println("* Not processed!");
}
return ret;
}
AuthenticationMiddleware& AuthenticationMiddleware::setUsername(const char* username)
{
_username = username;
return *this;
}
AuthenticationMiddleware& AuthenticationMiddleware::setPassword(const char* password)
{
_password = password;
return *this;
}
AuthenticationMiddleware& AuthenticationMiddleware::setRealm(const char* realm)
{
_realm = realm;
return *this;
}
AuthenticationMiddleware& AuthenticationMiddleware::setAuthMethod(HTTPAuthMethod method)
{
_method = method;
return *this;
}
AuthenticationMiddleware& AuthenticationMiddleware::setAuthFailureMessage(const char* message)
{
_authFailMsg = message;
return *this;
}
bool AuthenticationMiddleware::isAllowed(PsychicRequest* request) const
{
if (!_username.isEmpty() && !_password.isEmpty()) {
return request->authenticate(_username.c_str(), _password.c_str());
}
return true;
}
esp_err_t AuthenticationMiddleware::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
{
bool authenticationRequired = false;
if (!_username.isEmpty() && !_password.isEmpty()) {
authenticationRequired = !request->authenticate(_username.c_str(), _password.c_str());
}
if (authenticationRequired) {
return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str());
} else {
return next();
}
}
CorsMiddleware& CorsMiddleware::setOrigin(const char* origin)
{
_origin = origin;
return *this;
}
CorsMiddleware& CorsMiddleware::setMethods(const char* methods)
{
_methods = methods;
return *this;
}
CorsMiddleware& CorsMiddleware::setHeaders(const char* headers)
{
_headers = headers;
return *this;
}
CorsMiddleware& CorsMiddleware::setAllowCredentials(bool credentials)
{
_credentials = credentials;
return *this;
}
CorsMiddleware& CorsMiddleware::setMaxAge(uint32_t seconds)
{
_maxAge = seconds;
return *this;
}
void CorsMiddleware::addCORSHeaders(PsychicResponse* response)
{
response->addHeader("Access-Control-Allow-Origin", _origin.c_str());
response->addHeader("Access-Control-Allow-Methods", _methods.c_str());
response->addHeader("Access-Control-Allow-Headers", _headers.c_str());
response->addHeader("Access-Control-Allow-Credentials", _credentials ? "true" : "false");
response->addHeader("Access-Control-Max-Age", String(_maxAge).c_str());
}
esp_err_t CorsMiddleware::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
{
if (request->hasHeader("Origin")) {
addCORSHeaders(response);
if (request->method() == HTTP_OPTIONS) {
return response->send(200);
}
}
return next();
}

View File

@@ -0,0 +1,78 @@
#ifndef PsychicMiddlewares_h
#define PsychicMiddlewares_h
#include "PsychicMiddleware.h"
#include <Stream.h>
#include <http_status.h>
// curl-like logging middleware
class LoggingMiddleware : public PsychicMiddleware
{
public:
void setOutput(Print& output);
esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next) override;
private:
Print* _out;
};
class AuthenticationMiddleware : public PsychicMiddleware
{
public:
AuthenticationMiddleware& setUsername(const char* username);
AuthenticationMiddleware& setPassword(const char* password);
AuthenticationMiddleware& setRealm(const char* realm);
AuthenticationMiddleware& setAuthMethod(HTTPAuthMethod method);
AuthenticationMiddleware& setAuthFailureMessage(const char* message);
const String& getUsername() const { return _username; }
const String& getPassword() const { return _password; }
const String& getRealm() const { return _realm; }
HTTPAuthMethod getAuthMethod() const { return _method; }
const String& getAuthFailureMessage() const { return _authFailMsg; }
bool isAllowed(PsychicRequest* request) const;
esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next) override;
private:
String _username;
String _password;
String _realm;
HTTPAuthMethod _method = BASIC_AUTH;
String _authFailMsg;
};
class CorsMiddleware : public PsychicMiddleware
{
public:
CorsMiddleware& setOrigin(const char* origin);
CorsMiddleware& setMethods(const char* methods);
CorsMiddleware& setHeaders(const char* headers);
CorsMiddleware& setAllowCredentials(bool credentials);
CorsMiddleware& setMaxAge(uint32_t seconds);
const String& getOrigin() const { return _origin; }
const String& getMethods() const { return _methods; }
const String& getHeaders() const { return _headers; }
bool getAllowCredentials() const { return _credentials; }
uint32_t getMaxAge() const { return _maxAge; }
void addCORSHeaders(PsychicResponse* response);
esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next) override;
private:
String _origin = "*";
String _methods = "*";
String _headers = "*";
bool _credentials = true;
uint32_t _maxAge = 86400;
};
#endif

View File

@@ -1,91 +1,122 @@
#include "PsychicRequest.h"
#include "http_status.h"
#include "MultipartProcessor.h"
#include "PsychicHttpServer.h"
#include "http_status.h"
PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) :
_server(server),
_req(req),
_method(HTTP_GET),
_query(""),
_body(""),
_tempObject(NULL)
PsychicRequest::PsychicRequest(PsychicHttpServer* server, httpd_req_t* req) : _server(server),
_req(req),
_endpoint(nullptr),
_method(HTTP_GET),
_uri(""),
_query(""),
_body(""),
_tempObject(nullptr)
{
//load up our client.
// load up our client.
this->_client = server->getClient(req);
//handle our session data
// handle our session data
if (req->sess_ctx != NULL)
this->_session = (SessionData *)req->sess_ctx;
else
{
this->_session = (SessionData*)req->sess_ctx;
else {
this->_session = new SessionData();
req->sess_ctx = this->_session;
}
//callback for freeing the session later
// callback for freeing the session later
req->free_ctx = this->freeSession;
//load up some data
this->_uri = String(this->_req->uri);
// load and parse our uri.
this->_setUri(this->_req->uri);
_response = new PsychicResponse(this);
}
PsychicRequest::~PsychicRequest()
{
//temorary user object
// temorary user object
if (_tempObject != NULL)
free(_tempObject);
//our web parameters
for (auto *param : _params)
delete(param);
// our web parameters
for (auto* param : _params)
delete (param);
_params.clear();
delete _response;
}
void PsychicRequest::freeSession(void *ctx)
void PsychicRequest::freeSession(void* ctx)
{
if (ctx != NULL)
{
SessionData *session = (SessionData*)ctx;
if (ctx != NULL) {
SessionData* session = (SessionData*)ctx;
delete session;
}
}
PsychicHttpServer * PsychicRequest::server() {
PsychicHttpServer* PsychicRequest::server()
{
return _server;
}
httpd_req_t * PsychicRequest::request() {
httpd_req_t* PsychicRequest::request()
{
return _req;
}
PsychicClient * PsychicRequest::client() {
PsychicClient* PsychicRequest::client()
{
return _client;
}
PsychicEndpoint* PsychicRequest::endpoint()
{
return _endpoint;
}
void PsychicRequest::setEndpoint(PsychicEndpoint* endpoint)
{
_endpoint = endpoint;
}
#ifdef PSY_ENABLE_REGEX
bool PsychicRequest::getRegexMatches(std::smatch& matches, bool use_full_uri)
{
if (_endpoint != nullptr) {
std::regex pattern(_endpoint->uri().c_str());
std::string s(this->path().c_str());
if (use_full_uri)
s = this->uri().c_str();
return std::regex_search(s, matches, pattern);
}
return false;
}
#endif
const String PsychicRequest::getFilename()
{
//parse the content-disposition header
if (this->hasHeader("Content-Disposition"))
{
// 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");
// 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)
// 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.
// finally, unknown.
ESP_LOGE(PH_TAG, "Did not get a valid filename from the upload.");
return "unknown.txt";
}
@@ -103,21 +134,19 @@ const ContentDisposition PsychicRequest::getContentDisposition()
cd.disposition = ATTACHMENT;
else if (header.indexOf("inline") == 0)
cd.disposition = INLINE;
else
else
cd.disposition = NONE;
start = header.indexOf("filename=");
if (start)
{
end = header.indexOf('"', start+10);
cd.filename = header.substring(start+10, end-1);
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);
if (start) {
end = header.indexOf('"', start + 6);
cd.name = header.substring(start + 6, end - 1);
}
return cd;
@@ -125,16 +154,23 @@ const ContentDisposition PsychicRequest::getContentDisposition()
esp_err_t PsychicRequest::loadBody()
{
esp_err_t err = ESP_OK;
if (_bodyParsed != ESP_ERR_NOT_FINISHED)
return _bodyParsed;
// quick size check.
if (contentLength() > server()->maxRequestBodySize) {
ESP_LOGE(PH_TAG, "Body size larger than maxRequestBodySize");
return _bodyParsed = ESP_ERR_INVALID_SIZE;
}
this->_body = String();
size_t remaining = this->_req->content_len;
size_t actuallyReceived = 0;
char *buf = (char *)malloc(remaining + 1);
char* buf = (char*)malloc(remaining + 1);
if (buf == NULL) {
ESP_LOGE(PH_TAG, "Failed to allocate memory for body");
return ESP_FAIL;
return _bodyParsed = ESP_FAIL;
}
while (remaining > 0) {
@@ -142,10 +178,9 @@ esp_err_t PsychicRequest::loadBody()
if (received == HTTPD_SOCK_ERR_TIMEOUT) {
continue;
}
else if (received == HTTPD_SOCK_ERR_FAIL) {
} else if (received == HTTPD_SOCK_ERR_FAIL) {
ESP_LOGE(PH_TAG, "Failed to receive data.");
err = ESP_FAIL;
_bodyParsed = ESP_FAIL;
break;
}
@@ -156,30 +191,38 @@ esp_err_t PsychicRequest::loadBody()
buf[actuallyReceived] = '\0';
this->_body = String(buf);
free(buf);
return err;
_bodyParsed = ESP_OK;
return _bodyParsed;
}
http_method PsychicRequest::method() {
http_method PsychicRequest::method()
{
return (http_method)this->_req->method;
}
const String PsychicRequest::methodStr() {
const String PsychicRequest::methodStr()
{
return String(http_method_str((http_method)this->_req->method));
}
const String PsychicRequest::path() {
const String PsychicRequest::path()
{
int index = _uri.indexOf("?");
if(index == -1)
if (index == -1)
return _uri;
else
return _uri.substring(0, index);
return _uri.substring(0, index);
}
const String& PsychicRequest::uri() {
const String& PsychicRequest::uri()
{
return this->_uri;
}
const String& PsychicRequest::query() {
const String& PsychicRequest::query()
{
return this->_query;
}
@@ -188,35 +231,36 @@ const String& PsychicRequest::query() {
// {
// }
const String PsychicRequest::header(const char *name)
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];
// 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
} else
return "";
}
bool PsychicRequest::hasHeader(const char *name)
bool PsychicRequest::hasHeader(const char* name)
{
return httpd_req_get_hdr_value_len(this->_req, name) > 0;
}
const String PsychicRequest::host() {
const String PsychicRequest::host()
{
return this->header("Host");
}
const String PsychicRequest::contentType() {
const String PsychicRequest::contentType()
{
return header("Content-Type");
}
size_t PsychicRequest::contentLength() {
size_t PsychicRequest::contentLength()
{
return this->_req->content_len;
}
@@ -232,71 +276,111 @@ bool PsychicRequest::isMultipart()
return (this->contentType().indexOf("multipart/form-data") >= 0);
}
esp_err_t PsychicRequest::redirect(const char *url)
bool PsychicRequest::hasCookie(const char* key, size_t* size)
{
PsychicResponse response(this);
response.setCode(301);
response.addHeader("Location", url);
char buffer;
return response.send();
// this keeps our size for the user.
if (size != nullptr) {
*size = 1;
return getCookie(key, &buffer, size) != ESP_ERR_NOT_FOUND;
}
// this just checks that it exists.
else {
size_t mysize = 1;
return getCookie(key, &buffer, &mysize) != ESP_ERR_NOT_FOUND;
}
}
bool PsychicRequest::hasCookie(const char *key)
esp_err_t PsychicRequest::getCookie(const char* key, char* buffer, size_t* size)
{
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;
return httpd_req_get_cookie_val(this->_req, key, buffer, size);
}
const String PsychicRequest::getCookie(const char *key)
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);
String cookie = "";
//did we get anything?
// how big is our cookie?
size_t size;
if (!hasCookie("counter", &size))
return cookie;
// allocate cookie buffer... keep it on the stack
char buf[size];
// load it up.
esp_err_t err = getCookie(key, buf, &size);
if (err == ESP_OK)
return String(cookie);
else
return "";
cookie.concat(buf);
return cookie;
}
void PsychicRequest::replaceResponse(PsychicResponse* response)
{
delete _response;
_response = response;
}
void PsychicRequest::addResponseHeader(const char* key, const char* value)
{
_response->addHeader(key, value);
}
std::list<HTTPHeader>& PsychicRequest::getResponseHeaders()
{
return _response->headers();
}
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);
if (_paramsParsed != ESP_ERR_NOT_FINISHED)
return;
//parse them.
_addParams(_query, false);
// convenience shortcut to allow calling loadParams()
if (_bodyParsed == ESP_ERR_NOT_FINISHED)
loadBody();
// various form data as parameters
if (this->method() == HTTP_POST) {
if (this->contentType().startsWith("application/x-www-form-urlencoded"))
_addParams(_body, true);
if (this->isMultipart()) {
MultipartProcessor mpp(this);
_paramsParsed = mpp.process(_body.c_str());
return;
}
}
//did we get form data as body?
if (this->method() == HTTP_POST && this->contentType().startsWith("application/x-www-form-urlencoded"))
{
_addParams(_body, true);
_paramsParsed = ESP_OK;
}
void PsychicRequest::_setUri(const char* uri)
{
// save it
_uri = String(uri);
// look for our query separator
int index = _uri.indexOf('?', 0);
if (index) {
// parse them.
_query = _uri.substring(index + 1);
_addParams(_query, false);
}
}
void PsychicRequest::_addParams(const String& params, bool post){
void PsychicRequest::_addParams(const String& params, bool post)
{
size_t start = 0;
while (start < params.length()){
while (start < params.length()) {
int end = params.indexOf('&', start);
if (end < 0) end = params.length();
if (end < 0)
end = params.length();
int equal = params.indexOf('=', start);
if (equal < 0 || equal > end) equal = end;
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);
@@ -304,7 +388,7 @@ void PsychicRequest::_addParams(const String& params, bool post){
}
}
PsychicWebParameter * PsychicRequest::addParam(const String &name, const String &value, bool decode, bool post)
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));
@@ -312,40 +396,37 @@ PsychicWebParameter * PsychicRequest::addParam(const String &name, const String
return addParam(new PsychicWebParameter(name, value, post));
}
PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) {
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;
}
int PsychicRequest::params()
{
return _params.size();
}
bool PsychicRequest::hasParam(const char *key)
bool PsychicRequest::hasParam(const char* key)
{
return getParam(key) != NULL;
}
PsychicWebParameter * PsychicRequest::getParam(const char *key)
bool PsychicRequest::hasParam(const char* key, bool isPost, bool isFile)
{
for (auto *param : _params)
return getParam(key, isPost, isFile) != NULL;
}
PsychicWebParameter* PsychicRequest::getParam(const char* key)
{
for (auto* param : _params)
if (param->name().equals(key))
return param;
return NULL;
}
PsychicWebParameter * PsychicRequest::getParam(int index)
PsychicWebParameter* PsychicRequest::getParam(const char* key, bool isPost, bool isFile)
{
if (_params.size() > index){
std::list<PsychicWebParameter*>::iterator it = _params.begin();
for(int i=0; i<index; i++){
++it;
}
return *it;
}
for (auto* param : _params)
if (param->name().equals(key) && isPost == param->isPost() && isFile == param->isFile())
return param;
return NULL;
}
@@ -368,7 +449,8 @@ 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){
static const String md5str(const String& in)
{
MD5Builder md5 = MD5Builder();
md5.begin();
md5.add(in);
@@ -376,28 +458,27 @@ static const String md5str(const String &in){
return md5.toString();
}
bool PsychicRequest::authenticate(const char * username, const char * password)
bool PsychicRequest::authenticate(const char* username, const char* password)
{
if(hasHeader("Authorization"))
{
if (hasHeader("Authorization")) {
String authReq = header("Authorization");
if(authReq.startsWith("Basic")){
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){
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){
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)) {
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) {
authReq = "";
delete[] toencode;
delete[] encoded;
@@ -405,64 +486,61 @@ bool PsychicRequest::authenticate(const char * username, const char * password)
}
delete[] toencode;
delete[] encoded;
}
else if(authReq.startsWith(F("Digest")))
{
} else if (authReq.startsWith(F("Digest"))) {
authReq = authReq.substring(7);
String _username = _extractParam(authReq,F("username=\""),'\"');
if(!_username.length() || _username != String(username)) {
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=\""),'\"');
String _realm = _extractParam(authReq, F("realm=\""), '\"');
String _nonce = _extractParam(authReq, F("nonce=\""), '\"');
String _url = _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())) {
if ((!_realm.length()) || (!_nonce.length()) || (!_url.length()) || (!_resp.length()) || (!_opaque.length())) {
authReq = "";
return false;
}
if((_opaque != this->getSessionKey("opaque")) || (_nonce != this->getSessionKey("nonce")) || (_realm != this->getSessionKey("realm")))
{
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) {
String _nc, _cnonce;
if (authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) {
_nc = _extractParam(authReq, F("nc="), ',');
_cnonce = _extractParam(authReq, F("cnonce=\""),'\"');
_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());
// 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);
if (_method == HTTP_GET) {
_H2 = md5str(String(F("GET:")) + _url);
} else if (_method == HTTP_POST) {
_H2 = md5str(String(F("POST:")) + _url);
} else if (_method == HTTP_PUT) {
_H2 = md5str(String(F("PUT:")) + _url);
} else if (_method == HTTP_DELETE) {
_H2 = md5str(String(F("DELETE:")) + _url);
} else {
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
_H2 = md5str(String(F("GET:")) + _url);
}
//ESP_LOGD(PH_TAG, "The Proper response=%s", _responsecheck.c_str());
if(_resp == _responsecheck){
// 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;
}
@@ -477,23 +555,23 @@ const String PsychicRequest::_extractParam(const String& authReq, const String&
int _begin = authReq.indexOf(param);
if (_begin == -1)
return "";
return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length()));
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
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());
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, ""))
// what is thy realm, sire?
if (!strcmp(realm, ""))
this->setSessionKey("realm", "Login Required");
else
this->setSessionKey("realm", realm);
@@ -501,17 +579,14 @@ esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char*
PsychicResponse response(this);
String authStr;
//what kind of auth?
if(mode == BASIC_AUTH)
{
// 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
} else {
// only make new ones if we havent sent them yet
if (this->getSessionKey("nonce").isEmpty())
this->setSessionKey("nonce", _getRandomHexString());
this->setSessionKey("nonce", _getRandomHexString());
if (this->getSessionKey("opaque").isEmpty())
this->setSessionKey("opaque", _getRandomHexString());
@@ -521,39 +596,6 @@ esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char*
response.setCode(401);
response.setContentType("text/html");
response.setContent(authStr.c_str());
response.setContent(authFailMsg);
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();
}

View File

@@ -1,38 +1,55 @@
#ifndef PsychicRequest_h
#define PsychicRequest_h
#include "PsychicCore.h"
#include "PsychicHttpServer.h"
#include "PsychicClient.h"
#include "PsychicCore.h"
#include "PsychicEndpoint.h"
#include "PsychicHttpServer.h"
#include "PsychicWebParameter.h"
#include "PsychicResponse.h"
#ifdef PSY_ENABLE_REGEX
#include <regex>
#endif
typedef std::map<String, String> SessionData;
enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA};
struct ContentDisposition {
Disposition disposition;
String filename;
String name;
enum Disposition {
NONE,
INLINE,
ATTACHMENT,
FORM_DATA
};
class PsychicRequest {
friend PsychicHttpServer;
struct ContentDisposition {
Disposition disposition;
String filename;
String name;
};
class PsychicRequest
{
friend PsychicHttpServer;
friend PsychicResponse;
protected:
PsychicHttpServer *_server;
httpd_req_t *_req;
SessionData *_session;
PsychicClient *_client;
PsychicHttpServer* _server;
httpd_req_t* _req;
SessionData* _session;
PsychicClient* _client;
PsychicEndpoint* _endpoint;
http_method _method;
String _uri;
String _query;
String _body;
esp_err_t _bodyParsed = ESP_ERR_NOT_FINISHED;
esp_err_t _paramsParsed = ESP_ERR_NOT_FINISHED;
std::list<PsychicWebParameter*> _params;
PsychicResponse* _response;
void _setUri(const char* uri);
void _addParams(const String& params, bool post);
void _parseGETParams();
void _parsePOSTParams();
@@ -41,28 +58,59 @@ class PsychicRequest {
const String _getRandomHexString();
public:
PsychicRequest(PsychicHttpServer *server, httpd_req_t *req);
PsychicRequest(PsychicHttpServer* server, httpd_req_t* req);
virtual ~PsychicRequest();
void *_tempObject;
void* _tempObject;
PsychicHttpServer * server();
httpd_req_t * request();
virtual PsychicClient * client();
PsychicHttpServer* server();
httpd_req_t* request();
virtual PsychicClient* client();
PsychicEndpoint* endpoint();
void setEndpoint(PsychicEndpoint* endpoint);
#ifdef PSY_ENABLE_REGEX
bool getRegexMatches(std::smatch& matches, bool use_full_uri = false);
#endif
bool isMultipart();
esp_err_t loadBody();
const String header(const char *name);
bool hasHeader(const char *name);
const String header(const char* name);
bool hasHeader(const char* name);
static void freeSession(void *ctx);
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);
bool hasCookie(const char* key, size_t* size = nullptr);
PsychicResponse* response() { return _response; }
void replaceResponse(PsychicResponse* response);
void addResponseHeader(const char* key, const char* value);
std::list<HTTPHeader>& getResponseHeaders();
/**
* @brief Get the value string of a cookie value from the "Cookie" request headers by cookie name.
*
* @param[in] key The cookie name to be searched in the request
* @param[out] buffer Pointer to the buffer into which the value of cookie will be copied if the cookie is found
* @param[inout] size Pointer to size of the user buffer "val". This variable will contain cookie length if
* ESP_OK is returned and required buffer length in case ESP_ERR_HTTPD_RESULT_TRUNC is returned.
*
* @return
* - ESP_OK : Key is found in the cookie string and copied to buffer. The value is null-terminated.
* - ESP_ERR_NOT_FOUND : Key not found
* - ESP_ERR_INVALID_ARG : Null arguments
* - ESP_ERR_HTTPD_RESULT_TRUNC : Value string truncated
* - ESP_ERR_NO_MEM : Memory allocation failure
*/
esp_err_t getCookie(const char* key, char* buffer, size_t* size);
// convenience / lazy function for getting cookies.
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")
@@ -74,27 +122,23 @@ class PsychicRequest {
size_t contentLength(); // returns the Content-Length header value
const String& body(); // returns the body of the request
const ContentDisposition getContentDisposition();
const char* version() { return "HTTP/1.1"; }
const String& queryString() { return query(); } //compatability function. same as query()
const String& url() { return uri(); } //compatability function. same as uri()
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);
int params();
bool hasParam(const char *key);
PsychicWebParameter * getParam(const char *name);
PsychicWebParameter * getParam(int index);
PsychicWebParameter* addParam(PsychicWebParameter* param);
PsychicWebParameter* addParam(const String& name, const String& value, bool decode = true, bool post = false);
bool hasParam(const char* key);
bool hasParam(const char* key, bool isPost, bool isFile = false);
PsychicWebParameter* getParam(const char* name);
PsychicWebParameter* getParam(const char* name, bool isPost, bool isFile = false);
const String getFilename();
bool authenticate(const char * username, const char * password);
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

View File

@@ -2,52 +2,49 @@
#include "PsychicRequest.h"
#include <http_status.h>
PsychicResponse::PsychicResponse(PsychicRequest *request) :
_request(request),
_code(200),
_status(""),
_contentLength(0),
_body("")
PsychicResponse::PsychicResponse(PsychicRequest* request) : _request(request),
_code(200),
_status(""),
_contentType(emptyString),
_contentLength(0),
_body("")
{
// get our global headers out of the way
for (auto& header : DefaultHeaders::Instance().getHeaders())
addHeader(header.field.c_str(), header.value.c_str());
}
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)
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);
// erase any existing ones.
for (auto itr = _headers.begin(); itr != _headers.end();) {
if (itr->field.equalsIgnoreCase(field))
itr = _headers.erase(itr);
else
itr++;
}
strlcpy(header.field, field, strlen(field)+1);
strlcpy(header.value, value, strlen(value)+1);
_headers.push_back(header);
// now add it.
_headers.push_back({field, value});
}
void PsychicResponse::setCookie(const char *name, const char *value, unsigned long secondsFromNow, const char *extras)
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 current time isn't modern, default to using max age
if (now < 1700000000)
output += "; Max-Age=" + String(secondsFromNow);
//otherwise, set an expiration date
else
{
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
@@ -57,11 +54,11 @@ void PsychicResponse::setCookie(const char *name, const char *value, unsigned lo
output += "; Expires=" + String(expires);
}
//did we get any extras?
// did we get any extras?
if (strlen(extras))
output += "; " + String(extras);
//okay, add it in.
// okay, add it in.
addHeader("Set-Cookie", output.c_str());
}
@@ -70,24 +67,24 @@ void PsychicResponse::setCode(int code)
_code = code;
}
void PsychicResponse::setContentType(const char *contentType)
void PsychicResponse::setContentType(const char* contentType)
{
httpd_resp_set_type(_request->request(), contentType);
_contentType = contentType;
}
void PsychicResponse::setContent(const char *content)
void PsychicResponse::setContent(const char* content)
{
_body = content;
setContentLength(strlen(content));
}
void PsychicResponse::setContent(const uint8_t *content, size_t len)
void PsychicResponse::setContent(const uint8_t* content, size_t len)
{
_body = (char *)content;
_body = (char*)content;
setContentLength(len);
}
const char * PsychicResponse::getContent()
const char* PsychicResponse::getContent()
{
return _body;
}
@@ -99,17 +96,20 @@ size_t PsychicResponse::getContentLength()
esp_err_t PsychicResponse::send()
{
//esp-idf makes you set the whole status.
// 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
// set the content type
httpd_resp_set_type(_request->request(), _contentType.c_str());
// our headers too
this->sendHeaders();
//now send it off
// now send it off
esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength());
//did something happen?
// did something happen?
if (err != ESP_OK)
ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err));
@@ -118,38 +118,21 @@ esp_err_t PsychicResponse::send()
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();
// now do our individual headers
for (auto& header : _headers)
httpd_resp_set_hdr(this->_request->request(), header.field.c_str(), header.value.c_str());
}
esp_err_t PsychicResponse::sendChunk(uint8_t *chunk, size_t chunksize)
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_LOGD(PH_TAG, "Sending chunk: %d", chunksize);
esp_err_t err = httpd_resp_send_chunk(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;
@@ -159,4 +142,63 @@ esp_err_t PsychicResponse::finishChunking()
{
/* Respond with an empty chunk to signal HTTP response completion */
return httpd_resp_send_chunk(this->_request->request(), NULL, 0);
}
}
esp_err_t PsychicResponse::redirect(const char* url)
{
if (!_code)
setCode(301);
addHeader("Location", url);
return send();
}
esp_err_t PsychicResponse::send(int code)
{
setCode(code);
return send();
}
esp_err_t PsychicResponse::send(const char* content)
{
if (!_code)
setCode(200);
if (_contentType.isEmpty())
setContentType("text/html");
setContent(content);
return send();
}
esp_err_t PsychicResponse::send(const char* contentType, const char* content)
{
if (!_code)
setCode(200);
setContentType(contentType);
setContent(content);
return send();
}
esp_err_t PsychicResponse::send(int code, const char* contentType, const char* content)
{
setCode(code);
setContentType(contentType);
setContent(content);
return send();
}
esp_err_t PsychicResponse::send(int code, const char* contentType, const uint8_t* content, size_t len)
{
setCode(code);
setContentType(contentType);
setContent(content, len);
return send();
}
esp_err_t PsychicResponse::error(httpd_err_code_t code, const char* message)
{
return httpd_resp_send_err(_request->_req, code, message);
}
httpd_req_t* PsychicResponse::request()
{
return _request->_req;
}

View File

@@ -9,38 +9,101 @@ class PsychicRequest;
class PsychicResponse
{
protected:
PsychicRequest *_request;
PsychicRequest* _request;
int _code;
char _status[60];
std::list<HTTPHeader> _headers;
String _contentType;
int64_t _contentLength;
const char * _body;
const char* _body;
public:
PsychicResponse(PsychicRequest *request);
PsychicResponse(PsychicRequest* request);
virtual ~PsychicResponse();
void setCode(int code);
const char* version() { return "HTTP/1.1"; }
void setCode(int code);
int getCode() { return _code; }
void setContentType(const char* contentType);
String& getContentType() { return _contentType; }
void setContentType(const char *contentType);
void setContentLength(int64_t contentLength) { _contentLength = contentLength; }
int64_t getContentLength(int64_t contentLength) { return _contentLength; }
void addHeader(const char *field, const char *value);
void addHeader(const char* field, const char* value);
std::list<HTTPHeader>& headers() { return _headers; }
void setCookie(const char *key, const char *value, unsigned long max_age = 60*60*24*30, const char *extras = "");
void setCookie(const char* key, const char* value, unsigned long max_age = 60 * 60 * 24 * 30, const char* extras = "");
void setContent(const char *content);
void setContent(const uint8_t *content, size_t len);
void setContent(const char* content);
void setContent(const uint8_t* content, size_t len);
const char * getContent();
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 sendChunk(uint8_t* chunk, size_t chunksize);
esp_err_t finishChunking();
esp_err_t redirect(const char* url);
esp_err_t send(int code);
esp_err_t send(const char* content);
esp_err_t send(const char* contentType, const char* content);
esp_err_t send(int code, const char* contentType, const char* content);
esp_err_t send(int code, const char* contentType, const uint8_t* content, size_t len);
esp_err_t error(httpd_err_code_t code, const char* message);
httpd_req_t* request();
};
class PsychicResponseDelegate
{
protected:
PsychicResponse* _response;
public:
PsychicResponseDelegate(PsychicResponse* response) : _response(response) {}
virtual ~PsychicResponseDelegate() {}
const char* version() { return _response->version(); }
void setCode(int code) { _response->setCode(code); }
void setContentType(const char* contentType) { _response->setContentType(contentType); }
String& getContentType() { return _response->getContentType(); }
void setContentLength(int64_t contentLength) { _response->setContentLength(contentLength); }
int64_t getContentLength(int64_t contentLength) { return _response->getContentLength(); }
void addHeader(const char* field, const char* value) { _response->addHeader(field, value); }
void setCookie(const char* key, const char* value, unsigned long max_age = 60 * 60 * 24 * 30, const char* extras = "") { _response->setCookie(key, value, max_age, extras); }
void setContent(const char* content) { _response->setContent(content); }
void setContent(const uint8_t* content, size_t len) { _response->setContent(content, len); }
const char* getContent() { return _response->getContent(); }
size_t getContentLength() { return _response->getContentLength(); }
esp_err_t send() { return _response->send(); }
void sendHeaders() { _response->sendHeaders(); }
esp_err_t sendChunk(uint8_t* chunk, size_t chunksize) { return _response->sendChunk(chunk, chunksize); }
esp_err_t finishChunking() { return _response->finishChunking(); }
esp_err_t redirect(const char* url) { return _response->redirect(url); }
esp_err_t send(int code) { return _response->send(code); }
esp_err_t send(const char* content) { return _response->send(content); }
esp_err_t send(const char* contentType, const char* content) { return _response->send(contentType, content); }
esp_err_t send(int code, const char* contentType, const char* content) { return _response->send(code, contentType, content); }
esp_err_t send(int code, const char* contentType, const uint8_t* content, size_t len) { return _response->send(code, contentType, content, len); }
esp_err_t error(httpd_err_code_t code, const char* message) { return _response->error(code, message); }
httpd_req_t* request() { return _response->request(); }
};
#endif // PsychicResponse_h

View File

@@ -0,0 +1,54 @@
#include "PsychicRewrite.h"
#include "PsychicRequest.h"
PsychicRewrite::PsychicRewrite(const char* from, const char* to):
_fromPath(from),
_toUri(to),
_toPath(String()),
_toParams(String()),
_filter(nullptr)
{
int index = _toUri.indexOf('?');
if (index > 0) {
_toParams = _toUri.substring(index + 1);
_toPath = _toUri.substring(0, index);
}
else
_toPath = _toUri;
}
PsychicRewrite::~PsychicRewrite()
{
}
PsychicRewrite* PsychicRewrite::setFilter(PsychicRequestFilterFunction fn)
{
_filter = fn; return this;
}
bool PsychicRewrite::filter(PsychicRequest *request) const
{
return _filter == nullptr || _filter(request);
}
const String& PsychicRewrite::from(void) const
{
return _fromPath;
}
const String& PsychicRewrite::toUrl(void) const
{
return _toUri;
}
const String& PsychicRewrite::params(void) const
{
return _toParams;
}
bool PsychicRewrite::match(PsychicRequest *request)
{
if (!filter(request))
return false;
return _fromPath == request->path();
}

View File

@@ -0,0 +1,30 @@
#ifndef PsychicRewrite_h
#define PsychicRewrite_h
#include "PsychicCore.h"
/*
* REWRITE :: One instance can be handle any Request (done by the Server)
* */
class PsychicRewrite {
protected:
String _fromPath;
String _toUri;
String _toPath;
String _toParams;
PsychicRequestFilterFunction _filter;
public:
PsychicRewrite(const char* from, const char* to);
virtual ~PsychicRewrite();
PsychicRewrite* setFilter(PsychicRequestFilterFunction fn);
bool filter(PsychicRequest *request) const;
const String& from(void) const;
const String& toUrl(void) const;
const String& params(void) const;
virtual bool match(PsychicRequest *request);
};
#endif

Some files were not shown because too many files have changed in this diff Show More