Files
nuki_hub/lib/PsychicHttp/src/PsychicHttpServer.cpp
2024-08-27 18:31:29 +02:00

374 lines
9.6 KiB
C++

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