Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webserver hook: allow to handle external http protocol #7459

Merged
merged 14 commits into from
Jul 28, 2020
14 changes: 13 additions & 1 deletion cores/esp8266/core_esp8266_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,19 @@ inline uint32_t esp_get_cycle_count() {
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
return ccount;
}
#endif // not CORE_MOCK

inline uint32_t esp_get_program_counter() __attribute__((always_inline));
inline uint32_t esp_get_program_counter() {
uint32_t pc;
__asm__ __volatile__("movi %0, ." : "=r" (pc) : : ); // ©earlephilhower
return pc;
}

#else // CORE_MOCK

inline uint32_t esp_get_program_counter() { return 0; }

#endif // CORE_MOCK


// Tools for preloading code into the flash cache
Expand Down
62 changes: 61 additions & 1 deletion libraries/ESP8266WebServer/examples/HelloServer/HelloServer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const int led = 13;

void handleRoot() {
digitalWrite(led, 1);
server.send(200, "text/plain", "hello from esp8266!");
server.send(200, "text/plain", "hello from esp8266!\r\n");
digitalWrite(led, 0);
}

Expand Down Expand Up @@ -86,6 +86,66 @@ void setup(void) {

server.onNotFound(handleNotFound);

/////////////////////////////////////////////////////////
// Hook examples

server.addHook([](const String & method, const String & url, WiFiClient * client, ESP8266WebServer::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");
Serial.printf("(this hook is in 0x%08x area (401x=IRAM 402x=FLASH))\n", esp_get_program_counter());
return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
});

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

server.addHook([](const String&, const String & url, WiFiClient * client, ESP8266WebServer::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 ESP8266WebServer::CLIENT_IS_GIVEN;
}
return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
});

// Hook examples
/////////////////////////////////////////////////////////

server.begin();
Serial.println("HTTP server started");
}
Expand Down
80 changes: 44 additions & 36 deletions libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,12 @@
#include "FS.h"
#include "detail/RequestHandlersImpl.h"

//#define DEBUG_ESP_HTTP_SERVER
#ifdef DEBUG_ESP_PORT
#define DEBUG_OUTPUT DEBUG_ESP_PORT
#else
#define DEBUG_OUTPUT Serial
#endif

static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization";
static const char qop_auth[] PROGMEM = "qop=auth";
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
static const char WWW_Authenticate[] PROGMEM = "WWW-Authenticate";
static const char Content_Length[] PROGMEM = "Content-Length";


