PsychichHTTP v2-dev

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

View File

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

View File

@@ -7,19 +7,19 @@
class ChunkPrinter : public Print
{
private:
PsychicResponse *_response;
uint8_t *_buffer;
PsychicResponse* _response;
uint8_t* _buffer;
size_t _length;
size_t _pos;
public:
ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len);
ChunkPrinter(PsychicResponse* response, uint8_t* buffer, size_t len);
~ChunkPrinter();
size_t write(uint8_t c) override;
size_t write(const uint8_t *buffer, size_t size) override;
size_t copyFrom(Stream &stream);
size_t write(uint8_t c) override;
size_t write(const uint8_t* buffer, size_t size) override;
size_t copyFrom(Stream& stream);
void flush() override;
};

View File

@@ -0,0 +1,426 @@
#include "MultipartProcessor.h"
#include "PsychicRequest.h"
enum
{
EXPECT_BOUNDARY,
PARSE_HEADERS,
WAIT_FOR_RETURN1,
EXPECT_FEED1,
EXPECT_DASH1,
EXPECT_DASH2,
BOUNDARY_OR_DATA,
DASH3_OR_RETURN2,
EXPECT_FEED2,
PARSING_FINISHED,
PARSE_ERROR
};
MultipartProcessor::MultipartProcessor(PsychicRequest* request, PsychicUploadCallback uploadCallback) : _request(request),
_uploadCallback(uploadCallback),
_temp(),
_parsedLength(0),
_multiParseState(EXPECT_BOUNDARY),
_boundaryPosition(0),
_itemStartIndex(0),
_itemSize(0),
_itemName(),
_itemFilename(),
_itemType(),
_itemValue(),
_itemBuffer(0),
_itemBufferIndex(0),
_itemIsFile(false)
{
}
MultipartProcessor::~MultipartProcessor() {}
esp_err_t MultipartProcessor::process()
{
esp_err_t err = ESP_OK;
_parsedLength = 0;
String value = _request->header("Content-Type");
if (value.startsWith("multipart/"))
{
_boundary = value.substring(value.indexOf('=') + 1);
_boundary.replace("\"", "");
}
else
{
ESP_LOGE(PH_TAG, "No multipart boundary found.");
return ESP_ERR_HTTPD_INVALID_REQ;
}
char* buf = (char*)malloc(FILE_CHUNK_SIZE);
int received;
unsigned long index = 0;
/* Content length of the request gives the size of the file being uploaded */
int remaining = _request->contentLength();
while (remaining > 0)
{
#ifdef ENABLE_ASYNC
httpd_sess_update_lru_counter(request->server()->server, request->client()->socket());
#endif
/* Receive the file part by part into a buffer */
if ((received = httpd_req_recv(_request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0)
{
/* Retry if timeout occurred */
if (received == HTTPD_SOCK_ERR_TIMEOUT)
continue;
// bail if we got an error
else if (received == HTTPD_SOCK_ERR_FAIL)
{
ESP_LOGE(PH_TAG, "Socket error");
err = ESP_FAIL;
break;
}
}
// parse it 1 byte at a time.
for (int i = 0; i < received; i++)
{
/* Keep track of remaining size of the file left to be uploaded */
remaining--;
index++;
// send it to our parser
_parseMultipartPostByte(buf[i], !remaining);
_parsedLength++;
}
}
// dont forget to free our buffer
free(buf);
return err;
}
esp_err_t MultipartProcessor::process(const char* body)
{
esp_err_t err = ESP_OK;
_parsedLength = 0;
String value = _request->header("Content-Type");
if (value.startsWith("multipart/"))
{
_boundary = value.substring(value.indexOf('=') + 1);
_boundary.replace("\"", "");
}
else
{
ESP_LOGE(PH_TAG, "No multipart boundary found.");
return ESP_ERR_HTTPD_INVALID_REQ;
}
// loop over the whole string
unsigned int size = strlen(body);
for (unsigned i = 0; i < size; i++)
{
// send it to our parser
_parseMultipartPostByte(body[i], i == size - 1);
_parsedLength++;
}
return err;
}
void MultipartProcessor::_handleUploadByte(uint8_t data, bool last)
{
_itemBuffer[_itemBufferIndex++] = data;
if (last || _itemBufferIndex == FILE_CHUNK_SIZE)
{
if (_uploadCallback)
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, last);
_itemBufferIndex = 0;
}
}
#define itemWriteByte(b) \
do \
{ \
_itemSize++; \
if (_itemIsFile) \
_handleUploadByte(b, last); \
else \
_itemValue += (char)(b); \
} while (0)
void MultipartProcessor::_parseMultipartPostByte(uint8_t data, bool last)
{
if (_multiParseState == PARSE_ERROR)
{
// not sure we can end up with an error during buffer fill, but jsut to be safe
if (_itemBuffer != NULL)
{
free(_itemBuffer);
_itemBuffer = NULL;
}
return;
}
if (!_parsedLength)
{
_multiParseState = EXPECT_BOUNDARY;
_temp = String();
_itemName = String();
_itemFilename = String();
_itemType = String();
}
if (_multiParseState == WAIT_FOR_RETURN1)
{
if (data != '\r')
{
itemWriteByte(data);
}
else
{
_multiParseState = EXPECT_FEED1;
}
}
else if (_multiParseState == EXPECT_BOUNDARY)
{
if (_parsedLength < 2 && data != '-')
{
ESP_LOGE(PH_TAG, "Multipart: No boundary");
_multiParseState = PARSE_ERROR;
return;
}
else if (_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data)
{
ESP_LOGE(PH_TAG, "Multipart: Multipart malformed");
_multiParseState = PARSE_ERROR;
return;
}
else if (_parsedLength - 2 == _boundary.length() && data != '\r')
{
ESP_LOGE(PH_TAG, "Multipart: Multipart missing carriage return");
_multiParseState = PARSE_ERROR;
return;
}
else if (_parsedLength - 3 == _boundary.length())
{
if (data != '\n')
{
ESP_LOGE(PH_TAG, "Multipart: Multipart missing newline");
_multiParseState = PARSE_ERROR;
return;
}
_multiParseState = PARSE_HEADERS;
_itemIsFile = false;
}
}
else if (_multiParseState == PARSE_HEADERS)
{
if ((char)data != '\r' && (char)data != '\n')
_temp += (char)data;
if ((char)data == '\n')
{
if (_temp.length())
{
if (_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type"))
{
_itemType = _temp.substring(14);
_itemIsFile = true;
}
else if (_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition"))
{
_temp = _temp.substring(_temp.indexOf(';') + 2);
while (_temp.indexOf(';') > 0)
{
String name = _temp.substring(0, _temp.indexOf('='));
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
if (name == "name")
{
_itemName = nameVal;
}
else if (name == "filename")
{
_itemFilename = nameVal;
_itemIsFile = true;
}
_temp = _temp.substring(_temp.indexOf(';') + 2);
}
String name = _temp.substring(0, _temp.indexOf('='));
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
if (name == "name")
{
_itemName = nameVal;
}
else if (name == "filename")
{
_itemFilename = nameVal;
_itemIsFile = true;
}
}
_temp = String();
}
else
{
_multiParseState = WAIT_FOR_RETURN1;
// value starts from here
_itemSize = 0;
_itemStartIndex = _parsedLength;
_itemValue = String();
if (_itemIsFile)
{
if (_itemBuffer)
free(_itemBuffer);
_itemBuffer = (uint8_t*)malloc(FILE_CHUNK_SIZE);
if (_itemBuffer == NULL)
{
ESP_LOGE(PH_TAG, "Multipart: Failed to allocate buffer");
_multiParseState = PARSE_ERROR;
return;
}
_itemBufferIndex = 0;
}
}
}
}
else if (_multiParseState == EXPECT_FEED1)
{
if (data != '\n')
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
_parseMultipartPostByte(data, last);
}
else
{
_multiParseState = EXPECT_DASH1;
}
}
else if (_multiParseState == EXPECT_DASH1)
{
if (data != '-')
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
_parseMultipartPostByte(data, last);
}
else
{
_multiParseState = EXPECT_DASH2;
}
}
else if (_multiParseState == EXPECT_DASH2)
{
if (data != '-')
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
itemWriteByte('-');
_parseMultipartPostByte(data, last);
}
else
{
_multiParseState = BOUNDARY_OR_DATA;
_boundaryPosition = 0;
}
}
else if (_multiParseState == BOUNDARY_OR_DATA)
{
if (_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data)
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundaryPosition; i++)
itemWriteByte(_boundary.c_str()[i]);
_parseMultipartPostByte(data, last);
}
else if (_boundaryPosition == _boundary.length() - 1)
{
_multiParseState = DASH3_OR_RETURN2;
if (!_itemIsFile)
{
// External - Add parameter!
_request->addParam(_itemName, _itemValue);
}
else
{
if (_itemSize)
{
if (_uploadCallback)
{
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
}
_itemBufferIndex = 0;
// External - Add parameter!
_request->addParam(new PsychicWebParameter(_itemName, _itemFilename, true, true, _itemSize));
}
free(_itemBuffer);
_itemBuffer = NULL;
}
}
else
{
_boundaryPosition++;
}
}
else if (_multiParseState == DASH3_OR_RETURN2)
{
if (data == '-' && (_request->contentLength() - _parsedLength - 4) != 0)
{
ESP_LOGE(PH_TAG, "ERROR: The parser got to the end of the POST but is expecting more bytes!");
_multiParseState = PARSE_ERROR;
return;
}
if (data == '\r')
{
_multiParseState = EXPECT_FEED2;
}
else if (data == '-' && _request->contentLength() == (_parsedLength + 4))
{
_multiParseState = PARSING_FINISHED;
}
else
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundary.length(); i++)
itemWriteByte(_boundary.c_str()[i]);
_parseMultipartPostByte(data, last);
}
}
else if (_multiParseState == EXPECT_FEED2)
{
if (data == '\n')
{
_multiParseState = PARSE_HEADERS;
_itemIsFile = false;
}
else
{
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r');
itemWriteByte('\n');
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundary.length(); i++)
itemWriteByte(_boundary.c_str()[i]);
itemWriteByte('\r');
_parseMultipartPostByte(data, last);
}
}
}

View File

@@ -0,0 +1,42 @@
#ifndef MULTIPART_PROCESSOR_H
#define MULTIPART_PROCESSOR_H
#include "PsychicCore.h"
/*
* MultipartProcessor - handle parsing and processing a multipart form.
* */
class MultipartProcessor
{
protected:
PsychicRequest* _request;
PsychicUploadCallback _uploadCallback;
String _temp;
size_t _parsedLength;
uint8_t _multiParseState;
String _boundary;
uint8_t _boundaryPosition;
size_t _itemStartIndex;
size_t _itemSize;
String _itemName;
String _itemFilename;
String _itemType;
String _itemValue;
uint8_t* _itemBuffer;
size_t _itemBufferIndex;
bool _itemIsFile;
void _handleUploadByte(uint8_t data, bool last);
void _parseMultipartPostByte(uint8_t data, bool last);
public:
MultipartProcessor(PsychicRequest* request, PsychicUploadCallback uploadCallback = nullptr);
~MultipartProcessor();
esp_err_t process();
esp_err_t process(const char* body);
};
#endif

View File

