Merge pull request #1533 from proddy/https_36

keeping branch update. still memory and performance issues
This commit is contained in:
Proddy
2024-01-03 11:34:10 +01:00
committed by GitHub
49 changed files with 2107 additions and 1706 deletions

View File

@@ -52,8 +52,8 @@
"devDependencies": { "devDependencies": {
"@preact/compat": "^17.1.2", "@preact/compat": "^17.1.2",
"@preact/preset-vite": "^2.7.0", "@preact/preset-vite": "^2.7.0",
"@typescript-eslint/eslint-plugin": "^6.16.0", "@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.16.0", "@typescript-eslint/parser": "^6.17.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",

View File

@@ -11,7 +11,8 @@ const bytesPerLine = 20;
var totalSize = 0; var totalSize = 0;
const generateWWWClass = () => const generateWWWClass = () =>
`typedef std::function<void(const char *, const char * contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler; `typedef std::function<void(const String &, const String & contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
// Total size is ${totalSize} bytes
class WWWData { class WWWData {
${indent}public: ${indent}public:

View File

@@ -1,3 +1,4 @@
/*
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import Sockette from 'sockette'; import Sockette from 'sockette';
@@ -89,3 +90,4 @@ export const useWs = <D>(wsUrl: string, wsThrottle = 100) => {
return { connected, data, updateData } as const; return { connected, data, updateData } as const;
}; };
*/

View File

@@ -1,4 +1,4 @@
import { defineConfig } from 'vite'; import { defineConfig, splitVendorChunkPlugin } from 'vite';
import viteTsconfigPaths from 'vite-tsconfig-paths'; import viteTsconfigPaths from 'vite-tsconfig-paths';
import preact from '@preact/preset-vite'; import preact from '@preact/preset-vite';
import viteImagemin from 'vite-plugin-imagemin'; import viteImagemin from 'vite-plugin-imagemin';
@@ -44,6 +44,7 @@ export default defineConfig(({ command, mode }) => {
plugins: [ plugins: [
preact(), preact(),
viteTsconfigPaths(), viteTsconfigPaths(),
splitVendorChunkPlugin(),
{ {
...viteImagemin({ ...viteImagemin({
verbose: false, verbose: false,
@@ -112,6 +113,20 @@ export default defineConfig(({ command, mode }) => {
nameCache: null, nameCache: null,
safari10: false, safari10: false,
toplevel: false toplevel: false
},
rollupOptions: {
output: {
manualChunks(id: string) {
if (id.includes('node_modules')) {
// creating a chunk to react routes deps. Reducing the vendor chunk size
if (id.includes('react-router-dom') || id.includes('@remix-run') || id.includes('react-router')) {
return '@react-router';
}
return 'vendor';
}
}
}
} }
} }
}; };

View File

@@ -1689,15 +1689,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/eslint-plugin@npm:^6.16.0": "@typescript-eslint/eslint-plugin@npm:^6.17.0":
version: 6.16.0 version: 6.17.0
resolution: "@typescript-eslint/eslint-plugin@npm:6.16.0" resolution: "@typescript-eslint/eslint-plugin@npm:6.17.0"
dependencies: dependencies:
"@eslint-community/regexpp": "npm:^4.5.1" "@eslint-community/regexpp": "npm:^4.5.1"
"@typescript-eslint/scope-manager": "npm:6.16.0" "@typescript-eslint/scope-manager": "npm:6.17.0"
"@typescript-eslint/type-utils": "npm:6.16.0" "@typescript-eslint/type-utils": "npm:6.17.0"
"@typescript-eslint/utils": "npm:6.16.0" "@typescript-eslint/utils": "npm:6.17.0"
"@typescript-eslint/visitor-keys": "npm:6.16.0" "@typescript-eslint/visitor-keys": "npm:6.17.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
graphemer: "npm:^1.4.0" graphemer: "npm:^1.4.0"
ignore: "npm:^5.2.4" ignore: "npm:^5.2.4"
@@ -1710,44 +1710,44 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 4bedce948ac3c20492a59813ee5d4f1f2306310857864dfaac2736f6c38e18785002c36844fd64c9fbdf3059fc390b29412be105fd7a118177f1eeeb1eb533f7 checksum: f2a5774e9cc03e491a5a488501e5622c7eebd766f5a4fc2c30642864a3b89b0807946bde33a678f326ba7032f3f6a51aa0bf9c2d10adc823804fc9fb47db55a6
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/parser@npm:^6.16.0": "@typescript-eslint/parser@npm:^6.17.0":
version: 6.16.0 version: 6.17.0
resolution: "@typescript-eslint/parser@npm:6.16.0" resolution: "@typescript-eslint/parser@npm:6.17.0"
dependencies: dependencies:
"@typescript-eslint/scope-manager": "npm:6.16.0" "@typescript-eslint/scope-manager": "npm:6.17.0"
"@typescript-eslint/types": "npm:6.16.0" "@typescript-eslint/types": "npm:6.17.0"
"@typescript-eslint/typescript-estree": "npm:6.16.0" "@typescript-eslint/typescript-estree": "npm:6.17.0"
"@typescript-eslint/visitor-keys": "npm:6.16.0" "@typescript-eslint/visitor-keys": "npm:6.17.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 eslint: ^7.0.0 || ^8.0.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 3d941ce345dc2ce29957e2110957662873d514b094b8939923c3281d858c11cd1f9058db862644afe14f68d087770f39a0a1f9e523a2013ed5d2fdf3421b34d0 checksum: 2ed0ed4a5b30e953430ce3279df3655af09fa1caed2abf11804d239717daefc32a22864f6620ef57bb9c684c74a99a13241384fea5096e961385e3678fc2e920
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/scope-manager@npm:6.16.0": "@typescript-eslint/scope-manager@npm:6.17.0":
version: 6.16.0 version: 6.17.0
resolution: "@typescript-eslint/scope-manager@npm:6.16.0" resolution: "@typescript-eslint/scope-manager@npm:6.17.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:6.16.0" "@typescript-eslint/types": "npm:6.17.0"
"@typescript-eslint/visitor-keys": "npm:6.16.0" "@typescript-eslint/visitor-keys": "npm:6.17.0"
checksum: 3360aae4b85f5c31d20ad48d771cc09a6f8f6b1811b00d94f06e55b5a09c610ac75631b1c4edecb3bec682d41351b87e7d14d42bee84aa032064d0e13463035b checksum: fe09c628553c9336e6a36d32c1d34e78ebd20aa02130a6bf535329621ba5a98aaac171f607bc6e4d17b3478c42e7de6476376636897ce3f227c754eb99acd07e
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/type-utils@npm:6.16.0": "@typescript-eslint/type-utils@npm:6.17.0":
version: 6.16.0 version: 6.17.0
resolution: "@typescript-eslint/type-utils@npm:6.16.0" resolution: "@typescript-eslint/type-utils@npm:6.17.0"
dependencies: dependencies:
"@typescript-eslint/typescript-estree": "npm:6.16.0" "@typescript-eslint/typescript-estree": "npm:6.17.0"
"@typescript-eslint/utils": "npm:6.16.0" "@typescript-eslint/utils": "npm:6.17.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.0.1" ts-api-utils: "npm:^1.0.1"
peerDependencies: peerDependencies:
@@ -1755,23 +1755,23 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 5964b87a87252bed278a248eb568902babd7c34defd3af8c3df371926d96aec716f33f1dc14bde170e93f73ed1b0af6e591e647853d0f33f378e2c7b3b73fc5b checksum: dc7938429193acfda61b7282197ec046039e2c4da41cdcddf4daaf300d10229e4e23bb0fcf0503b19c0b99a874849c8a9f5bb35ce106260f56a14106d2b41d8c
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/types@npm:6.16.0": "@typescript-eslint/types@npm:6.17.0":
version: 6.16.0 version: 6.17.0
resolution: "@typescript-eslint/types@npm:6.16.0" resolution: "@typescript-eslint/types@npm:6.17.0"
checksum: 236ca318c2440c95068e5d4d147e2bfed62447775e18695e21c8ca04a341a74d01c37ed2b417629b7bf2fb91ad4fd5e2a6570215d16fc24dd1507ce6973b4e22 checksum: 87ab1b5a3270ab34b917c22a2fb90a9ad7d9f3b19d73a337bc9efbe65f924da13482c97e8ccbe3bd3d081aa96039eeff50de41c1da2a2128066429b931cdb21d
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/typescript-estree@npm:6.16.0": "@typescript-eslint/typescript-estree@npm:6.17.0":
version: 6.16.0 version: 6.17.0
resolution: "@typescript-eslint/typescript-estree@npm:6.16.0" resolution: "@typescript-eslint/typescript-estree@npm:6.17.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:6.16.0" "@typescript-eslint/types": "npm:6.17.0"
"@typescript-eslint/visitor-keys": "npm:6.16.0" "@typescript-eslint/visitor-keys": "npm:6.17.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
globby: "npm:^11.1.0" globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3" is-glob: "npm:^4.0.3"
@@ -1781,34 +1781,34 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 8e1ef03ecabaf3791b11240a51217836dbb74850e458258db77ac5eab5508cd9c63fb671924993d1e7654718c0c857c3550d51ecba0845fe489d143bb858e1b1 checksum: 1671b0d2f2fdf07074fb1e2524d61935cec173bd8db6e482cc5b2dcc77aed3ffa831396736ffa0ee2fdbddd8585ae9ca8d6c97bcaea1385b23907a1ec0508f83
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/utils@npm:6.16.0": "@typescript-eslint/utils@npm:6.17.0":
version: 6.16.0 version: 6.17.0
resolution: "@typescript-eslint/utils@npm:6.16.0" resolution: "@typescript-eslint/utils@npm:6.17.0"
dependencies: dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0" "@eslint-community/eslint-utils": "npm:^4.4.0"
"@types/json-schema": "npm:^7.0.12" "@types/json-schema": "npm:^7.0.12"
"@types/semver": "npm:^7.5.0" "@types/semver": "npm:^7.5.0"
"@typescript-eslint/scope-manager": "npm:6.16.0" "@typescript-eslint/scope-manager": "npm:6.17.0"
"@typescript-eslint/types": "npm:6.16.0" "@typescript-eslint/types": "npm:6.17.0"
"@typescript-eslint/typescript-estree": "npm:6.16.0" "@typescript-eslint/typescript-estree": "npm:6.17.0"
semver: "npm:^7.5.4" semver: "npm:^7.5.4"
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 eslint: ^7.0.0 || ^8.0.0
checksum: 84dd02f7c8e47fae699cc222da5cbea08b28c6e1cc7827860430bc86c2a17ee3f86e198a4356902b95930f85785aa662266ea9c476f69bf80c6a5f648e55f9f4 checksum: 37c63afcf66124bf84808699997953b8c84a378aa2c490a771b611d82cdac8499c58fac8eeb8378528e97660b59563d99297bfec4b982cd68760b0ffe54aa714
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/visitor-keys@npm:6.16.0": "@typescript-eslint/visitor-keys@npm:6.17.0":
version: 6.16.0 version: 6.17.0
resolution: "@typescript-eslint/visitor-keys@npm:6.16.0" resolution: "@typescript-eslint/visitor-keys@npm:6.17.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:6.16.0" "@typescript-eslint/types": "npm:6.17.0"
eslint-visitor-keys: "npm:^3.4.1" eslint-visitor-keys: "npm:^3.4.1"
checksum: 19e559f14ea0092585a374b8c5f1aca9b6b271fc23909d9857de9cf71a1e1d3abc0afd237e9c02d7a5fbdfe8e3be7853cf9fedf40a6f16bac3495cb7f4e67982 checksum: a2aed0e1437fdab8858ab9c7c8e355f8b72a5fa4b0adc54f28b8a2bbc29d4bb93214968ee940f83d013d0a4b83d00cd4eeeb05fb4c2c7d0ead324c6793f7d6d4
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1838,8 +1838,8 @@ __metadata:
"@types/react": "npm:^18.2.46" "@types/react": "npm:^18.2.46"
"@types/react-dom": "npm:^18.2.18" "@types/react-dom": "npm:^18.2.18"
"@types/react-router-dom": "npm:^5.3.3" "@types/react-router-dom": "npm:^5.3.3"
"@typescript-eslint/eslint-plugin": "npm:^6.16.0" "@typescript-eslint/eslint-plugin": "npm:^6.17.0"
"@typescript-eslint/parser": "npm:^6.16.0" "@typescript-eslint/parser": "npm:^6.17.0"
alova: "npm:^2.16.2" alova: "npm:^2.16.2"
async-validator: "npm:^4.2.5" async-validator: "npm:^4.2.5"
concurrently: "npm:^8.2.2" concurrently: "npm:^8.2.2"

View File

@@ -1,51 +1,55 @@
#ifndef ChunkPrinter_h #ifndef ChunkPrinter_h
#define ChunkPrinter_h #define ChunkPrinter_h
#include "PsychicCore.h" // #include "PsychicCore.h"
#include "PsychicResponse.h" #include "PsychicResponse.h"
#include <Print.h> #include <Print.h>
class ChunkPrinter : public Print { class ChunkPrinter : public Print
{
private: private:
PsychicResponse * _response; PsychicResponse *_response;
uint8_t * _buffer; uint8_t *_buffer;
size_t _length; size_t _length;
size_t _pos; size_t _pos;
public: public:
ChunkPrinter(PsychicResponse * response, uint8_t * buffer, size_t len) ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) :
: _response(response) _response(response),
, _buffer(buffer) _buffer(buffer),
, _length(len) _length(len),
, _pos(0) { _pos(0)
{}
virtual ~ChunkPrinter() {}
size_t write(uint8_t c)
{
esp_err_t err;
_buffer[_pos] = c;
_pos++;
//if we're full, send a chunk
if (_pos == _length)
{
_pos = 0;
err = _response->sendChunk(_buffer, _length);
if (err != ESP_OK)
return 0;
}
return 1;
} }
virtual ~ChunkPrinter() { virtual void flush() override
} {
if (_pos)
size_t write(uint8_t c) { {
esp_err_t err; _response->sendChunk(_buffer, _pos);
_pos = 0;
_buffer[_pos] = c; }
_pos++;
//if we're full, send a chunk
if (_pos == _length) {
_pos = 0;
err = _response->sendChunk(_buffer, _length);
if (err != ESP_OK)
return 0;
}
return 1;
}
virtual void flush() override {
if (_pos) {
_response->sendChunk(_buffer, _pos);
_pos = 0;
}
} }
}; };

View File

@@ -1,67 +1,72 @@
#include "PsychicClient.h" #include "PsychicClient.h"
#include "PsychicHttpServer.h"
#include <lwip/sockets.h>
PsychicClient::PsychicClient(httpd_handle_t server, int socket) PsychicClient::PsychicClient(httpd_handle_t server, int socket) :
: _server(server) _server(server),
, _socket(socket) _socket(socket),
, _friend(NULL) _friend(NULL),
, isNew(false) { isNew(false)
} {}
PsychicClient::~PsychicClient() { PsychicClient::~PsychicClient() {
} }
httpd_handle_t PsychicClient::server() { httpd_handle_t PsychicClient::server() {
return _server; return _server;
} }
int PsychicClient::socket() { int PsychicClient::socket() {
return _socket; return _socket;
} }
// I'm not sure this is entirely safe to call. I was having issues with race conditions when highly loaded using this. // I'm not sure this is entirely safe to call. I was having issues with race conditions when highly loaded using this.
esp_err_t PsychicClient::close() { esp_err_t PsychicClient::close()
esp_err_t err = httpd_sess_trigger_close(_server, _socket); {
//PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. esp_err_t err = httpd_sess_trigger_close(_server, _socket);
//PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list.
return err; return err;
} }
IPAddress PsychicClient::localIP() { IPAddress PsychicClient::localIP()
IPAddress address(0, 0, 0, 0); {
IPAddress address(0,0,0,0);
char ipstr[INET6_ADDRSTRLEN]; 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); socklen_t addr_size = sizeof(addr);
if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
ESP_LOGE(PH_TAG, "Error getting client IP");
return address;
}
// Convert to IPv4 string
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
ESP_LOGI(PH_TAG, "Client Local IP => %s", ipstr);
address.fromString(ipstr);
if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
ESP_LOGE(PH_TAG, "Error getting client IP");
return address; return address;
}
// Convert to IPv4 string
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
ESP_LOGI(PH_TAG, "Client Local IP => %s", ipstr);
address.fromString(ipstr);
return address;
} }
IPAddress PsychicClient::remoteIP() { IPAddress PsychicClient::remoteIP()
IPAddress address(0, 0, 0, 0); {
IPAddress address(0,0,0,0);
char ipstr[INET6_ADDRSTRLEN]; 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); socklen_t addr_size = sizeof(addr);
if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
ESP_LOGE(PH_TAG, "Error getting client IP");
return address;
}
// Convert to IPv4 string
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
ESP_LOGI(PH_TAG, "Client Remote IP => %s", ipstr);
address.fromString(ipstr);
if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
ESP_LOGE(PH_TAG, "Error getting client IP");
return address; return address;
}
// Convert to IPv4 string
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
ESP_LOGI(PH_TAG, "Client Remote IP => %s", ipstr);
address.fromString(ipstr);
return address;
} }

View File

@@ -2,8 +2,6 @@
#define PsychicClient_h #define PsychicClient_h
#include "PsychicCore.h" #include "PsychicCore.h"
#include "PsychicHttpServer.h"
#include <lwip/sockets.h>
/* /*
* PsychicClient :: Generic wrapper around the ESP-IDF socket * PsychicClient :: Generic wrapper around the ESP-IDF socket
@@ -12,7 +10,7 @@
class PsychicClient { class PsychicClient {
protected: protected:
httpd_handle_t _server; httpd_handle_t _server;
int _socket; int _socket;
public: public:
PsychicClient(httpd_handle_t server, int socket); PsychicClient(httpd_handle_t server, int socket);
@@ -20,17 +18,15 @@ class PsychicClient {
//no idea if this is the right way to do it or not, but lets see. //no idea if this is the right way to do it or not, but lets see.
//pointer to our derived class (eg. PsychicWebSocketConnection) //pointer to our derived class (eg. PsychicWebSocketConnection)
void * _friend; void *_friend;
bool isNew = false; bool isNew = false;
bool operator==(PsychicClient & rhs) const { bool operator==(PsychicClient& rhs) const { return _socket == rhs.socket(); }
return _socket == rhs.socket();
}
httpd_handle_t server(); httpd_handle_t server();
int socket(); int socket();
esp_err_t close(); esp_err_t close();
IPAddress localIP(); IPAddress localIP();
IPAddress remoteIP(); IPAddress remoteIP();

View File

@@ -3,7 +3,7 @@
#define PH_TAG "psychic" #define PH_TAG "psychic"
//version numbers // version numbers
#define PSYCHIC_HTTP_VERSION_MAJOR 1 #define PSYCHIC_HTTP_VERSION_MAJOR 1
#define PSYCHIC_HTTP_VERSION_MINOR 1 #define PSYCHIC_HTTP_VERSION_MINOR 1
#define PSYCHIC_HTTP_VERSION_PATCH 0 #define PSYCHIC_HTTP_VERSION_PATCH 0
@@ -16,12 +16,16 @@
#define FILE_CHUNK_SIZE 8 * 1024 #define FILE_CHUNK_SIZE 8 * 1024
#endif #endif
#ifndef STREAM_CHUNK_SIZE
#define STREAM_CHUNK_SIZE 1024
#endif
#ifndef MAX_UPLOAD_SIZE #ifndef MAX_UPLOAD_SIZE
#define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB #define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB
#endif #endif
#ifndef MAX_REQUEST_BODY_SIZE #ifndef MAX_REQUEST_BODY_SIZE
#define MAX_REQUEST_BODY_SIZE (16 * 1024) //16K #define MAX_REQUEST_BODY_SIZE (16 * 1024) // 16K
#endif #endif
#ifdef ARDUINO #ifdef ARDUINO
@@ -37,65 +41,75 @@
#include "MD5Builder.h" #include "MD5Builder.h"
#include <UrlEncode.h> #include <UrlEncode.h>
#include "FS.h" #include "FS.h"
#include <ArduinoJson.h>
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; enum HTTPAuthMethod
{
BASIC_AUTH,
DIGEST_AUTH
};
String urlDecode(const char * encoded); String urlDecode(const char *encoded);
class PsychicHttpServer; class PsychicHttpServer;
class PsychicRequest; class PsychicRequest;
class PsychicWebSocketRequest; class PsychicWebSocketRequest;
class PsychicClient; class PsychicClient;
//filter function definition // filter function definition
typedef std::function<bool(PsychicRequest * request)> PsychicRequestFilterFunction; typedef std::function<bool(PsychicRequest *request)> PsychicRequestFilterFunction;
//client connect callback // client connect callback
typedef std::function<void(PsychicClient * client)> PsychicClientCallback; typedef std::function<void(PsychicClient *client)> PsychicClientCallback;
// callback definitions
typedef std::function<esp_err_t(PsychicRequest *request)> PsychicHttpRequestCallback;
typedef std::function<esp_err_t(PsychicRequest *request, JsonVariant &json)> PsychicJsonRequestCallback;
struct HTTPHeader { struct HTTPHeader
char * field; {
char * value; char *field;
char *value;
}; };
class DefaultHeaders { class DefaultHeaders
std::list<HTTPHeader> _headers; {
std::list<HTTPHeader> _headers;
public: public:
DefaultHeaders() { DefaultHeaders() {}
}
void addHeader(const String & field, const String & value) { void addHeader(const String &field, const String &value)
addHeader(field.c_str(), value.c_str()); {
} addHeader(field.c_str(), value.c_str());
}
void addHeader(const char * field, const char * value) { void addHeader(const char *field, const char *value)
HTTPHeader header; {
HTTPHeader header;
//these are just going to stick around forever. // these are just going to stick around forever.
header.field = (char *)malloc(strlen(field) + 1); header.field = (char *)malloc(strlen(field) + 1);
header.value = (char *)malloc(strlen(value) + 1); header.value = (char *)malloc(strlen(value) + 1);
strlcpy(header.field, field, strlen(field) + 1); strlcpy(header.field, field, strlen(field) + 1);
strlcpy(header.value, value, strlen(value) + 1); strlcpy(header.value, value, strlen(value) + 1);
_headers.push_back(header); _headers.push_back(header);
} }
const std::list<HTTPHeader> & getHeaders() { const std::list<HTTPHeader> &getHeaders() { return _headers; }
return _headers;
}
//delete the copy constructor, singleton class // delete the copy constructor, singleton class
DefaultHeaders(DefaultHeaders const &) = delete; DefaultHeaders(DefaultHeaders const &) = delete;
DefaultHeaders & operator=(DefaultHeaders const &) = delete; DefaultHeaders &operator=(DefaultHeaders const &) = delete;
//single static class interface // single static class interface
static DefaultHeaders & Instance() { static DefaultHeaders &Instance()
static DefaultHeaders instance; {
return instance; static DefaultHeaders instance;
} return instance;
}
}; };
#endif //PsychicCore_h #endif // PsychicCore_h

View File

@@ -1,82 +1,90 @@
#include "PsychicEndpoint.h" #include "PsychicEndpoint.h"
#include "PsychicHttpServer.h"
PsychicEndpoint::PsychicEndpoint() PsychicEndpoint::PsychicEndpoint() :
: _server(NULL) _server(NULL),
, _uri("") _uri(""),
, _method(HTTP_GET) _method(HTTP_GET),
, _handler(NULL) { _handler(NULL)
{
} }
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer * server, http_method method, const char * uri) PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) :
: _server(server) _server(server),
, _uri(uri) _uri(uri),
, _method(method) _method(method),
, _handler(NULL) { _handler(NULL)
{
} }
PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler * handler) { PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler)
//clean up old / default handler {
if (_handler != NULL) //clean up old / default handler
delete _handler; if (_handler != NULL)
delete _handler;
//get our new pointer //get our new pointer
_handler = handler; _handler = handler;
//keep a pointer to the server //keep a pointer to the server
_handler->_server = _server; _handler->_server = _server;
return this; return this;
} }
PsychicHandler * PsychicEndpoint::handler() { PsychicHandler * PsychicEndpoint::handler()
return _handler; {
return _handler;
} }
String PsychicEndpoint::uri() { String PsychicEndpoint::uri() {
return _uri; return _uri;
} }
esp_err_t PsychicEndpoint::requestCallback(httpd_req_t * req) { esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req)
#ifdef ENABLE_ASYNC {
#ifdef ENABLE_ASYNC
if (is_on_async_worker_thread() == false) { if (is_on_async_worker_thread() == false) {
if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) { if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) {
return ESP_OK; return ESP_OK;
} else { } else {
httpd_resp_set_status(req, "503 Busy"); httpd_resp_set_status(req, "503 Busy");
httpd_resp_sendstr(req, "No workers available. Server busy.</div>"); httpd_resp_sendstr(req, "No workers available. Server busy.</div>");
return ESP_OK; return ESP_OK;
} }
} }
#endif #endif
PsychicEndpoint * self = (PsychicEndpoint *)req->user_ctx; PsychicEndpoint *self = (PsychicEndpoint *)req->user_ctx;
PsychicHandler * handler = self->handler(); PsychicHandler *handler = self->handler();
PsychicRequest request(self->_server, req); PsychicRequest request(self->_server, req);
//make sure we have a handler //make sure we have a handler
if (handler != NULL) { if (handler != NULL)
if (handler->filter(&request) && handler->canHandle(&request)) { {
//check our credentials if (handler->filter(&request) && handler->canHandle(&request))
if (handler->needsAuthentication(&request)) {
return handler->authenticate(&request); //check our credentials
if (handler->needsAuthentication(&request))
return handler->authenticate(&request);
//pass it to our handler //pass it to our handler
return handler->handleRequest(&request); return handler->handleRequest(&request);
} }
//pass it to our generic handlers //pass it to our generic handlers
else else
return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR); return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR);
} else }
return request.reply(500, "text/html", "No handler registered."); else
return request.reply(500, "text/html", "No handler registered.");
} }
PsychicEndpoint * PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) { PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) {
_handler->setFilter(fn); _handler->setFilter(fn);
return this; return this;
} }
PsychicEndpoint * PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) {
PsychicEndpoint::setAuthentication(const char * username, const char * password, HTTPAuthMethod method, const char * realm, const char * authFailMsg) { _handler->setAuthentication(username, password, method, realm, authFailMsg);
_handler->setAuthentication(username, password, method, realm, authFailMsg); return this;
return this;
}; };

View File

@@ -2,35 +2,36 @@
#define PsychicEndpoint_h #define PsychicEndpoint_h
#include "PsychicCore.h" #include "PsychicCore.h"
#include "PsychicHttpServer.h"
class PsychicHandler;
#ifdef ENABLE_ASYNC #ifdef ENABLE_ASYNC
#include "async_worker.h" #include "async_worker.h"
#endif #endif
class PsychicEndpoint { class PsychicEndpoint
friend PsychicHttpServer; {
friend PsychicHttpServer;
private: private:
PsychicHttpServer * _server; PsychicHttpServer *_server;
String _uri; String _uri;
http_method _method; http_method _method;
PsychicHandler * _handler; PsychicHandler *_handler;
public: public:
PsychicEndpoint(); PsychicEndpoint();
PsychicEndpoint(PsychicHttpServer * server, http_method method, const char * uri); PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri);
PsychicEndpoint * setHandler(PsychicHandler * handler); PsychicEndpoint *setHandler(PsychicHandler *handler);
PsychicHandler * handler(); PsychicHandler *handler();
PsychicEndpoint * setFilter(PsychicRequestFilterFunction fn); PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn);
PsychicEndpoint * PsychicEndpoint* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = "");
setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = "");
String uri(); String uri();
static esp_err_t requestCallback(httpd_req_t * req); static esp_err_t requestCallback(httpd_req_t *req);
}; };
#endif // PsychicEndpoint_h #endif // PsychicEndpoint_h

View File

@@ -24,194 +24,204 @@
// PsychicEventSource - Handler // PsychicEventSource - Handler
/*****************************************/ /*****************************************/
PsychicEventSource::PsychicEventSource() PsychicEventSource::PsychicEventSource() :
: PsychicHandler() PsychicHandler(),
, _onOpen(NULL) _onOpen(NULL),
, _onClose(NULL) { _onClose(NULL)
} {}
PsychicEventSource::~PsychicEventSource() { PsychicEventSource::~PsychicEventSource() {
} }
PsychicEventSourceClient * PsychicEventSource::getClient(int socket) { PsychicEventSourceClient * PsychicEventSource::getClient(int socket)
PsychicClient * client = PsychicHandler::getClient(socket); {
PsychicClient *client = PsychicHandler::getClient(socket);
if (client == NULL) if (client == NULL)
return 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()); return getClient(client->socket());
} }
esp_err_t PsychicEventSource::handleRequest(PsychicRequest * request) { esp_err_t PsychicEventSource::handleRequest(PsychicRequest *request)
//start our open ended HTTP response {
PsychicEventSourceResponse response(request); //start our open ended HTTP response
esp_err_t err = response.send(); PsychicEventSourceResponse response(request);
esp_err_t err = response.send();
//lookup our client //lookup our client
PsychicClient * client = checkForNewClient(request->client()); PsychicClient *client = checkForNewClient(request->client());
if (client->isNew) { if (client->isNew)
//did we get our last id? {
if (request->hasHeader("Last-Event-ID")) { //did we get our last id?
PsychicEventSourceClient * buddy = getClient(client); if(request->hasHeader("Last-Event-ID"))
buddy->_lastId = atoi(request->header("Last-Event-ID").c_str()); {
} PsychicEventSourceClient *buddy = getClient(client);
buddy->_lastId = atoi(request->header("Last-Event-ID").c_str());
//let our handler know.
openCallback(client);
} }
return err; //let our handler know.
openCallback(client);
}
return err;
} }
PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) { PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) {
_onOpen = fn; _onOpen = fn;
return this; return this;
} }
PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) { PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) {
_onClose = fn; _onClose = fn;
return this; return this;
} }
void PsychicEventSource::addClient(PsychicClient * client) { void PsychicEventSource::addClient(PsychicClient *client) {
client->_friend = new PsychicEventSourceClient(client); client->_friend = new PsychicEventSourceClient(client);
PsychicHandler::addClient(client); PsychicHandler::addClient(client);
} }
void PsychicEventSource::removeClient(PsychicClient * client) { void PsychicEventSource::removeClient(PsychicClient *client) {
PsychicHandler::removeClient(client); PsychicHandler::removeClient(client);
delete (PsychicEventSourceClient *)client->_friend; delete (PsychicEventSourceClient*)client->_friend;
client->_friend = NULL; client->_friend = NULL;
} }
void PsychicEventSource::openCallback(PsychicClient * client) { void PsychicEventSource::openCallback(PsychicClient *client) {
PsychicEventSourceClient * buddy = getClient(client); PsychicEventSourceClient *buddy = getClient(client);
if (buddy == NULL) { if (buddy == NULL)
TRACE(); {
return; TRACE();
} return;
}
if (_onOpen != NULL) if (_onOpen != NULL)
_onOpen(buddy); _onOpen(buddy);
} }
void PsychicEventSource::closeCallback(PsychicClient * client) { void PsychicEventSource::closeCallback(PsychicClient *client) {
PsychicEventSourceClient * buddy = getClient(client); PsychicEventSourceClient *buddy = getClient(client);
if (buddy == NULL) { if (buddy == NULL)
TRACE(); {
return; TRACE();
} return;
}
if (_onClose != NULL) if (_onClose != NULL)
_onClose(getClient(buddy)); _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) { String ev = generateEventMessage(message, event, id, reconnect);
((PsychicEventSourceClient *)c->_friend)->sendEvent(ev.c_str()); for(PsychicClient *c : _clients) {
} ((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str());
}
} }
/*****************************************/ /*****************************************/
// PsychicEventSourceClient // PsychicEventSourceClient
/*****************************************/ /*****************************************/
PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient * client) PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient *client) :
: PsychicClient(client->server(), client->socket()) PsychicClient(client->server(), client->socket()),
, _lastId(0) { _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); String ev = generateEventMessage(message, event, id, reconnect);
sendEvent(ev.c_str()); sendEvent(ev.c_str());
} }
void PsychicEventSourceClient::sendEvent(const char * event) { void PsychicEventSourceClient::sendEvent(const char *event) {
int result; int result;
do { do {
result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0); result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0);
} while (result == HTTPD_SOCK_ERR_TIMEOUT); } while (result == HTTPD_SOCK_ERR_TIMEOUT);
//if (result < 0) //if (result < 0)
//error log here //error log here
} }
/*****************************************/ /*****************************************/
// PsychicEventSourceResponse // PsychicEventSourceResponse
/*****************************************/ /*****************************************/
PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest * request) PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest *request)
: PsychicResponse(request) { : PsychicResponse(request)
{
} }
esp_err_t PsychicEventSourceResponse::send() { esp_err_t PsychicEventSourceResponse::send() {
//build our main header
String out = String();
out.concat("HTTP/1.1 200 OK\r\n");
out.concat("Content-Type: text/event-stream\r\n");
out.concat("Cache-Control: no-cache\r\n");
out.concat("Connection: keep-alive\r\n");
//get our global headers out of the way first //build our main header
for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) String out = String();
out.concat(String(header.field) + ": " + String(header.value) + "\r\n"); out.concat("HTTP/1.1 200 OK\r\n");
out.concat("Content-Type: text/event-stream\r\n");
out.concat("Cache-Control: no-cache\r\n");
out.concat("Connection: keep-alive\r\n");
//separator //get our global headers out of the way first
out.concat("\r\n"); for (HTTPHeader header : DefaultHeaders::Instance().getHeaders())
out.concat(String(header.field) + ": " + String(header.value) + "\r\n");
int result; //separator
do { out.concat("\r\n");
result = httpd_send(_request->request(), out.c_str(), out.length());
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
if (result < 0) int result;
ESP_LOGE(PH_TAG, "EventSource send failed with %s", esp_err_to_name(result)); do {
result = httpd_send(_request->request(), out.c_str(), out.length());
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
if (result > 0) if (result < 0)
return ESP_OK; ESP_LOGE(PH_TAG, "EventSource send failed with %s", esp_err_to_name(result));
else
return ESP_ERR_HTTPD_RESP_SEND; if (result > 0)
return ESP_OK;
else
return ESP_ERR_HTTPD_RESP_SEND;
} }
/*****************************************/ /*****************************************/
// Event Message Generator // 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 = ""; String ev = "";
if (reconnect) { if(reconnect){
ev += "retry: "; ev += "retry: ";
ev += String(reconnect); ev += String(reconnect);
ev += "\r\n";
}
if (id) {
ev += "id: ";
ev += String(id);
ev += "\r\n";
}
if (event != NULL) {
ev += "event: ";
ev += String(event);
ev += "\r\n";
}
if (message != NULL) {
ev += "data: ";
ev += String(message);
ev += "\r\n";
}
ev += "\r\n"; ev += "\r\n";
}
return ev; if(id){
ev += "id: ";
ev += String(id);
ev += "\r\n";
}
if(event != NULL){
ev += "event: ";
ev += String(event);
ev += "\r\n";
}
if(message != NULL){
ev += "data: ";
ev += String(message);
ev += "\r\n";
}
ev += "\r\n";
return ev;
} }

