Skip to content

Commit

Permalink
Add hook to webserver based on @d-a-v code
Browse files Browse the repository at this point in the history
  • Loading branch information
Luc committed Jan 15, 2021
1 parent d253085 commit b523379
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 44 deletions.
57 changes: 57 additions & 0 deletions libraries/WebServer/examples/HelloServer/HelloServer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,64 @@ void setup(void) {
});

server.onNotFound(handleNotFound);
/////////////////////////////////////////////////////////
// Hook examples

server.addHook([](const String & method, const String & url, WiFiClient * client, WebServer::ContentTypeFunction contentType) {
(void)method; // GET, PUT, ...
(void)url; // example: /root/myfile.html
(void)client; // the webserver tcp client connection
(void)contentType; // contentType(".html") => "text/html"
Serial.printf("A useless web hook has passed\n");
return CLIENT_REQUEST_CAN_CONTINUE;
});

server.addHook([](const String&, const String & url, WiFiClient*, WebServer::ContentTypeFunction) {
if (url.startsWith("/fail")) {
Serial.printf("An always failing web hook has been triggered\n");
return CLIENT_MUST_STOP;
}
return CLIENT_REQUEST_CAN_CONTINUE;
});

server.addHook([](const String&, const String & url, WiFiClient * client, WebServer::ContentTypeFunction) {
if (url.startsWith("/dump")) {
Serial.printf("The dumper web hook is on the run\n");

// Here the request is not interpreted, so we cannot for sure
// swallow the exact amount matching the full request+content,
// hence the tcp connection cannot be handled anymore by the
// webserver.
#ifdef STREAMTO_API
// we are lucky
client->toWithTimeout(Serial, 500);
#else
auto last = millis();
while ((millis() - last) < 500) {
char buf[32];
size_t len = client->read((uint8_t*)buf, sizeof(buf));
if (len > 0) {
Serial.printf("(<%d> chars)", (int)len);
Serial.write(buf, len);
last = millis();
}
}
#endif
// Two choices: return MUST STOP and webserver will close it
// (we already have the example with '/fail' hook)
// or IS GIVEN and webserver will forget it
// trying with IS GIVEN and storing it on a dumb WiFiClient.
// check the client connection: it should not immediately be closed
// (make another '/dump' one to close the first)
Serial.printf("\nTelling server to forget this connection\n");
static WiFiClient forgetme = *client; // stop previous one if present and transfer client refcounter
return CLIENT_IS_GIVEN;
}
return CLIENT_REQUEST_CAN_CONTINUE;
});

// Hook examples
/////////////////////////////////////////////////////////
server.begin();
Serial.println("HTTP server started");
}
Expand Down
19 changes: 12 additions & 7 deletions libraries/WebServer/src/Parsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t&
return buf;
}

bool WebServer::_parseRequest(WiFiClient& client) {
ClientFuture WebServer::_parseRequest(WiFiClient& client) {
// Read the first line of HTTP request
String req = client.readStringUntil('\r');
client.readStringUntil('\n');
//reset header value
for (int i = 0; i < _headerKeysCount; ++i) {
_currentHeaders[i].value =String();
_currentHeaders[i].value.clear();
}

// First line of HTTP request looks like "GET /path HTTP/1.1"
Expand All @@ -80,7 +80,7 @@ bool WebServer::_parseRequest(WiFiClient& client) {
int addr_end = req.indexOf(' ', addr_start + 1);
if (addr_start == -1 || addr_end == -1) {
log_e("Invalid request: %s", req.c_str());
return false;
return CLIENT_MUST_STOP;
}

String methodStr = req.substring(0, addr_start);
Expand All @@ -95,7 +95,12 @@ bool WebServer::_parseRequest(WiFiClient& client) {
}
_currentUri = url;
_chunked = false;

if (_hook)
{
auto whatNow = _hook(methodStr, url, &client, mime::getContentType);
if (whatNow != CLIENT_REQUEST_CAN_CONTINUE)
return whatNow;
}
HTTPMethod method = HTTP_GET;
if (methodStr == F("POST")) {
method = HTTP_POST;
Expand Down Expand Up @@ -170,7 +175,7 @@ bool WebServer::_parseRequest(WiFiClient& client) {
char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
if (plainLength < contentLength) {
free(plainBuf);
return false;
return CLIENT_MUST_STOP;
}
if (contentLength > 0) {
if(isEncoded){
Expand All @@ -197,7 +202,7 @@ bool WebServer::_parseRequest(WiFiClient& client) {
if (isForm){
_parseArguments(searchStr);
if (!_parseForm(client, boundaryStr, contentLength)) {
return false;
return CLIENT_MUST_STOP;
}
}
} else {
Expand Down Expand Up @@ -230,7 +235,7 @@ bool WebServer::_parseRequest(WiFiClient& client) {
log_v("Request: %s", url.c_str());
log_v(" Arguments: %s", searchStr.c_str());

return true;
return CLIENT_REQUEST_CAN_CONTINUE;
}

bool WebServer::_collectHeader(const char* headerName, const char* headerValue) {
Expand Down
49 changes: 34 additions & 15 deletions libraries/WebServer/src/WebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,34 +306,53 @@ void WebServer::handleClient() {
case HC_WAIT_READ:
// Wait for data from client to become available
if (_currentClient.available()) {
if (_parseRequest(_currentClient)) {
switch (_parseRequest(_currentClient))
{
case CLIENT_REQUEST_CAN_CONTINUE:
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
// it must be divided by 1000
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000);
_contentLength = CONTENT_LENGTH_NOT_SET;
_handleRequest();

// Fix for issue with Chrome based browsers: https://github.com/espressif/arduino-esp32/issues/3652
// if (_currentClient.connected()) {
// _currentStatus = HC_WAIT_CLOSE;
// _statusChange = millis();
// keepCurrentClient = true;
// }
}
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000);
_contentLength = CONTENT_LENGTH_NOT_SET;
_handleRequest();
/* fallthrough */
case CLIENT_REQUEST_IS_HANDLED://log_v
if (_currentClient.connected() || _currentClient.available()) {
_currentStatus = HC_WAIT_CLOSE;
_statusChange = millis();
keepCurrentClient = true;
} else
log_v("webserver: peer has closed after served");
break;
case CLIENT_MUST_STOP:
log_v("Close client\n");
_currentClient.stop();
break;
case CLIENT_IS_GIVEN:
// client must not be stopped but must not be handled here anymore
// (example: tcp connection given to websocket)
log_v("Give client\n");
break;
} // switch _parseRequest()
} else { // !_currentClient.available()
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
keepCurrentClient = true;
}
callYield = true;
else
log_v("webserver: closing after read timeout\n");
callYield = true;
}
break;
case HC_WAIT_CLOSE:
// Wait for client to close the connection
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
if (!_server.available() && (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT)) {
keepCurrentClient = true;
callYield = true;
if (_currentClient.available())
// continue serving current client
_currentStatus = HC_WAIT_READ;
}
}
break;
} // switch _currentStatus
}

if (!keepCurrentClient) {
Expand Down
26 changes: 21 additions & 5 deletions libraries/WebServer/src/WebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
UPLOAD_FILE_ABORTED };
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
enum ClientFuture { CLIENT_REQUEST_CAN_CONTINUE, CLIENT_REQUEST_IS_HANDLED, CLIENT_MUST_STOP, CLIENT_IS_GIVEN };

#define HTTP_DOWNLOAD_UNIT_SIZE 1436

Expand All @@ -49,6 +50,8 @@ enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
#define CONTENT_LENGTH_NOT_SET ((size_t) -2)

#define WEBSERVER_HAS_HOOK 1

class WebServer;

typedef struct {
Expand All @@ -73,7 +76,8 @@ class WebServer
WebServer(IPAddress addr, int port = 80);
WebServer(int port = 80);
virtual ~WebServer();

typedef String (*ContentTypeFunction) (const String&);
using HookFunction = std::function<ClientFuture(const String& method, const String& url, WiFiClient* client, ContentTypeFunction contentType)>;
virtual void begin();
virtual void begin(uint16_t port);
virtual void handleClient();
Expand Down Expand Up @@ -141,14 +145,26 @@ class WebServer
_streamFileCore(file.size(), file.name(), contentType);
return _currentClient.write(file);
}

void addHook (HookFunction hook) {
if (_hook) {
auto previousHook = _hook;
_hook = [previousHook, hook](const String& method, const String& url, WiFiClient* client, ContentTypeFunction contentType) {
auto whatNow = previousHook(method, url, client, contentType);
if (whatNow == CLIENT_REQUEST_CAN_CONTINUE)
return hook(method, url, client, contentType);
return whatNow;
};
} else {
_hook = hook;
}
}
protected:
virtual size_t _currentClientWrite(const char* b, size_t l) { return _currentClient.write( b, l ); }
virtual size_t _currentClientWrite_P(PGM_P b, size_t l) { return _currentClient.write_P( b, l ); }
void _addRequestHandler(RequestHandler* handler);
void _handleRequest();
void _finalizeResponse();
bool _parseRequest(WiFiClient& client);
ClientFuture _parseRequest(WiFiClient& client);
void _parseArguments(String data);
static String _responseCodeToString(int code);
bool _parseForm(WiFiClient& client, String boundary, uint32_t len);
Expand All @@ -159,7 +175,7 @@ class WebServer
bool _collectHeader(const char* headerName, const char* headerValue);

void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType);

String _getRandomHexString();
// for extracting Auth parameters
String _extractParam(String& authReq,const String& param,const char delimit = '"');
Expand Down Expand Up @@ -204,7 +220,7 @@ class WebServer
String _snonce; // Store noance and opaque for future comparison
String _sopaque;
String _srealm; // Store the Auth realm between Calls

HookFunction _hook;
};


Expand Down
17 changes: 1 addition & 16 deletions libraries/WebServer/src/detail/RequestHandlersImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class StaticRequestHandler : public RequestHandler {
}
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);

String contentType = getContentType(path);
String contentType = mime::getContentType(path);

// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
Expand All @@ -123,21 +123,6 @@ class StaticRequestHandler : public RequestHandler {
return true;
}

static String getContentType(const String& path) {
char buff[sizeof(mimeTable[0].mimeType)];
// Check all entries but last one for match, return if found
for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) {
strcpy_P(buff, mimeTable[i].endsWith);
if (path.endsWith(buff)) {
strcpy_P(buff, mimeTable[i].mimeType);
return String(buff);
}
}
// Fall-through and just return default type
strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType);
return String(buff);
}

protected:
FS _fs;
String _uri;
Expand Down
16 changes: 16 additions & 0 deletions libraries/WebServer/src/detail/mimetable.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "mimetable.h"
#include "pgmspace.h"
#include "WString.h"

namespace mime
{
Expand Down Expand Up @@ -32,4 +33,19 @@ const Entry mimeTable[maxType] =
{ "", "application/octet-stream" }
};

String getContentType(const String& path) {
char buff[sizeof(mimeTable[0].mimeType)];
// Check all entries but last one for match, return if found
for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) {
strcpy_P(buff, mimeTable[i].endsWith);
if (path.endsWith(buff)) {
strcpy_P(buff, mimeTable[i].mimeType);
return String(buff);
}
}
// Fall-through and just return default type
strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType);
return String(buff);
}

}
3 changes: 2 additions & 1 deletion libraries/WebServer/src/detail/mimetable.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#ifndef __MIMETABLE_H__
#define __MIMETABLE_H__

class String;

namespace mime
{
Expand Down Expand Up @@ -41,6 +41,7 @@ struct Entry


extern const Entry mimeTable[maxType];
String getContentType(const String& path);
}


Expand Down

0 comments on commit b523379

Please # to comment.