@@ -2,21 +2,24 @@
#include "PsychicHttpServer.h"
#include <lwip/sockets.h>
PsychicClient::PsychicClient(httpd_handle_t server, int socket) :
_server(server),
_socket(socket),
_friend(NULL),
isNew(false)
{}
PsychicClient::~PsychicClient() {
PsychicClient::PsychicClient(httpd_handle_t server, int socket) : _server(server),
_socket(socket),
_friend(NULL),
isNew(false)
{
}
httpd_handle_t PsychicClient::server() {
PsychicClient::~PsychicClient()
{
}
httpd_handle_t PsychicClient::server()
{
return _server;
}
int PsychicClient::socket() {
int PsychicClient::socket()
{
return _socket;
}
@@ -24,49 +27,67 @@ int PsychicClient::socket() {
esp_err_t PsychicClient::close()
{
esp_err_t err = httpd_sess_trigger_close(_server, _socket);
//PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list.
// PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list.
return err;
}
IPAddress PsychicClient::localIP()
{
IPAddress address(0,0,0,0);
IPAddress address(0, 0, 0, 0);
char ipstr[INET6_ADDRSTRLEN];
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
socklen_t addr_size = sizeof(addr);
if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
if (getsockname(_socket, (struct sockaddr*)&addr, &addr_size) < 0) {
ESP_LOGE(PH_TAG, "Error getting client IP");
return address;
}
// Convert to IPv4 string
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
//ESP_LOGD(PH_TAG, "Client Local IP => %s", ipstr);
// ESP_LOGD(PH_TAG, "Client Local IP => %s", ipstr);
address.fromString(ipstr);
return address;
}
uint16_t PsychicClient::localPort() const
{
struct sockaddr_storage addr;
socklen_t len = sizeof addr;
getsockname(_socket, (struct sockaddr*)&addr, &len);
struct sockaddr_in* s = (struct sockaddr_in*)&addr;
return ntohs(s->sin_port);
}
IPAddress PsychicClient::remoteIP()
{
IPAddress address(0,0,0,0);
IPAddress address(0, 0, 0, 0);
char ipstr[INET6_ADDRSTRLEN];
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
socklen_t addr_size = sizeof(addr);
if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
if (getpeername(_socket, (struct sockaddr*)&addr, &addr_size) < 0) {
ESP_LOGE(PH_TAG, "Error getting client IP");
return address;
}
// Convert to IPv4 string
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
//ESP_LOGD(PH_TAG, "Client Remote IP => %s", ipstr);
// ESP_LOGD(PH_TAG, "Client Remote IP => %s", ipstr);
address.fromString(ipstr);
return address;
}
}
uint16_t PsychicClient::remotePort() const
{
struct sockaddr_storage addr;
socklen_t len = sizeof addr;
getpeername(_socket, (struct sockaddr*)&addr, &len);
struct sockaddr_in* s = (struct sockaddr_in*)&addr;
return ntohs(s->sin_port);
}

View File

@@ -4,10 +4,12 @@
#include "PsychicCore.h"
/*
* PsychicClient :: Generic wrapper around the ESP-IDF socket
*/
* PsychicClient :: Generic wrapper around the ESP-IDF socket
*/
class PsychicClient
{
class PsychicClient {
protected:
httpd_handle_t _server;
int _socket;
@@ -16,9 +18,9 @@ class PsychicClient {
PsychicClient(httpd_handle_t server, int socket);
~PsychicClient();
//no idea if this is the right way to do it or not, but lets see.
//pointer to our derived class (eg. PsychicWebSocketConnection)
void *_friend;
// no idea if this is the right way to do it or not, but lets see.
// pointer to our derived class (eg. PsychicWebSocketConnection)
void* _friend;
bool isNew = false;
@@ -29,7 +31,9 @@ class PsychicClient {
esp_err_t close();
IPAddress localIP();
uint16_t localPort() const;
IPAddress remoteIP();
uint16_t remotePort() const;
};
#endif

View File

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

View File

@@ -1,90 +1,140 @@
#include "PsychicEndpoint.h"
#include "PsychicHttpServer.h"
PsychicEndpoint::PsychicEndpoint() :
_server(NULL),
_uri(""),
_method(HTTP_GET),
_handler(NULL)
PsychicEndpoint::PsychicEndpoint() : _server(NULL),
_uri(""),
_method(HTTP_GET),
_handler(NULL)
{
}
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) :
_server(server),
_uri(uri),
_method(method),
_handler(NULL)
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer* server, int method, const char* uri) : _server(server),
_uri(uri),
_method(method),
_handler(NULL)
{
}
PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler)
PsychicEndpoint* PsychicEndpoint::setHandler(PsychicHandler* handler)
{
//clean up old / default handler
// clean up old / default handler
if (_handler != NULL)
delete _handler;
//get our new pointer
// get our new pointer
_handler = handler;
//keep a pointer to the server
// keep a pointer to the server
_handler->_server = _server;
return this;
}
PsychicHandler * PsychicEndpoint::handler()
PsychicHandler* PsychicEndpoint::handler()
{
return _handler;
}
String PsychicEndpoint::uri() {
String PsychicEndpoint::uri()
{
return _uri;
}
esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req)
esp_err_t PsychicEndpoint::requestCallback(httpd_req_t* req)
{
#ifdef ENABLE_ASYNC
if (is_on_async_worker_thread() == false) {
if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) {
return ESP_OK;
} else {
httpd_resp_set_status(req, "503 Busy");
httpd_resp_sendstr(req, "No workers available. Server busy.</div>");
return ESP_OK;
}
#ifdef ENABLE_ASYNC
if (is_on_async_worker_thread() == false) {
if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) {
return ESP_OK;
} else {
httpd_resp_set_status(req, "503 Busy");
httpd_resp_sendstr(req, "No workers available. Server busy.</div>");
return ESP_OK;
}
#endif
}
#endif
PsychicEndpoint *self = (PsychicEndpoint *)req->user_ctx;
PsychicHandler *handler = self->handler();
PsychicEndpoint* self = (PsychicEndpoint*)req->user_ctx;
PsychicRequest request(self->_server, req);
//make sure we have a handler
if (handler != NULL)
{
if (handler->filter(&request) && handler->canHandle(&request))
{
//check our credentials
if (handler->needsAuthentication(&request))
return handler->authenticate(&request);
esp_err_t err = self->process(&request);
//pass it to our handler
return handler->handleRequest(&request);
}
//pass it to our generic handlers
else
return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR);
}
if (err == HTTPD_404_NOT_FOUND)
return PsychicHttpServer::requestHandler(req);
if (err == ESP_ERR_HTTPD_INVALID_REQ)
return request.response()->error(HTTPD_500_INTERNAL_SERVER_ERROR, "No handler registered.");
return err;
}
bool PsychicEndpoint::matches(const char* uri)
{
// we only want to match the path, no GET strings
char* ptr;
size_t position = 0;
// look for a ? and set our path length to that,
ptr = strchr(uri, '?');
if (ptr != NULL)
position = (size_t)(int)(ptr - uri);
// or use the whole uri if not found
else
return request.reply(500, "text/html", "No handler registered.");
position = strlen(uri);
// do we have a per-endpoint match function
if (this->getURIMatchFunction() != NULL) {
// ESP_LOGD(PH_TAG, "Match? %s == %s (%d)", _uri.c_str(), uri, position);
return this->getURIMatchFunction()(_uri.c_str(), uri, (size_t)position);
}
// do we have a global match function
if (_server->getURIMatchFunction() != NULL) {
// ESP_LOGD(PH_TAG, "Match? %s == %s (%d)", _uri.c_str(), uri, position);
return _server->getURIMatchFunction()(_uri.c_str(), uri, (size_t)position);
} else {
ESP_LOGE(PH_TAG, "No uri matching function set");
return false;
}
}
PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) {
_handler->setFilter(fn);
httpd_uri_match_func_t PsychicEndpoint::getURIMatchFunction()
{
return _uri_match_fn;
}
void PsychicEndpoint::setURIMatchFunction(httpd_uri_match_func_t match_fn)
{
_uri_match_fn = match_fn;
}
PsychicEndpoint* PsychicEndpoint::addFilter(PsychicRequestFilterFunction fn)
{
_handler->addFilter(fn);
return this;
}
PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) {
_handler->setAuthentication(username, password, method, realm, authFailMsg);
PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddleware* middleware)
{
_handler->addMiddleware(middleware);
return this;
};
}
PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddlewareCallback fn)
{
_handler->addMiddleware(fn);
return this;
}
void PsychicEndpoint::removeMiddleware(PsychicMiddleware* middleware)
{
_handler->removeMiddleware(middleware);
}
esp_err_t PsychicEndpoint::process(PsychicRequest* request)
{
esp_err_t ret = ESP_ERR_HTTPD_INVALID_REQ;
if (_handler != NULL)
ret = _handler->process(request);
ESP_LOGD(PH_TAG, "Endpoint %s processed %s: %s", _uri.c_str(), request->uri().c_str(), esp_err_to_name(ret));
return ret;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,23 +6,21 @@
class PsychicEndpoint;
class PsychicHttpServer;
class PsychicMiddleware;
class PsychicMiddlewareChain;
/*
* HANDLER :: Can be attached to any endpoint or as a generic request handler.
*/
* HANDLER :: Can be attached to any endpoint or as a generic request handler.
*/
class PsychicHandler {
friend PsychicEndpoint;
class PsychicHandler
{
friend PsychicEndpoint;
protected:
PsychicRequestFilterFunction _filter;
PsychicHttpServer *_server;
String _username;
String _password;
HTTPAuthMethod _method;
String _realm;
String _authFailMsg;
PsychicHttpServer* _server = nullptr;
PsychicMiddlewareChain* _chain = nullptr;
std::list<PsychicRequestFilterFunction> _filters;
String _subprotocol;
@@ -32,35 +30,39 @@ class PsychicHandler {
PsychicHandler();
virtual ~PsychicHandler();
PsychicHandler* setFilter(PsychicRequestFilterFunction fn);
bool filter(PsychicRequest *request);
PsychicHandler* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = "");
bool needsAuthentication(PsychicRequest *request);
esp_err_t authenticate(PsychicRequest *request);
virtual bool isWebSocket() { return false; };
void setSubprotocol(const String& subprotocol);
const char* getSubprotocol() const;
PsychicClient * checkForNewClient(PsychicClient *client);
void checkForClosedClient(PsychicClient *client);
PsychicClient* checkForNewClient(PsychicClient* client);
void checkForClosedClient(PsychicClient* client);
virtual void addClient(PsychicClient *client);
virtual void removeClient(PsychicClient *client);
virtual PsychicClient * getClient(int socket);
virtual PsychicClient * getClient(PsychicClient *client);
virtual void openCallback(PsychicClient *client) {};
virtual void closeCallback(PsychicClient *client) {};
virtual void addClient(PsychicClient* client);
virtual void removeClient(PsychicClient* client);
virtual PsychicClient* getClient(int socket);
virtual PsychicClient* getClient(PsychicClient* client);
virtual void openCallback(PsychicClient* client) {};
virtual void closeCallback(PsychicClient* client) {};
bool hasClient(PsychicClient *client);
bool hasClient(PsychicClient* client);
int count() { return _clients.size(); };
const std::list<PsychicClient*>& getClientList();
//derived classes must implement these functions
virtual bool canHandle(PsychicRequest *request) { return true; };
virtual esp_err_t handleRequest(PsychicRequest *request) = 0;
// called to process this handler with its middleware chain and filers
esp_err_t process(PsychicRequest* request);
//bool filter(PsychicRequest* request);
PsychicHandler* addFilter(PsychicRequestFilterFunction fn);
bool filter(PsychicRequest* request);
PsychicHandler* addMiddleware(PsychicMiddleware* middleware);
PsychicHandler* addMiddleware(PsychicMiddlewareCallback fn);
void removeMiddleware(PsychicMiddleware *middleware);
// derived classes must implement these functions
virtual bool canHandle(PsychicRequest* request) { return true; };
virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) { return HTTPD_404_NOT_FOUND; };
};
#endif

View File

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

View File

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

View File

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

View File

@@ -2,9 +2,9 @@
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer()
PsychicHttpsServer::PsychicHttpsServer(uint16_t port) : PsychicHttpServer(port)
{
//for a SSL server
// for a SSL server
ssl_config = HTTPD_SSL_CONFIG_DEFAULT();
ssl_config.httpd.open_fn = PsychicHttpServer::openCallback;
ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback;
@@ -18,44 +18,53 @@ PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer()
// if we set it higher than 2 and use all the connections, we get lots of memory errors.
// not to mention there is no heap left over for the program itself.
ssl_config.httpd.max_open_sockets = 2;
setPort(port);
}
PsychicHttpsServer::~PsychicHttpsServer() {}
esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, const char *private_key)
void PsychicHttpsServer::setPort(uint16_t port)
{
this->_use_ssl = true;
this->ssl_config.port_secure = port;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
this->ssl_config.servercert = (uint8_t *)cert;
this->ssl_config.servercert_len = strlen(cert)+1;
#else
this->ssl_config.cacert_pem = (uint8_t *)cert;
this->ssl_config.cacert_len = strlen(cert)+1;
#endif
uint16_t PsychicHttpsServer::getPort()
{
return this->ssl_config.port_secure;
}
this->ssl_config.prvtkey_pem = (uint8_t *)private_key;
this->ssl_config.prvtkey_len = strlen(private_key)+1;
void PsychicHttpsServer::setCertificate(const uint8_t* cert, size_t cert_size, const uint8_t* private_key, size_t private_key_size)
{
if (cert) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
this->ssl_config.servercert = cert;
this->ssl_config.servercert_len = cert_size;
#else
this->ssl_config.cacert_pem = cert;
this->ssl_config.cacert_len = cert_size;
#endif
}
return this->_start();
if (private_key) {
this->ssl_config.prvtkey_pem = private_key;
this->ssl_config.prvtkey_len = private_key_size;
}
}
esp_err_t PsychicHttpsServer::_startServer()
{
if (this->_use_ssl)
return httpd_ssl_start(&this->server, &this->ssl_config);
else
return httpd_start(&this->server, &this->config);
return httpd_ssl_start(&this->server, &this->ssl_config);
}
void PsychicHttpsServer::stop()
esp_err_t PsychicHttpsServer::_stopServer()
{
if (this->_use_ssl)
httpd_ssl_stop(this->server);
else
httpd_stop(this->server);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
return httpd_ssl_stop(this->server);
#else
httpd_ssl_stop(this->server);
return ESP_OK;
#endif
}
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,52 +2,49 @@
#include "PsychicRequest.h"
#include <http_status.h>
PsychicResponse::PsychicResponse(PsychicRequest *request) :
_request(request),
_code(200),
_status(""),
_contentLength(0),
_body("")
PsychicResponse::PsychicResponse(PsychicRequest* request) : _request(request),
_code(200),
_status(""),
_contentType(emptyString),
_contentLength(0),
_body("")
{
// get our global headers out of the way
for (auto& header : DefaultHeaders::Instance().getHeaders())
addHeader(header.field.c_str(), header.value.c_str());
}
PsychicResponse::~PsychicResponse()
{
//clean up our header variables. we have to do this on desctruct since httpd_resp_send doesn't store copies
for (HTTPHeader header : _headers)
{
free(header.field);
free(header.value);
}
_headers.clear();
}
void PsychicResponse::addHeader(const char *field, const char *value)
void PsychicResponse::addHeader(const char* field, const char* value)
{
//these get freed after send by the destructor
HTTPHeader header;
header.field =(char *)malloc(strlen(field)+1);
header.value = (char *)malloc(strlen(value)+1);
// erase any existing ones.
for (auto itr = _headers.begin(); itr != _headers.end();) {
if (itr->field.equalsIgnoreCase(field))
itr = _headers.erase(itr);
else
itr++;
}
strlcpy(header.field, field, strlen(field)+1);
strlcpy(header.value, value, strlen(value)+1);
_headers.push_back(header);
// now add it.
_headers.push_back({field, value});
}
void PsychicResponse::setCookie(const char *name, const char *value, unsigned long secondsFromNow, const char *extras)
void PsychicResponse::setCookie(const char* name, const char* value, unsigned long secondsFromNow, const char* extras)
{
time_t now = time(nullptr);
String output;
output = urlEncode(name) + "=" + urlEncode(value);
//if current time isn't modern, default to using max age
// if current time isn't modern, default to using max age
if (now < 1700000000)
output += "; Max-Age=" + String(secondsFromNow);
//otherwise, set an expiration date
else
{
output += "; Max-Age=" + String(secondsFromNow);
// otherwise, set an expiration date
else {
time_t expirationTimestamp = now + secondsFromNow;
// Convert the expiration timestamp to a formatted string for the "expires" attribute
@@ -57,11 +54,11 @@ void PsychicResponse::setCookie(const char *name, const char *value, unsigned lo
output += "; Expires=" + String(expires);
}
//did we get any extras?
// did we get any extras?
if (strlen(extras))
output += "; " + String(extras);
//okay, add it in.
// okay, add it in.
addHeader("Set-Cookie", output.c_str());
}
@@ -70,24 +67,24 @@ void PsychicResponse::setCode(int code)
_code = code;
}
void PsychicResponse::setContentType(const char *contentType)
void PsychicResponse::setContentType(const char* contentType)
{
httpd_resp_set_type(_request->request(), contentType);
_contentType = contentType;
}
void PsychicResponse::setContent(const char *content)
void PsychicResponse::setContent(const char* content)
{
_body = content;
setContentLength(strlen(content));
}
void PsychicResponse::setContent(const uint8_t *content, size_t len)
void PsychicResponse::setContent(const uint8_t* content, size_t len)
{
_body = (char *)content;
_body = (char*)content;
setContentLength(len);
}
const char * PsychicResponse::getContent()
const char* PsychicResponse::getContent()
{
return _body;
}
@@ -99,17 +96,20 @@ size_t PsychicResponse::getContentLength()
esp_err_t PsychicResponse::send()
{
//esp-idf makes you set the whole status.
// esp-idf makes you set the whole status.
sprintf(_status, "%u %s", _code, http_status_reason(_code));
httpd_resp_set_status(_request->request(), _status);
//our headers too
// set the content type
httpd_resp_set_type(_request->request(), _contentType.c_str());
// our headers too
this->sendHeaders();
//now send it off
// now send it off
esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength());
//did something happen?
// did something happen?
if (err != ESP_OK)
ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err));
@@ -118,38 +118,21 @@ esp_err_t PsychicResponse::send()
void PsychicResponse::sendHeaders()
{
//get our global headers out of the way first
for (HTTPHeader header : DefaultHeaders::Instance().getHeaders())
httpd_resp_set_hdr(_request->request(), header.field, header.value);
//now do our individual headers
for (HTTPHeader header : _headers)
httpd_resp_set_hdr(this->_request->request(), header.field, header.value);
// DO NOT RELEASE HEADERS HERE... released in the PsychicResponse destructor after they have been sent.
// httpd_resp_set_hdr just passes on the pointer, but its needed after this call.
// clean up our header variables after send
// for (HTTPHeader header : _headers)
// {
// free(header.field);
// free(header.value);
// }
// _headers.clear();
// now do our individual headers
for (auto& header : _headers)
httpd_resp_set_hdr(this->_request->request(), header.field.c_str(), header.value.c_str());
}
esp_err_t PsychicResponse::sendChunk(uint8_t *chunk, size_t chunksize)
esp_err_t PsychicResponse::sendChunk(uint8_t* chunk, size_t chunksize)
{
/* Send the buffer contents as HTTP response chunk */
esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize);
if (err != ESP_OK)
{
ESP_LOGD(PH_TAG, "Sending chunk: %d", chunksize);
esp_err_t err = httpd_resp_send_chunk(request(), (char*)chunk, chunksize);
if (err != ESP_OK) {
ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err));
/* Abort sending file */
httpd_resp_sendstr_chunk(this->_request->request(), NULL);
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
}
return err;
@@ -159,4 +142,63 @@ esp_err_t PsychicResponse::finishChunking()
{
/* Respond with an empty chunk to signal HTTP response completion */
return httpd_resp_send_chunk(this->_request->request(), NULL, 0);
}
}
esp_err_t PsychicResponse::redirect(const char* url)
{
if (!_code)
setCode(301);
addHeader("Location", url);
return send();
}
esp_err_t PsychicResponse::send(int code)
{
setCode(code);
return send();
}
esp_err_t PsychicResponse::send(const char* content)
{
if (!_code)
setCode(200);
if (_contentType.isEmpty())
setContentType("text/html");
setContent(content);
return send();
}
esp_err_t PsychicResponse::send(const char* contentType, const char* content)
{
if (!_code)
setCode(200);
setContentType(contentType);
setContent(content);
return send();
}
esp_err_t PsychicResponse::send(int code, const char* contentType, const char* content)
{
setCode(code);
setContentType(contentType);
setContent(content);
return send();
}
esp_err_t PsychicResponse::send(int code, const char* contentType, const uint8_t* content, size_t len)
{
setCode(code);
setContentType(contentType);
setContent(content, len);
return send();
}
esp_err_t PsychicResponse::error(httpd_err_code_t code, const char* message)
{
return httpd_resp_send_err(_request->_req, code, message);
}
httpd_req_t* PsychicResponse::request()
{
return _request->_req;
}