View File

@@ -21,8 +21,8 @@
#define PsychicEventSource_H_ #define PsychicEventSource_H_
#include "PsychicCore.h" #include "PsychicCore.h"
#include "PsychicClient.h"
#include "PsychicHandler.h" #include "PsychicHandler.h"
#include "PsychicClient.h"
#include "PsychicResponse.h" #include "PsychicResponse.h"
class PsychicEventSource; class PsychicEventSource;
@@ -30,23 +30,21 @@ class PsychicEventSourceResponse;
class PsychicEventSourceClient; class PsychicEventSourceClient;
class PsychicResponse; class PsychicResponse;
typedef std::function<void(PsychicEventSourceClient * client)> PsychicEventSourceClientCallback; typedef std::function<void(PsychicEventSourceClient *client)> PsychicEventSourceClientCallback;
class PsychicEventSourceClient : public PsychicClient { class PsychicEventSourceClient : public PsychicClient {
friend PsychicEventSource; friend PsychicEventSource;
protected: protected:
uint32_t _lastId; uint32_t _lastId;
public: public:
PsychicEventSourceClient(PsychicClient * client); PsychicEventSourceClient(PsychicClient *client);
~PsychicEventSourceClient(); ~PsychicEventSourceClient();
uint32_t lastId() const { uint32_t lastId() const { return _lastId; }
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 {
@@ -59,26 +57,26 @@ class PsychicEventSource : public PsychicHandler {
~PsychicEventSource(); ~PsychicEventSource();
PsychicEventSourceClient * getClient(int socket) override; PsychicEventSourceClient * getClient(int socket) override;
PsychicEventSourceClient * getClient(PsychicClient * client) override; PsychicEventSourceClient * getClient(PsychicClient *client) override;
void addClient(PsychicClient * client) override; void addClient(PsychicClient *client) override;
void removeClient(PsychicClient * client) override; void removeClient(PsychicClient *client) override;
void openCallback(PsychicClient * client) override; void openCallback(PsychicClient *client) override;
void closeCallback(PsychicClient * client) override; void closeCallback(PsychicClient *client) override;
PsychicEventSource * onOpen(PsychicEventSourceClientCallback fn); PsychicEventSource *onOpen(PsychicEventSourceClientCallback fn);
PsychicEventSource * onClose(PsychicEventSourceClientCallback fn); PsychicEventSource *onClose(PsychicEventSourceClientCallback fn);
esp_err_t handleRequest(PsychicRequest * request) override final; esp_err_t handleRequest(PsychicRequest *request) override final;
void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0); void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
}; };
class PsychicEventSourceResponse : public PsychicResponse { class PsychicEventSourceResponse: public PsychicResponse {
public: public:
PsychicEventSourceResponse(PsychicRequest * request); PsychicEventSourceResponse(PsychicRequest *request);
virtual esp_err_t send() override; virtual esp_err_t send() override;
}; };
String generateEventMessage(const char * message, const char * event, uint32_t id, uint32_t reconnect); String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect);
#endif /* PsychicEventSource_H_ */ #endif /* PsychicEventSource_H_ */

View File

@@ -3,167 +3,156 @@
#include "PsychicRequest.h" #include "PsychicRequest.h"
PsychicFileResponse::PsychicFileResponse(PsychicRequest * request, FS & fs, const String & path, const String & contentType, bool download) PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType, bool download)
: PsychicResponse(request) { : PsychicResponse(request) {
//_code = 200; //_code = 200;
_path = path; _path = path;
if (!download && !fs.exists(_path) && fs.exists(_path + ".gz")) { if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
_path = _path + ".gz"; _path = _path+".gz";
addHeader("Content-Encoding", "gzip"); addHeader("Content-Encoding", "gzip");
_sendContentLength = true; _sendContentLength = true;
_chunked = false; _chunked = false;
} }
_content = fs.open(_path, "r"); _content = fs.open(_path, "r");
_contentLength = _content.size(); _contentLength = _content.size();
if (contentType == "") if(contentType == "")
_setContentType(path); _setContentType(path);
else else
_contentType = contentType; _contentType = contentType;
setContentType(_contentType.c_str()); setContentType(_contentType.c_str());
int filenameStart = path.lastIndexOf('/') + 1; int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart]; char buf[26+path.length()-filenameStart];
char * filename = (char *)path.c_str() + filenameStart; char* filename = (char*)path.c_str() + filenameStart;
if (download) { if(download) {
// set filename and force download // set filename and force download
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename); snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
} else { } else {
// set filename and force rendering // 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); addHeader("Content-Disposition", buf);
} }
PsychicFileResponse::PsychicFileResponse(PsychicRequest * request, File content, const String & path, const String & contentType, bool download) PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType, bool download)
: PsychicResponse(request) { : PsychicResponse(request) {
_path = path; _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"); addHeader("Content-Encoding", "gzip");
_sendContentLength = true; _sendContentLength = true;
_chunked = false; _chunked = false;
}
_content = content;
_contentLength = _content.size();
setContentLength(_contentLength);
if(contentType == "")
_setContentType(path);
else
_contentType = contentType;
setContentType(_contentType.c_str());
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
} else {
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
}
addHeader("Content-Disposition", buf);
}
PsychicFileResponse::~PsychicFileResponse()
{
if(_content)
_content.close();
}
void PsychicFileResponse::_setContentType(const String& path){
if (path.endsWith(".html")) _contentType = "text/html";
else if (path.endsWith(".htm")) _contentType = "text/html";
else if (path.endsWith(".css")) _contentType = "text/css";
else if (path.endsWith(".json")) _contentType = "application/json";
else if (path.endsWith(".js")) _contentType = "application/javascript";
else if (path.endsWith(".png")) _contentType = "image/png";
else if (path.endsWith(".gif")) _contentType = "image/gif";
else if (path.endsWith(".jpg")) _contentType = "image/jpeg";
else if (path.endsWith(".ico")) _contentType = "image/x-icon";
else if (path.endsWith(".svg")) _contentType = "image/svg+xml";
else if (path.endsWith(".eot")) _contentType = "font/eot";
else if (path.endsWith(".woff")) _contentType = "font/woff";
else if (path.endsWith(".woff2")) _contentType = "font/woff2";
else if (path.endsWith(".ttf")) _contentType = "font/ttf";
else if (path.endsWith(".xml")) _contentType = "text/xml";
else if (path.endsWith(".pdf")) _contentType = "application/pdf";
else if (path.endsWith(".zip")) _contentType = "application/zip";
else if(path.endsWith(".gz")) _contentType = "application/x-gzip";
else _contentType = "text/plain";
}
esp_err_t PsychicFileResponse::send()
{
esp_err_t err = ESP_OK;
//just send small files directly
size_t size = getContentLength();
if (size < FILE_CHUNK_SIZE)
{
uint8_t *buffer = (uint8_t *)malloc(size);
int readSize = _content.readBytes((char *)buffer, size);
this->setContent(buffer, size);
err = PsychicResponse::send();
free(buffer);
}
else
{
/* Retrieve the pointer to scratch buffer for temporary storage */
char *chunk = (char *)malloc(FILE_CHUNK_SIZE);
if (chunk == NULL)
{
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
return ESP_FAIL;
} }
_content = content; this->sendHeaders();
_contentLength = _content.size();
setContentLength(_contentLength);
if (contentType == "") size_t chunksize;
_setContentType(path); do {
else /* Read file in chunks into the scratch buffer */
_contentType = contentType; chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE);
setContentType(_contentType.c_str()); if (chunksize > 0)
{
int filenameStart = path.lastIndexOf('/') + 1; err = this->sendChunk((uint8_t *)chunk, chunksize);
char buf[26 + path.length() - filenameStart]; if (err != ESP_OK)
char * filename = (char *)path.c_str() + filenameStart; break;
if (download) {
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename);
} else {
snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename);
}
addHeader("Content-Disposition", buf);
}
PsychicFileResponse::~PsychicFileResponse() {
if (_content)
_content.close();
}
void PsychicFileResponse::_setContentType(const String & path) {
if (path.endsWith(".html"))
_contentType = "text/html";
else if (path.endsWith(".htm"))
_contentType = "text/html";
else if (path.endsWith(".css"))
_contentType = "text/css";
else if (path.endsWith(".json"))
_contentType = "application/json";
else if (path.endsWith(".js"))
_contentType = "application/javascript";
else if (path.endsWith(".png"))
_contentType = "image/png";
else if (path.endsWith(".gif"))
_contentType = "image/gif";
else if (path.endsWith(".jpg"))
_contentType = "image/jpeg";
else if (path.endsWith(".ico"))
_contentType = "image/x-icon";
else if (path.endsWith(".svg"))
_contentType = "image/svg+xml";
else if (path.endsWith(".eot"))
_contentType = "font/eot";
else if (path.endsWith(".woff"))
_contentType = "font/woff";
else if (path.endsWith(".woff2"))
_contentType = "font/woff2";
else if (path.endsWith(".ttf"))
_contentType = "font/ttf";
else if (path.endsWith(".xml"))
_contentType = "text/xml";
else if (path.endsWith(".pdf"))
_contentType = "application/pdf";
else if (path.endsWith(".zip"))
_contentType = "application/zip";
else if (path.endsWith(".gz"))
_contentType = "application/x-gzip";
else
_contentType = "text/plain";
}
esp_err_t PsychicFileResponse::send() {
esp_err_t err = ESP_OK;
//just send small files directly
size_t size = getContentLength();
if (size < FILE_CHUNK_SIZE) {
uint8_t * buffer = (uint8_t *)malloc(size);
int readSize = _content.readBytes((char *)buffer, size);
this->setContent(buffer, size);
err = PsychicResponse::send();
free(buffer);
} else {
/* Retrieve the pointer to scratch buffer for temporary storage */
char * chunk = (char *)malloc(FILE_CHUNK_SIZE);
if (chunk == NULL) {
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
return ESP_FAIL;
} }
this->sendHeaders(); /* Keep looping till the whole file is sent */
} while (chunksize != 0);
size_t chunksize; //keep track of our memory
do { free(chunk);
/* Read file in chunks into the scratch buffer */
chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE);
if (chunksize > 0) {
err = this->sendChunk((uint8_t *)chunk, chunksize);
if (err != ESP_OK)
break;
}
/* Keep looping till the whole file is sent */ if (err == ESP_OK)
} while (chunksize != 0); {
ESP_LOGI(PH_TAG, "File sending complete");
//keep track of our memory this->finishChunking();
free(chunk);
if (err == ESP_OK) {
ESP_LOGI(PH_TAG, "File sending complete");
this->finishChunking();
}
/* Close file after sending complete */
_content.close();
} }
return err; /* Close file after sending complete */
_content.close();
}
return err;
} }

