Skip to content

Commit b523379

Browse files
author
Luc
committed
Add hook to webserver based on @d-a-v code
1 parent d253085 commit b523379

File tree

7 files changed

+143
-44
lines changed

7 files changed

+143
-44
lines changed

libraries/WebServer/examples/HelloServer/HelloServer.ino

+57
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,64 @@ void setup(void) {
6363
});
6464

6565
server.onNotFound(handleNotFound);
66+
/////////////////////////////////////////////////////////
67+
// Hook examples
6668

69+
server.addHook([](const String & method, const String & url, WiFiClient * client, WebServer::ContentTypeFunction contentType) {
70+
(void)method; // GET, PUT, ...
71+
(void)url; // example: /root/myfile.html
72+
(void)client; // the webserver tcp client connection
73+
(void)contentType; // contentType(".html") => "text/html"
74+
Serial.printf("A useless web hook has passed\n");
75+
return CLIENT_REQUEST_CAN_CONTINUE;
76+
});
77+
78+
server.addHook([](const String&, const String & url, WiFiClient*, WebServer::ContentTypeFunction) {
79+
if (url.startsWith("/fail")) {
80+
Serial.printf("An always failing web hook has been triggered\n");
81+
return CLIENT_MUST_STOP;
82+
}
83+
return CLIENT_REQUEST_CAN_CONTINUE;
84+
});
85+
86+
server.addHook([](const String&, const String & url, WiFiClient * client, WebServer::ContentTypeFunction) {
87+
if (url.startsWith("/dump")) {
88+
Serial.printf("The dumper web hook is on the run\n");
89+
90+
// Here the request is not interpreted, so we cannot for sure
91+
// swallow the exact amount matching the full request+content,
92+
// hence the tcp connection cannot be handled anymore by the
93+
// webserver.
94+
#ifdef STREAMTO_API
95+
// we are lucky
96+
client->toWithTimeout(Serial, 500);
97+
#else
98+
auto last = millis();
99+
while ((millis() - last) < 500) {
100+
char buf[32];
101+
size_t len = client->read((uint8_t*)buf, sizeof(buf));
102+
if (len > 0) {
103+
Serial.printf("(<%d> chars)", (int)len);
104+
Serial.write(buf, len);
105+
last = millis();
106+
}
107+
}
108+
#endif
109+
// Two choices: return MUST STOP and webserver will close it
110+
// (we already have the example with '/fail' hook)
111+
// or IS GIVEN and webserver will forget it
112+
// trying with IS GIVEN and storing it on a dumb WiFiClient.
113+
// check the client connection: it should not immediately be closed
114+
// (make another '/dump' one to close the first)
115+
Serial.printf("\nTelling server to forget this connection\n");
116+
static WiFiClient forgetme = *client; // stop previous one if present and transfer client refcounter
117+
return CLIENT_IS_GIVEN;
118+
}
119+
return CLIENT_REQUEST_CAN_CONTINUE;
120+
});
121+
122+
// Hook examples
123+
/////////////////////////////////////////////////////////
67124
server.begin();
68125
Serial.println("HTTP server started");
69126
}

libraries/WebServer/src/Parsing.cpp

+12-7
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,13 @@ static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t&
6565
return buf;
6666
}
6767

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

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

8686
String methodStr = req.substring(0, addr_start);
@@ -95,7 +95,12 @@ bool WebServer::_parseRequest(WiFiClient& client) {
9595
}
9696
_currentUri = url;
9797
_chunked = false;
98-
98+
if (_hook)
99+
{
100+
auto whatNow = _hook(methodStr, url, &client, mime::getContentType);
101+
if (whatNow != CLIENT_REQUEST_CAN_CONTINUE)
102+
return whatNow;
103+
}
99104
HTTPMethod method = HTTP_GET;
100105
if (methodStr == F("POST")) {
101106
method = HTTP_POST;
@@ -170,7 +175,7 @@ bool WebServer::_parseRequest(WiFiClient& client) {
170175
char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
171176
if (plainLength < contentLength) {
172177
free(plainBuf);
173-
return false;
178+
return CLIENT_MUST_STOP;
174179
}
175180
if (contentLength > 0) {
176181
if(isEncoded){
@@ -197,7 +202,7 @@ bool WebServer::_parseRequest(WiFiClient& client) {
197202
if (isForm){
198203
_parseArguments(searchStr);
199204
if (!_parseForm(client, boundaryStr, contentLength)) {
200-
return false;
205+
return CLIENT_MUST_STOP;
201206
}
202207
}
203208
} else {
@@ -230,7 +235,7 @@ bool WebServer::_parseRequest(WiFiClient& client) {
230235
log_v("Request: %s", url.c_str());
231236
log_v(" Arguments: %s", searchStr.c_str());
232237

233-
return true;
238+
return CLIENT_REQUEST_CAN_CONTINUE;
234239
}
235240

236241
bool WebServer::_collectHeader(const char* headerName, const char* headerValue) {

libraries/WebServer/src/WebServer.cpp

+34-15
Original file line numberDiff line numberDiff line change
@@ -306,34 +306,53 @@ void WebServer::handleClient() {
306306
case HC_WAIT_READ:
307307
// Wait for data from client to become available
308308
if (_currentClient.available()) {
309-
if (_parseRequest(_currentClient)) {
309+
switch (_parseRequest(_currentClient))
310+
{
311+
case CLIENT_REQUEST_CAN_CONTINUE:
310312
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
311313
// it must be divided by 1000
312-
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000);
313-
_contentLength = CONTENT_LENGTH_NOT_SET;
314-
_handleRequest();
315-
316-
// Fix for issue with Chrome based browsers: https://github.com/espressif/arduino-esp32/issues/3652
317-
// if (_currentClient.connected()) {
318-
// _currentStatus = HC_WAIT_CLOSE;
319-
// _statusChange = millis();
320-
// keepCurrentClient = true;
321-
// }
322-
}
314+
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000);
315+
_contentLength = CONTENT_LENGTH_NOT_SET;
316+
_handleRequest();
317+
/* fallthrough */
318+
case CLIENT_REQUEST_IS_HANDLED://log_v
319+
if (_currentClient.connected() || _currentClient.available()) {
320+
_currentStatus = HC_WAIT_CLOSE;
321+
_statusChange = millis();
322+
keepCurrentClient = true;
323+
} else
324+
log_v("webserver: peer has closed after served");
325+
break;
326+
case CLIENT_MUST_STOP:
327+
log_v("Close client\n");
328+
_currentClient.stop();
329+
break;
330+
case CLIENT_IS_GIVEN:
331+
// client must not be stopped but must not be handled here anymore
332+
// (example: tcp connection given to websocket)
333+
log_v("Give client\n");
334+
break;
335+
} // switch _parseRequest()
323336
} else { // !_currentClient.available()
324337
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
325338
keepCurrentClient = true;
326339
}
327-
callYield = true;
340+
else
341+
log_v("webserver: closing after read timeout\n");
342+
callYield = true;
328343
}
329344
break;
330345
case HC_WAIT_CLOSE:
331346
// Wait for client to close the connection
332-
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
347+
if (!_server.available() && (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT)) {
333348
keepCurrentClient = true;
334349
callYield = true;
350+
if (_currentClient.available())
351+
// continue serving current client
352+
_currentStatus = HC_WAIT_READ;
335353
}
336-
}
354+
break;
355+
} // switch _currentStatus
337356
}
338357

339358
if (!keepCurrentClient) {

libraries/WebServer/src/WebServer.h

+21-5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
3434
UPLOAD_FILE_ABORTED };
3535
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
3636
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
37+
enum ClientFuture { CLIENT_REQUEST_CAN_CONTINUE, CLIENT_REQUEST_IS_HANDLED, CLIENT_MUST_STOP, CLIENT_IS_GIVEN };
3738

3839
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
3940

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

53+
#define WEBSERVER_HAS_HOOK 1
54+
5255
class WebServer;
5356

5457
typedef struct {
@@ -73,7 +76,8 @@ class WebServer
7376
WebServer(IPAddress addr, int port = 80);
7477
WebServer(int port = 80);
7578
virtual ~WebServer();
76-
79+
typedef String (*ContentTypeFunction) (const String&);
80+
using HookFunction = std::function<ClientFuture(const String& method, const String& url, WiFiClient* client, ContentTypeFunction contentType)>;
7781
virtual void begin();
7882
virtual void begin(uint16_t port);
7983
virtual void handleClient();
@@ -141,14 +145,26 @@ class WebServer
141145
_streamFileCore(file.size(), file.name(), contentType);
142146
return _currentClient.write(file);
143147
}
144-
148+
void addHook (HookFunction hook) {
149+
if (_hook) {
150+
auto previousHook = _hook;
151+
_hook = [previousHook, hook](const String& method, const String& url, WiFiClient* client, ContentTypeFunction contentType) {
152+
auto whatNow = previousHook(method, url, client, contentType);
153+
if (whatNow == CLIENT_REQUEST_CAN_CONTINUE)
154+
return hook(method, url, client, contentType);
155+
return whatNow;
156+
};
157+
} else {
158+
_hook = hook;
159+
}
160+
}
145161
protected:
146162
virtual size_t _currentClientWrite(const char* b, size_t l) { return _currentClient.write( b, l ); }
147163
virtual size_t _currentClientWrite_P(PGM_P b, size_t l) { return _currentClient.write_P( b, l ); }
148164
void _addRequestHandler(RequestHandler* handler);
149165
void _handleRequest();
150166
void _finalizeResponse();
151-
bool _parseRequest(WiFiClient& client);
167+
ClientFuture _parseRequest(WiFiClient& client);
152168
void _parseArguments(String data);
153169
static String _responseCodeToString(int code);
154170
bool _parseForm(WiFiClient& client, String boundary, uint32_t len);
@@ -159,7 +175,7 @@ class WebServer
159175
bool _collectHeader(const char* headerName, const char* headerValue);
160176

161177
void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType);
162-
178+
163179
String _getRandomHexString();
164180
// for extracting Auth parameters
165181
String _extractParam(String& authReq,const String& param,const char delimit = '"');
@@ -204,7 +220,7 @@ class WebServer
204220
String _snonce; // Store noance and opaque for future comparison
205221
String _sopaque;
206222
String _srealm; // Store the Auth realm between Calls
207-
223+
HookFunction _hook;
208224
};
209225

210226

libraries/WebServer/src/detail/RequestHandlersImpl.h

+1-16
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class StaticRequestHandler : public RequestHandler {
102102
}
103103
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
104104

105-
String contentType = getContentType(path);
105+
String contentType = mime::getContentType(path);
106106

107107
// 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
108108
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
@@ -123,21 +123,6 @@ class StaticRequestHandler : public RequestHandler {
123123
return true;
124124
}
125125

126-
static String getContentType(const String& path) {
127-
char buff[sizeof(mimeTable[0].mimeType)];
128-
// Check all entries but last one for match, return if found
129-
for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) {
130-
strcpy_P(buff, mimeTable[i].endsWith);
131-
if (path.endsWith(buff)) {
132-
strcpy_P(buff, mimeTable[i].mimeType);
133-
return String(buff);
134-
}
135-
}
136-
// Fall-through and just return default type
137-
strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType);
138-
return String(buff);
139-
}
140-
141126
protected:
142127
FS _fs;
143128
String _uri;

libraries/WebServer/src/detail/mimetable.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "mimetable.h"
22
#include "pgmspace.h"
3+
#include "WString.h"
34

45
namespace mime
56
{
@@ -32,4 +33,19 @@ const Entry mimeTable[maxType] =
3233
{ "", "application/octet-stream" }
3334
};
3435

36+
String getContentType(const String& path) {
37+
char buff[sizeof(mimeTable[0].mimeType)];
38+
// Check all entries but last one for match, return if found
39+
for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) {
40+
strcpy_P(buff, mimeTable[i].endsWith);
41+
if (path.endsWith(buff)) {
42+
strcpy_P(buff, mimeTable[i].mimeType);
43+
return String(buff);
44+
}
45+
}
46+
// Fall-through and just return default type
47+
strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType);
48+
return String(buff);
49+
}
50+
3551
}

libraries/WebServer/src/detail/mimetable.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#ifndef __MIMETABLE_H__
22
#define __MIMETABLE_H__
3-
3+
class String;
44

55
namespace mime
66
{
@@ -41,6 +41,7 @@ struct Entry
4141

4242

4343
extern const Entry mimeTable[maxType];
44+
String getContentType(const String& path);
4445
}
4546

4647

0 commit comments

Comments
 (0)