View File

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

View File

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

View File

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

View File

@@ -5,70 +5,88 @@
/*************************************/
PsychicStaticFileHandler::PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
: _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("")
: _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("")
{
// Ensure leading '/'
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path;
if (_uri.length() == 0 || _uri[0] != '/')
_uri = "/" + _uri;
if (_path.length() == 0 || _path[0] != '/')
_path = "/" + _path;
// If path ends with '/' we assume a hint that this is a directory to improve performance.
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
_isDir = _path[_path.length()-1] == '/';
_isDir = _path[_path.length() - 1] == '/';
// Remove the trailing '/' so we can handle default file
// Notice that root will be "" not "/"
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
if (_uri[_uri.length() - 1] == '/')
_uri = _uri.substring(0, _uri.length() - 1);
if (_path[_path.length() - 1] == '/')
_path = _path.substring(0, _path.length() - 1);
// Reset stats
_gzipFirst = false;
_gzipStats = 0xF8;
}
PsychicStaticFileHandler& PsychicStaticFileHandler::setIsDir(bool isDir){
_isDir = isDir;
return *this;
}
PsychicStaticFileHandler& PsychicStaticFileHandler::setDefaultFile(const char* filename){
_default_file = String(filename);
return *this;
}
PsychicStaticFileHandler& PsychicStaticFileHandler::setCacheControl(const char* cache_control){
_cache_control = String(cache_control);
return *this;
}
PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(const char* last_modified){
_last_modified = String(last_modified);
return *this;
}
PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(struct tm* last_modified){
char result[30];
strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified);
return setLastModified((const char *)result);
}
bool PsychicStaticFileHandler::canHandle(PsychicRequest *request)
PsychicStaticFileHandler* PsychicStaticFileHandler::setIsDir(bool isDir)
{
if(request->method() != HTTP_GET || !request->uri().startsWith(_uri) )
_isDir = isDir;
return this;
}
PsychicStaticFileHandler* PsychicStaticFileHandler::setDefaultFile(const char* filename)
{
_default_file = filename;
return this;
}
PsychicStaticFileHandler* PsychicStaticFileHandler::setCacheControl(const char* cache_control)
{
_cache_control = cache_control;
return this;
}
PsychicStaticFileHandler* PsychicStaticFileHandler::setLastModified(const char* last_modified)
{
_last_modified = String(last_modified);
return this;
}
PsychicStaticFileHandler* PsychicStaticFileHandler::setLastModified(struct tm* last_modified)
{
char result[30];
strftime(result, 30, "%a, %d %b %Y %H:%M:%S %Z", last_modified);
return setLastModified((const char*)result);
}
bool PsychicStaticFileHandler::canHandle(PsychicRequest* request)
{
if (request->method() != HTTP_GET) {
ESP_LOGD(PH_TAG, "Request %s refused by PsychicStaticFileHandler: %s", request->uri().c_str(), request->methodStr().c_str());
return false;
}
if (_getFile(request))
if (!request->uri().startsWith(_uri)) {
ESP_LOGD(PH_TAG, "Request %s refused by PsychicStaticFileHandler: does not start with %s", request->uri().c_str(), _uri.c_str());
return false;
}
if (_getFile(request)) {
return true;
}
ESP_LOGD(PH_TAG, "Request %s refused by PsychicStaticFileHandler: file not found", request->uri().c_str());
return false;
}
bool PsychicStaticFileHandler::_getFile(PsychicRequest *request)
bool PsychicStaticFileHandler::_getFile(PsychicRequest* request)
{
// Remove the found uri
String path = request->uri().substring(_uri.length());
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/');
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/');
path = _path + path;
@@ -81,7 +99,7 @@ bool PsychicStaticFileHandler::_getFile(PsychicRequest *request)
return false;
// 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 += _default_file;
@@ -100,14 +118,14 @@ bool PsychicStaticFileHandler::_fileExists(const String& path)
if (_gzipFirst) {
_file = _fs.open(gzip, "r");
gzipFound = FILE_IS_REAL(_file);
if (!gzipFound){
if (!gzipFound) {
_file = _fs.open(path, "r");
fileFound = FILE_IS_REAL(_file);
}
} else {
_file = _fs.open(path, "r");
fileFound = FILE_IS_REAL(_file);
if (!fileFound){
if (!fileFound) {
_file = _fs.open(gzip, "r");
gzipFound = FILE_IS_REAL(_file);
}
@@ -115,17 +133,21 @@ bool PsychicStaticFileHandler::_fileExists(const String& path)
bool found = fileFound || gzipFound;
if (found)
{
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
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
}
ESP_LOGD(PH_TAG, "PsychicStaticFileHandler _fileExists(%s): %d", path.c_str(), found);
return found;
}
@@ -133,36 +155,32 @@ uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const
{
uint8_t w = value;
uint8_t n;
for (n=0; w!=0; n++) w&=w-1;
for (n = 0; w != 0; n++)
w &= w - 1;
return n;
}
esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request)
esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest* request, PsychicResponse* res)
{
if (_file == true)
{
//is it not modified?
if (_file == true) {
// is it not modified?
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")) {
_file.close();
request->reply(304); // Not modified
res->send(304); // Not modified
}
//does our Etag match?
else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag))
{
// does our Etag match?
else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) {
_file.close();
PsychicResponse response(request);
response.addHeader("Cache-Control", _cache_control.c_str());
response.addHeader("ETag", etag.c_str());
response.setCode(304);
response.send();
res->addHeader("Cache-Control", _cache_control.c_str());
res->addHeader("ETag", etag.c_str());
res->setCode(304);
res->send();
}
//nope, send them the full file.
else
{
PsychicFileResponse response(request, _fs, _filename);
// nope, send them the full file.
else {
PsychicFileResponse response(res, _fs, _filename);
if (_last_modified.length())
response.addHeader("Last-Modified", _last_modified.c_str());
@@ -174,7 +192,7 @@ esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request)
return response.send();
}
} else {
return request->reply(404);
return res->send(404);
}
return ESP_OK;