View File

@@ -6,21 +6,20 @@
class PsychicRequest; class PsychicRequest;
class PsychicFileResponse : public PsychicResponse { class PsychicFileResponse: public PsychicResponse
using File = fs::File; {
using FS = fs::FS; using File = fs::File;
using FS = fs::FS;
private: private:
File _content; File _content;
String _path; String _path;
bool _sendContentLength; bool _sendContentLength;
bool _chunked; bool _chunked;
String _contentType; String _contentType;
void _setContentType(const String & path); void _setContentType(const String& path);
public: public:
PsychicFileResponse(PsychicRequest * request, FS & fs, const String & path, const String & contentType = String(), bool download = false); PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType=String(), bool download=false);
PsychicFileResponse(PsychicRequest * request, File content, const String & path, const String & contentType = String(), bool download = false); PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType=String(), bool download=false);
~PsychicFileResponse(); ~PsychicFileResponse();
esp_err_t send(); esp_err_t send();
}; };

View File

@@ -5,63 +5,57 @@
#include "PsychicRequest.h" #include "PsychicRequest.h"
class PsychicEndpoint; class PsychicEndpoint;
class PsychicHttpServer;
/* /*
* 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 { class PsychicHandler {
friend PsychicEndpoint; friend PsychicEndpoint;
protected: protected:
PsychicRequestFilterFunction _filter; PsychicRequestFilterFunction _filter;
PsychicHttpServer * _server; PsychicHttpServer *_server;
String _username; String _username;
String _password; String _password;
HTTPAuthMethod _method; HTTPAuthMethod _method;
String _realm; String _realm;
String _authFailMsg; String _authFailMsg;
std::list<PsychicClient *> _clients; std::list<PsychicClient*> _clients;
public: public:
PsychicHandler(); PsychicHandler();
~PsychicHandler(); ~PsychicHandler();
PsychicHandler * setFilter(PsychicRequestFilterFunction fn); PsychicHandler* setFilter(PsychicRequestFilterFunction fn);
bool filter(PsychicRequest * request); bool filter(PsychicRequest *request);
PsychicHandler * PsychicHandler* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = "");
setAuthentication(const char * username, const char * password, HTTPAuthMethod method = BASIC_AUTH, const char * realm = "", const char * authFailMsg = ""); bool needsAuthentication(PsychicRequest *request);
bool needsAuthentication(PsychicRequest * request); esp_err_t authenticate(PsychicRequest *request);
esp_err_t authenticate(PsychicRequest * request);
virtual bool isWebSocket() { virtual bool isWebSocket() { return false; };
return false;
};
PsychicClient * checkForNewClient(PsychicClient * client); PsychicClient * checkForNewClient(PsychicClient *client);
void checkForClosedClient(PsychicClient * client); void checkForClosedClient(PsychicClient *client);
virtual void addClient(PsychicClient * client); virtual void addClient(PsychicClient *client);
virtual void removeClient(PsychicClient * client); virtual void removeClient(PsychicClient *client);
virtual PsychicClient * getClient(int socket); virtual PsychicClient * getClient(int socket);
virtual PsychicClient * getClient(PsychicClient * client); virtual PsychicClient * getClient(PsychicClient *client);
virtual void openCallback(PsychicClient * client){}; virtual void openCallback(PsychicClient *client) {};
virtual void closeCallback(PsychicClient * client){}; virtual void closeCallback(PsychicClient *client) {};
bool hasClient(PsychicClient * client); bool hasClient(PsychicClient *client);
int count() { int count() { return _clients.size(); };
return _clients.size(); const std::list<PsychicClient*>& getClientList();
};
const std::list<PsychicClient *> & getClientList();
//derived classes must implement these functions //derived classes must implement these functions
virtual bool canHandle(PsychicRequest * request) { virtual bool canHandle(PsychicRequest *request) { return true; };
return true; virtual esp_err_t handleRequest(PsychicRequest *request) = 0;
};
virtual esp_err_t handleRequest(PsychicRequest * request) = 0;
}; };
#endif #endif

View File

@@ -1,7 +1,7 @@
#ifndef PsychicHttp_h #ifndef PsychicHttp_h
#define 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 <http_status.h>
#include "PsychicHttpServer.h" #include "PsychicHttpServer.h"
@@ -11,6 +11,7 @@
#include "PsychicHandler.h" #include "PsychicHandler.h"
#include "PsychicStaticFileHandler.h" #include "PsychicStaticFileHandler.h"
#include "PsychicFileResponse.h" #include "PsychicFileResponse.h"
#include "PsychicStreamResponse.h"
#include "PsychicUploadHandler.h" #include "PsychicUploadHandler.h"
#include "PsychicWebSocket.h" #include "PsychicWebSocket.h"
#include "PsychicEventSource.h" #include "PsychicEventSource.h"

View File

@@ -4,28 +4,29 @@
#include "PsychicWebHandler.h" #include "PsychicWebHandler.h"
#include "PsychicStaticFileHandler.h" #include "PsychicStaticFileHandler.h"
#include "PsychicWebSocket.h" #include "PsychicWebSocket.h"
#include "PsychicJson.h"
#include "WiFi.h" #include "WiFi.h"
#include "PsychicJson.h" // added by proddy
PsychicHttpServer::PsychicHttpServer() PsychicHttpServer::PsychicHttpServer() :
: _onOpen(NULL) _onOpen(NULL),
, _onClose(NULL) { _onClose(NULL)
maxRequestBodySize = MAX_REQUEST_BODY_SIZE; {
maxUploadSize = MAX_UPLOAD_SIZE; maxRequestBodySize = MAX_REQUEST_BODY_SIZE;
maxUploadSize = MAX_UPLOAD_SIZE;
defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, ""); defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, "");
onNotFound(PsychicHttpServer::defaultNotFoundHandler); onNotFound(PsychicHttpServer::defaultNotFoundHandler);
//for a regular server
config = HTTPD_DEFAULT_CONFIG();
config.open_fn = PsychicHttpServer::openCallback;
config.close_fn = PsychicHttpServer::closeCallback;
config.uri_match_fn = httpd_uri_match_wildcard;
config.global_user_ctx = this;
config.global_user_ctx_free_fn = destroy;
config.max_uri_handlers = 20;
//for a regular server #ifdef ENABLE_ASYNC
config = HTTPD_DEFAULT_CONFIG();
config.open_fn = PsychicHttpServer::openCallback;
config.close_fn = PsychicHttpServer::closeCallback;
config.uri_match_fn = httpd_uri_match_wildcard;
config.global_user_ctx = this;
config.global_user_ctx_free_fn = destroy;
config.max_uri_handlers = 20;
#ifdef ENABLE_ASYNC
// It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS // It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS
// Why? This leaves at least one socket still available to handle // Why? This leaves at least one socket still available to handle
// quick synchronous requests. Otherwise, all the sockets will // quick synchronous requests. Otherwise, all the sockets will
@@ -33,297 +34,333 @@ PsychicHttpServer::PsychicHttpServer()
// longer be responsive. // longer be responsive.
config.max_open_sockets = ASYNC_WORKER_COUNT + 1; config.max_open_sockets = ASYNC_WORKER_COUNT + 1;
config.lru_purge_enable = true; config.lru_purge_enable = true;
#endif #endif
} }
PsychicHttpServer::~PsychicHttpServer() { PsychicHttpServer::~PsychicHttpServer()
for (auto * client : _clients) {
delete (client); for (auto *client : _clients)
_clients.clear(); delete(client);
_clients.clear();
for (auto * endpoint : _endpoints) for (auto *endpoint : _endpoints)
delete (endpoint); delete(endpoint);
_endpoints.clear(); _endpoints.clear();
for (auto * handler : _handlers) for (auto *handler : _handlers)
delete (handler); delete(handler);
_handlers.clear(); _handlers.clear();
delete defaultEndpoint; delete defaultEndpoint;
} }
void PsychicHttpServer::destroy(void * ctx) { void PsychicHttpServer::destroy(void *ctx)
PsychicHttpServer * temp = (PsychicHttpServer *)ctx; {
delete temp; PsychicHttpServer *temp = (PsychicHttpServer *)ctx;
delete temp;
} }
esp_err_t PsychicHttpServer::listen(uint16_t port) { esp_err_t PsychicHttpServer::listen(uint16_t port)
this->_use_ssl = false; {
this->config.server_port = port; this->_use_ssl = false;
this->config.server_port = port;
return this->_start(); return this->_start();
} }
esp_err_t PsychicHttpServer::_start() { esp_err_t PsychicHttpServer::_start()
esp_err_t ret; {
esp_err_t ret;
#ifdef ENABLE_ASYNC #ifdef ENABLE_ASYNC
// start workers // start workers
start_async_req_workers(); start_async_req_workers();
#endif #endif
//fire it up.
ret = _startServer();
if (ret != ESP_OK) {
ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret));
return ret;
}
// Register handler
ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler);
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret));
//fire it up.
ret = _startServer();
if (ret != ESP_OK)
{
ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret));
return ret; return ret;
}
// Register handler
ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler);
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret));
return ret;
} }
esp_err_t PsychicHttpServer::_startServer() { esp_err_t PsychicHttpServer::_startServer() {
return httpd_start(&this->server, &this->config); return httpd_start(&this->server, &this->config);
} }
void PsychicHttpServer::stop() { void PsychicHttpServer::stop()
httpd_stop(this->server); {
httpd_stop(this->server);
} }
PsychicHandler & PsychicHttpServer::addHandler(PsychicHandler * handler) { PsychicHandler& PsychicHttpServer::addHandler(PsychicHandler* handler){
_handlers.push_back(handler); _handlers.push_back(handler);
return *handler; return *handler;
} }
void PsychicHttpServer::removeHandler(PsychicHandler * handler) { void PsychicHttpServer::removeHandler(PsychicHandler *handler){
_handlers.remove(handler); _handlers.remove(handler);
} }
PsychicEndpoint * PsychicHttpServer::on(const char * uri) { PsychicEndpoint* PsychicHttpServer::on(const char* uri) {
return on(uri, HTTP_GET); return on(uri, HTTP_GET);
} }
PsychicEndpoint * PsychicHttpServer::on(const char * uri, PsychicHttpRequestCallback fn) { PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method)
return on(uri, HTTP_GET, fn); {
PsychicWebHandler *handler = new PsychicWebHandler();
return on(uri, method, handler);
} }
PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicHttpRequestCallback fn) { PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler *handler)
//these basic requests need a basic web handler {
PsychicWebHandler * handler = new PsychicWebHandler(); return on(uri, HTTP_GET, handler);
handler->onRequest(fn);
return on(uri, method, handler);
} }
// added by Proddy PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHandler *handler)
PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicJsonRequestCallback fn) { {
PsychicJsonHandler * handler = new PsychicJsonHandler(fn); //make our endpoint
PsychicEndpoint *endpoint = new PsychicEndpoint(this, method, uri);
return on(uri, method, handler); //set our handler
endpoint->setHandler(handler);
// URI handler structure
httpd_uri_t my_uri {
.uri = uri,
.method = method,
.handler = PsychicEndpoint::requestCallback,
.user_ctx = endpoint,
.is_websocket = handler->isWebSocket()
};
// Register endpoint with ESP-IDF server
esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri);
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret));
//save it for later
_endpoints.push_back(endpoint);
return endpoint;
} }
PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method) { PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpRequestCallback fn)
PsychicWebHandler * handler = new PsychicWebHandler(); {
return on(uri, HTTP_GET, fn);
return on(uri, method, handler);
} }
PsychicEndpoint * PsychicHttpServer::on(const char * uri, PsychicHandler * handler) { PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHttpRequestCallback fn)
return on(uri, HTTP_GET, handler); {
//these basic requests need a basic web handler
PsychicWebHandler *handler = new PsychicWebHandler();
handler->onRequest(fn);
return on(uri, method, handler);
} }
PsychicEndpoint * PsychicHttpServer::on(const char * uri, http_method method, PsychicHandler * handler) { PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonRequestCallback fn)
//make our endpoint {
PsychicEndpoint * endpoint = new PsychicEndpoint(this, method, uri); return on(uri, HTTP_GET, fn);
//set our handler
endpoint->setHandler(handler);
// URI handler structure
httpd_uri_t my_uri{.uri = uri, .method = method, .handler = PsychicEndpoint::requestCallback, .user_ctx = endpoint, .is_websocket = handler->isWebSocket()};
// Register endpoint with ESP-IDF server
esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri);
if (ret != ESP_OK) {
ESP_LOGE(PH_TAG, "Add endpoint %s failed (%s)", uri, esp_err_to_name(ret)); // modified by proddy
}
//save it for later
_endpoints.push_back(endpoint);
return endpoint;
} }
void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn) { PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicJsonRequestCallback fn)
PsychicWebHandler * handler = new PsychicWebHandler(); {
handler->onRequest(fn); //these basic requests need a basic web handler
PsychicJsonHandler *handler = new PsychicJsonHandler();
handler->onRequest(fn);
this->defaultEndpoint->setHandler(handler); return on(uri, method, handler);
} }
esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t * req, httpd_err_code_t err) { void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn)
PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(req->handle); {
PsychicRequest request(server, req); PsychicWebHandler *handler = new PsychicWebHandler();
handler->onRequest(fn);
//loop through our global handlers and see if anyone wants it this->defaultEndpoint->setHandler(handler);
for (auto * handler : server->_handlers) { }
//are we capable of handling this?
if (handler->filter(&request) && handler->canHandle(&request)) {
//check our credentials
if (handler->needsAuthentication(&request))
return handler->authenticate(&request);
else
return handler->handleRequest(&request);
}
}
//nothing found, give it to our defaultEndpoint esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t *req, httpd_err_code_t err)
PsychicHandler * handler = server->defaultEndpoint->handler(); {
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)) if (handler->filter(&request) && handler->canHandle(&request))
{
//check our credentials
if (handler->needsAuthentication(&request))
return handler->authenticate(&request);
else
return handler->handleRequest(&request); return handler->handleRequest(&request);
}
}
//not sure how we got this far. //nothing found, give it to our defaultEndpoint
return ESP_ERR_HTTPD_INVALID_REQ; PsychicHandler *handler = server->defaultEndpoint->handler();
if (handler->filter(&request) && handler->canHandle(&request))
return handler->handleRequest(&request);
//not sure how we got this far.
return ESP_ERR_HTTPD_INVALID_REQ;
} }
esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest * request) { esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest *request)
request->reply(404, "text/html", "That URI does not exist."); {
request->reply(404, "text/html", "That URI does not exist.");
return ESP_OK; return ESP_OK;
} }
void PsychicHttpServer::onOpen(PsychicClientCallback handler) { void PsychicHttpServer::onOpen(PsychicClientCallback handler) {
this->_onOpen = handler; this->_onOpen = handler;
} }
esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd) { esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd)
ESP_LOGI(PH_TAG, "New client connected %d", sockfd); {
ESP_LOGI(PH_TAG, "New client connected %d", sockfd);
//get our global server reference //get our global server reference
PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(hd); PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd);
//lookup our client //lookup our client
PsychicClient * client = server->getClient(sockfd); PsychicClient *client = server->getClient(sockfd);
if (client == NULL) { if (client == NULL)
client = new PsychicClient(hd, sockfd); {
server->addClient(client); client = new PsychicClient(hd, sockfd);
} server->addClient(client);
}
//user callback //user callback
if (server->_onOpen != NULL) if (server->_onOpen != NULL)
server->_onOpen(client); server->_onOpen(client);
return ESP_OK; return ESP_OK;
} }
void PsychicHttpServer::onClose(PsychicClientCallback handler) { void PsychicHttpServer::onClose(PsychicClientCallback handler) {
this->_onClose = handler; this->_onClose = handler;
} }
void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd) { void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd)
ESP_LOGI(PH_TAG, "Client disconnected %d", sockfd); {
ESP_LOGI(PH_TAG, "Client disconnected %d", sockfd);
PsychicHttpServer * server = (PsychicHttpServer *)httpd_get_global_user_ctx(hd); PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd);
//lookup our client //lookup our client
PsychicClient * client = server->getClient(sockfd); PsychicClient *client = server->getClient(sockfd);
if (client != NULL) { if (client != NULL)
//give our handlers a chance to handle a disconnect first {
for (PsychicEndpoint * endpoint : server->_endpoints) { //give our handlers a chance to handle a disconnect first
PsychicHandler * handler = endpoint->handler(); for (PsychicEndpoint * endpoint : server->_endpoints)
handler->checkForClosedClient(client); {
} PsychicHandler *handler = endpoint->handler();
handler->checkForClosedClient(client);
}
//do we have a callback attached? //do we have a callback attached?
if (server->_onClose != NULL) if (server->_onClose != NULL)
server->_onClose(client); server->_onClose(client);
//remove it from our list //remove it from our list
server->removeClient(client); server->removeClient(client);
} else }
ESP_LOGE(PH_TAG, "No client record %d", sockfd); else
ESP_LOGE(PH_TAG, "No client record %d", sockfd);
//finally close it out. //finally close it out.
close(sockfd); close(sockfd);
} }
PsychicStaticFileHandler * PsychicHttpServer::serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control) { PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control)
PsychicStaticFileHandler * handler = new PsychicStaticFileHandler(uri, fs, path, cache_control); {
this->addHandler(handler); PsychicStaticFileHandler* handler = new PsychicStaticFileHandler(uri, fs, path, cache_control);
this->addHandler(handler);
return handler; return handler;
} }
void PsychicHttpServer::addClient(PsychicClient * client) { void PsychicHttpServer::addClient(PsychicClient *client) {
_clients.push_back(client); _clients.push_back(client);
} }
void PsychicHttpServer::removeClient(PsychicClient * client) { void PsychicHttpServer::removeClient(PsychicClient *client) {
_clients.remove(client); _clients.remove(client);
delete client; delete client;
} }
PsychicClient * PsychicHttpServer::getClient(int socket) { PsychicClient * PsychicHttpServer::getClient(int socket) {
for (PsychicClient * client : _clients) for (PsychicClient * client : _clients)
if (client->socket() == socket) if (client->socket() == socket)
return client; return client;
return NULL; return NULL;
} }
PsychicClient * PsychicHttpServer::getClient(httpd_req_t * req) { PsychicClient * PsychicHttpServer::getClient(httpd_req_t *req) {
return getClient(httpd_req_to_sockfd(req)); return getClient(httpd_req_to_sockfd(req));
} }
bool PsychicHttpServer::hasClient(int socket) { bool PsychicHttpServer::hasClient(int socket) {
return getClient(socket) != NULL; return getClient(socket) != NULL;
} }
const std::list<PsychicClient *> & PsychicHttpServer::getClientList() { const std::list<PsychicClient*>& PsychicHttpServer::getClientList() {
return _clients; return _clients;
} }
bool ON_STA_FILTER(PsychicRequest * request) { bool ON_STA_FILTER(PsychicRequest *request) {
return WiFi.localIP() == request->client()->localIP(); return WiFi.localIP() == request->client()->localIP();
} }
bool ON_AP_FILTER(PsychicRequest * request) { bool ON_AP_FILTER(PsychicRequest *request) {
return WiFi.softAPIP() == request->client()->localIP(); return WiFi.softAPIP() == request->client()->localIP();
} }
String urlDecode(const char * encoded) { String urlDecode(const char* encoded)
size_t length = strlen(encoded); {
char * decoded = (char *)malloc(length + 1); size_t length = strlen(encoded);
if (!decoded) { char* decoded = (char*)malloc(length + 1);
return ""; if (!decoded) {
} return "";
}
size_t i, j = 0; size_t i, j = 0;
for (i = 0; i < length; ++i) { for (i = 0; i < length; ++i) {
if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) { if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) {
// Valid percent-encoded sequence // Valid percent-encoded sequence
int hex; int hex;
sscanf(encoded + i + 1, "%2x", &hex); sscanf(encoded + i + 1, "%2x", &hex);
decoded[j++] = (char)hex; decoded[j++] = (char)hex;
i += 2; // Skip the two hexadecimal characters i += 2; // Skip the two hexadecimal characters
} else if (encoded[i] == '+') { } else if (encoded[i] == '+') {
// Convert '+' to space // Convert '+' to space
decoded[j++] = ' '; decoded[j++] = ' ';
} else { } else {
// Copy other characters as they are // Copy other characters as they are
decoded[j++] = encoded[i]; decoded[j++] = encoded[i];
} }
} }
decoded[j] = '\0'; // Null-terminate the decoded string decoded[j] = '\0'; // Null-terminate the decoded string
String output(decoded); String output(decoded);
free(decoded); free(decoded);
return output; return output;
} }

View File

@@ -4,27 +4,23 @@
#include "PsychicCore.h" #include "PsychicCore.h"
#include "PsychicClient.h" #include "PsychicClient.h"
#include "PsychicHandler.h" #include "PsychicHandler.h"
#include <ArduinoJson.h> // added by proddy
class PsychicEndpoint; class PsychicEndpoint;
class PsychicHandler; class PsychicHandler;
class PsychicStaticFileHandler; class PsychicStaticFileHandler;
//callback definitions class PsychicHttpServer
typedef std::function<esp_err_t(PsychicRequest * request)> PsychicHttpRequestCallback; {
typedef std::function<esp_err_t(PsychicRequest * request, JsonVariant & json)> PsychicJsonRequestCallback; // added by proddy
class PsychicHttpServer {
protected: protected:
bool _use_ssl = false; bool _use_ssl = false;
std::list<PsychicEndpoint *> _endpoints; std::list<PsychicEndpoint*> _endpoints;
std::list<PsychicHandler *> _handlers; std::list<PsychicHandler*> _handlers;
std::list<PsychicClient *> _clients; std::list<PsychicClient*> _clients;
PsychicClientCallback _onOpen; PsychicClientCallback _onOpen;
PsychicClientCallback _onClose; PsychicClientCallback _onClose;
esp_err_t _start(); esp_err_t _start();
virtual esp_err_t _startServer(); virtual esp_err_t _startServer();
public: public:
@@ -39,48 +35,47 @@ class PsychicHttpServer {
unsigned long maxUploadSize; unsigned long maxUploadSize;
unsigned long maxRequestBodySize; unsigned long maxRequestBodySize;
PsychicEndpoint * defaultEndpoint; PsychicEndpoint *defaultEndpoint;
static void destroy(void * ctx); static void destroy(void *ctx);
esp_err_t listen(uint16_t port); esp_err_t listen(uint16_t port);
virtual void stop(); virtual void stop();
PsychicHandler & addHandler(PsychicHandler * handler); PsychicHandler& addHandler(PsychicHandler* handler);
void removeHandler(PsychicHandler * handler); void removeHandler(PsychicHandler* handler);
void addClient(PsychicClient * client); void addClient(PsychicClient *client);
void removeClient(PsychicClient * client); void removeClient(PsychicClient *client);
PsychicClient * getClient(int socket); PsychicClient* getClient(int socket);
PsychicClient * getClient(httpd_req_t * req); PsychicClient* getClient(httpd_req_t *req);
bool hasClient(int socket); bool hasClient(int socket);
int count() { int count() { return _clients.size(); };
return _clients.size(); const std::list<PsychicClient*>& getClientList();
};
const std::list<PsychicClient *> & getClientList();
PsychicEndpoint * on(const char * uri); PsychicEndpoint* on(const char* uri);
PsychicEndpoint * on(const char * uri, http_method method); PsychicEndpoint* on(const char* uri, http_method method);
PsychicEndpoint * on(const char * uri, PsychicHttpRequestCallback onRequest); PsychicEndpoint* on(const char* uri, PsychicHandler *handler);
PsychicEndpoint * on(const char * uri, http_method method, PsychicHttpRequestCallback onRequest); PsychicEndpoint* on(const char* uri, http_method method, PsychicHandler *handler);
PsychicEndpoint * on(const char * uri, PsychicHandler * handler); PsychicEndpoint* on(const char* uri, PsychicHttpRequestCallback onRequest);
PsychicEndpoint * on(const char * uri, http_method method, PsychicHandler * handler); PsychicEndpoint* on(const char* uri, http_method method, PsychicHttpRequestCallback onRequest);
PsychicEndpoint * on(const char * uri, http_method method, PsychicJsonRequestCallback onRequest); // added proddy PsychicEndpoint* on(const char* uri, PsychicJsonRequestCallback onRequest);
PsychicEndpoint* on(const char* uri, http_method method, PsychicJsonRequestCallback onRequest);
static esp_err_t notFoundHandler(httpd_req_t * req, httpd_err_code_t err); static esp_err_t notFoundHandler(httpd_req_t *req, httpd_err_code_t err);
static esp_err_t defaultNotFoundHandler(PsychicRequest * request); static esp_err_t defaultNotFoundHandler(PsychicRequest *request);
void onNotFound(PsychicHttpRequestCallback fn); void onNotFound(PsychicHttpRequestCallback fn);
void onOpen(PsychicClientCallback handler); void onOpen(PsychicClientCallback handler);
void onClose(PsychicClientCallback handler); void onClose(PsychicClientCallback handler);
static esp_err_t openCallback(httpd_handle_t hd, int sockfd); static esp_err_t openCallback(httpd_handle_t hd, int sockfd);
static void closeCallback(httpd_handle_t hd, int sockfd); static void closeCallback(httpd_handle_t hd, int sockfd);
PsychicStaticFileHandler * serveStatic(const char * uri, fs::FS & fs, const char * path, const char * cache_control = NULL); PsychicStaticFileHandler* serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
}; };
bool ON_STA_FILTER(PsychicRequest * request); bool ON_STA_FILTER(PsychicRequest *request);
bool ON_AP_FILTER(PsychicRequest * request); bool ON_AP_FILTER(PsychicRequest *request);
#endif // PsychicHttpServer_h #endif // PsychicHttpServer_h

View File

@@ -1,48 +1,50 @@
#include "PsychicHttpsServer.h" #include "PsychicHttpsServer.h"
PsychicHttpsServer::PsychicHttpsServer() PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer()
: PsychicHttpServer() { {
//for a SSL server //for a SSL server
ssl_config = HTTPD_SSL_CONFIG_DEFAULT(); ssl_config = HTTPD_SSL_CONFIG_DEFAULT();
ssl_config.httpd.open_fn = PsychicHttpServer::openCallback; ssl_config.httpd.open_fn = PsychicHttpServer::openCallback;
ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback; ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback;
ssl_config.httpd.uri_match_fn = httpd_uri_match_wildcard; ssl_config.httpd.uri_match_fn = httpd_uri_match_wildcard;
ssl_config.httpd.global_user_ctx = this; ssl_config.httpd.global_user_ctx = this;
ssl_config.httpd.global_user_ctx_free_fn = destroy; ssl_config.httpd.global_user_ctx_free_fn = destroy;
ssl_config.httpd.max_uri_handlers = 20; ssl_config.httpd.max_uri_handlers = 20;
// each SSL connection takes about 45kb of heap // each SSL connection takes about 45kb of heap
// a barebones sketch with PsychicHttp has ~150kb of heap available // a barebones sketch with PsychicHttp has ~150kb of heap available
// if we set it higher than 2 and use all the connections, we get lots of memory errors. // 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. // not to mention there is no heap left over for the program itself.
ssl_config.httpd.max_open_sockets = 2; ssl_config.httpd.max_open_sockets = 2;
} }
PsychicHttpsServer::~PsychicHttpsServer() { PsychicHttpsServer::~PsychicHttpsServer() {}
esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, const char *private_key)
{
this->_use_ssl = true;
this->ssl_config.port_secure = port;
this->ssl_config.cacert_pem = (uint8_t *)cert;
this->ssl_config.cacert_len = strlen(cert)+1;
this->ssl_config.prvtkey_pem = (uint8_t *)private_key;
this->ssl_config.prvtkey_len = strlen(private_key)+1;
return this->_start();
} }
esp_err_t PsychicHttpsServer::listen(uint16_t port, const char * cert, const char * private_key) { esp_err_t PsychicHttpsServer::_startServer()
this->_use_ssl = true; {
if (this->_use_ssl)
this->ssl_config.port_secure = port; return httpd_ssl_start(&this->server, &this->ssl_config);
this->ssl_config.cacert_pem = (uint8_t *)cert; else
this->ssl_config.cacert_len = strlen(cert) + 1; return httpd_start(&this->server, &this->config);
this->ssl_config.prvtkey_pem = (uint8_t *)private_key;
this->ssl_config.prvtkey_len = strlen(private_key) + 1;
return this->_start();
} }
esp_err_t PsychicHttpsServer::_startServer() { void PsychicHttpsServer::stop()
if (this->_use_ssl) {
return httpd_ssl_start(&this->server, &this->ssl_config); if (this->_use_ssl)
else httpd_ssl_stop(this->server);
return httpd_start(&this->server, &this->config); else
} httpd_stop(this->server);
void PsychicHttpsServer::stop() {
if (this->_use_ssl)
httpd_ssl_stop(this->server);
else
httpd_stop(this->server);
} }

View File

@@ -6,12 +6,13 @@
#include <esp_https_server.h> #include <esp_https_server.h>
#if !CONFIG_HTTPD_WS_SUPPORT #if !CONFIG_HTTPD_WS_SUPPORT
#error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration #error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
#endif #endif
#define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features #define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features
class PsychicHttpsServer : public PsychicHttpServer { class PsychicHttpsServer : public PsychicHttpServer
{
protected: protected:
bool _use_ssl = false; bool _use_ssl = false;
@@ -22,10 +23,10 @@ class PsychicHttpsServer : public PsychicHttpServer {
httpd_ssl_config_t ssl_config; httpd_ssl_config_t ssl_config;
using PsychicHttpServer::listen; //keep the regular version using PsychicHttpServer::listen; //keep the regular version
esp_err_t listen(uint16_t port, const char * cert, const char * private_key); esp_err_t listen(uint16_t port, const char *cert, const char *private_key);
virtual esp_err_t _startServer() override final; virtual esp_err_t _startServer() override final;
virtual void stop() override final; virtual void stop() override final;
}; };
#endif // PsychicHttpsServer_h #endif // PsychicHttpsServer_h

View File

@@ -0,0 +1,150 @@
#include "PsychicJson.h"
#ifdef ARDUINOJSON_5_COMPATIBILITY
PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray) : PsychicResponse(request)
{
setContentType(JSON_MIMETYPE);
if (isArray)
_root = _jsonBuffer.createArray();
else
_root = _jsonBuffer.createObject();
}
#else
PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray, size_t maxJsonBufferSize) :
PsychicResponse(request),
_jsonBuffer(maxJsonBufferSize)
{
setContentType(JSON_MIMETYPE);
if (isArray)
_root = _jsonBuffer.createNestedArray();
else
_root = _jsonBuffer.createNestedObject();
}
#endif
JsonVariant &PsychicJsonResponse::getRoot() { return _root; }
size_t PsychicJsonResponse::getLength()
{
#ifdef ARDUINOJSON_5_COMPATIBILITY
return _root.measureLength();
#else
return measureJson(_root);
#endif
}
esp_err_t PsychicJsonResponse::send()
{
esp_err_t err = ESP_OK;
size_t length = getLength();
size_t buffer_size;
char *buffer;
DUMP(length);
//how big of a buffer do we want?
if (length < JSON_BUFFER_SIZE)
buffer_size = length+1;
else
buffer_size = JSON_BUFFER_SIZE;
DUMP(buffer_size);
buffer = (char *)malloc(buffer_size);
if (buffer == NULL) {
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
return ESP_FAIL;
}
//send it in one shot or no?
if (length < JSON_BUFFER_SIZE)
{
TRACE();
#ifdef ARDUINOJSON_5_COMPATIBILITY
_root.printTo(buffer, buffer_size);
#else
serializeJson(_root, buffer, buffer_size);
#endif
this->setContent((uint8_t *)buffer, length);
this->setContentType(JSON_MIMETYPE);
err = PsychicResponse::send();
}
else
{
//helper class that acts as a stream to print chunked responses
ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size);
//keep our headers
this->sendHeaders();
//these commands write to the ChunkPrinter which does the sending
#ifdef ARDUINOJSON_5_COMPATIBILITY
_root.printTo(dest);
#else
serializeJson(_root, dest);
#endif
//send the last bits
dest.flush();
//done with our chunked response too
err = this->finishChunking();
}
//let the buffer go
free(buffer);
return err;
}
#ifdef ARDUINOJSON_5_COMPATIBILITY
PsychicJsonHandler::PsychicJsonHandler() :
_onRequest(NULL)
{};
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) :
_onRequest(onRequest)
{}
#else
PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) :
_onRequest(NULL),
_maxJsonBufferSize(maxJsonBufferSize)
{};
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) :
_onRequest(onRequest),
_maxJsonBufferSize(maxJsonBufferSize)
{}
#endif
void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) { _onRequest = fn; }
esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest *request)
{
//process basic stuff
PsychicWebHandler::handleRequest(request);
if (_onRequest)
{
#ifdef ARDUINOJSON_5_COMPATIBILITY
DynamicJsonBuffer jsonBuffer;
JsonVariant json = jsonBuffer.parse();
if (!json.success())
return request->reply(400);
#else
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, request->body());
if (error)
return request->reply(400);
JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif
return _onRequest(request, json);
}
else
return request->reply(500);
}

View File

@@ -98,10 +98,12 @@ class PsychicJsonResponse : public PsychicResponse {
// } // }
virtual esp_err_t send() override { virtual esp_err_t send() override {
esp_err_t err = ESP_OK; esp_err_t err = ESP_OK;
size_t length = getLength(); // size_t length = getLength();
size_t buffer_size; size_t length = JSON_BUFFER_SIZE; // TODO force chunking
char * buffer;
size_t buffer_size;
char * buffer;
DUMP(length); DUMP(length);

View File

@@ -1,6 +1,7 @@
#include "PsychicRequest.h" #include "PsychicRequest.h"
#include "PsychicResponse.h" #include "http_status.h"
#include <http_status.h> #include "PsychicHttpServer.h"
PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) : PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) :
_server(server), _server(server),

View File

@@ -5,6 +5,7 @@
#include "PsychicHttpServer.h" #include "PsychicHttpServer.h"
#include "PsychicClient.h" #include "PsychicClient.h"
#include "PsychicWebParameter.h" #include "PsychicWebParameter.h"
#include "PsychicResponse.h"
typedef std::map<String, String> SessionData; typedef std::map<String, String> SessionData;

View File

@@ -2,137 +2,154 @@
#include "PsychicRequest.h" #include "PsychicRequest.h"
#include <http_status.h> #include <http_status.h>
PsychicResponse::PsychicResponse(PsychicRequest * request) PsychicResponse::PsychicResponse(PsychicRequest *request) :
: _request(request) _request(request),
, _code(200) _code(200),
, _status("") _status(""),
, _contentLength(0) _contentLength(0),
, _body("") { _body("")
{
} }
PsychicResponse::~PsychicResponse() { PsychicResponse::~PsychicResponse()
//clean up our header variables. we have to do this since httpd_resp_send doesn't store copies {
for (HTTPHeader header : _headers) { //clean up our header variables. we have to do this since httpd_resp_send doesn't store copies
free(header.field); for (HTTPHeader header : _headers)
free(header.value); {
} free(header.field);
_headers.clear(); 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 during send() {
HTTPHeader header; //these get freed during send()
header.field = (char *)malloc(strlen(field) + 1); HTTPHeader header;
header.value = (char *)malloc(strlen(value) + 1); header.field =(char *)malloc(strlen(field)+1);
header.value = (char *)malloc(strlen(value)+1);
strlcpy(header.field, field, strlen(field) + 1); strlcpy(header.field, field, strlen(field)+1);
strlcpy(header.value, value, strlen(value) + 1); strlcpy(header.value, value, strlen(value)+1);
_headers.push_back(header); _headers.push_back(header);
} }
void PsychicResponse::setCookie(const char * name, const char * value, unsigned long secondsFromNow, const char * extras) { void PsychicResponse::setCookie(const char *name, const char *value, unsigned long secondsFromNow, const char *extras)
time_t now = time(nullptr); {
time_t now = time(nullptr);
String output; String output;
output = urlEncode(name) + "=" + urlEncode(value); 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) if (now < 1700000000)
output += "; Max-Age=" + String(secondsFromNow); output += "; Max-Age=" + String(secondsFromNow);
//otherwise, set an expiration date //otherwise, set an expiration date
else { else
time_t expirationTimestamp = now + secondsFromNow; {
time_t expirationTimestamp = now + secondsFromNow;
// Convert the expiration timestamp to a formatted string for the "expires" attribute // Convert the expiration timestamp to a formatted string for the "expires" attribute
struct tm * tmInfo = gmtime(&expirationTimestamp); struct tm* tmInfo = gmtime(&expirationTimestamp);
char expires[30]; char expires[30];
strftime(expires, sizeof(expires), "%a, %d %b %Y %H:%M:%S GMT", tmInfo); strftime(expires, sizeof(expires), "%a, %d %b %Y %H:%M:%S GMT", tmInfo);
output += "; Expires=" + String(expires); output += "; Expires=" + String(expires);
} }
//did we get any extras? //did we get any extras?
if (strlen(extras)) if (strlen(extras))
output += "; " + String(extras); output += "; " + String(extras);
//okay, add it in. //okay, add it in.
addHeader("Set-Cookie", output.c_str()); addHeader("Set-Cookie", output.c_str());
} }
// time_t now = time(nullptr); // time_t now = time(nullptr);
// // Set the cookie with the "expires" attribute // // Set the cookie with the "expires" attribute
void PsychicResponse::setCode(int code) { void PsychicResponse::setCode(int code)
_code = code; {
_code = code;
} }
void PsychicResponse::setContentType(const char * contentType) { void PsychicResponse::setContentType(const char *contentType)
httpd_resp_set_type(_request->request(), contentType); {
httpd_resp_set_type(_request->request(), contentType);
} }
void PsychicResponse::setContent(const char * content) { void PsychicResponse::setContent(const char *content)
_body = content; {
setContentLength(strlen(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; {
setContentLength(len); _body = (char *)content;
setContentLength(len);
} }
const char * PsychicResponse::getContent() { const char * PsychicResponse::getContent()
return _body; {
return _body;
} }
size_t PsychicResponse::getContentLength() { size_t PsychicResponse::getContentLength()
return _contentLength; {
return _contentLength;
} }
esp_err_t PsychicResponse::send() { esp_err_t PsychicResponse::send()
//esp-idf makes you set the whole status. {
sprintf(_status, "%u %s", _code, http_status_reason(_code)); //esp-idf makes you set the whole status.
httpd_resp_set_status(_request->request(), _status); sprintf(_status, "%u %s", _code, http_status_reason(_code));
httpd_resp_set_status(_request->request(), _status);
//our headers too //our headers too
this->sendHeaders(); this->sendHeaders();
//now send it off //now send it off
esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength()); esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength());
//did something happen? //did something happen?
if (err != ESP_OK) if (err != ESP_OK)
ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err)); ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err));
return err; return err;
} }
void PsychicResponse::sendHeaders() { void PsychicResponse::sendHeaders()
//get our global headers out of the way first {
for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) //get our global headers out of the way first
httpd_resp_set_hdr(_request->request(), header.field, header.value); for (HTTPHeader header : DefaultHeaders::Instance().getHeaders())
httpd_resp_set_hdr(_request->request(), header.field, header.value);
//now do our individual headers //now do our individual headers
for (HTTPHeader header : _headers) for (HTTPHeader header : _headers)
httpd_resp_set_hdr(this->_request->request(), header.field, header.value); httpd_resp_set_hdr(this->_request->request(), header.field, header.value);
} }
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); /* Send the buffer contents as HTTP response chunk */
if (err != ESP_OK) { esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize);
ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err)); if (err != ESP_OK)
{
ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err));
/* Abort sending file */ /* Abort sending file */
httpd_resp_sendstr_chunk(this->_request->request(), NULL); httpd_resp_sendstr_chunk(this->_request->request(), NULL);
/* Respond with 500 Internal Server Error */ /* Respond with 500 Internal Server Error */
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
} }
return err; return err;
} }
esp_err_t PsychicResponse::finishChunking() { 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); /* Respond with an empty chunk to signal HTTP response completion */
return httpd_resp_send_chunk(this->_request->request(), NULL, 0);
} }

