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
50 changes: 43 additions & 7 deletions libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
#define DEBUG_OUTPUT Serial
#endif

#ifdef DEBUG_ESP_HTTP_SERVER
#define DBGWS(x...) do { DEBUG_OUTPUT.printf(x); } while (0)
#else
#define DBGWS(x...) do { (void)0; } while (0)
#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\"";
Expand Down Expand Up @@ -326,6 +332,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()) {
switch (_currentStatus) {
case HC_NONE:
Expand All @@ -334,34 +347,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
26 changes: 23 additions & 3 deletions libraries/ESP8266WebServer/src/ESP8266WebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include <functional>
#include <memory>
#include <functional>
#include <ESP8266WiFi.h>
#include <FS.h>
#include "detail/mimetable.h"
Expand Down Expand Up @@ -80,6 +81,9 @@ class ESP8266WebServerTemplate
using ClientType = typename ServerType::ClientType;
using RequestHandlerType = RequestHandler<ServerType>;
using WebServerType = ESP8266WebServerTemplate<ServerType>;
using ContentType_f = std::function<String(const String&)>;
enum ClientFuture_e { CLIENT_REQUEST_CAN_CONTINUE, CLIENT_REQUEST_IS_HANDLED, CLIENT_MUST_STOP, CLIENT_IS_GIVEN };
using Hook_f = std::function<ClientFuture_e(const String& method, const String& url, WiFiClient* client, ContentType_f contentType)>;

void begin();
void begin(uint16_t port);
Expand Down Expand Up @@ -192,11 +196,28 @@ class ESP8266WebServerTemplate

static String responseCodeToString(const int code);

void addHook (Hook_f hook)
{
if (_hook)
{
auto previous = _hook;
_hook = [previous, hook](const String& method, const String& url, WiFiClient* client, ContentType_f contentType)
{
auto whatNow = previous(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_e _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 @@ -252,8 +273,7 @@ class ESP8266WebServerTemplate
String _sopaque;
String _srealm; // Store the Auth realm between Calls



Hook_f _hook;
};


Expand Down
19 changes: 13 additions & 6 deletions libraries/ESP8266WebServer/src/Parsing-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t
}

template <typename ServerType>
bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
typename ESP8266WebServerTemplate<ServerType>::ClientFuture_e ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
// Read the first line of HTTP request
String req = client.readStringUntil('\r');
#ifdef DEBUG_ESP_HTTP_SERVER
Expand All @@ -71,7 +71,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
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 @@ -82,7 +82,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("Invalid request");
#endif
return false;
return CLIENT_MUST_STOP;
}

String methodStr = req.substring(0, addr_start);
Expand All @@ -98,6 +98,13 @@ bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& 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("HEAD")) {
method = HTTP_HEAD;
Expand Down Expand Up @@ -188,7 +195,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
)
)
{
return false;
return CLIENT_MUST_STOP;
}

if (isEncoded) {
Expand All @@ -213,7 +220,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
} else { // isForm is true
// here: content is not yet read (plainBuf is still empty)
if (!_parseForm(client, boundaryStr, contentLength)) {
return false;
return CLIENT_MUST_STOP;
}
}
} else {
Expand Down Expand Up @@ -260,7 +267,7 @@ bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) {
_currentArgs[i].value.c_str());
#endif

return true;
return CLIENT_REQUEST_CAN_CONTINUE;
}

template <typename ServerType>
Expand Down