View File

@@ -2,18 +2,21 @@
#define PsychicStaticFileHandler_h
#include "PsychicCore.h"
#include "PsychicWebHandler.h"
#include "PsychicFileResponse.h"
#include "PsychicRequest.h"
#include "PsychicResponse.h"
#include "PsychicFileResponse.h"
#include "PsychicWebHandler.h"
class PsychicStaticFileHandler : public PsychicWebHandler
{
using File = fs::File;
using FS = fs::FS;
class PsychicStaticFileHandler : public PsychicWebHandler {
using File = fs::File;
using FS = fs::FS;
private:
bool _getFile(PsychicRequest *request);
bool _getFile(PsychicRequest* request);
bool _fileExists(const String& path);
uint8_t _countBits(const uint8_t value) const;
protected:
FS _fs;
File _file;
@@ -26,16 +29,17 @@ class PsychicStaticFileHandler : public PsychicWebHandler {
bool _isDir;
bool _gzipFirst;
uint8_t _gzipStats;
public:
PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
bool canHandle(PsychicRequest *request) override;
esp_err_t handleRequest(PsychicRequest *request) override;
PsychicStaticFileHandler& setIsDir(bool isDir);
PsychicStaticFileHandler& setDefaultFile(const char* filename);
PsychicStaticFileHandler& setCacheControl(const char* cache_control);
PsychicStaticFileHandler& setLastModified(const char* last_modified);
PsychicStaticFileHandler& setLastModified(struct tm* last_modified);
//PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
bool canHandle(PsychicRequest* request) override;
esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override;
PsychicStaticFileHandler* setIsDir(bool isDir);
PsychicStaticFileHandler* setDefaultFile(const char* filename);
PsychicStaticFileHandler* setCacheControl(const char* cache_control);
PsychicStaticFileHandler* setLastModified(const char* last_modified);
PsychicStaticFileHandler* setLastModified(struct tm* last_modified);
// PsychicStaticFileHandler* setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
};
#endif /* PsychicHttp_h */

View File

@@ -1,63 +1,62 @@
#include "PsychicStreamResponse.h"
#include "PsychicResponse.h"
#include "PsychicRequest.h"
#include "PsychicResponse.h"
PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType)
: PsychicResponse(request), _buffer(NULL) {
PsychicStreamResponse::PsychicStreamResponse(PsychicResponse* response, const String& contentType)
: PsychicResponseDelegate(response), _buffer(NULL)
{
//setContentType(contentType.c_str());
//addHeader("Content-Disposition", "inline");
setContentType(contentType.c_str());
addHeader("Content-Disposition", "inline");
}
PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name)
: PsychicResponse(request), _buffer(NULL) {
PsychicStreamResponse::PsychicStreamResponse(PsychicResponse* response, const String& contentType, const String& name)
: PsychicResponseDelegate(response), _buffer(NULL)
{
setContentType(contentType.c_str());
char buf[26+name.length()];
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", name.c_str());
char buf[26 + name.length()];
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", name.c_str());
addHeader("Content-Disposition", buf);
}
PsychicStreamResponse::~PsychicStreamResponse()
{
endSend();
}
esp_err_t PsychicStreamResponse::beginSend()
{
if(_buffer)
if (_buffer)
return ESP_OK;
//Buffer to hold ChunkPrinter and stream buffer. Using placement new will keep us at a single allocation.
// 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)
if (!_buffer)
{
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", STREAM_CHUNK_SIZE + sizeof(ChunkPrinter));
httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
return ESP_FAIL;
}
_printer = new (_buffer) ChunkPrinter(this, _buffer + sizeof(ChunkPrinter), STREAM_CHUNK_SIZE);
_printer = new (_buffer) ChunkPrinter(_response, _buffer + sizeof(ChunkPrinter), STREAM_CHUNK_SIZE);
sendHeaders();
return ESP_OK;
}
esp_err_t PsychicStreamResponse::endSend()
{
esp_err_t err = ESP_OK;
if(!_buffer)
if (!_buffer)
err = ESP_FAIL;
else
{
_printer->~ChunkPrinter(); //flushed on destruct
_printer->~ChunkPrinter(); // flushed on destruct
err = finishChunking();
free(_buffer);
_buffer = NULL;
@@ -65,29 +64,25 @@ esp_err_t PsychicStreamResponse::endSend()
return err;
}
void PsychicStreamResponse::flush()
{
if(_buffer)
if (_buffer)
_printer->flush();
}
size_t PsychicStreamResponse::write(uint8_t data)
{
return _buffer ? _printer->write(data) : 0;
}
size_t PsychicStreamResponse::write(const uint8_t *buffer, size_t size)
size_t PsychicStreamResponse::write(const uint8_t* buffer, size_t size)
{
return _buffer ? _printer->write(buffer, size) : 0;
}
size_t PsychicStreamResponse::copyFrom(Stream &stream)
size_t PsychicStreamResponse::copyFrom(Stream& stream)
{
if(_buffer)
if (_buffer)
return _printer->copyFrom(stream);
return 0;

View File

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

View File

@@ -1,34 +1,19 @@
#include "PsychicUploadHandler.h"
PsychicUploadHandler::PsychicUploadHandler() :
PsychicWebHandler()
, _temp()
, _parsedLength(0)
, _multiParseState(EXPECT_BOUNDARY)
, _boundaryPosition(0)
, _itemStartIndex(0)
, _itemSize(0)
, _itemName()
, _itemFilename()
, _itemType()
, _itemValue()
, _itemBuffer(0)
, _itemBufferIndex(0)
, _itemIsFile(false)
{}
PsychicUploadHandler::PsychicUploadHandler() : PsychicWebHandler(), _uploadCallback(nullptr)
{
}
PsychicUploadHandler::~PsychicUploadHandler() {}
bool PsychicUploadHandler::canHandle(PsychicRequest *request) {
bool PsychicUploadHandler::canHandle(PsychicRequest* request)
{
return true;
}
esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest *request)
esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest* request, PsychicResponse* response)
{
esp_err_t err = ESP_OK;
//save it for later (multipart)
_request = request;
_parsedLength = 0;
/* File cannot be larger than a limit */
if (request->contentLength() > request->server()->maxUploadSize)
{
@@ -37,63 +22,59 @@ esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest *request)
/* Respond with 400 Bad Request */
char error[50];
sprintf(error, "File size must be less than %lu bytes!", request->server()->maxUploadSize);
httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error);
/* Return failure to close underlying connection else the incoming file content will keep the socket busy */
return ESP_FAIL;
return httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error);
}
//we might want to access some of these params
request->loadParams();
// TODO: support for the 100 header. not sure if we can do it.
// if (request->header("Expect").equals("100-continue"))
// {
// char response[] = "100 Continue";
// httpd_socket_send(self->server, httpd_req_to_sockfd(req), response, strlen(response), 0);
// }
//TODO: support for the 100 header. not sure if we can do it.
// if (request->header("Expect").equals("100-continue"))
// {
// char response[] = "100 Continue";
// httpd_socket_send(self->server, httpd_req_to_sockfd(req), response, strlen(response), 0);
// }
//2 types of upload requests
// 2 types of upload requests
if (request->isMultipart())
err = _multipartUploadHandler(request);
else
err = _basicUploadHandler(request);
//we can also call onRequest for some final processing and response
// we can also call onRequest for some final processing and response
if (err == ESP_OK)
{
if (_requestCallback != NULL)
err = _requestCallback(request);
err = _requestCallback(request, response);
else
err = request->reply("Upload Successful.");
err = response->send("Upload Successful.");
}
else if (err == ESP_ERR_HTTPD_INVALID_REQ)
response->send(400, "text/html", "No multipart boundary found.");
else
request->reply(500, "text/html", "Error processing upload.");
response->send(500, "text/html", "Error processing upload.");
return err;
}
esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request)
esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest* request)
{
esp_err_t err = ESP_OK;
String filename = request->getFilename();
/* Retrieve the pointer to scratch buffer for temporary storage */
char *buf = (char *)malloc(FILE_CHUNK_SIZE);
char* buf = (char*)malloc(FILE_CHUNK_SIZE);
int received;
unsigned long index = 0;
unsigned long index = 0;
/* Content length of the request gives the size of the file being uploaded */
int remaining = request->contentLength();
while (remaining > 0)
{
#ifdef ENABLE_ASYNC
httpd_sess_update_lru_counter(request->server()->server, request->client()->socket());
#endif
#ifdef ENABLE_ASYNC
httpd_sess_update_lru_counter(request->server()->server, request->client()->socket());
#endif
//ESP_LOGD(PH_TAG, "Remaining size : %d", remaining);
// ESP_LOGD(PH_TAG, "Remaining size : %d", remaining);
/* Receive the file part by part into a buffer */
if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0)
@@ -101,7 +82,7 @@ esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request)
/* Retry if timeout occurred */
if (received == HTTPD_SOCK_ERR_TIMEOUT)
continue;
//bail if we got an error
// bail if we got an error
else if (received == HTTPD_SOCK_ERR_FAIL)
{
ESP_LOGE(PH_TAG, "Socket error");
@@ -110,10 +91,10 @@ esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request)
}
}
//call our upload callback here.
// call our upload callback here.
if (_uploadCallback != NULL)
{
err = _uploadCallback(request, filename, index, (uint8_t *)buf, received, (remaining - received == 0));
err = _uploadCallback(request, filename, index, (uint8_t*)buf, received, (remaining - received == 0));
if (err != ESP_OK)
break;
}
@@ -129,267 +110,20 @@ esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request)
index += received;
}
//dont forget to free our buffer
// dont forget to free our buffer
free(buf);
return err;
}
esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest *request)
esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest* request)
{
esp_err_t err = ESP_OK;
String value = request->header("Content-Type");
if (value.startsWith("multipart/")){
_boundary = value.substring(value.indexOf('=')+1);
_boundary.replace("\"","");
} else {
ESP_LOGE(PH_TAG, "No multipart boundary found.");
return request->reply(400, "text/html", "No multipart boundary found.");
}
char *buf = (char *)malloc(FILE_CHUNK_SIZE);
int received;
unsigned long index = 0;
/* Content length of the request gives the size of the file being uploaded */
int remaining = request->contentLength();
while (remaining > 0)
{
#ifdef ENABLE_ASYNC
httpd_sess_update_lru_counter(request->server()->server, request->client()->socket());
#endif
//ESP_LOGD(PH_TAG, "Remaining size : %d", remaining);
/* Receive the file part by part into a buffer */
if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0)
{
/* Retry if timeout occurred */
if (received == HTTPD_SOCK_ERR_TIMEOUT)
continue;
//bail if we got an error
else if (received == HTTPD_SOCK_ERR_FAIL)
{
ESP_LOGE(PH_TAG, "Socket error");
err = ESP_FAIL;
break;
}
}
//parse it 1 byte at a time.
for (int i=0; i<received; i++)
{
/* Keep track of remaining size of the file left to be uploaded */
remaining--;
index++;
//send it to our parser
_parseMultipartPostByte(buf[i], !remaining);
_parsedLength++;
}
}
//dont forget to free our buffer
free(buf);
return err;
MultipartProcessor mpp(request, _uploadCallback);
return mpp.process();
}
PsychicUploadHandler * PsychicUploadHandler::onUpload(PsychicUploadCallback fn) {
PsychicUploadHandler* PsychicUploadHandler::onUpload(PsychicUploadCallback fn)
{
_uploadCallback = fn;
return this;
}
void PsychicUploadHandler::_handleUploadByte(uint8_t data, bool last)
{
_itemBuffer[_itemBufferIndex++] = data;
if(last || _itemBufferIndex == FILE_CHUNK_SIZE)
{
if(_uploadCallback)
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, last);
_itemBufferIndex = 0;
}
}
#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0)
void PsychicUploadHandler::_parseMultipartPostByte(uint8_t data, bool last)
{
if (_multiParseState == PARSE_ERROR)
{
// not sure we can end up with an error during buffer fill, but jsut to be safe
if (_itemBuffer != NULL)
{
free(_itemBuffer);
_itemBuffer = NULL;
}
return;
}
if(!_parsedLength){
_multiParseState = EXPECT_BOUNDARY;
_temp = String();
_itemName = String();
_itemFilename = String();
_itemType = String();
}
if(_multiParseState == WAIT_FOR_RETURN1){
if(data != '\r'){
itemWriteByte(data);
} else {
_multiParseState = EXPECT_FEED1;
}
} else if(_multiParseState == EXPECT_BOUNDARY){
if(_parsedLength < 2 && data != '-'){
ESP_LOGE(PH_TAG, "Multipart: No boundary");
_multiParseState = PARSE_ERROR;
return;
} else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){
ESP_LOGE(PH_TAG, "Multipart: Multipart malformed");
_multiParseState = PARSE_ERROR;
return;
} else if(_parsedLength - 2 == _boundary.length() && data != '\r'){
ESP_LOGE(PH_TAG, "Multipart: Multipart missing carriage return");
_multiParseState = PARSE_ERROR;
return;
} else if(_parsedLength - 3 == _boundary.length()){
if(data != '\n'){
ESP_LOGE(PH_TAG, "Multipart: Multipart missing newline");
_multiParseState = PARSE_ERROR;
return;
}
_multiParseState = PARSE_HEADERS;
_itemIsFile = false;
}
} else if(_multiParseState == PARSE_HEADERS){
if((char)data != '\r' && (char)data != '\n')
_temp += (char)data;
if((char)data == '\n'){
if(_temp.length()){
if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type")){
_itemType = _temp.substring(14);
_itemIsFile = true;
} else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition")){
_temp = _temp.substring(_temp.indexOf(';') + 2);
while(_temp.indexOf(';') > 0){
String name = _temp.substring(0, _temp.indexOf('='));
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
if(name == "name"){
_itemName = nameVal;
} else if(name == "filename"){
_itemFilename = nameVal;
_itemIsFile = true;
}
_temp = _temp.substring(_temp.indexOf(';') + 2);
}
String name = _temp.substring(0, _temp.indexOf('='));
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
if(name == "name"){
_itemName = nameVal;
} else if(name == "filename"){
_itemFilename = nameVal;
_itemIsFile = true;
}
}
_temp = String();
} else {
_multiParseState = WAIT_FOR_RETURN1;
//value starts from here
_itemSize = 0;
_itemStartIndex = _parsedLength;
_itemValue = String();
if(_itemIsFile){
if(_itemBuffer)
free(_itemBuffer);
_itemBuffer = (uint8_t*)malloc(FILE_CHUNK_SIZE);
if(_itemBuffer == NULL){
ESP_LOGE(PH_TAG, "Multipart: Failed to allocate buffer");
_multiParseState = PARSE_ERROR;
return;
}
_itemBufferIndex = 0;
}
}
}
} else if(_multiParseState == EXPECT_FEED1){
if(data != '\n'){
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
} else {
_multiParseState = EXPECT_DASH1;
}
} else if(_multiParseState == EXPECT_DASH1){
if(data != '-'){
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last);
} else {
_multiParseState = EXPECT_DASH2;
}
} else if(_multiParseState == EXPECT_DASH2){
if(data != '-'){
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last);
} else {
_multiParseState = BOUNDARY_OR_DATA;
_boundaryPosition = 0;
}
} else if(_multiParseState == BOUNDARY_OR_DATA){
if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
uint8_t i;
for(i=0; i<_boundaryPosition; i++)
itemWriteByte(_boundary.c_str()[i]);
_parseMultipartPostByte(data, last);
} else if(_boundaryPosition == _boundary.length() - 1){
_multiParseState = DASH3_OR_RETURN2;
if(!_itemIsFile){
_request->addParam(_itemName, _itemValue);
//_addParam(new AsyncWebParameter(_itemName, _itemValue, true));
} else {
if(_itemSize){
if(_uploadCallback)
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
_itemBufferIndex = 0;
_request->addParam(new PsychicWebParameter(_itemName, _itemFilename, true, true, _itemSize));
}
free(_itemBuffer);
_itemBuffer = NULL;
}
} else {
_boundaryPosition++;
}
} else if(_multiParseState == DASH3_OR_RETURN2){
if(data == '-' && (_request->contentLength() - _parsedLength - 4) != 0){
ESP_LOGE(PH_TAG, "ERROR: The parser got to the end of the POST but is expecting more bytes!");
_multiParseState = PARSE_ERROR;
return;
}
if(data == '\r'){
_multiParseState = EXPECT_FEED2;
} else if(data == '-' && _request->contentLength() == (_parsedLength + 4)){
_multiParseState = PARSING_FINISHED;
} else {
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
_parseMultipartPostByte(data, last);
}
} else if(_multiParseState == EXPECT_FEED2){
if(data == '\n'){
_multiParseState = PARSE_HEADERS;
_itemIsFile = false;
} else {
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
}
}
}