View File

@@ -6,44 +6,41 @@
class PsychicRequest; class PsychicRequest;
class PsychicResponse { class PsychicResponse
{
protected: protected:
PsychicRequest * _request; PsychicRequest *_request;
int _code; int _code;
char _status[60]; char _status[60];
std::list<HTTPHeader> _headers; std::list<HTTPHeader> _headers;
int64_t _contentLength; int64_t _contentLength;
const char * _body; const char * _body;
public: public:
PsychicResponse(PsychicRequest * request); PsychicResponse(PsychicRequest *request);
virtual ~PsychicResponse(); virtual ~PsychicResponse();
void setCode(int code); void setCode(int code);
void setContentType(const char * contentType); void setContentType(const char *contentType);
void setContentLength(int64_t contentLength) { void setContentLength(int64_t contentLength) { _contentLength = contentLength; }
_contentLength = contentLength; int64_t getContentLength(int64_t contentLength) { return _contentLength; }
}
int64_t getContentLength(int64_t contentLength) {
return _contentLength;
}
void addHeader(const char * field, const char * value); void addHeader(const char *field, const char *value);
void setCookie(const char * key, const char * value, unsigned long max_age = 60 * 60 * 24 * 30, const char * extras = ""); void setCookie(const char *key, const char *value, unsigned long max_age = 60*60*24*30, const char *extras = "");
void setContent(const char * content); void setContent(const char *content);
void setContent(const uint8_t * content, size_t len); void setContent(const uint8_t *content, size_t len);
const char * getContent(); const char * getContent();
size_t getContentLength(); size_t getContentLength();
virtual esp_err_t send(); virtual esp_err_t send();
void sendHeaders(); 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 finishChunking();
}; };
#endif // PsychicResponse_h #endif // PsychicResponse_h