template <typename ServerType>
ESP8266WebServerTemplate<ServerType>::ESP8266WebServerTemplate(IPAddress addr, int port)
: _server(addr, port)
Expand Down Expand Up @@ -171,9 +163,7 @@ bool ESP8266WebServerTemplate<ServerType>::authenticateDigest(const String& user
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
if(authReq.startsWith(F("Digest"))) {
authReq = authReq.substring(7);
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println(authReq);
#endif
DBGWS("%s\n", authReq.c_str());
String _username = _extractParam(authReq,F("username=\""));
if(!_username.length() || _username != String(username)) {
authReq = "";
Expand All @@ -200,9 +190,7 @@ bool ESP8266WebServerTemplate<ServerType>::authenticateDigest(const String& user
_nc = _extractParam(authReq, F("nc="), ',');
_cnonce = _extractParam(authReq, F("cnonce=\""));
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("Hash of user:realm:pass=" + H1);
#endif
DBGWS("Hash of user:realm:pass=%s\n", H1.c_str());
MD5Builder md5;
md5.begin();
if(_currentMethod == HTTP_GET){
Expand All @@ -218,9 +206,7 @@ bool ESP8266WebServerTemplate<ServerType>::authenticateDigest(const String& user
}
md5.calculate();
String _H2 = md5.toString();
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2);
#endif
DBGWS("Hash of GET:uri=%s\n", _H2.c_str());
md5.begin();
if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
md5.add(H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
Expand All @@ -229,9 +215,7 @@ bool ESP8266WebServerTemplate<ServerType>::authenticateDigest(const String& user
}
md5.calculate();
String _responsecheck = md5.toString();
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("The Proper response=" +_responsecheck);
#endif
DBGWS("The Proper response=%s\n", _responsecheck.c_str());
if(_response == _responsecheck){
authReq = "";
return true;
Expand Down Expand Up @@ -315,9 +299,7 @@ void ESP8266WebServerTemplate<ServerType>::handleClient() {
return;
}

#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("New client");
#endif
DBGWS("New client\n");

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

DBGWS("http-server loop: conn=%d avail=%d status=%s\n",
_currentClient.connected(), _currentClient.available(),
_currentStatus==HC_NONE?"none":
_currentStatus==HC_WAIT_READ?"wait-read":
_currentStatus==HC_WAIT_CLOSE?"wait-close":
"??");

if (_currentClient.connected() || _currentClient.available()) {
if (_currentClient.available() && _keepAlive) {
_currentStatus = HC_WAIT_READ;
Expand All @@ -339,34 +328,57 @@ void ESP8266WebServerTemplate<ServerType>::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:
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT);
_contentLength = CONTENT_LENGTH_NOT_SET;
_handleRequest();

if (_currentClient.connected()) {
/* fallthrough */
case CLIENT_REQUEST_IS_HANDLED:
if (_currentClient.connected() || _currentClient.available()) {
_currentStatus = HC_WAIT_CLOSE;
_statusChange = millis();
keepCurrentClient = true;
}
}
} else { // !_currentClient.available()
else
DBGWS("webserver: peer has closed after served\n");
break;
case CLIENT_MUST_STOP:
DBGWS("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)
DBGWS("Give client\n");
break;
} // switch _parseRequest()
} else {
// !_currentClient.available(): waiting for more data
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
keepCurrentClient = true;
}
else
DBGWS("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) {
DBGWS("Drop client\n");
_currentClient = ClientType();
_currentStatus = HC_NONE;
_currentUpload.reset();
Expand Down Expand Up @@ -687,17 +699,13 @@ template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::_handleRequest() {
bool handled = false;
if (!_currentHandler){
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("request handler not found");
#endif
DBGWS("request handler not found\n");
}
else {
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
#ifdef DEBUG_ESP_HTTP_SERVER
if (!handled) {
DEBUG_OUTPUT.println("request handler failed to handle request");
DBGWS("request handler failed to handle request\n");
}
#endif
}
if (!handled && _notFoundHandler) {
_notFoundHandler();
Expand Down
35 changes: 32 additions & 3 deletions libraries/ESP8266WebServer/src/ESP8266WebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,24 @@

#include <functional>
#include <memory>
#include <functional>
#include <ESP8266WiFi.h>
#include <FS.h>
#include "detail/mimetable.h"
#include "Uri.h"

//#define DEBUG_ESP_HTTP_SERVER

#ifdef DEBUG_ESP_HTTP_SERVER
#ifdef DEBUG_ESP_PORT
#define DBGWS(f,...) do { DEBUG_ESP_PORT.printf(PSTR(f), ##__VA_ARGS__); } while (0)
#else
#define DBGWS(f,...) do { Serial.printf(PSTR(f), ##__VA_ARGS__); } while (0)
#endif
#else
#define DBGWS(x...) do { (void)0; } while (0)
#endif

enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_HEAD, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
UPLOAD_FILE_ABORTED };
Expand Down Expand Up @@ -80,6 +93,9 @@ class ESP8266WebServerTemplate
using ClientType = typename ServerType::ClientType;
using RequestHandlerType = RequestHandler<ServerType>;
using WebServerType = ESP8266WebServerTemplate<ServerType>;
enum ClientFuture { CLIENT_REQUEST_CAN_CONTINUE, CLIENT_REQUEST_IS_HANDLED, CLIENT_MUST_STOP, CLIENT_IS_GIVEN };
typedef String (*ContentTypeFunction) (const String&);
using HookFunction = std::function<ClientFuture(const String& method, const String& url, WiFiClient* client, ContentTypeFunction contentType)>;

void begin();
void begin(uint16_t port);
Expand Down Expand Up @@ -200,11 +216,25 @@ class ESP8266WebServerTemplate

static String responseCodeToString(const int code);

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:
void _addRequestHandler(RequestHandlerType* handler);
void _handleRequest();
void _finalizeResponse();
bool _parseRequest(ClientType& client);
ClientFuture _parseRequest(ClientType& client);
void _parseArguments(const String& data);
int _parseArgumentsPrivate(const String& data, std::function<void(String&,String&,const String&,int,int,int,int)> handler);
bool _parseForm(ClientType& client, const String& boundary, uint32_t len);
Expand Down Expand Up @@ -261,8 +291,7 @@ class ESP8266WebServerTemplate
String _sopaque;
String _srealm; // Store the Auth realm between Calls



HookFunction _hook;
};


Expand Down
Loading