View File

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

View File

@@ -0,0 +1,47 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
/** Major version number (X.x.x) */
#define PSYCHIC_VERSION_MAJOR 2
/** Minor version number (x.X.x) */
#define PSYCHIC_VERSION_MINOR 0
/** Patch version number (x.x.X) */
#define PSYCHIC_VERSION_PATCH 0
/**
* Macro to convert PsychicHttp version number into an integer
*
* To be used in comparisons, such as PSYCHIC_VERSION >= PSYCHIC_VERSION_VAL(2, 0, 0)
*/
#define PSYCHIC_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
/**
* Current PsychicHttp version, as an integer
*
* To be used in comparisons, such as PSYCHIC_VERSION >= PSYCHIC_VERSION_VAL(2, 0, 0)
*/
#define PSYCHIC_VERSION PSYCHIC_VERSION_VAL(PSYCHIC_VERSION_MAJOR, PSYCHIC_VERSION_MINOR, PSYCHIC_VERSION_PATCH)
/**
* Current PsychicHttp version, as string
*/
#ifndef PSYCHIC_df2xstr
#define PSYCHIC_df2xstr(s) #s
#endif
#ifndef PSYCHIC_df2str
#define PSYCHIC_df2str(s) PSYCHIC_df2xstr(s)
#endif
#define PSYCHIC_VERSION_STR PSYCHIC_df2str(PSYCHIC_VERSION_MAJOR) "." PSYCHIC_df2str(PSYCHIC_VERSION_MINOR) "." PSYCHIC_df2str(PSYCHIC_VERSION_PATCH)

View File

@@ -1,21 +1,22 @@
#include "PsychicWebHandler.h"
PsychicWebHandler::PsychicWebHandler() :
PsychicHandler(),
_requestCallback(NULL),
_onOpen(NULL),
_onClose(NULL)
{}
PsychicWebHandler::PsychicWebHandler() : PsychicHandler(),
_requestCallback(NULL),
_onOpen(NULL),
_onClose(NULL)
{
}
PsychicWebHandler::~PsychicWebHandler() {}
bool PsychicWebHandler::canHandle(PsychicRequest *request) {
bool PsychicWebHandler::canHandle(PsychicRequest* request)
{
return true;
}
esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request)
esp_err_t PsychicWebHandler::handleRequest(PsychicRequest* request, PsychicResponse* response)
{
//lookup our client
PsychicClient *client = checkForNewClient(request->client());
// lookup our client
PsychicClient* client = checkForNewClient(request->client());
if (client->isNew)
openCallback(client);
@@ -27,48 +28,53 @@ esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request)
/* Respond with 400 Bad Request */
char error[60];
sprintf(error, "Request body must be less than %lu bytes!", request->server()->maxRequestBodySize);
httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error);
response->send(400, "text/html", error);
/* Return failure to close underlying connection else the incoming file content will keep the socket busy */
return ESP_FAIL;
}
//get our body loaded up.
// get our body loaded up.
esp_err_t err = request->loadBody();
if (err != ESP_OK)
return err;
return response->send(400, "text/html", "Error loading request body.");
//load our params in.
// load our params in.
request->loadParams();
//okay, pass on to our callback.
// okay, pass on to our callback.
if (this->_requestCallback != NULL)
err = this->_requestCallback(request);
err = this->_requestCallback(request, response);
return err;
}
PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) {
PsychicWebHandler* PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn)
{
_requestCallback = fn;
return this;
}
void PsychicWebHandler::openCallback(PsychicClient *client) {
void PsychicWebHandler::openCallback(PsychicClient* client)
{
if (_onOpen != NULL)
_onOpen(client);
}
void PsychicWebHandler::closeCallback(PsychicClient *client) {
void PsychicWebHandler::closeCallback(PsychicClient* client)
{
if (_onClose != NULL)
_onClose(getClient(client));
}
PsychicWebHandler * PsychicWebHandler::onOpen(PsychicClientCallback fn) {
PsychicWebHandler* PsychicWebHandler::onOpen(PsychicClientCallback fn)
{
_onOpen = fn;
return this;
}
PsychicWebHandler * PsychicWebHandler::onClose(PsychicClientCallback fn) {
PsychicWebHandler* PsychicWebHandler::onClose(PsychicClientCallback fn)
{
_onClose = fn;
return this;
}

View File

@@ -7,10 +7,11 @@
#include "PsychicHandler.h"
/*
* 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 PsychicWebHandler : public PsychicHandler {
class PsychicWebHandler : public PsychicHandler
{
protected:
PsychicHttpRequestCallback _requestCallback;
PsychicClientCallback _onOpen;
@@ -20,15 +21,15 @@ class PsychicWebHandler : public PsychicHandler {
PsychicWebHandler();
~PsychicWebHandler();
virtual bool canHandle(PsychicRequest *request) override;
virtual esp_err_t handleRequest(PsychicRequest *request) override;
PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn);
virtual bool canHandle(PsychicRequest* request) override;
virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override;
PsychicWebHandler* onRequest(PsychicHttpRequestCallback fn);
virtual void openCallback(PsychicClient *client);
virtual void closeCallback(PsychicClient *client);
virtual void openCallback(PsychicClient* client);
virtual void closeCallback(PsychicClient* client);
PsychicWebHandler *onOpen(PsychicClientCallback fn);
PsychicWebHandler *onClose(PsychicClientCallback fn);
PsychicWebHandler* onOpen(PsychicClientCallback fn);
PsychicWebHandler* onClose(PsychicClientCallback fn);
};
#endif

View File

@@ -5,7 +5,8 @@
* PARAMETER :: Chainable object to hold GET/POST and FILE parameters
* */
class PsychicWebParameter {
class PsychicWebParameter
{
private:
String _name;
String _value;
@@ -14,7 +15,7 @@ class PsychicWebParameter {
bool _isFile;
public:
PsychicWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){}
PsychicWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0) : _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {}
const String& name() const { return _name; }
const String& value() const { return _value; }
size_t size() const { return _size; }
@@ -22,4 +23,4 @@ class PsychicWebParameter {
bool isFile() const { return _isFile; }
};
#endif //PsychicWebParameter_h
#endif // PsychicWebParameter_h

View File

@@ -4,9 +4,8 @@
/* PsychicWebSocketRequest */
/*************************************/
PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest *req) :
PsychicRequest(req->server(), req->request()),
_client(req->client())
PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest* req) : PsychicRequest(req->server(), req->request()),
_client(req->client())
{
}
@@ -14,16 +13,17 @@ PsychicWebSocketRequest::~PsychicWebSocketRequest()
{
}
PsychicWebSocketClient * PsychicWebSocketRequest::client() {
PsychicWebSocketClient* PsychicWebSocketRequest::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);
}
}
esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void *data, size_t len)
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));
@@ -35,7 +35,7 @@ esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void *data, s
return this->reply(&ws_pkt);
}
esp_err_t PsychicWebSocketRequest::reply(const char *buf)
esp_err_t PsychicWebSocketRequest::reply(const char* buf)
{
return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf));
}
@@ -44,80 +44,123 @@ esp_err_t PsychicWebSocketRequest::reply(const char *buf)
/* PsychicWebSocketClient */
/*************************************/
PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient *client)
: PsychicClient(client->server(), client->socket())
PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient* client)
: PsychicClient(client->server(), client->socket())
{
}
PsychicWebSocketClient::~PsychicWebSocketClient() {
PsychicWebSocketClient::~PsychicWebSocketClient()
{
}
esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt)
void PsychicWebSocketClient::_sendMessageCallback(esp_err_t err, int socket, void* arg)
{
return httpd_ws_send_frame_async(this->server(), this->socket(), ws_pkt);
}
// free our frame.
httpd_ws_frame_t* ws_pkt = (httpd_ws_frame_t*)arg;
free(ws_pkt->payload);
free(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);
if (err == ESP_OK)
return;
else if (err == ESP_FAIL)
ESP_LOGE(PH_TAG, "Websocket: send - socket error (#%d)", socket);
else if (err == ESP_ERR_INVALID_STATE)
ESP_LOGE(PH_TAG, "Websocket: Handshake was already done beforehand (#%d)", socket);
else if (err == ESP_ERR_INVALID_ARG)
ESP_LOGE(PH_TAG, "Websocket: Argument is invalid (null or non-WebSocket) (#%d)", socket);
else
ESP_LOGE(PH_TAG, "Websocket: Send message unknown error. (#%d)", socket);
}
esp_err_t PsychicWebSocketClient::sendMessage(const char *buf)
esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t* ws_pkt)
{
return sendMessage(ws_pkt->type, ws_pkt->payload, ws_pkt->len);
}
esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void* data, size_t len)
{
// init our frame.
httpd_ws_frame_t* ws_pkt = (httpd_ws_frame_t*)malloc(sizeof(httpd_ws_frame_t));
if (ws_pkt == NULL) {
ESP_LOGE(PH_TAG, "Websocket: out of memory");
return ESP_ERR_NO_MEM;
}
memset(ws_pkt, 0, sizeof(httpd_ws_frame_t)); // zero the datastructure out
// allocate for event text
ws_pkt->payload = (uint8_t*)malloc(len);
if (ws_pkt->payload == NULL) {
ESP_LOGE(PH_TAG, "Websocket: out of memory");
free(ws_pkt); // free our other memory
return ESP_ERR_NO_MEM;
}
memcpy(ws_pkt->payload, data, len);
ws_pkt->len = len;
ws_pkt->type = op;
esp_err_t err = httpd_ws_send_data_async(server(), socket(), ws_pkt, PsychicWebSocketClient::_sendMessageCallback, ws_pkt);
// take care of memory
if (err != ESP_OK) {
free(ws_pkt->payload);
free(ws_pkt);
}
return err;
}
esp_err_t PsychicWebSocketClient::sendMessage(const char* buf)
{
return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf));
}
PsychicWebSocketHandler::PsychicWebSocketHandler() :
PsychicHandler(),
_onOpen(NULL),
_onFrame(NULL),
_onClose(NULL)
{
}
PsychicWebSocketHandler::~PsychicWebSocketHandler() {
PsychicWebSocketHandler::PsychicWebSocketHandler() : PsychicHandler(),
_onOpen(NULL),
_onFrame(NULL),
_onClose(NULL)
{
}
PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket)
PsychicWebSocketHandler::~PsychicWebSocketHandler()
{
PsychicClient *client = PsychicHandler::getClient(socket);
}
PsychicWebSocketClient* PsychicWebSocketHandler::getClient(int socket)
{
PsychicClient* client = PsychicHandler::getClient(socket);
if (client == NULL)
return NULL;
if (client->_friend == NULL)
{
if (client->_friend == NULL) {
return NULL;
}
return (PsychicWebSocketClient *)client->_friend;
return (PsychicWebSocketClient*)client->_friend;
}
PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClient *client) {
PsychicWebSocketClient* PsychicWebSocketHandler::getClient(PsychicClient* client)
{
return getClient(client->socket());
}
void PsychicWebSocketHandler::addClient(PsychicClient *client) {
void PsychicWebSocketHandler::addClient(PsychicClient* client)
{
client->_friend = new PsychicWebSocketClient(client);
PsychicHandler::addClient(client);
}
void PsychicWebSocketHandler::removeClient(PsychicClient *client) {
void PsychicWebSocketHandler::removeClient(PsychicClient* client)
{
PsychicHandler::removeClient(client);
delete (PsychicWebSocketClient*)client->_friend;
client->_friend = NULL;
}
void PsychicWebSocketHandler::openCallback(PsychicClient *client) {
PsychicWebSocketClient *buddy = getClient(client);
if (buddy == NULL)
{
void PsychicWebSocketHandler::openCallback(PsychicClient* client)
{
PsychicWebSocketClient* buddy = getClient(client);
if (buddy == NULL) {
return;
}
@@ -125,10 +168,10 @@ void PsychicWebSocketHandler::openCallback(PsychicClient *client) {
_onOpen(getClient(buddy));
}
void PsychicWebSocketHandler::closeCallback(PsychicClient *client) {
PsychicWebSocketClient *buddy = getClient(client);
if (buddy == NULL)
{
void PsychicWebSocketHandler::closeCallback(PsychicClient* client)
{
PsychicWebSocketClient* buddy = getClient(client);
if (buddy == NULL) {
return;
}
@@ -138,28 +181,27 @@ void PsychicWebSocketHandler::closeCallback(PsychicClient *client) {
bool PsychicWebSocketHandler::isWebSocket() { return true; }
esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request)
esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest* request, PsychicResponse* response)
{
//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
if (request->method() == HTTP_GET)
{
if (request->method() == HTTP_GET) {
if (client->isNew)
openCallback(client);
return ESP_OK;
}
//prep our request
// prep our request
PsychicWebSocketRequest wsRequest(request);
//init our memory for storing the packet
// init our memory for storing the packet
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
uint8_t *buf = NULL;
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);
@@ -168,11 +210,11 @@ esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request)
return ret;
}
//okay, now try to load the packet
//ESP_LOGD(PH_TAG, "frame len is %d", ws_pkt.len);
// okay, now try to load the packet
// ESP_LOGD(PH_TAG, "frame len is %d", ws_pkt.len);
if (ws_pkt.len) {
/* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
buf = (uint8_t*) calloc(1, ws_pkt.len + 1);
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;
@@ -185,53 +227,53 @@ esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request)
free(buf);
return ret;
}
//ESP_LOGD(PH_TAG, "Got packet with message: %s", ws_pkt.payload);
// ESP_LOGD(PH_TAG, "Got packet with message: %s", ws_pkt.payload);
}
// Text messages are our payload.
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_BINARY)
{
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_BINARY) {
if (this->_onFrame != NULL)
ret = this->_onFrame(&wsRequest, &ws_pkt);
}
//logging housekeeping
// logging housekeeping
if (ret != ESP_OK)
ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret));
// ESP_LOGD(PH_TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d",
// request->server(),
// httpd_req_to_sockfd(request->request()),
// httpd_ws_get_fd_info(request->server()->server, httpd_req_to_sockfd(request->request())));
// ESP_LOGD(PH_TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d",
// request->server(),
// httpd_req_to_sockfd(request->request()),
// httpd_ws_get_fd_info(request->server()->server, httpd_req_to_sockfd(request->request())));
//dont forget to release our buffer memory
// dont forget to release our buffer memory
free(buf);
return ret;
}
PsychicWebSocketHandler * PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn) {
PsychicWebSocketHandler* PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn)
{
_onOpen = fn;
return this;
}
PsychicWebSocketHandler * PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn) {
PsychicWebSocketHandler* PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn)
{
_onFrame = fn;
return this;
}
PsychicWebSocketHandler * PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn) {
PsychicWebSocketHandler* PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn)
{
_onClose = fn;
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_LOGD(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket());
for (PsychicClient* client : _clients) {
// ESP_LOGD(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket());
if (client->_friend == NULL)
{
if (client->_friend == NULL) {
return;
}
@@ -240,7 +282,7 @@ void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt)
}
}
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));
@@ -252,7 +294,7 @@ void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void *data, size
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));
}

View File

@@ -7,19 +7,22 @@
class PsychicWebSocketRequest;
class PsychicWebSocketClient;
//callback function definitions
typedef std::function<void(PsychicWebSocketClient *client)> PsychicWebSocketClientCallback;
typedef std::function<esp_err_t(PsychicWebSocketRequest *request, httpd_ws_frame *frame)> PsychicWebSocketFrameCallback;
// callback function definitions
typedef std::function<void(PsychicWebSocketClient* client)> PsychicWebSocketClientCallback;
typedef std::function<esp_err_t(PsychicWebSocketRequest* request, httpd_ws_frame* frame)> PsychicWebSocketFrameCallback;
class PsychicWebSocketClient : public PsychicClient
{
protected:
static void _sendMessageCallback(esp_err_t err, int socket, void* arg);
public:
PsychicWebSocketClient(PsychicClient *client);
PsychicWebSocketClient(PsychicClient* client);
~PsychicWebSocketClient();
esp_err_t sendMessage(httpd_ws_frame_t * ws_pkt);
esp_err_t sendMessage(httpd_ws_type_t op, const void *data, size_t len);
esp_err_t sendMessage(const char *buf);
esp_err_t sendMessage(httpd_ws_frame_t* ws_pkt);
esp_err_t sendMessage(httpd_ws_type_t op, const void* data, size_t len);
esp_err_t sendMessage(const char* buf);
};
class PsychicWebSocketRequest : public PsychicRequest
@@ -28,17 +31,18 @@ class PsychicWebSocketRequest : public PsychicRequest
PsychicWebSocketClient _client;
public:
PsychicWebSocketRequest(PsychicRequest *req);
PsychicWebSocketRequest(PsychicRequest* req);
virtual ~PsychicWebSocketRequest();
PsychicWebSocketClient * client() override;
PsychicWebSocketClient* client() override;
esp_err_t reply(httpd_ws_frame_t * ws_pkt);
esp_err_t reply(httpd_ws_type_t op, const void *data, size_t len);
esp_err_t reply(const char *buf);
esp_err_t reply(httpd_ws_frame_t* ws_pkt);
esp_err_t reply(httpd_ws_type_t op, const void* data, size_t len);
esp_err_t reply(const char* buf);
};
class PsychicWebSocketHandler : public PsychicHandler {
class PsychicWebSocketHandler : public PsychicHandler
{
protected:
PsychicWebSocketClientCallback _onOpen;
PsychicWebSocketFrameCallback _onFrame;
@@ -48,23 +52,23 @@ class PsychicWebSocketHandler : public PsychicHandler {
PsychicWebSocketHandler();
~PsychicWebSocketHandler();
PsychicWebSocketClient * getClient(int socket) override;
PsychicWebSocketClient * getClient(PsychicClient *client) override;
void addClient(PsychicClient *client) override;
void removeClient(PsychicClient *client) override;
void openCallback(PsychicClient *client) override;
void closeCallback(PsychicClient *client) override;
PsychicWebSocketClient* getClient(int socket) override;
PsychicWebSocketClient* getClient(PsychicClient* client) override;
void addClient(PsychicClient* client) override;
void removeClient(PsychicClient* client) override;
void openCallback(PsychicClient* client) override;
void closeCallback(PsychicClient* client) override;
bool isWebSocket() override final;
esp_err_t handleRequest(PsychicRequest *request) override;
esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override;
PsychicWebSocketHandler *onOpen(PsychicWebSocketClientCallback fn);
PsychicWebSocketHandler *onFrame(PsychicWebSocketFrameCallback fn);
PsychicWebSocketHandler *onClose(PsychicWebSocketClientCallback fn);
PsychicWebSocketHandler* onOpen(PsychicWebSocketClientCallback fn);
PsychicWebSocketHandler* onFrame(PsychicWebSocketFrameCallback fn);
PsychicWebSocketHandler* onClose(PsychicWebSocketClientCallback fn);
void sendAll(httpd_ws_frame_t * ws_pkt);
void sendAll(httpd_ws_type_t op, const void *data, size_t len);
void sendAll(const char *buf);
void sendAll(httpd_ws_frame_t* ws_pkt);
void sendAll(httpd_ws_type_t op, const void* data, size_t len);
void sendAll(const char* buf);
};
#endif // PsychicWebSocket_h

View File

@@ -1,90 +1,112 @@
/************************************************************
TemplatePrinter Class
A basic templating engine for a stream of text.
This wraps the Arduino Print interface and writes to any
Print interface.
Written by Christopher Andrews (https://github.com/Chris--A)
************************************************************/
/************************************************************
TemplatePrinter Class
A basic templating engine for a stream of text.
This wraps the Arduino Print interface and writes to any
Print interface.
Written by Christopher Andrews (https://github.com/Chris--A)
************************************************************/
#include "TemplatePrinter.h"
void TemplatePrinter::resetParam(bool flush){
if(flush && _inParam){
void TemplatePrinter::resetParam(bool flush)
{
if (flush && _inParam)
{
_stream.write(_delimiter);
if(_paramPos)
if (_paramPos)
_stream.print(_paramBuffer);
}
memset(_paramBuffer, 0, sizeof(_paramBuffer));
_paramPos = 0;
_inParam = false;
}
void TemplatePrinter::flush(){
void TemplatePrinter::flush()
{
resetParam(true);
_stream.flush();
}
size_t TemplatePrinter::write(uint8_t data){
if(data == _delimiter){
size_t TemplatePrinter::write(uint8_t data)
{
if (data == _delimiter)
{
// End of parameter, send to callback
if(_inParam){
if (_inParam)
{
// On false, return the parameter place holder as is: not a parameter
// Bug fix: ignore parameters that are zero length.
if(!_paramPos || !_cb(_stream, _paramBuffer)){
// Bug fix: ignore parameters that are zero length.
if (!_paramPos || !_cb(_stream, _paramBuffer))
{
resetParam(true);
_stream.write(data);
}else{
}
else
{
resetParam(false);
}
// Start collecting parameter
}else{
// Start collecting parameter
}
else
{
_inParam = true;
}
}else{
}
else
{
// Are we collecting
if(_inParam){
if (_inParam)
{
// Is param still valid
if(isalnum(data) || data == '_'){
if (isalnum(data) || data == '_')
{
// Total param len must be 63, 1 for null.
if(_paramPos < sizeof(_paramBuffer) - 1){
if (_paramPos < sizeof(_paramBuffer) - 1)
{
_paramBuffer[_paramPos++] = data;
// Not a valid param
}else{
// Not a valid param
}
else
{
resetParam(true);
}
}else{
}
else
{
resetParam(true);
_stream.write(data);
}
// Just output
}else{
// Just output
}
else
{
_stream.write(data);
}
}
return 1;
}
size_t TemplatePrinter::copyFrom(Stream &stream){
size_t TemplatePrinter::copyFrom(Stream& stream)
{
size_t count = 0;
while(stream.available())
while (stream.available())
count += this->write(stream.read());
return count;
}

View File

@@ -1,51 +1,53 @@
#ifndef TemplatePrinter_h
#define TemplatePrinter_h
#define TemplatePrinter_h
#include "PsychicCore.h"
#include <Print.h>
/************************************************************
TemplatePrinter Class
A basic templating engine for a stream of text.
This wraps the Arduino Print interface and writes to any
Print interface.
Written by Christopher Andrews (https://github.com/Chris--A)
************************************************************/
class TemplatePrinter;
#include "PsychicCore.h"
#include <Print.h>
typedef std::function<bool(Print &output, const char *parameter)> TemplateCallback;
typedef std::function<void(TemplatePrinter &printer)> TemplateSourceCallback;
/************************************************************
class TemplatePrinter : public Print{
private:
bool _inParam;
char _paramBuffer[64];
uint8_t _paramPos;
Print &_stream;
TemplateCallback _cb;
char _delimiter;
void resetParam(bool flush);
public:
using Print::write;
TemplatePrinter Class
static void start(Print &stream, TemplateCallback cb, TemplateSourceCallback entry){
TemplatePrinter printer(stream, cb);
entry(printer);
}
A basic templating engine for a stream of text.
This wraps the Arduino Print interface and writes to any
Print interface.
TemplatePrinter(Print &stream, TemplateCallback cb, const char delimeter = '%') : _stream(stream), _cb(cb), _delimiter(delimeter) { resetParam(false); }
~TemplatePrinter(){ flush(); }
Written by Christopher Andrews (https://github.com/Chris--A)
void flush() override;
size_t write(uint8_t data) override;
size_t copyFrom(Stream &stream);
};
************************************************************/
class TemplatePrinter;
typedef std::function<bool(Print& output, const char* parameter)> TemplateCallback;
typedef std::function<void(TemplatePrinter& printer)> TemplateSourceCallback;
class TemplatePrinter : public Print
{
private:
bool _inParam;
char _paramBuffer[64];
uint8_t _paramPos;
Print& _stream;
TemplateCallback _cb;
char _delimiter;
void resetParam(bool flush);
public:
using Print::write;
static void start(Print& stream, TemplateCallback cb, TemplateSourceCallback entry)
{
TemplatePrinter printer(stream, cb);
entry(printer);
}
TemplatePrinter(Print& stream, TemplateCallback cb, const char delimeter = '%') : _stream(stream), _cb(cb), _delimiter(delimeter) { resetParam(false); }
~TemplatePrinter() { flush(); }
void flush() override;
size_t write(uint8_t data) override;
size_t copyFrom(Stream& stream);
};
#endif

View File

@@ -2,202 +2,222 @@
bool is_on_async_worker_thread(void)
{
// is our handle one of the known async handles?
TaskHandle_t handle = xTaskGetCurrentTaskHandle();
for (int i = 0; i < ASYNC_WORKER_COUNT; i++) {
if (worker_handles[i] == handle) {
return true;
}
// is our handle one of the known async handles?
TaskHandle_t handle = xTaskGetCurrentTaskHandle();
for (int i = 0; i < ASYNC_WORKER_COUNT; i++)
{
if (worker_handles[i] == handle)
{
return true;
}
return false;
}
return false;
}
// Submit an HTTP req to the async worker queue
esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler)
esp_err_t submit_async_req(httpd_req_t* req, httpd_req_handler_t handler)
{
// must create a copy of the request that we own
httpd_req_t* copy = NULL;
esp_err_t err = httpd_req_async_handler_begin(req, &copy);
if (err != ESP_OK) {
return err;
}
// must create a copy of the request that we own
httpd_req_t* copy = NULL;
esp_err_t err = httpd_req_async_handler_begin(req, &copy);
if (err != ESP_OK)
{
return err;
}
httpd_async_req_t async_req = {
.req = copy,
.handler = handler,
};
httpd_async_req_t async_req = {
.req = copy,
.handler = handler,
};
// How should we handle resource exhaustion?
// In this example, we immediately respond with an
// http error if no workers are available.
int ticks = 0;
// How should we handle resource exhaustion?
// In this example, we immediately respond with an
// http error if no workers are available.
int ticks = 0;
// counting semaphore: if success, we know 1 or
// more asyncReqTaskWorkers are available.
if (xSemaphoreTake(worker_ready_count, ticks) == false) {
ESP_LOGE(PH_TAG, "No workers are available");
httpd_req_async_handler_complete(copy); // cleanup
return ESP_FAIL;
}
// counting semaphore: if success, we know 1 or
// more asyncReqTaskWorkers are available.
if (xSemaphoreTake(worker_ready_count, ticks) == false)
{
ESP_LOGE(PH_TAG, "No workers are available");
httpd_req_async_handler_complete(copy); // cleanup
return ESP_FAIL;
}
// Since worker_ready_count > 0 the queue should already have space.
// But lets wait up to 100ms just to be safe.
if (xQueueSend(async_req_queue, &async_req, pdMS_TO_TICKS(100)) == false) {
ESP_LOGE(PH_TAG, "worker queue is full");
httpd_req_async_handler_complete(copy); // cleanup
return ESP_FAIL;
}
// Since worker_ready_count > 0 the queue should already have space.
// But lets wait up to 100ms just to be safe.
if (xQueueSend(async_req_queue, &async_req, pdMS_TO_TICKS(100)) == false)
{
ESP_LOGE(PH_TAG, "worker queue is full");
httpd_req_async_handler_complete(copy); // cleanup
return ESP_FAIL;
}
return ESP_OK;
return ESP_OK;
}
void async_req_worker_task(void *p)
void async_req_worker_task(void* p)
{
ESP_LOGI(PH_TAG, "starting async req task worker");
ESP_LOGI(PH_TAG, "starting async req task worker");
while (true) {
while (true)
{
// counting semaphore - this signals that a worker
// is ready to accept work
xSemaphoreGive(worker_ready_count);
// counting semaphore - this signals that a worker
// is ready to accept work
xSemaphoreGive(worker_ready_count);
// wait for a request
httpd_async_req_t async_req;
if (xQueueReceive(async_req_queue, &async_req, portMAX_DELAY)) {
// wait for a request
httpd_async_req_t async_req;
if (xQueueReceive(async_req_queue, &async_req, portMAX_DELAY))
{
ESP_LOGI(PH_TAG, "invoking %s", async_req.req->uri);
ESP_LOGI(PH_TAG, "invoking %s", async_req.req->uri);
// call the handler
async_req.handler(async_req.req);
// call the handler
async_req.handler(async_req.req);
// Inform the server that it can purge the socket used for
// this request, if needed.
if (httpd_req_async_handler_complete(async_req.req) != ESP_OK) {
ESP_LOGE(PH_TAG, "failed to complete async req");
}
}
// Inform the server that it can purge the socket used for
// this request, if needed.
if (httpd_req_async_handler_complete(async_req.req) != ESP_OK)
{
ESP_LOGE(PH_TAG, "failed to complete async req");
}
}
}
ESP_LOGW(PH_TAG, "worker stopped");
vTaskDelete(NULL);
ESP_LOGW(PH_TAG, "worker stopped");
vTaskDelete(NULL);
}
void start_async_req_workers(void)
{
// counting semaphore keeps track of available workers
worker_ready_count = xSemaphoreCreateCounting(
ASYNC_WORKER_COUNT, // Max Count
0); // Initial Count
if (worker_ready_count == NULL) {
ESP_LOGE(PH_TAG, "Failed to create workers counting Semaphore");
return;
}
// create queue
async_req_queue = xQueueCreate(1, sizeof(httpd_async_req_t));
if (async_req_queue == NULL){
ESP_LOGE(PH_TAG, "Failed to create async_req_queue");
vSemaphoreDelete(worker_ready_count);
return;
}
// start worker tasks
for (int i = 0; i < ASYNC_WORKER_COUNT; i++) {
bool success = xTaskCreate(async_req_worker_task, "async_req_worker",
ASYNC_WORKER_TASK_STACK_SIZE, // stack size
(void *)0, // argument
ASYNC_WORKER_TASK_PRIORITY, // priority
&worker_handles[i]);
if (!success) {
ESP_LOGE(PH_TAG, "Failed to start asyncReqWorker");
continue;
}
// counting semaphore keeps track of available workers
worker_ready_count = xSemaphoreCreateCounting(
ASYNC_WORKER_COUNT, // Max Count
0); // Initial Count
if (worker_ready_count == NULL)
{
ESP_LOGE(PH_TAG, "Failed to create workers counting Semaphore");
return;
}
// create queue
async_req_queue = xQueueCreate(1, sizeof(httpd_async_req_t));
if (async_req_queue == NULL)
{
ESP_LOGE(PH_TAG, "Failed to create async_req_queue");
vSemaphoreDelete(worker_ready_count);
return;
}
// start worker tasks
for (int i = 0; i < ASYNC_WORKER_COUNT; i++)
{
bool success = xTaskCreate(async_req_worker_task, "async_req_worker",
ASYNC_WORKER_TASK_STACK_SIZE, // stack size
(void*)0, // argument
ASYNC_WORKER_TASK_PRIORITY, // priority
&worker_handles[i]);
if (!success)
{
ESP_LOGE(PH_TAG, "Failed to start asyncReqWorker");
continue;
}
}
}
/****
*
*
* This code is backported from the 5.1.x branch
*
****/
*
****/
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
/* Calculate the maximum size needed for the scratch buffer */
#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN)
#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN)
/**
* @brief Auxiliary data structure for use during reception and processing
* of requests and temporarily keeping responses
*/
struct httpd_req_aux {
struct sock_db *sd; /*!< Pointer to socket database */
char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */
size_t remaining_len; /*!< Amount of data remaining to be fetched */
char *status; /*!< HTTP response's status code */
char *content_type; /*!< HTTP response's content type */
bool first_chunk_sent; /*!< Used to indicate if first chunk sent */
unsigned req_hdrs_count; /*!< Count of total headers in request packet */
unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */
struct resp_hdr {
const char *field;
const char *value;
} *resp_hdrs; /*!< Additional headers in response packet */
struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */
struct httpd_req_aux
{
struct sock_db* sd; /*!< Pointer to socket database */
char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */
size_t remaining_len; /*!< Amount of data remaining to be fetched */
char* status; /*!< HTTP response's status code */
char* content_type; /*!< HTTP response's content type */
bool first_chunk_sent; /*!< Used to indicate if first chunk sent */
unsigned req_hdrs_count; /*!< Count of total headers in request packet */
unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */
struct resp_hdr
{
const char* field;
const char* value;
}* resp_hdrs; /*!< Additional headers in response packet */
struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */
#ifdef CONFIG_HTTPD_WS_SUPPORT
bool ws_handshake_detect; /*!< WebSocket handshake detection flag */
httpd_ws_type_t ws_type; /*!< WebSocket frame type */
bool ws_final; /*!< WebSocket FIN bit (final frame or not) */
uint8_t mask_key[4]; /*!< WebSocket mask key for this payload */
bool ws_handshake_detect; /*!< WebSocket handshake detection flag */
httpd_ws_type_t ws_type; /*!< WebSocket frame type */
bool ws_final; /*!< WebSocket FIN bit (final frame or not) */
uint8_t mask_key[4]; /*!< WebSocket mask key for this payload */
#endif
};
esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out)
esp_err_t httpd_req_async_handler_begin(httpd_req_t* r, httpd_req_t** out)
{
if (r == NULL || out == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (r == NULL || out == NULL)
{
return ESP_ERR_INVALID_ARG;
}
// alloc async req
httpd_req_t *async = (httpd_req_t *)malloc(sizeof(httpd_req_t));
if (async == NULL) {
return ESP_ERR_NO_MEM;
}
memcpy((void *)async, (void *)r, sizeof(httpd_req_t));
// alloc async req
httpd_req_t* async = (httpd_req_t*)malloc(sizeof(httpd_req_t));
if (async == NULL)
{
return ESP_ERR_NO_MEM;
}
memcpy((void*)async, (void*)r, sizeof(httpd_req_t));
// alloc async aux
async->aux = (httpd_req_aux *)malloc(sizeof(struct httpd_req_aux));
if (async->aux == NULL) {
free(async);
return ESP_ERR_NO_MEM;
}
memcpy(async->aux, r->aux, sizeof(struct httpd_req_aux));
// alloc async aux
async->aux = (httpd_req_aux*)malloc(sizeof(struct httpd_req_aux));
if (async->aux == NULL)
{
free(async);
return ESP_ERR_NO_MEM;
}
memcpy(async->aux, r->aux, sizeof(struct httpd_req_aux));
// not available in 4.4.x
// mark socket as "in use"
// struct httpd_req_aux *ra = r->aux;
//ra->sd->for_async_req = true;
// not available in 4.4.x
// mark socket as "in use"
// struct httpd_req_aux *ra = r->aux;
// ra->sd->for_async_req = true;
*out = async;
*out = async;
return ESP_OK;
return ESP_OK;
}
esp_err_t httpd_req_async_handler_complete(httpd_req_t *r)
esp_err_t httpd_req_async_handler_complete(httpd_req_t* r)
{
if (r == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (r == NULL)
{
return ESP_ERR_INVALID_ARG;
}
// not available in 4.4.x
// struct httpd_req_aux *ra = (httpd_req_aux *)r->aux;
// ra->sd->for_async_req = false;
// not available in 4.4.x
// struct httpd_req_aux *ra = (httpd_req_aux *)r->aux;
// ra->sd->for_async_req = false;
free(r->aux);
free(r);
free(r->aux);
free(r);
return ESP_OK;
return ESP_OK;
}

View File

@@ -5,9 +5,9 @@
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#define ASYNC_WORKER_TASK_PRIORITY 5
#define ASYNC_WORKER_TASK_STACK_SIZE (4*1024)
#define ASYNC_WORKER_COUNT 8
#define ASYNC_WORKER_TASK_PRIORITY 5
#define ASYNC_WORKER_TASK_STACK_SIZE (4 * 1024)
#define ASYNC_WORKER_COUNT 8
// Async requests are queued here while they wait to be processed by the workers
static QueueHandle_t async_req_queue;
@@ -18,19 +18,20 @@ static SemaphoreHandle_t worker_ready_count;
// Each worker has its own thread
static TaskHandle_t worker_handles[ASYNC_WORKER_COUNT];
typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t *req);
typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t* req);
typedef struct {
typedef struct
{
httpd_req_t* req;
httpd_req_handler_t handler;
} httpd_async_req_t;
bool is_on_async_worker_thread(void);
esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler);
void async_req_worker_task(void *p);
esp_err_t submit_async_req(httpd_req_t* req, httpd_req_handler_t handler);
void async_req_worker_task(void* p);
void start_async_req_workers(void);
esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out);
esp_err_t httpd_req_async_handler_complete(httpd_req_t *r);
esp_err_t httpd_req_async_handler_begin(httpd_req_t* r, httpd_req_t** out);
esp_err_t httpd_req_async_handler_complete(httpd_req_t* r);
#endif //async_worker_h
#endif // async_worker_h

View File

@@ -2,193 +2,193 @@
bool http_informational(int code)
{
return code >= 100 && code < 200;
return code >= 100 && code < 200;
}
bool http_success(int code)
{
return code >= 200 && code < 300;
return code >= 200 && code < 300;
}
bool http_redirection(int code)
{
return code >= 300 && code < 400;
return code >= 300 && code < 400;
}
bool http_client_error(int code)
{
return code >= 400 && code < 500;
return code >= 400 && code < 500;
}
bool http_server_error(int code)
{
return code >= 500 && code < 600;
return code >= 500 && code < 600;
}
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))
return "Informational";
if (http_informational(code))
return "Informational";
if (http_success(code))
return "Success";
if (http_success(code))
return "Success";
if (http_redirection(code))
return "Redirection";
if (http_redirection(code))
return "Redirection";
if (http_client_error(code))
return "Client Error";
if (http_client_error(code))
return "Client Error";
if (http_server_error(code))
return "Server Error";
if (http_server_error(code))
return "Server Error";
return "Unknown";
return "Unknown";
}
const char *http_status_reason(int code)
const char* http_status_reason(int code)
{
switch (code)
{
switch (code)
{
/*####### 1xx - Informational #######*/
case 100:
return "Continue";
return "Continue";
case 101:
return "Switching Protocols";
return "Switching Protocols";
case 102:
return "Processing";
return "Processing";
case 103:
return "Early Hints";
return "Early Hints";
/*####### 2xx - Successful #######*/
case 200:
return "OK";
return "OK";
case 201:
return "Created";
return "Created";
case 202:
return "Accepted";
return "Accepted";
case 203:
return "Non-Authoritative Information";
return "Non-Authoritative Information";
case 204:
return "No Content";
return "No Content";
case 205:
return "Reset Content";
return "Reset Content";
case 206:
return "Partial Content";
return "Partial Content";
case 207:
return "Multi-Status";
return "Multi-Status";
case 208:
return "Already Reported";
return "Already Reported";
case 226:
return "IM Used";
return "IM Used";
/*####### 3xx - Redirection #######*/
case 300:
return "Multiple Choices";
return "Multiple Choices";
case 301:
return "Moved Permanently";
return "Moved Permanently";
case 302:
return "Found";
return "Found";
case 303:
return "See Other";
return "See Other";
case 304:
return "Not Modified";
return "Not Modified";
case 305:
return "Use Proxy";
return "Use Proxy";
case 307:
return "Temporary Redirect";
return "Temporary Redirect";
case 308:
return "Permanent Redirect";
return "Permanent Redirect";
/*####### 4xx - Client Error #######*/
case 400:
return "Bad Request";
return "Bad Request";
case 401:
return "Unauthorized";
return "Unauthorized";
case 402:
return "Payment Required";
return "Payment Required";
case 403:
return "Forbidden";
return "Forbidden";
case 404:
return "Not Found";
return "Not Found";
case 405:
return "Method Not Allowed";
return "Method Not Allowed";
case 406:
return "Not Acceptable";
return "Not Acceptable";
case 407:
return "Proxy Authentication Required";
return "Proxy Authentication Required";
case 408:
return "Request Timeout";
return "Request Timeout";
case 409:
return "Conflict";
return "Conflict";
case 410:
return "Gone";
return "Gone";
case 411:
return "Length Required";
return "Length Required";
case 412:
return "Precondition Failed";
return "Precondition Failed";
case 413:
return "Content Too Large";
return "Content Too Large";
case 414:
return "URI Too Long";
return "URI Too Long";
case 415:
return "Unsupported Media Type";
return "Unsupported Media Type";
case 416:
return "Range Not Satisfiable";
return "Range Not Satisfiable";
case 417:
return "Expectation Failed";
return "Expectation Failed";
case 418:
return "I'm a teapot";
return "I'm a teapot";
case 421:
return "Misdirected Request";
return "Misdirected Request";
case 422:
return "Unprocessable Content";
return "Unprocessable Content";
case 423:
return "Locked";
return "Locked";
case 424:
return "Failed Dependency";
return "Failed Dependency";
case 425:
return "Too Early";
return "Too Early";
case 426:
return "Upgrade Required";
return "Upgrade Required";
case 428:
return "Precondition Required";
return "Precondition Required";
case 429:
return "Too Many Requests";
return "Too Many Requests";
case 431:
return "Request Header Fields Too Large";
return "Request Header Fields Too Large";
case 451:
return "Unavailable For Legal Reasons";
return "Unavailable For Legal Reasons";
/*####### 5xx - Server Error #######*/
case 500:
return "Internal Server Error";
return "Internal Server Error";
case 501:
return "Not Implemented";
return "Not Implemented";
case 502:
return "Bad Gateway";
return "Bad Gateway";
case 503:
return "Service Unavailable";
return "Service Unavailable";
case 504:
return "Gateway Timeout";
return "Gateway Timeout";
case 505:
return "HTTP Version Not Supported";
return "HTTP Version Not Supported";
case 506:
return "Variant Also Negotiates";
return "Variant Also Negotiates";
case 507:
return "Insufficient Storage";
return "Insufficient Storage";
case 508:
return "Loop Detected";
return "Loop Detected";
case 510:
return "Not Extended";
return "Not Extended";
case 511:
return "Network Authentication Required";
return "Network Authentication Required";
default:
return "Unknown";
}
return "Unknown";
}
}

View File

@@ -9,7 +9,7 @@ bool http_redirection(int code);
bool http_client_error(int code);
bool http_server_error(int code);
bool http_failure(int code);
const char *http_status_group(int code);
const char *http_status_reason(int code);
const char* http_status_group(int code);
const char* http_status_reason(int code);
#endif // MICRO_HTTP_STATUS_H