View File

@@ -4,192 +4,190 @@
/* PsychicStaticFileHandler */ /* PsychicStaticFileHandler */
/*************************************/ /*************************************/
PsychicStaticFileHandler::PsychicStaticFileHandler(const char * uri, FS & fs, const char * path, const char * cache_control) PsychicStaticFileHandler::PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
: _fs(fs) : _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("")
, _uri(uri) {
, _path(path) // Ensure leading '/'
, _default_file("index.html") if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
, _cache_control(cache_control) if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path;
, _last_modified("") {
// Ensure leading '/'
if (_uri.length() == 0 || _uri[0] != '/')
_uri = "/" + _uri;
if (_path.length() == 0 || _path[0] != '/')
_path = "/" + _path;
// If path ends with '/' we assume a hint that this is a directory to improve performance. // If path ends with '/' we assume a hint that this is a directory to improve performance.
// However - if it does not end with '/' we, can't assume a file, path can still be a directory. // However - if it does not end with '/' we, can't assume a file, path can still be a directory.
_isDir = _path[_path.length() - 1] == '/'; _isDir = _path[_path.length()-1] == '/';
// Remove the trailing '/' so we can handle default file // Remove the trailing '/' so we can handle default file
// Notice that root will be "" not "/" // Notice that root will be "" not "/"
if (_uri[_uri.length() - 1] == '/') if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
_uri = _uri.substring(0, _uri.length() - 1); if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
if (_path[_path.length() - 1] == '/')
_path = _path.substring(0, _path.length() - 1);
// Reset stats // Reset stats
_gzipFirst = false; _gzipFirst = false;
_gzipStats = 0xF8; _gzipStats = 0xF8;
} }
PsychicStaticFileHandler & PsychicStaticFileHandler::setIsDir(bool isDir) { PsychicStaticFileHandler& PsychicStaticFileHandler::setIsDir(bool isDir){
_isDir = isDir; _isDir = isDir;
return *this; return *this;
} }
PsychicStaticFileHandler & PsychicStaticFileHandler::setDefaultFile(const char * filename) { PsychicStaticFileHandler& PsychicStaticFileHandler::setDefaultFile(const char* filename){
_default_file = String(filename); _default_file = String(filename);
return *this; return *this;
} }
PsychicStaticFileHandler & PsychicStaticFileHandler::setCacheControl(const char * cache_control) { PsychicStaticFileHandler& PsychicStaticFileHandler::setCacheControl(const char* cache_control){
_cache_control = String(cache_control); _cache_control = String(cache_control);
return *this; return *this;
} }
PsychicStaticFileHandler & PsychicStaticFileHandler::setLastModified(const char * last_modified) { PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(const char* last_modified){
_last_modified = String(last_modified); _last_modified = String(last_modified);
return *this; return *this;
} }
PsychicStaticFileHandler & PsychicStaticFileHandler::setLastModified(struct tm * last_modified) { PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(struct tm* last_modified){
char result[30]; char result[30];
strftime(result, 30, "%a, %d %b %Y %H:%M:%S %Z", last_modified); strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified);
return setLastModified((const char *)result); return setLastModified((const char *)result);
} }
bool PsychicStaticFileHandler::canHandle(PsychicRequest * request) { bool PsychicStaticFileHandler::canHandle(PsychicRequest *request)
if (request->method() != HTTP_GET || !request->uri().startsWith(_uri)) {
return false; if(request->method() != HTTP_GET || !request->uri().startsWith(_uri) )
if (_getFile(request))
return true;
return false; return false;
if (_getFile(request))
return true;
return false;
} }
bool PsychicStaticFileHandler::_getFile(PsychicRequest * request) { bool PsychicStaticFileHandler::_getFile(PsychicRequest *request)
// Remove the found uri {
String path = request->uri().substring(_uri.length()); // Remove the found uri
String path = request->uri().substring(_uri.length());
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/'); bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/');
path = _path + path; path = _path + path;
// Do we have a file or .gz file // Do we have a file or .gz file
if (!canSkipFileCheck && _fileExists(path)) if (!canSkipFileCheck && _fileExists(path))
return true; return true;
// Can't handle if not default file // Can't handle if not default file
if (_default_file.length() == 0) if (_default_file.length() == 0)
return false; return false;
// Try to add default file, ensure there is a trailing '/' ot the path. // Try to add default file, ensure there is a trailing '/' ot the path.
if (path.length() == 0 || path[path.length() - 1] != '/') if (path.length() == 0 || path[path.length()-1] != '/')
path += "/"; path += "/";
path += _default_file; path += _default_file;
return _fileExists(path); return _fileExists(path);
} }
#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) #define FILE_IS_REAL(f) (f == true && !f.isDirectory())
bool PsychicStaticFileHandler::_fileExists(const String & path) { bool PsychicStaticFileHandler::_fileExists(const String& path)
bool fileFound = false; {
bool gzipFound = false; bool fileFound = false;
bool gzipFound = false;
String gzip = path + ".gz"; String gzip = path + ".gz";
if (_gzipFirst) { if (_gzipFirst) {
_file = _fs.open(gzip, "r"); _file = _fs.open(gzip, "r");
gzipFound = FILE_IS_REAL(_file); gzipFound = FILE_IS_REAL(_file);
if (!gzipFound) { if (!gzipFound){
_file = _fs.open(path, "r"); _file = _fs.open(path, "r");
fileFound = FILE_IS_REAL(_file); fileFound = FILE_IS_REAL(_file);
}
} else {
_file = _fs.open(path, "r");
fileFound = FILE_IS_REAL(_file);
if (!fileFound) {
_file = _fs.open(gzip, "r");
gzipFound = FILE_IS_REAL(_file);
}
} }
} else {
bool found = fileFound || gzipFound; _file = _fs.open(path, "r");
fileFound = FILE_IS_REAL(_file);
if (found) { if (!fileFound){
_filename = path; _file = _fs.open(gzip, "r");
gzipFound = FILE_IS_REAL(_file);
// Calculate gzip statistic
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
if (_gzipStats == 0x00)
_gzipFirst = false; // All files are not gzip
else if (_gzipStats == 0xFF)
_gzipFirst = true; // All files are gzip
else
_gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
} }
}
return found; bool found = fileFound || gzipFound;
if (found)
{
_filename = path;
// Calculate gzip statistic
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
}
return found;
} }
uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const { uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const
uint8_t w = value; {
uint8_t n; uint8_t w = value;
for (n = 0; w != 0; n++) uint8_t n;
w &= w - 1; for (n=0; w!=0; n++) w&=w-1;
return n; return n;
} }
esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest * request) { esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request)
if (_file == true) { {
DUMP(_filename); if (_file == true)
{
DUMP(_filename);
//is it not modified? //is it not modified?
String etag = String(_file.size()); String etag = String(_file.size());
if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { if (_last_modified.length() && _last_modified == request->header("If-Modified-Since"))
DUMP("Last Modified Hit"); {
TRACE(); DUMP("Last Modified Hit");
_file.close(); TRACE();
request->reply(304); // Not modified _file.close();
} request->reply(304); // Not modified
//does our Etag match?
else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) {
DUMP("Etag Hit");
DUMP(etag);
DUMP(_cache_control);
_file.close();
PsychicResponse response(request);
response.addHeader("Cache-Control", _cache_control.c_str());
response.addHeader("ETag", etag.c_str());
response.setCode(304);
response.send();
}
//nope, send them the full file.
else {
DUMP("No cache hit");
DUMP(_last_modified);
DUMP(_cache_control);
PsychicFileResponse response(request, _fs, _filename);
if (_last_modified.length())
response.addHeader("Last-Modified", _last_modified.c_str());
if (_cache_control.length()) {
response.addHeader("Cache-Control", _cache_control.c_str());
response.addHeader("ETag", etag.c_str());
}
return response.send();
}
} else {
return request->reply(404);
} }
//does our Etag match?
else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag))
{
DUMP("Etag Hit");
DUMP(etag);
DUMP(_cache_control);
return ESP_OK; _file.close();
PsychicResponse response(request);
response.addHeader("Cache-Control", _cache_control.c_str());
response.addHeader("ETag", etag.c_str());
response.setCode(304);
response.send();
}
//nope, send them the full file.
else
{
DUMP("No cache hit");
DUMP(_last_modified);
DUMP(_cache_control);
PsychicFileResponse response(request, _fs, _filename);
if (_last_modified.length())
response.addHeader("Last-Modified", _last_modified.c_str());
if (_cache_control.length()) {
response.addHeader("Cache-Control", _cache_control.c_str());
response.addHeader("ETag", etag.c_str());
}
return response.send();
}
} else {
return request->reply(404);
}
return ESP_OK;
} }

View File

@@ -8,36 +8,33 @@
#include "PsychicFileResponse.h" #include "PsychicFileResponse.h"
class PsychicStaticFileHandler : public PsychicWebHandler { class PsychicStaticFileHandler : public PsychicWebHandler {
using File = fs::File; using File = fs::File;
using FS = fs::FS; using FS = fs::FS;
private: private:
bool _getFile(PsychicRequest * request); bool _getFile(PsychicRequest *request);
bool _fileExists(const String & path); bool _fileExists(const String& path);
uint8_t _countBits(const uint8_t value) const; uint8_t _countBits(const uint8_t value) const;
protected: protected:
FS _fs; FS _fs;
File _file; File _file;
String _filename; String _filename;
String _uri; String _uri;
String _path; String _path;
String _default_file; String _default_file;
String _cache_control; String _cache_control;
String _last_modified; String _last_modified;
bool _isDir; bool _isDir;
bool _gzipFirst; bool _gzipFirst;
uint8_t _gzipStats; uint8_t _gzipStats;
public: public:
PsychicStaticFileHandler(const char * uri, FS & fs, const char * path, const char * cache_control); PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
bool canHandle(PsychicRequest * request) override; bool canHandle(PsychicRequest *request) override;
esp_err_t handleRequest(PsychicRequest * request) override; esp_err_t handleRequest(PsychicRequest *request) override;
PsychicStaticFileHandler & setIsDir(bool isDir); PsychicStaticFileHandler& setIsDir(bool isDir);
PsychicStaticFileHandler & setDefaultFile(const char * filename); PsychicStaticFileHandler& setDefaultFile(const char* filename);
PsychicStaticFileHandler & setCacheControl(const char * cache_control); PsychicStaticFileHandler& setCacheControl(const char* cache_control);
PsychicStaticFileHandler & setLastModified(const char * last_modified); PsychicStaticFileHandler& setLastModified(const char* last_modified);
PsychicStaticFileHandler & setLastModified(struct tm * last_modified); PsychicStaticFileHandler& setLastModified(struct tm* last_modified);
//PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} //PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
}; };

