Skip to content

Commit 3308386

Browse files
authored
webserver hook: allow to handle external http protocol (#7459)
* webhook api * simplify webserver debug printouts, move text to flash * Hook examples in HelloServer example * print executable code address in example * simplify example per @mcspr suggestion
1 parent 63b41bc commit 3308386

File tree

5 files changed

+187
-144
lines changed

5 files changed

+187
-144
lines changed

Diff for: cores/esp8266/core_esp8266_features.h

+13-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,19 @@ inline uint32_t esp_get_cycle_count() {
9292
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
9393
return ccount;
9494
}
95-
#endif // not CORE_MOCK
95+
96+
inline uint32_t esp_get_program_counter() __attribute__((always_inline));
97+
inline uint32_t esp_get_program_counter() {
98+
uint32_t pc;
99+
__asm__ __volatile__("movi %0, ." : "=r" (pc) : : ); // ©earlephilhower
100+
return pc;
101+
}
102+
103+
#else // CORE_MOCK
104+
105+
inline uint32_t esp_get_program_counter() { return 0; }
106+
107+
#endif // CORE_MOCK
96108

97109

98110
// Tools for preloading code into the flash cache

Diff for: libraries/ESP8266WebServer/examples/HelloServer/HelloServer.ino

+61-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const int led = 13;
1717

1818
void handleRoot() {
1919
digitalWrite(led, 1);
20-
server.send(200, "text/plain", "hello from esp8266!");
20+
server.send(200, "text/plain", "hello from esp8266!\r\n");
2121
digitalWrite(led, 0);
2222
}
2323

@@ -86,6 +86,66 @@ void setup(void) {
8686

8787
server.onNotFound(handleNotFound);
8888

89+
/////////////////////////////////////////////////////////
90+
// Hook examples
91+
92+
server.addHook([](const String & method, const String & url, WiFiClient * client, ESP8266WebServer::ContentTypeFunction contentType) {
93+
(void)method; // GET, PUT, ...
94+
(void)url; // example: /root/myfile.html
95+
(void)client; // the webserver tcp client connection
96+
(void)contentType; // contentType(".html") => "text/html"
97+
Serial.printf("A useless web hook has passed\n");
98+
Serial.printf("(this hook is in 0x%08x area (401x=IRAM 402x=FLASH))\n", esp_get_program_counter());
99+
return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
100+
});
101+
102+
server.addHook([](const String&, const String & url, WiFiClient*, ESP8266WebServer::ContentTypeFunction) {
103+
if (url.startsWith("/fail")) {
104+
Serial.printf("An always failing web hook has been triggered\n");
105+
return ESP8266WebServer::CLIENT_MUST_STOP;
106+
}
107+
return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
108+
});
109+
110+
server.addHook([](const String&, const String & url, WiFiClient * client, ESP8266WebServer::ContentTypeFunction) {
111+
if (url.startsWith("/dump")) {
112+
Serial.printf("The dumper web hook is on the run\n");
113+
114+
// Here the request is not interpreted, so we cannot for sure
115+
// swallow the exact amount matching the full request+content,
116+
// hence the tcp connection cannot be handled anymore by the
117+
// webserver.
118+
#ifdef STREAMTO_API
119+
// we are lucky
120+
client->toWithTimeout(Serial, 500);
121+
#else
122+
auto last = millis();
123+
while ((millis() - last) < 500) {
124+
char buf[32];
125+
size_t len = client->read((uint8_t*)buf, sizeof(buf));
126+
if (len > 0) {
127+
Serial.printf("(<%d> chars)", (int)len);
128+
Serial.write(buf, len);
129+
last = millis();
130+
}
131+
}
132+
#endif
133+
// Two choices: return MUST STOP and webserver will close it
134+
// (we already have the example with '/fail' hook)
135+
// or IS GIVEN and webserver will forget it
136+
// trying with IS GIVEN and storing it on a dumb WiFiClient.
137+
// check the client connection: it should not immediately be closed
138+
// (make another '/dump' one to close the first)
139+
Serial.printf("\nTelling server to forget this connection\n");
140+
static WiFiClient forgetme = *client; // stop previous one if present and transfer client refcounter
141+
return ESP8266WebServer::CLIENT_IS_GIVEN;
142+
}
143+
return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
144+
});
145+
146+
// Hook examples
147+
/////////////////////////////////////////////////////////
148+
89149
server.begin();
90150
Serial.println("HTTP server started");
91151
}

Diff for: libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h

+44-36
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,12 @@
2828
#include "FS.h"
2929
#include "detail/RequestHandlersImpl.h"
3030

31-
//#define DEBUG_ESP_HTTP_SERVER
32-
#ifdef DEBUG_ESP_PORT
33-
#define DEBUG_OUTPUT DEBUG_ESP_PORT
34-
#else
35-
#define DEBUG_OUTPUT Serial
36-
#endif
37-
3831
static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization";
3932
static const char qop_auth[] PROGMEM = "qop=auth";
4033
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
4134
static const char WWW_Authenticate[] PROGMEM = "WWW-Authenticate";
4235
static const char Content_Length[] PROGMEM = "Content-Length";
4336

44-
4537
template <typename ServerType>
4638
ESP8266WebServerTemplate<ServerType>::ESP8266WebServerTemplate(IPAddress addr, int port)
4739
: _server(addr, port)
@@ -171,9 +163,7 @@ bool ESP8266WebServerTemplate<ServerType>::authenticateDigest(const String& user
171163
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
172164
if(authReq.startsWith(F("Digest"))) {
173165
authReq = authReq.substring(7);
174-
#ifdef DEBUG_ESP_HTTP_SERVER
175-
DEBUG_OUTPUT.println(authReq);
176-
#endif
166+
DBGWS("%s\n", authReq.c_str());
177167
String _username = _extractParam(authReq,F("username=\""));
178168
if(!_username.length() || _username != String(username)) {
179169
authReq = "";
@@ -200,9 +190,7 @@ bool ESP8266WebServerTemplate<ServerType>::authenticateDigest(const String& user
200190
_nc = _extractParam(authReq, F("nc="), ',');
201191
_cnonce = _extractParam(authReq, F("cnonce=\""));
202192
}
203-
#ifdef DEBUG_ESP_HTTP_SERVER
204-
DEBUG_OUTPUT.println("Hash of user:realm:pass=" + H1);
205-
#endif
193+
DBGWS("Hash of user:realm:pass=%s\n", H1.c_str());
206194
MD5Builder md5;
207195
md5.begin();
208196
if(_currentMethod == HTTP_GET){
@@ -218,9 +206,7 @@ bool ESP8266WebServerTemplate<ServerType>::authenticateDigest(const String& user
218206
}
219207
md5.calculate();
220208
String _H2 = md5.toString();
221-
#ifdef DEBUG_ESP_HTTP_SERVER
222-
DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2);
223-
#endif
209+
DBGWS("Hash of GET:uri=%s\n", _H2.c_str());
224210
md5.begin();
225211
if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
226212
md5.add(H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
@@ -229,9 +215,7 @@ bool ESP8266WebServerTemplate<ServerType>::authenticateDigest(const String& user
229215
}
230216
md5.calculate();
231217
String _responsecheck = md5.toString();
232-
#ifdef DEBUG_ESP_HTTP_SERVER
233-
DEBUG_OUTPUT.println("The Proper response=" +_responsecheck);
234-
#endif
218+
DBGWS("The Proper response=%s\n", _responsecheck.c_str());
235219
if(_response == _responsecheck){
236220
authReq = "";
237221
return true;
@@ -315,9 +299,7 @@ void ESP8266WebServerTemplate<ServerType>::handleClient() {
315299
return;
316300
}
317301

318-
#ifdef DEBUG_ESP_HTTP_SERVER
319-
DEBUG_OUTPUT.println("New client");
320-
#endif
302+
DBGWS("New client\n");
321303

322304
_currentClient = client;
323305
_currentStatus = HC_WAIT_READ;
@@ -327,6 +309,13 @@ void ESP8266WebServerTemplate<ServerType>::handleClient() {
327309
bool keepCurrentClient = false;
328310
bool callYield = false;
329311

312+
DBGWS("http-server loop: conn=%d avail=%d status=%s\n",
313+
_currentClient.connected(), _currentClient.available(),
314+
_currentStatus==HC_NONE?"none":
315+
_currentStatus==HC_WAIT_READ?"wait-read":
316+
_currentStatus==HC_WAIT_CLOSE?"wait-close":
317+
"??");
318+
330319
if (_currentClient.connected() || _currentClient.available()) {
331320
if (_currentClient.available() && _keepAlive) {
332321
_currentStatus = HC_WAIT_READ;
@@ -339,34 +328,57 @@ void ESP8266WebServerTemplate<ServerType>::handleClient() {
339328
case HC_WAIT_READ:
340329
// Wait for data from client to become available
341330
if (_currentClient.available()) {
342-
if (_parseRequest(_currentClient)) {
331+
switch (_parseRequest(_currentClient))
332+
{
333+
case CLIENT_REQUEST_CAN_CONTINUE:
343334
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT);
344335
_contentLength = CONTENT_LENGTH_NOT_SET;
345336
_handleRequest();
346-
347-
if (_currentClient.connected()) {
337+
/* fallthrough */
338+
case CLIENT_REQUEST_IS_HANDLED:
339+
if (_currentClient.connected() || _currentClient.available()) {
348340
_currentStatus = HC_WAIT_CLOSE;
349341
_statusChange = millis();
350342
keepCurrentClient = true;
351343
}
352-
}
353-
} else { // !_currentClient.available()
344+
else
345+
DBGWS("webserver: peer has closed after served\n");
346+
break;
347+
case CLIENT_MUST_STOP:
348+
DBGWS("Close client\n");
349+
_currentClient.stop();
350+
break;
351+
case CLIENT_IS_GIVEN:
352+
// client must not be stopped but must not be handled here anymore
353+
// (example: tcp connection given to websocket)
354+
DBGWS("Give client\n");
355+
break;
356+
} // switch _parseRequest()
357+
} else {
358+
// !_currentClient.available(): waiting for more data
354359
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
355360
keepCurrentClient = true;
356361
}
362+
else
363+
DBGWS("webserver: closing after read timeout\n");
357364
callYield = true;
358365
}
359366
break;
360367
case HC_WAIT_CLOSE:
361368
// Wait for client to close the connection
362-
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
369+
if (!_server.available() && (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT)) {
363370
keepCurrentClient = true;
364371
callYield = true;
372+
if (_currentClient.available())
373+
// continue serving current client
374+
_currentStatus = HC_WAIT_READ;
365375
}
366-
}
376+
break;
377+
} // switch _currentStatus
367378
}
368379

369380
if (!keepCurrentClient) {
381+
DBGWS("Drop client\n");
370382
_currentClient = ClientType();
371383
_currentStatus = HC_NONE;
372384
_currentUpload.reset();
@@ -687,17 +699,13 @@ template <typename ServerType>
687699
void ESP8266WebServerTemplate<ServerType>::_handleRequest() {
688700
bool handled = false;
689701
if (!_currentHandler){
690-
#ifdef DEBUG_ESP_HTTP_SERVER
691-
DEBUG_OUTPUT.println("request handler not found");
692-
#endif
702+
DBGWS("request handler not found\n");
693703
}
694704
else {
695705
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
696-
#ifdef DEBUG_ESP_HTTP_SERVER
697706
if (!handled) {
698-
DEBUG_OUTPUT.println("request handler failed to handle request");
707+
DBGWS("request handler failed to handle request\n");
699708
}
700-
#endif
701709
}
702710
if (!handled && _notFoundHandler) {
703711
_notFoundHandler();

Diff for: libraries/ESP8266WebServer/src/ESP8266WebServer.h

+32-3
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,24 @@
2626

2727
#include <functional>
2828
#include <memory>
29+
#include <functional>
2930
#include <ESP8266WiFi.h>
3031
#include <FS.h>
3132
#include "detail/mimetable.h"
3233
#include "Uri.h"
3334

35+
//#define DEBUG_ESP_HTTP_SERVER
36+
37+
#ifdef DEBUG_ESP_HTTP_SERVER
38+
#ifdef DEBUG_ESP_PORT
39+
#define DBGWS(f,...) do { DEBUG_ESP_PORT.printf(PSTR(f), ##__VA_ARGS__); } while (0)
40+
#else
41+
#define DBGWS(f,...) do { Serial.printf(PSTR(f), ##__VA_ARGS__); } while (0)
42+
#endif
43+
#else
44+
#define DBGWS(x...) do { (void)0; } while (0)
45+
#endif
46+
3447
enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_HEAD, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
3548
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
3649
UPLOAD_FILE_ABORTED };
@@ -80,6 +93,9 @@ class ESP8266WebServerTemplate
8093
using ClientType = typename ServerType::ClientType;
8194
using RequestHandlerType = RequestHandler<ServerType>;
8295
using WebServerType = ESP8266WebServerTemplate<ServerType>;
96+
enum ClientFuture { CLIENT_REQUEST_CAN_CONTINUE, CLIENT_REQUEST_IS_HANDLED, CLIENT_MUST_STOP, CLIENT_IS_GIVEN };
97+
typedef String (*ContentTypeFunction) (const String&);
98+
using HookFunction = std::function<ClientFuture(const String& method, const String& url, WiFiClient* client, ContentTypeFunction contentType)>;
8399

84100
void begin();
85101
void begin(uint16_t port);
@@ -200,11 +216,25 @@ class ESP8266WebServerTemplate
200216

201217
static String responseCodeToString(const int code);
202218

219+
void addHook (HookFunction hook) {
220+
if (_hook) {
221+
auto previousHook = _hook;
222+
_hook = [previousHook, hook](const String& method, const String& url, WiFiClient* client, ContentTypeFunction contentType) {
223+
auto whatNow = previousHook(method, url, client, contentType);
224+
if (whatNow == CLIENT_REQUEST_CAN_CONTINUE)
225+
return hook(method, url, client, contentType);
226+
return whatNow;
227+
};
228+
} else {
229+
_hook = hook;
230+
}
231+
}
232+
203233
protected:
204234
void _addRequestHandler(RequestHandlerType* handler);
205235
void _handleRequest();
206236
void _finalizeResponse();
207-
bool _parseRequest(ClientType& client);
237+
ClientFuture _parseRequest(ClientType& client);
208238
void _parseArguments(const String& data);
209239
int _parseArgumentsPrivate(const String& data, std::function<void(String&,String&,const String&,int,int,int,int)> handler);
210240
bool _parseForm(ClientType& client, const String& boundary, uint32_t len);
@@ -261,8 +291,7 @@ class ESP8266WebServerTemplate
261291
String _sopaque;
262292
String _srealm; // Store the Auth realm between Calls
263293

264-
265-
294+
HookFunction _hook;
266295
};
267296

268297

0 commit comments

Comments
 (0)