View File

@@ -0,0 +1,89 @@
#include "PsychicStreamResponse.h"
#include "PsychicResponse.h"
#include "PsychicRequest.h"
PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType)
: PsychicResponse(request), _buffer(NULL) {
setContentType(contentType.c_str());
addHeader("Content-Disposition", "inline");
}
PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name)
: PsychicResponse(request), _buffer(NULL) {
setContentType(contentType.c_str());
char buf[26+name.length()];
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", name);
addHeader("Content-Disposition", buf);
}
PsychicStreamResponse::~PsychicStreamResponse()
{
endSend();
}
esp_err_t PsychicStreamResponse::beginSend()
{
if(_buffer)
return ESP_OK;
//Buffer to hold ChunkPrinter and stream buffer. Using placement new will keep us at a single allocation.
_buffer = (uint8_t*)malloc(STREAM_CHUNK_SIZE + sizeof(ChunkPrinter));
if(!_buffer)
{
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
return ESP_FAIL;
}
_printer = new (_buffer) ChunkPrinter(this, _buffer + sizeof(ChunkPrinter), STREAM_CHUNK_SIZE);
sendHeaders();
return ESP_OK;
}
esp_err_t PsychicStreamResponse::endSend()
{
esp_err_t err = ESP_OK;
if(!_buffer)
err = ESP_FAIL;
else
{
//flush & send remaining data.
_printer->flush();
err = finishChunking();
//Free memory
_printer->~ChunkPrinter();
free(_buffer);
_buffer = NULL;
}
return err;
}
void PsychicStreamResponse::flush()
{
if(_buffer)
_printer->flush();
}
size_t PsychicStreamResponse::write(uint8_t data)
{
return _buffer ? _printer->write(data) : 0;
}
size_t PsychicStreamResponse::copyFrom(Stream &stream)
{
size_t sentCount = 0;
if(_buffer)
{
while(stream.available())
sentCount += _printer->write(stream.read());
}
return sentCount;
}

View File

@@ -0,0 +1,33 @@
#ifndef PsychicStreamResponse_h
#define PsychicStreamResponse_h
#include "PsychicCore.h"
#include "PsychicResponse.h"
#include "ChunkPrinter.h"
class PsychicRequest;
class PsychicStreamResponse : public PsychicResponse, public Print
{
private:
ChunkPrinter *_printer;
uint8_t *_buffer;
public:
PsychicStreamResponse(PsychicRequest *request, const String& contentType);
PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name); //Download
~PsychicStreamResponse();
esp_err_t beginSend();
esp_err_t endSend();
virtual void flush() override;
size_t write(uint8_t data);
size_t copyFrom(Stream &stream);
using Print::write;
};
#endif // PsychicStreamResponse_h

View File

@@ -8,7 +8,7 @@
#include "PsychicWebParameter.h" #include "PsychicWebParameter.h"
//callback definitions //callback definitions
typedef std::function<esp_err_t(PsychicRequest * request, const String & filename, uint64_t index, uint8_t * data, size_t len, bool final)> PsychicUploadCallback; typedef std::function<esp_err_t(PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final)> PsychicUploadCallback;
/* /*
* HANDLER :: Can be attached to any endpoint or as a generic request handler. * HANDLER :: Can be attached to any endpoint or as a generic request handler.
@@ -18,25 +18,25 @@ class PsychicUploadHandler : public PsychicWebHandler {
protected: protected:
PsychicUploadCallback _uploadCallback; PsychicUploadCallback _uploadCallback;
PsychicRequest * _request; PsychicRequest *_request;
String _temp; String _temp;
size_t _parsedLength; size_t _parsedLength;
uint8_t _multiParseState; uint8_t _multiParseState;
String _boundary; String _boundary;
uint8_t _boundaryPosition; uint8_t _boundaryPosition;
size_t _itemStartIndex; size_t _itemStartIndex;
size_t _itemSize; size_t _itemSize;
String _itemName; String _itemName;
String _itemFilename; String _itemFilename;
String _itemType; String _itemType;
String _itemValue; String _itemValue;
uint8_t * _itemBuffer; uint8_t *_itemBuffer;
size_t _itemBufferIndex; size_t _itemBufferIndex;
bool _itemIsFile; bool _itemIsFile;
esp_err_t _basicUploadHandler(PsychicRequest * request); esp_err_t _basicUploadHandler(PsychicRequest *request);
esp_err_t _multipartUploadHandler(PsychicRequest * request); esp_err_t _multipartUploadHandler(PsychicRequest *request);
void _handleUploadByte(uint8_t data, bool last); void _handleUploadByte(uint8_t data, bool last);
void _parseMultipartPostByte(uint8_t data, bool last); void _parseMultipartPostByte(uint8_t data, bool last);
@@ -45,24 +45,24 @@ class PsychicUploadHandler : public PsychicWebHandler {
PsychicUploadHandler(); PsychicUploadHandler();
~PsychicUploadHandler(); ~PsychicUploadHandler();
bool canHandle(PsychicRequest * request) override; bool canHandle(PsychicRequest *request) override;
esp_err_t handleRequest(PsychicRequest * request) override; esp_err_t handleRequest(PsychicRequest *request) override;
PsychicUploadHandler * onUpload(PsychicUploadCallback fn); PsychicUploadHandler * onUpload(PsychicUploadCallback fn);
}; };
enum { enum {
EXPECT_BOUNDARY, EXPECT_BOUNDARY,
PARSE_HEADERS, PARSE_HEADERS,
WAIT_FOR_RETURN1, WAIT_FOR_RETURN1,
EXPECT_FEED1, EXPECT_FEED1,
EXPECT_DASH1, EXPECT_DASH1,
EXPECT_DASH2, EXPECT_DASH2,
BOUNDARY_OR_DATA, BOUNDARY_OR_DATA,
DASH3_OR_RETURN2, DASH3_OR_RETURN2,
EXPECT_FEED2, EXPECT_FEED2,
PARSING_FINISHED, PARSING_FINISHED,
PARSE_ERROR PARSE_ERROR
}; };
#endif // PsychicUploadHandler_h #endif // PsychicUploadHandler_h

View File

@@ -1,46 +1,74 @@
#include "PsychicWebHandler.h" #include "PsychicWebHandler.h"
PsychicWebHandler::PsychicWebHandler() PsychicWebHandler::PsychicWebHandler() :
: PsychicHandler() PsychicHandler(),
, _requestCallback(NULL) { _requestCallback(NULL),
} _onOpen(NULL),
PsychicWebHandler::~PsychicWebHandler() { _onClose(NULL)
{}
PsychicWebHandler::~PsychicWebHandler() {}
bool PsychicWebHandler::canHandle(PsychicRequest *request) {
return true;
} }
bool PsychicWebHandler::canHandle(PsychicRequest * request) { esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request)
return true; {
} //lookup our client
PsychicClient *client = checkForNewClient(request->client());
if (client->isNew)
openCallback(client);
esp_err_t PsychicWebHandler::handleRequest(PsychicRequest * request) { /* Request body cannot be larger than a limit */
/* Request body cannot be larger than a limit */ if (request->contentLength() > request->server()->maxRequestBodySize)
if (request->contentLength() > request->server()->maxRequestBodySize) { {
ESP_LOGE(PH_TAG, "Request body too large : %d bytes", request->contentLength()); ESP_LOGE(PH_TAG, "Request body too large : %d bytes", request->contentLength());
/* Respond with 400 Bad Request */ /* Respond with 400 Bad Request */
char error[60]; char error[60];
sprintf(error, "Request body must be less than %u bytes!", request->server()->maxRequestBodySize); sprintf(error, "Request body must be less than %u bytes!", request->server()->maxRequestBodySize);
httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error);
/* Return failure to close underlying connection else the incoming file content will keep the socket busy */ /* Return failure to close underlying connection else the incoming file content will keep the socket busy */
return ESP_FAIL; return ESP_FAIL;
} }
//get our body loaded up.
esp_err_t err = request->loadBody();
if (err != ESP_OK)
return err;
//load our params in.
request->loadParams();
//okay, pass on to our callback.
if (this->_requestCallback != NULL)
err = this->_requestCallback(request);
//get our body loaded up.
esp_err_t err = request->loadBody();
if (err != ESP_OK)
return err; return err;
//load our params in.
request->loadParams();
//okay, pass on to our callback.
if (this->_requestCallback != NULL)
err = this->_requestCallback(request);
return err;
} }
PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) { PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) {
_requestCallback = fn; _requestCallback = fn;
return this; return this;
}
void PsychicWebHandler::openCallback(PsychicClient *client) {
if (_onOpen != NULL)
_onOpen(client);
}
void PsychicWebHandler::closeCallback(PsychicClient *client) {
if (_onClose != NULL)
_onClose(getClient(client));
}
PsychicWebHandler * PsychicWebHandler::onOpen(PsychicClientCallback fn) {
_onOpen = fn;
return this;
}
PsychicWebHandler * PsychicWebHandler::onClose(PsychicClientCallback fn) {
_onClose = fn;
return this;
} }

View File

@@ -1,9 +1,9 @@
#ifndef PsychicWebHandler_h #ifndef PsychicWebHandler_h
#define PsychicWebHandler_h #define PsychicWebHandler_h
#include "PsychicCore.h" // #include "PsychicCore.h"
#include "PsychicHttpServer.h" // #include "PsychicHttpServer.h"
#include "PsychicRequest.h" // #include "PsychicRequest.h"
#include "PsychicHandler.h" #include "PsychicHandler.h"
/* /*
@@ -13,14 +13,22 @@
class PsychicWebHandler : public PsychicHandler { class PsychicWebHandler : public PsychicHandler {
protected: protected:
PsychicHttpRequestCallback _requestCallback; PsychicHttpRequestCallback _requestCallback;
PsychicClientCallback _onOpen;
PsychicClientCallback _onClose;
public: public:
PsychicWebHandler(); PsychicWebHandler();
~PsychicWebHandler(); ~PsychicWebHandler();
virtual bool canHandle(PsychicRequest * request) override; virtual bool canHandle(PsychicRequest *request) override;
virtual esp_err_t handleRequest(PsychicRequest * request) override; virtual esp_err_t handleRequest(PsychicRequest *request) override;
PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn); PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn);
virtual void openCallback(PsychicClient *client);
virtual void closeCallback(PsychicClient *client);
PsychicWebHandler *onOpen(PsychicClientCallback fn);
PsychicWebHandler *onClose(PsychicClientCallback fn);
}; };
#endif #endif

View File

@@ -10,32 +10,16 @@ class PsychicWebParameter {
String _name; String _name;
String _value; String _value;
size_t _size; size_t _size;
bool _isForm; bool _isForm;
bool _isFile; bool _isFile;
public: public:
PsychicWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0) PsychicWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){}
: _name(name) const String& name() const { return _name; }
, _value(value) const String& value() const { return _value; }
, _size(size) size_t size() const { return _size; }
, _isForm(form) bool isPost() const { return _isForm; }
, _isFile(file) { bool isFile() const { return _isFile; }
}
const String & name() const {
return _name;
}
const String & value() const {
return _value;
}
size_t size() const {
return _size;
}
bool isPost() const {
return _isForm;
}
bool isFile() const {
return _isFile;
}
}; };
#endif //PsychicWebParameter_h #endif //PsychicWebParameter_h

View File

@@ -4,240 +4,257 @@
/* PsychicWebSocketRequest */ /* PsychicWebSocketRequest */
/*************************************/ /*************************************/
PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest * req) PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest *req) :
: PsychicRequest(req->server(), req->request()) PsychicRequest(req->server(), req->request()),
, _client(req->client()) { _client(req->client())
{
} }
PsychicWebSocketRequest::~PsychicWebSocketRequest() { PsychicWebSocketRequest::~PsychicWebSocketRequest()
{
} }
PsychicWebSocketClient * PsychicWebSocketRequest::client() { PsychicWebSocketClient * PsychicWebSocketRequest::client() {
return &_client; return &_client;
} }
esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t * ws_pkt) { esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t * ws_pkt)
return httpd_ws_send_frame(this->_req, ws_pkt); {
return httpd_ws_send_frame(this->_req, ws_pkt);
}
esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void *data, size_t len)
{
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = (uint8_t*)data;
ws_pkt.len = len;
ws_pkt.type = op;
return this->reply(&ws_pkt);
} }
esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void * data, size_t len) { esp_err_t PsychicWebSocketRequest::reply(const char *buf)
httpd_ws_frame_t ws_pkt; {
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf));
ws_pkt.payload = (uint8_t *)data;
ws_pkt.len = len;
ws_pkt.type = op;
return this->reply(&ws_pkt);
}
esp_err_t PsychicWebSocketRequest::reply(const char * buf) {
return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf));
} }
/*************************************/ /*************************************/
/* PsychicWebSocketClient */ /* PsychicWebSocketClient */
/*************************************/ /*************************************/
PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient * client) PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient *client)
: PsychicClient(client->server(), client->socket()) { : PsychicClient(client->server(), client->socket())
{
} }
PsychicWebSocketClient::~PsychicWebSocketClient() { PsychicWebSocketClient::~PsychicWebSocketClient() {
} }
esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt) { esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt)
return httpd_ws_send_frame_async(this->server(), this->socket(), ws_pkt); {
return httpd_ws_send_frame_async(this->server(), this->socket(), ws_pkt);
}
esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void *data, size_t len)
{
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = (uint8_t*)data;
ws_pkt.len = len;
ws_pkt.type = op;
return this->sendMessage(&ws_pkt);
} }
esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void * data, size_t len) { esp_err_t PsychicWebSocketClient::sendMessage(const char *buf)
httpd_ws_frame_t ws_pkt; {
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf));
ws_pkt.payload = (uint8_t *)data;
ws_pkt.len = len;
ws_pkt.type = op;
return this->sendMessage(&ws_pkt);
} }
esp_err_t PsychicWebSocketClient::sendMessage(const char * buf) { PsychicWebSocketHandler::PsychicWebSocketHandler() :
return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); PsychicHandler(),
} _onOpen(NULL),
_onFrame(NULL),
PsychicWebSocketHandler::PsychicWebSocketHandler() _onClose(NULL)
: PsychicHandler() {
, _onOpen(NULL) }
, _onFrame(NULL)
, _onClose(NULL) {
}
PsychicWebSocketHandler::~PsychicWebSocketHandler() { PsychicWebSocketHandler::~PsychicWebSocketHandler() {
} }
PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket) { PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket)
PsychicClient * client = PsychicHandler::getClient(socket); {
if (client == NULL) PsychicClient *client = PsychicHandler::getClient(socket);
return NULL; if (client == NULL)
return NULL;
if (client->_friend == NULL) { if (client->_friend == NULL)
DUMP(socket); {
return NULL; DUMP(socket);
} return NULL;
}
return (PsychicWebSocketClient *)client->_friend; return (PsychicWebSocketClient *)client->_friend;
} }
PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClient * client) { PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClient *client) {
return getClient(client->socket()); return getClient(client->socket());
} }
void PsychicWebSocketHandler::addClient(PsychicClient * client) { void PsychicWebSocketHandler::addClient(PsychicClient *client) {
client->_friend = new PsychicWebSocketClient(client); client->_friend = new PsychicWebSocketClient(client);
PsychicHandler::addClient(client); PsychicHandler::addClient(client);
} }
void PsychicWebSocketHandler::removeClient(PsychicClient * client) { void PsychicWebSocketHandler::removeClient(PsychicClient *client) {
PsychicHandler::removeClient(client); PsychicHandler::removeClient(client);
delete (PsychicWebSocketClient *)client->_friend; delete (PsychicWebSocketClient*)client->_friend;
client->_friend = NULL; client->_friend = NULL;
} }
void PsychicWebSocketHandler::openCallback(PsychicClient * client) { void PsychicWebSocketHandler::openCallback(PsychicClient *client) {
PsychicWebSocketClient * buddy = getClient(client); PsychicWebSocketClient *buddy = getClient(client);
if (buddy == NULL) { if (buddy == NULL)
TRACE(); {
return; TRACE();
} return;
}
if (_onOpen != NULL) if (_onOpen != NULL)
_onOpen(getClient(buddy)); _onOpen(getClient(buddy));
} }
void PsychicWebSocketHandler::closeCallback(PsychicClient * client) { void PsychicWebSocketHandler::closeCallback(PsychicClient *client) {
PsychicWebSocketClient * buddy = getClient(client); PsychicWebSocketClient *buddy = getClient(client);
if (buddy == NULL) { if (buddy == NULL)
TRACE(); {
return; TRACE();
} return;
}
if (_onClose != NULL) if (_onClose != NULL)
_onClose(getClient(buddy)); _onClose(getClient(buddy));
} }
bool PsychicWebSocketHandler::isWebSocket() { bool PsychicWebSocketHandler::isWebSocket() { return true; }
return true;
}
esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest * request) { esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request)
//lookup our client {
PsychicClient * client = checkForNewClient(request->client()); //lookup our client
PsychicClient *client = checkForNewClient(request->client());
// beginning of the ws URI handler and our onConnect hook // beginning of the ws URI handler and our onConnect hook
if (request->method() == HTTP_GET) { if (request->method() == HTTP_GET)
if (client->isNew) {
openCallback(client); if (client->isNew)
openCallback(client);
return ESP_OK; return ESP_OK;
} }
//prep our request //prep our request
PsychicWebSocketRequest wsRequest(request); PsychicWebSocketRequest wsRequest(request);
//init our memory for storing the packet //init our memory for storing the packet
httpd_ws_frame_t ws_pkt; httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.type = HTTPD_WS_TYPE_TEXT; ws_pkt.type = HTTPD_WS_TYPE_TEXT;
uint8_t * buf = NULL; uint8_t *buf = NULL;
/* Set max_len = 0 to get the frame len */
esp_err_t ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, 0);
if (ret != ESP_OK) {
ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed to get frame len with %s", esp_err_to_name(ret));
return ret;
}
//okay, now try to load the packet
ESP_LOGI(PH_TAG, "frame len is %d", ws_pkt.len);
if (ws_pkt.len) {
/* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
buf = (uint8_t *)calloc(1, ws_pkt.len + 1);
if (buf == NULL) {
ESP_LOGE(PH_TAG, "Failed to calloc memory for buf");
return ESP_ERR_NO_MEM;
}
ws_pkt.payload = buf;
/* Set max_len = ws_pkt.len to get the frame payload */
ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, ws_pkt.len);
if (ret != ESP_OK) {
ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed with %s", esp_err_to_name(ret));
free(buf);
return ret;
}
ESP_LOGI(PH_TAG, "Got packet with message: %s", ws_pkt.payload);
}
// Text messages are our payload.
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
if (this->_onFrame != NULL)
ret = this->_onFrame(&wsRequest, &ws_pkt);
}
//logging housekeeping
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret));
ESP_LOGI(PH_TAG,
"ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d",
request->server(),
httpd_req_to_sockfd(request->request()),
httpd_ws_get_fd_info(request->server(), httpd_req_to_sockfd(request->request())));
//dont forget to release our buffer memory
free(buf);
/* Set max_len = 0 to get the frame len */
esp_err_t ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, 0);
if (ret != ESP_OK) {
ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed to get frame len with %s", esp_err_to_name(ret));
return ret; return ret;
}
//okay, now try to load the packet
ESP_LOGI(PH_TAG, "frame len is %d", ws_pkt.len);
if (ws_pkt.len) {
/* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
buf = (uint8_t*) calloc(1, ws_pkt.len + 1);
if (buf == NULL) {
ESP_LOGE(PH_TAG, "Failed to calloc memory for buf");
return ESP_ERR_NO_MEM;
}
ws_pkt.payload = buf;
/* Set max_len = ws_pkt.len to get the frame payload */
ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, ws_pkt.len);
if (ret != ESP_OK) {
ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed with %s", esp_err_to_name(ret));
free(buf);
return ret;
}
ESP_LOGI(PH_TAG, "Got packet with message: %s", ws_pkt.payload);
}
// Text messages are our payload.
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT)
{
if (this->_onFrame != NULL)
ret = this->_onFrame(&wsRequest, &ws_pkt);
}
//logging housekeeping
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret));
ESP_LOGI(PH_TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", request->server(),
httpd_req_to_sockfd(request->request()), httpd_ws_get_fd_info(request->server(), httpd_req_to_sockfd(request->request())));
//dont forget to release our buffer memory
free(buf);
return ret;
} }
PsychicWebSocketHandler * PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn) { PsychicWebSocketHandler * PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn) {
_onOpen = fn; _onOpen = fn;
return this; return this;
} }
PsychicWebSocketHandler * PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn) { PsychicWebSocketHandler * PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn) {
_onFrame = fn; _onFrame = fn;
return this; return this;
} }
PsychicWebSocketHandler * PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn) { PsychicWebSocketHandler * PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn) {
_onClose = fn; _onClose = fn;
return this; return this;
} }
void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt) { void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt)
for (PsychicClient * client : _clients) { {
ESP_LOGI(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket()); for (PsychicClient *client : _clients)
{
ESP_LOGI(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket());
if (client->_friend == NULL) { if (client->_friend == NULL)
TRACE(); {
return; TRACE();
} return;
if (((PsychicWebSocketClient *)client->_friend)->sendMessage(ws_pkt) != ESP_OK)
break;
} }
if (((PsychicWebSocketClient*)client->_friend)->sendMessage(ws_pkt) != ESP_OK)
break;
}
} }
void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void * data, size_t len) { void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void *data, size_t len)
httpd_ws_frame_t ws_pkt; {
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = (uint8_t *)data; ws_pkt.payload = (uint8_t*)data;
ws_pkt.len = len; ws_pkt.len = len;
ws_pkt.type = op; ws_pkt.type = op;
this->sendAll(&ws_pkt); this->sendAll(&ws_pkt);
} }
void PsychicWebSocketHandler::sendAll(const char * buf) { void PsychicWebSocketHandler::sendAll(const char *buf)
this->sendAll(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); {
this->sendAll(HTTPD_WS_TYPE_TEXT, buf, strlen(buf));
} }

View File

@@ -8,38 +8,40 @@ class PsychicWebSocketRequest;
class PsychicWebSocketClient; class PsychicWebSocketClient;
//callback function definitions //callback function definitions
typedef std::function<void(PsychicWebSocketClient * client)> PsychicWebSocketClientCallback; typedef std::function<void(PsychicWebSocketClient *client)> PsychicWebSocketClientCallback;
typedef std::function<esp_err_t(PsychicWebSocketRequest * request, httpd_ws_frame * frame)> PsychicWebSocketFrameCallback; typedef std::function<esp_err_t(PsychicWebSocketRequest *request, httpd_ws_frame *frame)> PsychicWebSocketFrameCallback;
class PsychicWebSocketClient : public PsychicClient { class PsychicWebSocketClient : public PsychicClient
{
public: public:
PsychicWebSocketClient(PsychicClient * client); PsychicWebSocketClient(PsychicClient *client);
~PsychicWebSocketClient(); ~PsychicWebSocketClient();
esp_err_t sendMessage(httpd_ws_frame_t * ws_pkt); esp_err_t sendMessage(httpd_ws_frame_t * ws_pkt);
esp_err_t sendMessage(httpd_ws_type_t op, const void * data, size_t len); esp_err_t sendMessage(httpd_ws_type_t op, const void *data, size_t len);
esp_err_t sendMessage(const char * buf); esp_err_t sendMessage(const char *buf);
}; };
class PsychicWebSocketRequest : public PsychicRequest { class PsychicWebSocketRequest : public PsychicRequest
{
private: private:
PsychicWebSocketClient _client; PsychicWebSocketClient _client;
public: public:
PsychicWebSocketRequest(PsychicRequest * req); PsychicWebSocketRequest(PsychicRequest *req);
virtual ~PsychicWebSocketRequest(); virtual ~PsychicWebSocketRequest();
PsychicWebSocketClient * client() override; PsychicWebSocketClient * client() override;
esp_err_t reply(httpd_ws_frame_t * ws_pkt); esp_err_t reply(httpd_ws_frame_t * ws_pkt);
esp_err_t reply(httpd_ws_type_t op, const void * data, size_t len); esp_err_t reply(httpd_ws_type_t op, const void *data, size_t len);
esp_err_t reply(const char * buf); esp_err_t reply(const char *buf);
}; };
class PsychicWebSocketHandler : public PsychicHandler { class PsychicWebSocketHandler : public PsychicHandler {
protected: protected:
PsychicWebSocketClientCallback _onOpen; PsychicWebSocketClientCallback _onOpen;
PsychicWebSocketFrameCallback _onFrame; PsychicWebSocketFrameCallback _onFrame;
PsychicWebSocketClientCallback _onClose; PsychicWebSocketClientCallback _onClose;
public: public:
@@ -47,22 +49,22 @@ class PsychicWebSocketHandler : public PsychicHandler {
~PsychicWebSocketHandler(); ~PsychicWebSocketHandler();
PsychicWebSocketClient * getClient(int socket) override; PsychicWebSocketClient * getClient(int socket) override;
PsychicWebSocketClient * getClient(PsychicClient * client) override; PsychicWebSocketClient * getClient(PsychicClient *client) override;
void addClient(PsychicClient * client) override; void addClient(PsychicClient *client) override;
void removeClient(PsychicClient * client) override; void removeClient(PsychicClient *client) override;
void openCallback(PsychicClient * client) override; void openCallback(PsychicClient *client) override;
void closeCallback(PsychicClient * client) override; void closeCallback(PsychicClient *client) override;
bool isWebSocket() override final; bool isWebSocket() override final;
esp_err_t handleRequest(PsychicRequest * request) override; esp_err_t handleRequest(PsychicRequest *request) override;
PsychicWebSocketHandler * onOpen(PsychicWebSocketClientCallback fn); PsychicWebSocketHandler *onOpen(PsychicWebSocketClientCallback fn);
PsychicWebSocketHandler * onFrame(PsychicWebSocketFrameCallback fn); PsychicWebSocketHandler *onFrame(PsychicWebSocketFrameCallback fn);
PsychicWebSocketHandler * onClose(PsychicWebSocketClientCallback fn); PsychicWebSocketHandler *onClose(PsychicWebSocketClientCallback fn);
void sendAll(httpd_ws_frame_t * ws_pkt); void sendAll(httpd_ws_frame_t * ws_pkt);
void sendAll(httpd_ws_type_t op, const void * data, size_t len); void sendAll(httpd_ws_type_t op, const void *data, size_t len);
void sendAll(const char * buf); void sendAll(const char *buf);
}; };
#endif // PsychicWebSocket_h #endif // PsychicWebSocket_h

View File

@@ -1,30 +1,37 @@
#include "http_status.h" #include "http_status.h"
bool http_informational(int code) { bool http_informational(int code)
{
return code >= 100 && code < 200; return code >= 100 && code < 200;
} }
bool http_success(int code) { bool http_success(int code)
{
return code >= 200 && code < 300; return code >= 200 && code < 300;
} }
bool http_redirection(int code) { bool http_redirection(int code)
{
return code >= 300 && code < 400; return code >= 300 && code < 400;
} }
bool http_client_error(int code) { bool http_client_error(int code)
{
return code >= 400 && code < 500; return code >= 400 && code < 500;
} }
bool http_server_error(int code) { bool http_server_error(int code)
{
return code >= 500 && code < 600; return code >= 500 && code < 600;
} }
bool http_failure(int code) { bool http_failure(int code)
{
return code >= 400 && code < 600; return code >= 400 && code < 600;
} }
const char * http_status_group(int code) { const char *http_status_group(int code)
{
if (http_informational(code)) if (http_informational(code))
return "Informational"; return "Informational";
@@ -43,8 +50,10 @@ const char * http_status_group(int code) {
return "Unknown"; return "Unknown";
} }
const char * http_status_reason(int code) { const char *http_status_reason(int code)
switch (code) { {
switch (code)
{
/*####### 1xx - Informational #######*/ /*####### 1xx - Informational #######*/
case 100: case 100:
return "Continue"; return "Continue";

View File

@@ -3,13 +3,13 @@
#include <stdbool.h> #include <stdbool.h>
bool http_informational(int code); bool http_informational(int code);
bool http_success(int code); bool http_success(int code);
bool http_redirection(int code); bool http_redirection(int code);
bool http_client_error(int code); bool http_client_error(int code);
bool http_server_error(int code); bool http_server_error(int code);
bool http_failure(int code); bool http_failure(int code);
const char * http_status_group(int code); const char *http_status_group(int code);
const char * http_status_reason(int code); const char *http_status_reason(int code);
#endif // MICRO_HTTP_STATUS_H #endif // MICRO_HTTP_STATUS_H

View File

@@ -49,6 +49,7 @@ void ESP8266React::registerURI() {
_mqttStatus.registerURI(); _mqttStatus.registerURI();
_securitySettingsService.registerURI(); _securitySettingsService.registerURI();
_otaSettingsService.registerURI(); _otaSettingsService.registerURI();
_restartService.registerURI(); _restartService.registerURI();

View File

@@ -46,6 +46,7 @@ class HttpEndpoint {
// register the web server on() endpoints // register the web server on() endpoints
void registerURI() { void registerURI() {
// GET
_server->on(_servicePath, _server->on(_servicePath,
HTTP_GET, HTTP_GET,
_securityManager->wrapRequest( _securityManager->wrapRequest(
@@ -57,6 +58,7 @@ class HttpEndpoint {
}, },
_authenticationPredicate)); _authenticationPredicate));
// POST
_server->on(_servicePath, _server->on(_servicePath,
HTTP_POST, HTTP_POST,
_securityManager->wrapCallback( _securityManager->wrapCallback(

View File

@@ -17,12 +17,14 @@ void NetworkSettingsService::begin() {
// WiFi.mode(WIFI_OFF); // WiFi.mode(WIFI_OFF);
// } // }
WiFi.mode(WIFI_MODE_STA); // TODO added otherwise crashes. this is the default.
// WiFi.useStaticBuffers(true); // uses 40kb more heap and max alloc, so not recommended // WiFi.useStaticBuffers(true); // uses 40kb more heap and max alloc, so not recommended
WiFi.persistent(false); WiFi.persistent(false);
WiFi.setAutoReconnect(false); WiFi.setAutoReconnect(false);
WiFi.mode(WIFI_MODE_MAX); // WiFi.mode(WIFI_MODE_MAX); // TODO commented out, not sure what use it has
WiFi.mode(WIFI_MODE_NULL); // WiFi.mode(WIFI_MODE_NULL);
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN, connect issues in 2.0.14 // WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN, connect issues in 2.0.14
// WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set // WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set

View File

@@ -1,3 +1,4 @@
/*
#ifndef WebSocketTxRx_h #ifndef WebSocketTxRx_h
#define WebSocketTxRx_h #define WebSocketTxRx_h
@@ -107,13 +108,12 @@ class WebSocketTx : virtual public WebSocketConnector<T> {
} }
} }
/** // Broadcasts the payload to the destination, if provided. Otherwise broadcasts to all clients except the origin, if
* Broadcasts the payload to the destination, if provided. Otherwise broadcasts to all clients except the origin, if // specified.
* specified. //
* // Original implementation sent clients their own IDs so they could ignore updates they initiated. This approach
* Original implementation sent clients their own IDs so they could ignore updates they initiated. This approach // simplifies the client and the server implementation but may not be sufficent for all use-cases.
* simplifies the client and the server implementation but may not be sufficent for all use-cases. //
*/
void transmitData(AsyncWebSocketClient * client, const String & originId) { void transmitData(AsyncWebSocketClient * client, const String & originId) {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(WebSocketConnector<T>::_bufferSize); DynamicJsonDocument jsonDocument = DynamicJsonDocument(WebSocketConnector<T>::_bufferSize);
JsonObject root = jsonDocument.to<JsonObject>(); JsonObject root = jsonDocument.to<JsonObject>();
@@ -214,3 +214,4 @@ class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
}; };
#endif #endif
*/

View File

@@ -1,71 +1,60 @@
; PlatformIO Project Configuration File for EMS-ESP ; PlatformIO Project Configuration File
; override any settings with your own local ones in pio_local.ini ;
; 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] [platformio]
; default_envs = esp32_4M
default_envs = lolin_s3 default_envs = lolin_s3
; default_envs = esp32_16M extra_configs =
; default_envs = standalone factory_settings.ini
; default_envs = https pio_local.ini
extra_configs =
factory_settings.ini
pio_local.ini
[common] [common]
core_build_flags = core_build_flags =
-D ARDUINO_ARCH_ESP32=1 -D ARDUINO_ARCH_ESP32=1
-D ESP32=1 -D ESP32=1
; -std=gnu++17 core_unbuild_flags =
my_build_flags =
; core_unbuild_flags = -std=gnu++11 build_flags =
core_unbuild_flags = ${common.core_build_flags}
${factory_settings.build_flags}
; my_build_flags is set in pio_local.ini ${common.my_build_flags}
my_build_flags = -D ONEWIRE_CRC16=0
-D NO_GLOBAL_ARDUINOOTA
build_flags = -D ARDUINOJSON_ENABLE_STD_STRING=1
${common.core_build_flags} -D ARDUINOJSON_USE_DOUBLE=0
${factory_settings.build_flags} -D ARDUINOTRACE_ENABLE=0
${common.my_build_flags} -D CONFIG_ETH_ENABLED
-D ONEWIRE_CRC16=0 -D ARDUINOTRACE_ENABLE=0
-D NO_GLOBAL_ARDUINOOTA unbuild_flags =
-D ARDUINOJSON_ENABLE_STD_STRING=1 ${common.core_unbuild_flags}
-D ARDUINOJSON_USE_DOUBLE=0
-D ARDUINOTRACE_ENABLE=0
; -D CONFIG_UART_ISR_IN_IRAM
unbuild_flags =
${common.core_unbuild_flags}
[espressi32_base] [espressi32_base]
platform = espressif32@6.5.0 platform = espressif32
framework = arduino framework = arduino
board_build.filesystem = littlefs board_build.filesystem = littlefs
build_flags = ${common.build_flags} build_flags = ${common.build_flags}
build_unflags = ${common.unbuild_flags} build_unflags = ${common.unbuild_flags}
extra_scripts = extra_scripts =
pre:scripts/build_interface.py pre:scripts/build_interface.py
scripts/rename_fw.py scripts/rename_fw.py
[espressi32_base_tasmota] [espressi32_base_tasmota]
; use Tasmota's libary which removes some libs (like mbedtsl) and increases available heap platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.03/platform-espressif32-2023.10.03.zip
; platform = https://github.com/tasmota/platform-espressif32.git ; latest development
; latest release with WiFi_secure.h
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.03/platform-espressif32-2023.10.03.zip ; latest stable
; latest arduino 2.xx release:
; platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.11.01/platform-espressif32.zip
; latest arduino 3.0/IDF 5.1.(alpha 3):
; platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.12.10/platform-espressif32.zip
framework = arduino framework = arduino
board_build.filesystem = littlefs board_build.filesystem = littlefs
build_flags = build_flags =
${common.build_flags} ${common.build_flags}
-DTASMOTA_SDK -DTASMOTA_SDK
build_unflags = ${common.unbuild_flags} build_unflags = ${common.unbuild_flags}
extra_scripts = extra_scripts =
pre:scripts/build_interface.py pre:scripts/build_interface.py
scripts/rename_fw.py scripts/rename_fw.py
[env] [env]
monitor_speed = 115200 monitor_speed = 115200
@@ -73,16 +62,12 @@ monitor_filters = esp32_exception_decoder
upload_speed = 921600 upload_speed = 921600
build_type = release build_type = release
lib_ldf_mode = chain+ lib_ldf_mode = chain+
; board_build.flash_mode = qio
check_tool = cppcheck, clangtidy check_tool = cppcheck, clangtidy
check_severity = high, medium check_severity = high, medium
check_flags = check_flags =
cppcheck: --std=c++11 -v cppcheck: --std=c++11 -v
clangtidy: --checks=-*,clang-analyzer-*,performance-* clangtidy: --checks=-*,clang-analyzer-*,performance-*
; build for GitHub Actions CI
; the Web interface is built seperately
[env:ci] [env:ci]
extends = espressi32_base_tasmota extends = espressi32_base_tasmota
extra_scripts = scripts/rename_fw.py extra_scripts = scripts/rename_fw.py
@@ -98,19 +83,19 @@ board_build.f_cpu = 240000000L
board_upload.flash_size = 16MB board_upload.flash_size = 16MB
board_build.partitions = esp32_partition_16M.csv board_build.partitions = esp32_partition_16M.csv
build_unflags = ${common.unbuild_flags} build_unflags = ${common.unbuild_flags}
build_flags = build_flags =
${espressi32_base.build_flags} ${espressi32_base.build_flags}
-O2 -O2
'-DEMSESP_DEFAULT_BOARD_PROFILE="S32S3"' '-DEMSESP_DEFAULT_BOARD_PROFILE="S32S3"'
[env:esp32_4M] [env:esp32_4M]
extends = espressi32_base_tasmota extends = espressi32_base_tasmota
board = esp32dev board = esp32dev
board_upload.flash_size = 4MB board_upload.flash_size = 4MB
board_build.partitions = esp32_partition_4M.csv board_build.partitions = esp32_partition_4M.csv
build_flags = build_flags =
${espressi32_base_tasmota.build_flags} ${espressi32_base_tasmota.build_flags}
-Os -Os
[env:esp32_4Mplus] [env:esp32_4Mplus]
extends = espressi32_base_tasmota extends = espressi32_base_tasmota
@@ -130,30 +115,28 @@ extends = espressi32_base_tasmota
board = lolin_c3_mini board = lolin_c3_mini
board_upload.flash_size = 4MB board_upload.flash_size = 4MB
board_build.partitions = esp32_partition_4M.csv board_build.partitions = esp32_partition_4M.csv
build_flags = build_flags =
${espressi32_base_tasmota.build_flags} ${espressi32_base_tasmota.build_flags}
'-DEMSESP_DEFAULT_BOARD_PROFILE="C3MINI"' '-DEMSESP_DEFAULT_BOARD_PROFILE="C3MINI"'
; lolin C3 mini v1 needs special wifi init.
; https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
[env:lolin_c3_mini_v1] [env:lolin_c3_mini_v1]
extends = espressi32_base_tasmota extends = espressi32_base_tasmota
board = lolin_c3_mini board = lolin_c3_mini
board_upload.flash_size = 4MB board_upload.flash_size = 4MB
board_build.partitions = esp32_partition_4M.csv board_build.partitions = esp32_partition_4M.csv
build_flags = build_flags =
${espressi32_base_tasmota.build_flags} ${espressi32_base_tasmota.build_flags}
-DBOARD_C3_MINI_V1 -DBOARD_C3_MINI_V1
'-DEMSESP_DEFAULT_BOARD_PROFILE="C3MINI"' '-DEMSESP_DEFAULT_BOARD_PROFILE="C3MINI"'
[env:lolin_s2_mini] [env:lolin_s2_mini]
extends = espressi32_base_tasmota extends = espressi32_base_tasmota
board = lolin_s2_mini board = lolin_s2_mini
board_upload.flash_size = 4MB board_upload.flash_size = 4MB
board_build.partitions = esp32_partition_4M.csv board_build.partitions = esp32_partition_4M.csv
build_flags = build_flags =
${espressi32_base_tasmota.build_flags} ${espressi32_base_tasmota.build_flags}
'-DEMSESP_DEFAULT_BOARD_PROFILE="S2MINI"' '-DEMSESP_DEFAULT_BOARD_PROFILE="S2MINI"'
[env:lolin_s3] [env:lolin_s3]
extends = espressi32_base extends = espressi32_base
@@ -163,91 +146,110 @@ board_upload.flash_size = 16MB
board_build.partitions = esp32_partition_16M.csv board_build.partitions = esp32_partition_16M.csv
board_upload.use_1200bps_touch = false board_upload.use_1200bps_touch = false
board_upload.wait_for_upload_port = false board_upload.wait_for_upload_port = false
build_flags = build_flags =
${espressi32_base.build_flags} ${espressi32_base.build_flags}
-O2 -O2
'-DEMSESP_DEFAULT_BOARD_PROFILE="S32S3"' '-DEMSESP_DEFAULT_BOARD_PROFILE="S32S3"'
[env:https] [env:https]
; use Tasmota's libary which removes some libs (like mbedtsl) and increases available heap platform = espressif32
; platform = https://github.com/tasmota/platform-espressif32.git ; latest development
;
; latest release with WiFi_secure.h
; platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.03/platform-espressif32-2023.10.03.zip ; latest stable
;
; latest arduino 2.xx release:
; platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.11.01/platform-espressif32.zip
;
; latest arduino 3.0/IDF 5.1.(alpha 3):
; platform = https://github.com/platformio/platform-espressif32.git
; platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5
; platform_packages = https://github.com/espressif/arduino-esp32.git#3.0.0-alpha2
platform = espressif32@6.5.0
framework = arduino framework = arduino
board = esp32dev board = esp32dev
board_build.filesystem = littlefs board_build.filesystem = littlefs
board_build.f_cpu = 240000000L board_build.f_cpu = 240000000L
board_upload.flash_size = 4MB board_upload.flash_size = 4MB
board_build.partitions = esp32_partition_4M.csv board_build.partitions = esp32_partition_4M.csv
; board_upload.flash_size = 16MB
; board_build.partitions = esp32_partition_16M.csv
board_upload.use_1200bps_touch = false board_upload.use_1200bps_touch = false
board_upload.wait_for_upload_port = true board_upload.wait_for_upload_port = true
upload_port = /dev/ttyUSB0 upload_port = /dev/ttyUSB0
extra_scripts = extra_scripts =
; pre:scripts/build_interface.py pre:scripts/build_interface.py
scripts/rename_fw.py scripts/rename_fw.py
build_unflags = ${common.unbuild_flags} build_unflags = ${common.unbuild_flags}
build_flags = build_flags =
${common.core_build_flags} ${common.core_build_flags}
${factory_settings.build_flags} ${factory_settings.build_flags}
${common.my_build_flags} ${common.my_build_flags}
-D ONEWIRE_CRC16=0 -D ONEWIRE_CRC16=0
-D NO_GLOBAL_ARDUINOOTA -D NO_GLOBAL_ARDUINOOTA
-D ARDUINOJSON_ENABLE_STD_STRING=1 -D ARDUINOJSON_ENABLE_STD_STRING=1
-D ARDUINOJSON_USE_DOUBLE=0 -D ARDUINOJSON_USE_DOUBLE=0
; -D ARDUINOTRACE_ENABLE=1 -D ARDUINOTRACE_ENABLE=1
-D ARDUINOTRACE_ENABLE=0 -D TASMOTA_SDK
-D TASMOTA_SDK -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN
; -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN -D EMSESP_TEST
-D EMSESP_TEST -D EMSESP_DEBUG
-D EMSESP_DEBUG -D CONFIG_ETH_ENABLED
-D CONFIG_ETH_ENABLED '-DEMSESP_DEFAULT_BOARD_PROFILE="Test"'
; -D BOARD_HAS_PSRAM
'-DEMSESP_DEFAULT_BOARD_PROFILE="Test"'
; to build and run: pio run -e standalone -t exec
[env:standalone] [env:standalone]
platform = native platform = native
build_flags = build_flags =
-DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0 -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0
-DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__ -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.4-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.4-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
-lpthread -lpthread
-std=gnu++11 -Og -ggdb -std=gnu++11 -Og -ggdb
build_src_flags = build_src_flags =
; -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-unused-lambda-capture -Wno-sign-compare -Wno-missing-braces
; -Wall -Wextra -Werror -I./lib_standalone
-Wno-missing-braces -I./lib/ArduinoJson/src
-I./lib_standalone -I./lib/uuid-common/src
-I./lib/ArduinoJson/src -I./lib/uuid-console/src
-I./lib/uuid-common/src -I./lib/uuid-log/src
-I./lib/uuid-console/src -I./lib/semver
-I./lib/uuid-log/src -I./lib/PButton
-I./lib/semver -I./lib/espMqttClient/src
-I./lib/PButton -I./lib/espMqttClient/src/Transport
-I./lib/espMqttClient/src build_src_filter =
-I./lib/espMqttClient/src/Transport
build_src_filter =
+<*> +<*>
-<.git/> -<.git/>
+<../lib_standalone> +<../lib_standalone>
+<../lib/uuid-common> +<../lib/uuid-common>
+<../lib/uuid-console> +<../lib/uuid-console>
+<../lib/uuid-log> +<../lib/uuid-log>
+<../lib/semver> +<../lib/semver>
+<../lib/PButton> +<../lib/PButton>
+<../lib/espMqttClient/src> +<../lib/espMqttClient/src>
+<../lib/espMqttClient/src/Transport> +<../lib/espMqttClient/src/Transport>
lib_compat_mode = off lib_compat_mode = off
lib_ldf_mode = off lib_ldf_mode = off
[factory_settings]
build_flags =
-D FACTORY_WIFI_SSID=\"\"
-D FACTORY_WIFI_PASSWORD=\"\"
-D FACTORY_WIFI_HOSTNAME=\"ems-esp\"
-D FACTORY_AP_PROVISION_MODE=AP_MODE_DISCONNECTED
-D FACTORY_AP_SSID=\"ems-esp\"
-D FACTORY_AP_PASSWORD=\"ems-esp-neo\"
-D FACTORY_AP_LOCAL_IP=\"192.168.4.1\"
-D FACTORY_AP_GATEWAY_IP=\"192.168.4.1\"
-D FACTORY_AP_SUBNET_MASK=\"255.255.255.0\"
-D FACTORY_ADMIN_USERNAME=\"admin\"
-D FACTORY_ADMIN_PASSWORD=\"admin\"
-D FACTORY_GUEST_USERNAME=\"guest\"
-D FACTORY_GUEST_PASSWORD=\"guest\"
-D FACTORY_NTP_ENABLED=false
-D FACTORY_NTP_TIME_ZONE_LABEL=\"Europe/Amsterdam\"
-D FACTORY_NTP_TIME_ZONE_FORMAT=\"CET-1CEST,M3.5.0,M10.5.0/3\"
-D FACTORY_NTP_SERVER=\"time.google.com\"
-D FACTORY_OTA_PORT=8266
-D FACTORY_OTA_PASSWORD=\"ems-esp-neo\"
-D FACTORY_OTA_ENABLED=false
-D FACTORY_MQTT_ENABLED=false
-D FACTORY_MQTT_HOST=\"\"
-D FACTORY_MQTT_PORT=1883
-D FACTORY_MQTT_USERNAME=\"\"
-D FACTORY_MQTT_PASSWORD=\"\"
-D FACTORY_MQTT_CLIENT_ID=\"ems-esp\"
-D FACTORY_MQTT_KEEP_ALIVE=60
-D FACTORY_MQTT_CLEAN_SESSION=false
-D FACTORY_MQTT_MAX_TOPIC_LENGTH=128
-D FACTORY_JWT_SECRET=\"ems-esp-neo\"

View File

@@ -1405,24 +1405,26 @@ EMSESP::EMSESP()
} }
// add web server endpoint // add web server endpoint
void EMSESP::handler(const char * uri, const char * contentType, const uint8_t * content, size_t len) { void EMSESP::handler(const String & uri, const String & contentType, const uint8_t * content, size_t len) {
PsychicHttpRequestCallback fn = [contentType, content, len](PsychicRequest * request) { PsychicHttpRequestCallback fn = [contentType, content, len](PsychicRequest * request) {
PsychicResponse response(request); PsychicResponse response(request);
response.setCode(200); response.setCode(200);
response.setContentType(contentType); response.setContentType(contentType.c_str());
response.addHeader("Content-Encoding", "gzip");
// response.addHeader("Content-Encoding", "br"); // Brotli - only works over HTTPS // response.addHeader("Content-Encoding", "br"); // Brotli - only works over HTTPS
response.addHeader("Content-Encoding", "gzip");
response.addHeader("Cache-Control", "public, immutable, max-age=31536000");
response.setContent(content, len); response.setContent(content, len);
return response.send(); return response.send();
}; };
PsychicWebHandler * handler = new PsychicWebHandler(); PsychicWebHandler * handler = new PsychicWebHandler();
handler->onRequest(fn); handler->onRequest(fn);
webServer.on(uri, HTTP_GET, handler); webServer.on(uri.c_str(), HTTP_GET, handler);
// Set default end-point for all non matching requests // Set default end-point for all non matching requests
// this is easier than using webServer.onNotFound() // this is easier than using webServer.onNotFound()
if (strcmp(uri, "/index.html") == 0) { if (uri.equals("/index.html")) {
// if (strcmp(uri, "/index.html") == 0) {
webServer.defaultEndpoint->setHandler(handler); webServer.defaultEndpoint->setHandler(handler);
} }
}; };
@@ -1435,14 +1437,43 @@ void EMSESP::setupWeb() {
// esp8266React services has 13 // esp8266React services has 13
// custom projects has around 23 // custom projects has around 23
webServer.config.max_uri_handlers = 80; webServer.config.max_uri_handlers = 80;
// webServer.config.uri_match_fn = NULL; // don't use wildcards
/*
#define HTTPD_DEFAULT_CONFIG() { \
.task_priority = tskIDLE_PRIORITY+5, \
.stack_size = 4096, \
.core_id = tskNO_AFFINITY, \
.server_port = 80, \
.ctrl_port = 32768, \
.max_open_sockets = 7, \
.max_uri_handlers = 8, \
.max_resp_headers = 8, \
.backlog_conn = 5, \
.lru_purge_enable = false, \
.recv_wait_timeout = 5, \
.send_wait_timeout = 5, \
.global_user_ctx = NULL, \
.global_user_ctx_free_fn = NULL, \
.global_transport_ctx = NULL, \
.global_transport_ctx_free_fn = NULL, \
.enable_so_linger = false, \
.linger_timeout = 0, \
.open_fn = NULL, \
.close_fn = NULL, \
.uri_match_fn = NULL \
*/
// TODO remove experimental stuff
// webServer.config.uri_match_fn = NULL; // don't use wildcards
// webServer.config.stack_size = 8192; // default is 4096, we had 2x8192 in AsyncTCP
// webServer.config.max_open_sockets = 3;
// webServer.config.task_priority = uxTaskPriorityGet(nullptr); // seems to make it slightly slower // webServer.config.task_priority = uxTaskPriorityGet(nullptr); // seems to make it slightly slower
// webServer.config.task_priority = 5; // same as AsyncTCP, but looks like even slower
// webServer.config.lru_purge_enable = true; // makes no diff. is as in https://github.com/espressif/esp-idf/blob/master/examples/protocols/http_server/simple/main/main.c
// TODO add support for https // TODO add support for https
webServer.listen(80); // start the web server webServer.listen(80); // start the web server
DefaultHeaders::Instance().addHeader("Server", "EMS-ESP");
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
WWWData::registerRoutes(handler); // add webServer.on() endpoints from the generated web code WWWData::registerRoutes(handler); // add webServer.on() endpoints from the generated web code
@@ -1562,6 +1593,9 @@ void EMSESP::start() {
analogsensor_.start(); // Analog external sensors analogsensor_.start(); // Analog external sensors
webLogService.start(); // apply settings to weblog service webLogService.start(); // apply settings to weblog service
// set hostname on web server
DefaultHeaders::Instance().addHeader("Server", system_.hostname().c_str()); // TODO use hostname
// Load our library of known devices into stack mem. Names are stored in Flash memory // Load our library of known devices into stack mem. Names are stored in Flash memory
device_library_ = { device_library_ = {
#include "device_library.h" #include "device_library.h"

View File

@@ -241,7 +241,8 @@ class EMSESP {
static WebSchedulerService webSchedulerService; static WebSchedulerService webSchedulerService;
static WebCustomEntityService webCustomEntityService; static WebCustomEntityService webCustomEntityService;
static void handler(const char * uri, const char * contentType, const uint8_t * content, size_t len); static void handler(const String & uri, const String & contentType, const uint8_t * content, size_t len);
// static void handler(const char * uri, const char * contentType, const uint8_t * content, size_t len);
private: private:
static std::string device_tostring(const uint8_t device_id); static std::string device_tostring(const uint8_t device_id);

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.6.5-https.1" #define EMSESP_APP_VERSION "3.6.5-https.2"

View File

@@ -23,27 +23,19 @@ using namespace std::placeholders;
namespace emsesp { namespace emsesp {
WebLogService::WebLogService(PsychicHttpServer * server, SecurityManager * securityManager) WebLogService::WebLogService(PsychicHttpServer * server, SecurityManager * securityManager)
// TODO fix event source : _server(server)
: // : _events(EVENT_SOURCE_LOG_PATH)
_server(server)
, _securityManager(securityManager) { , _securityManager(securityManager) {
} }
void WebLogService::registerURI() { void WebLogService::registerURI() {
// TODO fix event source // eventsource
/* _server->on(EVENT_SOURCE_LOG_PATH, &_events);
_events_.onOpen([](PsychicEventSourceClient * client) {
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
client->send("Hello user!", NULL, millis(), 1000);
});
_events_.onClose([](PsychicEventSourceClient * client) {
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
_server->on(EVENT_SOURCE_LOG_PATH, &events_);
*/
// post // TODO this doesn't work with PsychicEventSource
// _server->on(EVENT_SOURCE_LOG_PATH, &_events)->setFilter(_securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN));
// _events.setFilter(_securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN));
// POST
_server->on(LOG_SETTINGS_PATH, HTTP_POST, [this](PsychicRequest * request, JsonVariant & json) { _server->on(LOG_SETTINGS_PATH, HTTP_POST, [this](PsychicRequest * request, JsonVariant & json) {
auto && body = json.as<JsonObject>(); auto && body = json.as<JsonObject>();
@@ -59,9 +51,7 @@ void WebLogService::registerURI() {
return request->reply(200); // OK return request->reply(200); // OK
}); });
_events.setFilter(_securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); // GET settings
// get settings
_server->on(LOG_SETTINGS_PATH, HTTP_GET, [this](PsychicRequest * request) { _server->on(LOG_SETTINGS_PATH, HTTP_GET, [this](PsychicRequest * request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_SMALL); PsychicJsonResponse response = PsychicJsonResponse(request, false, EMSESP_JSON_SIZE_SMALL);
JsonObject root = response.getRoot(); JsonObject root = response.getRoot();
@@ -72,6 +62,7 @@ void WebLogService::registerURI() {
return response.send(); return response.send();
}); });
// /rest/fetchLog
// for bring back the whole log - is a command, hence a POST // for bring back the whole log - is a command, hence a POST
// send the complete log buffer to the API, not filtering on log level // send the complete log buffer to the API, not filtering on log level
// done by resetting the pointer // done by resetting the pointer
@@ -79,9 +70,6 @@ void WebLogService::registerURI() {
log_message_id_tail_ = 0; log_message_id_tail_ = 0;
return request->reply(200); return request->reply(200);
}); });
// TODO this can be removed when ported over
// server->addHandler(&events_);
} }
// start the log service with INFO level // start the log service with INFO level
@@ -232,9 +220,9 @@ char * WebLogService::messagetime(char * out, const uint64_t t, const size_t buf
// send to web eventsource // send to web eventsource
void WebLogService::transmit(const QueuedLogMessage & message) { void WebLogService::transmit(const QueuedLogMessage & message) {
DynamicJsonDocument jsonDocument(EMSESP_JSON_SIZE_LARGE); DynamicJsonDocument jsonDocument(EMSESP_JSON_SIZE_LARGE);
if (jsonDocument.capacity() == 0) { // if (jsonDocument.capacity() == 0) {
return; // return;
} // }
JsonObject logEvent = jsonDocument.to<JsonObject>(); JsonObject logEvent = jsonDocument.to<JsonObject>();
char time_string[25]; char time_string[25];
@@ -244,47 +232,13 @@ void WebLogService::transmit(const QueuedLogMessage & message) {
logEvent["n"] = message.content_->name; logEvent["n"] = message.content_->name;
logEvent["m"] = message.content_->text; logEvent["m"] = message.content_->text;
size_t len = measureJson(jsonDocument); size_t len = measureJson(jsonDocument) + 1;
char * buffer = new char[len + 1]; char * buffer = (char *)malloc(len);
if (buffer) { if (buffer != NULL) {
serializeJson(jsonDocument, buffer, len + 1); serializeJson(jsonDocument, buffer, len);
_events.send(buffer, "message", message.id_); _events.send(buffer, "message", message.id_);
} }
delete[] buffer; free(buffer);
} }
// send the complete log buffer to the API, not filtering on log level } // namespace emsesp
// done by resetting the pointer
// esp_err_t WebLogService::fetchLog(PsychicRequest * request) {
// log_message_id_tail_ = 0;
// request->send(200);
// }
// return the current value settings after a GET
// esp_err_t WebLogService::getValues(PsychicRequest * request) {
// auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL);
// JsonObject root = response->getRoot();
// root["level"] = log_level();
// root["max_messages"] = maximum_log_messages();
// root["compact"] = compact();
// response->setLength();
// request->send(response);
// }
// sets the values like level after a POST
// esp_err_t WebLogService::setValues(PsychicRequest * request, JsonVariant & json) {
// auto && body = json.as<JsonObject>();
// uuid::log::Level level = body["level"];
// log_level(level);
// uint8_t max_messages = body["max_messages"];
// maximum_log_messages(max_messages);
// bool comp = body["compact"];
// compact(comp);
// return request->reply(200); // OK
// }
} // namespace emsesp

View File

@@ -50,8 +50,7 @@ class WebLogService : public uuid::log::Handler {
private: private:
SecurityManager * _securityManager; SecurityManager * _securityManager;
PsychicHttpServer * _server; PsychicHttpServer * _server;
PsychicEventSource _events;
PsychicEventSource _events;
class QueuedLogMessage { class QueuedLogMessage {
public: public: