Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 93c630f

Browse files
committedSep 25, 2024··
feat(webserver): Middleware with default middleware for cors, authc, curl-like logging
1 parent ae052f4 commit 93c630f

File tree

15 files changed

+902
-100
lines changed

15 files changed

+902
-100
lines changed
 

‎libraries/WebServer/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.pio
2+
.vscode/.browse.c_cpp.db*
3+
.vscode/c_cpp_properties.json
4+
.vscode/launch.json
5+
.vscode/ipch
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#include <WiFi.h>
2+
#include <WebServer.h>
3+
#include <Middlewares.h>
4+
5+
// Your AP WiFi Credentials
6+
// ( This is the AP your ESP will broadcast )
7+
const char *ap_ssid = "ESP32_Demo";
8+
const char *ap_password = "";
9+
10+
WebServer server(80);
11+
12+
LoggingMiddleware logger;
13+
CorsMiddleware cors;
14+
AuthenticationMiddleware auth;
15+
16+
void setup(void) {
17+
Serial.begin(115200);
18+
WiFi.softAP(ap_ssid, ap_password);
19+
20+
Serial.print("IP address: ");
21+
Serial.println(WiFi.AP.localIP());
22+
23+
// curl-like output example:
24+
//
25+
// > curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/
26+
//
27+
// Connection from 192.168.4.2:51683
28+
// > OPTIONS / HTTP/1.1
29+
// > Host: 192.168.4.1
30+
// > User-Agent: curl/8.10.0
31+
// > Accept: */*
32+
// > origin: http://192.168.4.1
33+
// >
34+
// * Processed in 5 ms
35+
// < HTTP/1.HTTP/1.1 200 OK
36+
// < Content-Type: text/html
37+
// < Access-Control-Allow-Origin: http://192.168.4.1
38+
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
39+
// < Access-Control-Allow-Headers: X-Custom-Header
40+
// < Access-Control-Allow-Credentials: false
41+
// < Access-Control-Max-Age: 600
42+
// < Content-Length: 0
43+
// < Connection: close
44+
// <
45+
logger.setOutput(Serial);
46+
47+
cors.setOrigin("http://192.168.4.1");
48+
cors.setMethods("POST,GET,OPTIONS,DELETE");
49+
cors.setHeaders("X-Custom-Header");
50+
cors.setAllowCredentials(false);
51+
cors.setMaxAge(600);
52+
53+
auth.setUsername("admin");
54+
auth.setPassword("admin");
55+
auth.setRealm("My Super App");
56+
auth.setAuthMethod(DIGEST_AUTH);
57+
auth.setAuthFailureMessage("Authentication Failed");
58+
59+
server.addMiddleware(&logger);
60+
server.addMiddleware(&cors);
61+
62+
// Not authenticated
63+
//
64+
// Test CORS preflight request with:
65+
// > curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/
66+
//
67+
// Test cross-domain request with:
68+
// > curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/
69+
//
70+
server.on("/", []() {
71+
server.send(200, "text/plain", "Home");
72+
});
73+
74+
// Authenticated
75+
//
76+
// > curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/protected
77+
//
78+
// Outputs:
79+
//
80+
// * Connection from 192.168.4.2:51750
81+
// > GET /protected HTTP/1.1
82+
// > Host: 192.168.4.1
83+
// > User-Agent: curl/8.10.0
84+
// > Accept: */*
85+
// > origin: http://192.168.4.1
86+
// >
87+
// * Processed in 7 ms
88+
// < HTTP/1.HTTP/1.1 401 Unauthorized
89+
// < Content-Type: text/html
90+
// < Access-Control-Allow-Origin: http://192.168.4.1
91+
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
92+
// < Access-Control-Allow-Headers: X-Custom-Header
93+
// < Access-Control-Allow-Credentials: false
94+
// < Access-Control-Max-Age: 600
95+
// < WWW-Authenticate: Digest realm="My Super App", qop="auth", nonce="ac388a64184e3e102aae6fff1c9e8d76", opaque="e7d158f2b54d25328142d118ff0f932d"
96+
// < Content-Length: 21
97+
// < Connection: close
98+
// <
99+
//
100+
// > curl -v -X GET -H "origin: http://192.168.4.1" --digest -u admin:admin http://192.168.4.1/protected
101+
//
102+
// Outputs:
103+
//
104+
// * Connection from 192.168.4.2:53662
105+
// > GET /protected HTTP/1.1
106+
// > Authorization: Digest username="admin", realm="My Super App", nonce="db9e6824eb2a13bc7b2bf8f3c43db896", uri="/protected", cnonce="NTliZDZiNTcwODM2MzAyY2JjMDBmZGJmNzFiY2ZmNzk=", nc=00000001, qop=auth, response="6ebd145ba0d3496a4a73f5ae79ff5264", opaque="23d739c22810282ff820538cba98bda4"
107+
// > Host: 192.168.4.1
108+
// > User-Agent: curl/8.10.0
109+
// > Accept: */*
110+
// > origin: http://192.168.4.1
111+
// >
112+
// Request handling...
113+
// * Processed in 7 ms
114+
// < HTTP/1.HTTP/1.1 200 OK
115+
// < Content-Type: text/plain
116+
// < Access-Control-Allow-Origin: http://192.168.4.1
117+
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
118+
// < Access-Control-Allow-Headers: X-Custom-Header
119+
// < Access-Control-Allow-Credentials: false
120+
// < Access-Control-Max-Age: 600
121+
// < Content-Length: 9
122+
// < Connection: close
123+
// <
124+
server
125+
.on(
126+
"/protected",
127+
[]() {
128+
Serial.println("Request handling...");
129+
server.send(200, "text/plain", "Protected");
130+
}
131+
)
132+
.addMiddleware(&auth);
133+
134+
// Not found is also handled by global middleware
135+
//
136+
// curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/inexsting
137+
//
138+
// Outputs:
139+
//
140+
// * Connection from 192.168.4.2:53683
141+
// > GET /inexsting HTTP/1.1
142+
// > Host: 192.168.4.1
143+
// > User-Agent: curl/8.10.0
144+
// > Accept: */*
145+
// > origin: http://192.168.4.1
146+
// >
147+
// * Processed in 16 ms
148+
// < HTTP/1.HTTP/1.1 404 Not Found
149+
// < Content-Type: text/plain
150+
// < Access-Control-Allow-Origin: http://192.168.4.1
151+
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
152+
// < Access-Control-Allow-Headers: X-Custom-Header
153+
// < Access-Control-Allow-Credentials: false
154+
// < Access-Control-Max-Age: 600
155+
// < Content-Length: 14
156+
// < Connection: close
157+
// <
158+
server.onNotFound([]() {
159+
server.send(404, "text/plain", "Page not found");
160+
});
161+
162+
server.collectAllHeaders();
163+
server.begin();
164+
Serial.println("HTTP server started");
165+
}
166+
167+
void loop(void) {
168+
server.handleClient();
169+
delay(2); //allow the cpu to switch to other tasks
170+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"targets": {
3+
"esp32h2": false
4+
}
5+
}

‎libraries/WebServer/src/Middlewares.h

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#ifndef MIDDLEWARES_H
2+
#define MIDDLEWARES_H
3+
4+
#include <WebServer.h>
5+
#include <Stream.h>
6+
7+
#include <assert.h>
8+
9+
// curl-like logging middleware
10+
class LoggingMiddleware : public Middleware {
11+
public:
12+
void setOutput(Print &output);
13+
14+
bool run(WebServer &server, Middleware::Callback next) override;
15+
16+
private:
17+
Print *_out = nullptr;
18+
};
19+
20+
class CorsMiddleware : public Middleware {
21+
public:
22+
CorsMiddleware &setOrigin(const char *origin);
23+
CorsMiddleware &setMethods(const char *methods);
24+
CorsMiddleware &setHeaders(const char *headers);
25+
CorsMiddleware &setAllowCredentials(bool credentials);
26+
CorsMiddleware &setMaxAge(uint32_t seconds);
27+
28+
void addCORSHeaders(WebServer &server);
29+
30+
bool run(WebServer &server, Middleware::Callback next) override;
31+
32+
private:
33+
String _origin = F("*");
34+
String _methods = F("*");
35+
String _headers = F("*");
36+
bool _credentials = true;
37+
uint32_t _maxAge = 86400;
38+
};
39+
40+
class AuthenticationMiddleware : public Middleware {
41+
public:
42+
AuthenticationMiddleware &setUsername(const char *username);
43+
AuthenticationMiddleware &setPassword(const char *password);
44+
AuthenticationMiddleware &setPasswordHash(const char *sha1AsBase64orHex);
45+
AuthenticationMiddleware &setCallback(WebServer::THandlerFunctionAuthCheck fn);
46+
47+
AuthenticationMiddleware &setRealm(const char *realm);
48+
AuthenticationMiddleware &setAuthMethod(HTTPAuthMethod method);
49+
AuthenticationMiddleware &setAuthFailureMessage(const char *message);
50+
51+
bool isAllowed(WebServer &server) const;
52+
53+
bool run(WebServer &server, Middleware::Callback next) override;
54+
55+
private:
56+
String _username;
57+
String _password;
58+
bool _hash = false;
59+
WebServer::THandlerFunctionAuthCheck _callback;
60+
61+
const char *_realm = nullptr;
62+
HTTPAuthMethod _method = BASIC_AUTH;
63+
String _authFailMsg;
64+
65+
// AuthType _auth = AuthType::NOT_AUTHENTICATED;
66+
// String _username;
67+
// String _password;
68+
// String _sha1;
69+
70+
// // authenticate request
71+
// HTTPAuthMethod _mode = BASIC_AUTH;
72+
// String _realm;
73+
// String _authFailMsg;
74+
};
75+
76+
#endif

‎libraries/WebServer/src/Parsing.cpp

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,14 @@ bool WebServer::_parseRequest(NetworkClient &client) {
7878
String req = client.readStringUntil('\r');
7979
client.readStringUntil('\n');
8080
//reset header value
81-
for (int i = 0; i < _headerKeysCount; ++i) {
82-
_currentHeaders[i].value = String();
81+
if (_collectAllHeaders) {
82+
// clear previous headers
83+
collectAllHeaders();
84+
} else {
85+
// clear previous headers
86+
for (RequestArgument *header = _currentHeaders; header; header = header->next) {
87+
header->value = String();
88+
}
8389
}
8490

8591
// First line of HTTP request looks like "GET /path HTTP/1.1"
@@ -154,9 +160,6 @@ bool WebServer::_parseRequest(NetworkClient &client) {
154160
headerValue.trim();
155161
_collectHeader(headerName.c_str(), headerValue.c_str());
156162

157-
log_v("headerName: %s", headerName.c_str());
158-
log_v("headerValue: %s", headerValue.c_str());
159-
160163
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) {
161164
using namespace mime;
162165
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))) {
@@ -253,9 +256,6 @@ bool WebServer::_parseRequest(NetworkClient &client) {
253256
headerValue = req.substring(headerDiv + 2);
254257
_collectHeader(headerName.c_str(), headerValue.c_str());
255258

256-
log_v("headerName: %s", headerName.c_str());
257-
log_v("headerValue: %s", headerValue.c_str());
258-
259259
if (headerName.equalsIgnoreCase("Host")) {
260260
_hostHeader = headerValue;
261261
}
@@ -271,12 +271,29 @@ bool WebServer::_parseRequest(NetworkClient &client) {
271271
}
272272

273273
bool WebServer::_collectHeader(const char *headerName, const char *headerValue) {
274-
for (int i = 0; i < _headerKeysCount; i++) {
275-
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
276-
_currentHeaders[i].value = headerValue;
274+
RequestArgument *last = nullptr;
275+
for (RequestArgument *header = _currentHeaders; header; header = header->next) {
276+
if (header->next == nullptr) {
277+
last = header;
278+
}
279+
if (header->key.equalsIgnoreCase(headerName)) {
280+
header->value = headerValue;
281+
log_v("header collected: %s: %s", headerName, headerValue);
277282
return true;
278283
}
279284
}
285+
assert(last);
286+
if (_collectAllHeaders) {
287+
last->next = new RequestArgument();
288+
last->next->key = headerName;
289+
last->next->value = headerValue;
290+
_headerKeysCount++;
291+
log_v("header collected: %s: %s", headerName, headerValue);
292+
return true;
293+
}
294+
295+
log_v("header skipped: %s: %s", headerName, headerValue);
296+
280297
return false;
281298
}
282299

‎libraries/WebServer/src/WebServer.cpp

Lines changed: 181 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -41,31 +41,28 @@ static const char WWW_Authenticate[] = "WWW-Authenticate";
4141
static const char Content_Length[] = "Content-Length";
4242
static const char ETAG_HEADER[] = "If-None-Match";
4343

44-
WebServer::WebServer(IPAddress addr, int port)
45-
: _corsEnabled(false), _server(addr, port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE), _statusChange(0), _nullDelay(true),
46-
_currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr), _currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr),
47-
_headerKeysCount(0), _currentHeaders(nullptr), _contentLength(0), _clientContentLength(0), _chunked(false) {
44+
WebServer::WebServer(IPAddress addr, int port) : _server(addr, port) {
4845
log_v("WebServer::Webserver(addr=%s, port=%d)", addr.toString().c_str(), port);
4946
}
5047

51-
WebServer::WebServer(int port)
52-
: _corsEnabled(false), _server(port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE), _statusChange(0), _nullDelay(true),
53-
_currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr), _currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr),
54-
_headerKeysCount(0), _currentHeaders(nullptr), _contentLength(0), _clientContentLength(0), _chunked(false) {
48+
WebServer::WebServer(int port) : _server(port) {
5549
log_v("WebServer::Webserver(port=%d)", port);
5650
}
5751

5852
WebServer::~WebServer() {
5953
_server.close();
60-
if (_currentHeaders) {
61-
delete[] _currentHeaders;
62-
}
54+
55+
_clearRequestHeaders();
56+
_clearResponseHeaders();
57+
delete _chain;
58+
6359
RequestHandler *handler = _firstHandler;
6460
while (handler) {
6561
RequestHandler *next = handler->next();
6662
delete handler;
6763
handler = next;
6864
}
65+
_firstHandler = nullptr;
6966
}
7067

7168
void WebServer::begin() {
@@ -436,7 +433,17 @@ void WebServer::handleClient() {
436433
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT); /* / 1000 removed, WifiClient setTimeout changed to ms */
437434
if (_parseRequest(_currentClient)) {
438435
_contentLength = CONTENT_LENGTH_NOT_SET;
439-
_handleRequest();
436+
_responseCode = 0;
437+
_clearResponseHeaders();
438+
439+
// Run server-level middlewares
440+
if (_chain) {
441+
_chain->runChain(*this, [this]() {
442+
return _handleRequest();
443+
});
444+
} else {
445+
_handleRequest();
446+
}
440447

441448
if (_currentClient.isSSE()) {
442449
_currentStatus = HC_WAIT_CLOSE;
@@ -495,16 +502,22 @@ void WebServer::stop() {
495502
}
496503

497504
void WebServer::sendHeader(const String &name, const String &value, bool first) {
498-
String headerLine = name;
499-
headerLine += F(": ");
500-
headerLine += value;
501-
headerLine += "\r\n";
505+
RequestArgument *header = new RequestArgument();
506+
header->key = name;
507+
header->value = value;
502508

503-
if (first) {
504-
_responseHeaders = headerLine + _responseHeaders;
509+
if (!_responseHeaders || first) {
510+
header->next = _responseHeaders;
511+
_responseHeaders = header;
505512
} else {
506-
_responseHeaders += headerLine;
513+
RequestArgument *last = _responseHeaders;
514+
while (last->next) {
515+
last = last->next;
516+
}
517+
last->next = header;
507518
}
519+
520+
_responseHeaderCount++;
508521
}
509522

510523
void WebServer::setContentLength(const size_t contentLength) {
@@ -529,11 +542,14 @@ void WebServer::enableETag(bool enable, ETagFunction fn) {
529542
}
530543

531544
void WebServer::_prepareHeader(String &response, int code, const char *content_type, size_t contentLength) {
532-
response = String(F("HTTP/1.")) + String(_currentVersion) + ' ';
533-
response += String(code);
534-
response += ' ';
535-
response += _responseCodeToString(code);
536-
response += "\r\n";
545+
_responseCode = code;
546+
547+
response.concat(version());
548+
response.concat(' ');
549+
response.concat(String(code));
550+
response.concat(' ');
551+
response.concat(responseCodeToString(code));
552+
response.concat(F("\r\n"));
537553

538554
using namespace mime;
539555
if (!content_type) {
@@ -558,19 +574,21 @@ void WebServer::_prepareHeader(String &response, int code, const char *content_t
558574
}
559575
sendHeader(String(F("Connection")), String(F("close")));
560576

561-
response += _responseHeaders;
562-
response += "\r\n";
563-
_responseHeaders = "";
577+
for (RequestArgument *header = _responseHeaders; header; header = header->next) {
578+
response.concat(header->key);
579+
response.concat(F(": "));
580+
response.concat(header->value);
581+
response.concat(F("\r\n"));
582+
}
583+
584+
response.concat(F("\r\n"));
564585
}
565586

566587
void WebServer::send(int code, const char *content_type, const String &content) {
567588
String header;
568589
// Can we assume the following?
569590
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
570591
// _contentLength = CONTENT_LENGTH_UNKNOWN;
571-
if (content.length() == 0) {
572-
log_w("content length is zero");
573-
}
574592
_prepareHeader(header, code, content_type, content.length());
575593
_currentClientWrite(header.c_str(), header.length());
576594
if (content.length()) {
@@ -728,52 +746,80 @@ bool WebServer::hasArg(const String &name) const {
728746
}
729747

730748
String WebServer::header(const String &name) const {
731-
for (int i = 0; i < _headerKeysCount; ++i) {
732-
if (_currentHeaders[i].key.equalsIgnoreCase(name)) {
733-
return _currentHeaders[i].value;
749+
for (RequestArgument *current = _currentHeaders; current; current = current->next) {
750+
if (current->key.equalsIgnoreCase(name)) {
751+
return current->value;
734752
}
735753
}
736754
return "";
737755
}
738756

739757
void WebServer::collectHeaders(const char *headerKeys[], const size_t headerKeysCount) {
740-
_headerKeysCount = headerKeysCount + 2;
741-
if (_currentHeaders) {
742-
delete[] _currentHeaders;
743-
}
744-
_currentHeaders = new RequestArgument[_headerKeysCount];
745-
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
746-
_currentHeaders[1].key = FPSTR(ETAG_HEADER);
758+
collectAllHeaders();
759+
_collectAllHeaders = false;
760+
761+
_headerKeysCount += headerKeysCount;
762+
763+
RequestArgument *last = _currentHeaders->next;
764+
747765
for (int i = 2; i < _headerKeysCount; i++) {
748-
_currentHeaders[i].key = headerKeys[i - 2];
766+
last->next = new RequestArgument();
767+
last->next->key = headerKeys[i - 2];
768+
last = last->next;
749769
}
750770
}
751771

752772
String WebServer::header(int i) const {
753-
if (i < _headerKeysCount) {
754-
return _currentHeaders[i].value;
773+
RequestArgument *current = _currentHeaders;
774+
while (current && i--) {
775+
current = current->next;
755776
}
756-
return "";
777+
return current ? current->value : emptyString;
757778
}
758779

759780
String WebServer::headerName(int i) const {
760-
if (i < _headerKeysCount) {
761-
return _currentHeaders[i].key;
781+
RequestArgument *current = _currentHeaders;
782+
while (current && i--) {
783+
current = current->next;
762784
}
763-
return "";
785+
return current ? current->key : emptyString;
764786
}
765787

766788
int WebServer::headers() const {
767789
return _headerKeysCount;
768790
}
769791

770792
bool WebServer::hasHeader(const String &name) const {
771-
for (int i = 0; i < _headerKeysCount; ++i) {
772-
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0)) {
773-
return true;
793+
return header(name).length() > 0;
794+
}
795+
796+
const String &WebServer::responseHeader(String name) const {
797+
for (RequestArgument *current = _responseHeaders; current; current = current->next) {
798+
if (current->key.equalsIgnoreCase(name)) {
799+
return current->value;
774800
}
775801
}
776-
return false;
802+
return emptyString;
803+
}
804+
805+
const String &WebServer::responseHeader(int i) const {
806+
RequestArgument *current = _responseHeaders;
807+
while (current && i--) {
808+
current = current->next;
809+
}
810+
return current ? current->value : emptyString;
811+
}
812+
813+
const String &WebServer::responseHeaderName(int i) const {
814+
RequestArgument *current = _responseHeaders;
815+
while (current && i--) {
816+
current = current->next;
817+
}
818+
return current ? current->key : emptyString;
819+
}
820+
821+
bool WebServer::hasResponseHeader(const String &name) const {
822+
return header(name).length() > 0;
777823
}
778824

779825
String WebServer::hostHeader() const {
@@ -788,16 +834,17 @@ void WebServer::onNotFound(THandlerFunction fn) {
788834
_notFoundHandler = fn;
789835
}
790836

791-
void WebServer::_handleRequest() {
837+
bool WebServer::_handleRequest() {
792838
bool handled = false;
793-
if (!_currentHandler) {
794-
log_e("request handler not found");
795-
} else {
796-
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
839+
if (_currentHandler) {
840+
handled = _currentHandler->process(*this, _currentMethod, _currentUri);
797841
if (!handled) {
798842
log_e("request handler failed to handle request");
799843
}
800844
}
845+
// DO NOT LOG if _currentHandler == null !!
846+
// This is is valid use case to handle any other requests
847+
// Also, this is just causing log flooding
801848
if (!handled && _notFoundHandler) {
802849
_notFoundHandler();
803850
handled = true;
@@ -811,6 +858,7 @@ void WebServer::_handleRequest() {
811858
_finalizeResponse();
812859
}
813860
_currentUri = "";
861+
return handled;
814862
}
815863

816864
void WebServer::_finalizeResponse() {
@@ -819,7 +867,29 @@ void WebServer::_finalizeResponse() {
819867
}
820868
}
821869

822-
String WebServer::_responseCodeToString(int code) {
870+
void WebServer::_clearResponseHeaders() {
871+
_responseHeaderCount = 0;
872+
RequestArgument *current = _responseHeaders;
873+
while (current) {
874+
RequestArgument *next = current->next;
875+
delete current;
876+
current = next;
877+
}
878+
_responseHeaders = nullptr;
879+
}
880+
881+
void WebServer::_clearRequestHeaders() {
882+
_headerKeysCount = 0;
883+
RequestArgument *current = _currentHeaders;
884+
while (current) {
885+
RequestArgument *next = current->next;
886+
delete current;
887+
current = next;
888+
}
889+
_currentHeaders = nullptr;
890+
}
891+
892+
String WebServer::responseCodeToString(int code) {
823893
switch (code) {
824894
case 100: return F("Continue");
825895
case 101: return F("Switching Protocols");
@@ -864,3 +934,57 @@ String WebServer::_responseCodeToString(int code) {
864934
default: return F("");
865935
}
866936
}
937+
938+
void WebServer::collectAllHeaders() {
939+
_clearRequestHeaders();
940+
941+
_currentHeaders = new RequestArgument();
942+
_currentHeaders->key = FPSTR(AUTHORIZATION_HEADER);
943+
944+
_currentHeaders->next = new RequestArgument();
945+
_currentHeaders->next->key = FPSTR(ETAG_HEADER);
946+
947+
_headerKeysCount = 2;
948+
_collectAllHeaders = true;
949+
}
950+
951+
int WebServer::clientContentLength() const {
952+
return _clientContentLength;
953+
}
954+
955+
const String WebServer::version() const {
956+
String v;
957+
v.reserve(8);
958+
v.concat(F("HTTP/1."));
959+
v.concat(_currentVersion);
960+
return v;
961+
}
962+
int WebServer::responseCode() const {
963+
return _responseCode;
964+
}
965+
int WebServer::responseHeaders() const {
966+
return _responseHeaderCount;
967+
}
968+
969+
WebServer &WebServer::addMiddleware(Middleware *middleware) {
970+
if (!_chain) {
971+
_chain = new MiddlewareChain();
972+
}
973+
_chain->addMiddleware(middleware);
974+
return *this;
975+
}
976+
977+
WebServer &WebServer::addMiddleware(Middleware::Function fn) {
978+
if (!_chain) {
979+
_chain = new MiddlewareChain();
980+
}
981+
_chain->addMiddleware(fn);
982+
return *this;
983+
}
984+
985+
WebServer &WebServer::removeMiddleware(Middleware *middleware) {
986+
if (_chain) {
987+
_chain->removeMiddleware(middleware);
988+
}
989+
return *this;
990+
}

‎libraries/WebServer/src/WebServer.h

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ typedef struct {
9292
void *data; // additional data
9393
} HTTPRaw;
9494

95+
#include "detail/Middleware.h"
9596
#include "detail/RequestHandler.h"
9697

9798
namespace fs {
@@ -181,17 +182,21 @@ class WebServer {
181182
int args() const; // get arguments count
182183
bool hasArg(const String &name) const; // check if argument exists
183184
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount); // set the request headers to collect
185+
void collectAllHeaders(); // collect all request headers
184186
String header(const String &name) const; // get request header value by name
185187
String header(int i) const; // get request header value by number
186188
String headerName(int i) const; // get request header name by number
187189
int headers() const; // get header count
188190
bool hasHeader(const String &name) const; // check if header exists
189-
190-
int clientContentLength() const {
191-
return _clientContentLength;
192-
} // return "content-length" of incoming HTTP header from "_currentClient"
193-
194-
String hostHeader() const; // get request host header if available or empty String if not
191+
const String version() const; // get the HTTP version string
192+
int responseCode() const; // get the HTTP response code set
193+
int responseHeaders() const; // get the HTTP response headers count
194+
const String &responseHeader(String name) const; // get the HTTP response header value by name
195+
const String &responseHeader(int i) const; // get the HTTP response header value by number
196+
const String &responseHeaderName(int i) const; // get the HTTP response header name by number
197+
bool hasResponseHeader(const String& name) const; // check if response header exists
198+
int clientContentLength() const; // return "content-length" of incoming HTTP header from "_currentClient"
199+
String hostHeader() const; // get request host header if available or empty String if not
195200

196201
// send response to the client
197202
// code - HTTP response code, can be 200 or 404
@@ -218,6 +223,10 @@ class WebServer {
218223
void sendContent_P(PGM_P content);
219224
void sendContent_P(PGM_P content, size_t size);
220225

226+
WebServer &addMiddleware(Middleware *middleware);
227+
WebServer &addMiddleware(Middleware::Function fn);
228+
WebServer &removeMiddleware(Middleware *middleware);
229+
221230
static String urlDecode(const String &text);
222231

223232
template<typename T> size_t streamFile(T &file, const String &contentType, const int code = 200) {
@@ -228,6 +237,8 @@ class WebServer {
228237
bool _eTagEnabled = false;
229238
ETagFunction _eTagFunction = nullptr;
230239

240+
static String responseCodeToString(int code);
241+
231242
protected:
232243
virtual size_t _currentClientWrite(const char *b, size_t l) {
233244
return _currentClient.write(b, l);
@@ -237,11 +248,10 @@ class WebServer {
237248
}
238249
void _addRequestHandler(RequestHandler *handler);
239250
bool _removeRequestHandler(RequestHandler *handler);
240-
void _handleRequest();
251+
bool _handleRequest();
241252
void _finalizeResponse();
242253
bool _parseRequest(NetworkClient &client);
243254
void _parseArguments(const String &data);
244-
static String _responseCodeToString(int code);
245255
bool _parseForm(NetworkClient &client, const String &boundary, uint32_t len);
246256
bool _parseFormUploadAborted();
247257
void _uploadWriteByte(uint8_t b);
@@ -255,48 +265,57 @@ class WebServer {
255265
// for extracting Auth parameters
256266
String _extractParam(String &authReq, const String &param, const char delimit = '"');
257267

268+
void _clearResponseHeaders();
269+
void _clearRequestHeaders();
270+
258271
struct RequestArgument {
259272
String key;
260273
String value;
274+
RequestArgument *next;
261275
};
262276

263-
boolean _corsEnabled;
277+
boolean _corsEnabled = false;
264278
NetworkServer _server;
265279

266280
NetworkClient _currentClient;
267-
HTTPMethod _currentMethod;
281+
HTTPMethod _currentMethod = HTTP_ANY;
268282
String _currentUri;
269-
uint8_t _currentVersion;
270-
HTTPClientStatus _currentStatus;
271-
unsigned long _statusChange;
272-
boolean _nullDelay;
273-
274-
RequestHandler *_currentHandler;
275-
RequestHandler *_firstHandler;
276-
RequestHandler *_lastHandler;
277-
THandlerFunction _notFoundHandler;
278-
THandlerFunction _fileUploadHandler;
279-
280-
int _currentArgCount;
281-
RequestArgument *_currentArgs;
282-
int _postArgsLen;
283-
RequestArgument *_postArgs;
283+
uint8_t _currentVersion = 0;
284+
HTTPClientStatus _currentStatus = HC_NONE;
285+
unsigned long _statusChange = 0;
286+
boolean _nullDelay = true;
287+
288+
RequestHandler *_currentHandler = nullptr;
289+
RequestHandler *_firstHandler = nullptr;
290+
RequestHandler *_lastHandler = nullptr;
291+
THandlerFunction _notFoundHandler = nullptr;
292+
THandlerFunction _fileUploadHandler = nullptr;
293+
294+
int _currentArgCount = 0;
295+
RequestArgument *_currentArgs = nullptr;
296+
int _postArgsLen = 0;
297+
RequestArgument *_postArgs = nullptr;
284298

285299
std::unique_ptr<HTTPUpload> _currentUpload;
286300
std::unique_ptr<HTTPRaw> _currentRaw;
287301

288-
int _headerKeysCount;
289-
RequestArgument *_currentHeaders;
290-
size_t _contentLength;
291-
int _clientContentLength; // "Content-Length" from header of incoming POST or GET request
292-
String _responseHeaders;
302+
int _headerKeysCount = 0;
303+
RequestArgument *_currentHeaders = nullptr;
304+
size_t _contentLength = 0;
305+
int _clientContentLength = 0; // "Content-Length" from header of incoming POST or GET request
306+
RequestArgument *_responseHeaders = nullptr;
293307

294308
String _hostHeader;
295-
bool _chunked;
309+
bool _chunked = false;
296310

297311
String _snonce; // Store noance and opaque for future comparison
298312
String _sopaque;
299313
String _srealm; // Store the Auth realm between Calls
314+
315+
int _responseHeaderCount = 0;
316+
int _responseCode = 0;
317+
bool _collectAllHeaders = false;
318+
MiddlewareChain *_chain = nullptr;
300319
};
301320

302321
#endif //ESP8266WEBSERVER_H
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#ifndef MIDDLEWARE_H
2+
#define MIDDLEWARE_H
3+
4+
#include <assert.h>
5+
#include <functional>
6+
7+
class MiddlewareChain;
8+
class WebServer;
9+
10+
class Middleware {
11+
public:
12+
typedef std::function<bool(void)> Callback;
13+
typedef std::function<bool(WebServer &server, Callback next)> Function;
14+
15+
virtual ~Middleware() {}
16+
17+
virtual bool run(WebServer &server, Callback next) {
18+
return next();
19+
};
20+
21+
private:
22+
friend MiddlewareChain;
23+
Middleware *_next = nullptr;
24+
bool _freeOnRemoval = false;
25+
};
26+
27+
class MiddlewareFunction : public Middleware {
28+
public:
29+
MiddlewareFunction(Middleware::Function fn) : _fn(fn) {}
30+
31+
bool run(WebServer &server, Middleware::Callback next) override {
32+
return _fn(server, next);
33+
}
34+
35+
private:
36+
Middleware::Function _fn;
37+
};
38+
39+
class MiddlewareChain {
40+
private:
41+
Middleware *_root = nullptr;
42+
Middleware *_current = nullptr;
43+
44+
public:
45+
~MiddlewareChain();
46+
47+
void addMiddleware(Middleware::Function fn);
48+
void addMiddleware(Middleware *middleware);
49+
bool removeMiddleware(Middleware *middleware);
50+
51+
bool runChain(WebServer &server, Middleware::Callback finalizer);
52+
};
53+
54+
#endif
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include "Middleware.h"
2+
3+
MiddlewareChain::~MiddlewareChain() {
4+
Middleware *current = _root;
5+
while (current) {
6+
Middleware *next = current->_next;
7+
if (current->_freeOnRemoval) {
8+
delete current;
9+
}
10+
current = next;
11+
}
12+
_root = nullptr;
13+
}
14+
15+
void MiddlewareChain::addMiddleware(Middleware::Function fn) {
16+
MiddlewareFunction *middleware = new MiddlewareFunction(fn);
17+
middleware->_freeOnRemoval = true;
18+
addMiddleware(middleware);
19+
}
20+
21+
void MiddlewareChain::addMiddleware(Middleware *middleware) {
22+
if (!_root) {
23+
_root = middleware;
24+
return;
25+
}
26+
Middleware *current = _root;
27+
while (current->_next) {
28+
current = current->_next;
29+
}
30+
current->_next = middleware;
31+
}
32+
33+
bool MiddlewareChain::removeMiddleware(Middleware *middleware) {
34+
if (!_root) {
35+
return false;
36+
}
37+
if (_root == middleware) {
38+
_root = _root->_next;
39+
if (middleware->_freeOnRemoval) {
40+
delete middleware;
41+
}
42+
return true;
43+
}
44+
Middleware *current = _root;
45+
while (current->_next) {
46+
if (current->_next == middleware) {
47+
current->_next = current->_next->_next;
48+
if (middleware->_freeOnRemoval) {
49+
delete middleware;
50+
}
51+
return true;
52+
}
53+
current = current->_next;
54+
}
55+
return false;
56+
}
57+
58+
bool MiddlewareChain::runChain(WebServer &server, Middleware::Callback finalizer) {
59+
if (!_root) {
60+
return finalizer();
61+
}
62+
_current = _root;
63+
Middleware::Callback next;
64+
next = [this, &server, &next, finalizer]() {
65+
if (!_current) {
66+
return finalizer();
67+
}
68+
Middleware *that = _current;
69+
_current = _current->_next;
70+
return that->run(server, next);
71+
};
72+
return next();
73+
}

‎libraries/WebServer/src/detail/RequestHandler.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
class RequestHandler {
88
public:
9-
virtual ~RequestHandler() {}
9+
virtual ~RequestHandler() {
10+
delete _chain;
11+
}
1012

1113
/*
1214
note: old handler API for backward compatibility
@@ -75,8 +77,14 @@ class RequestHandler {
7577
_next = r;
7678
}
7779

80+
RequestHandler &addMiddleware(Middleware *middleware);
81+
RequestHandler &addMiddleware(Middleware::Function fn);
82+
RequestHandler &removeMiddleware(Middleware *middleware);
83+
bool process(WebServer &server, HTTPMethod requestMethod, String requestUri);
84+
7885
private:
7986
RequestHandler *_next = nullptr;
87+
MiddlewareChain *_chain = nullptr;
8088

8189
protected:
8290
std::vector<String> pathArgs;

‎libraries/WebServer/src/detail/RequestHandlersImpl.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,41 @@
1010

1111
using namespace mime;
1212

13+
14+
RequestHandler &RequestHandler::addMiddleware(Middleware *middleware) {
15+
if (!_chain) {
16+
_chain = new MiddlewareChain();
17+
}
18+
_chain->addMiddleware(middleware);
19+
return *this;
20+
}
21+
22+
RequestHandler &RequestHandler::addMiddleware(Middleware::Function fn) {
23+
if (!_chain) {
24+
_chain = new MiddlewareChain();
25+
}
26+
_chain->addMiddleware(fn);
27+
return *this;
28+
}
29+
30+
RequestHandler &RequestHandler::removeMiddleware(Middleware *middleware) {
31+
if (_chain) {
32+
_chain->removeMiddleware(middleware);
33+
}
34+
return *this;
35+
}
36+
37+
bool RequestHandler::process(WebServer &server, HTTPMethod requestMethod, String requestUri) {
38+
if (_chain) {
39+
return _chain->runChain(server, [this, &server, &requestMethod, &requestUri]() {
40+
return handle(server, requestMethod, requestUri);
41+
});
42+
} else {
43+
return handle(server, requestMethod, requestUri);
44+
}
45+
}
46+
47+
1348
class FunctionRequestHandler : public RequestHandler {
1449
public:
1550
FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include "Middlewares.h"
2+
3+
AuthenticationMiddleware &AuthenticationMiddleware::setUsername(const char *username) {
4+
_username = username;
5+
_callback = nullptr;
6+
return *this;
7+
}
8+
9+
AuthenticationMiddleware &AuthenticationMiddleware::setPassword(const char *password) {
10+
_password = password;
11+
_hash = false;
12+
_callback = nullptr;
13+
return *this;
14+
}
15+
16+
AuthenticationMiddleware &AuthenticationMiddleware::setPasswordHash(const char *sha1AsBase64orHex) {
17+
_password = sha1AsBase64orHex;
18+
_hash = true;
19+
_callback = nullptr;
20+
return *this;
21+
}
22+
23+
AuthenticationMiddleware &AuthenticationMiddleware::setCallback(WebServer::THandlerFunctionAuthCheck fn) {
24+
assert(fn);
25+
_callback = fn;
26+
_hash = false;
27+
_username = emptyString;
28+
_password = emptyString;
29+
return *this;
30+
}
31+
32+
AuthenticationMiddleware &AuthenticationMiddleware::setRealm(const char *realm) {
33+
_realm = realm;
34+
return *this;
35+
}
36+
37+
AuthenticationMiddleware &AuthenticationMiddleware::setAuthMethod(HTTPAuthMethod method) {
38+
_method = method;
39+
return *this;
40+
}
41+
42+
AuthenticationMiddleware &AuthenticationMiddleware::setAuthFailureMessage(const char *message) {
43+
_authFailMsg = message;
44+
return *this;
45+
}
46+
47+
bool AuthenticationMiddleware::isAllowed(WebServer &server) const {
48+
if (_callback) {
49+
return server.authenticate(_callback);
50+
}
51+
52+
if (!_username.isEmpty() && !_password.isEmpty()) {
53+
if (_hash) {
54+
return server.authenticateBasicSHA1(_username.c_str(), _password.c_str());
55+
} else {
56+
return server.authenticate(_username.c_str(), _password.c_str());
57+
}
58+
}
59+
60+
return true;
61+
}
62+
63+
bool AuthenticationMiddleware::run(WebServer &server, Middleware::Callback next) {
64+
bool authenticationRequired = false;
65+
66+
if (_callback) {
67+
authenticationRequired = !server.authenticate(_callback);
68+
} else if (!_username.isEmpty() && !_password.isEmpty()) {
69+
if (_hash) {
70+
authenticationRequired = !server.authenticateBasicSHA1(_username.c_str(), _password.c_str());
71+
} else {
72+
authenticationRequired = !server.authenticate(_username.c_str(), _password.c_str());
73+
}
74+
}
75+
76+
if (authenticationRequired) {
77+
server.requestAuthentication(_method, _realm, _authFailMsg);
78+
return true;
79+
} else {
80+
return next();
81+
}
82+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include "Middlewares.h"
2+
3+
CorsMiddleware &CorsMiddleware::setOrigin(const char *origin) {
4+
_origin = origin;
5+
return *this;
6+
}
7+
8+
CorsMiddleware &CorsMiddleware::setMethods(const char *methods) {
9+
_methods = methods;
10+
return *this;
11+
}
12+
13+
CorsMiddleware &CorsMiddleware::setHeaders(const char *headers) {
14+
_headers = headers;
15+
return *this;
16+
}
17+
18+
CorsMiddleware &CorsMiddleware::setAllowCredentials(bool credentials) {
19+
_credentials = credentials;
20+
return *this;
21+
}
22+
23+
CorsMiddleware &CorsMiddleware::setMaxAge(uint32_t seconds) {
24+
_maxAge = seconds;
25+
return *this;
26+
}
27+
28+
void CorsMiddleware::addCORSHeaders(WebServer &server) {
29+
server.sendHeader(F("Access-Control-Allow-Origin"), _origin.c_str());
30+
server.sendHeader(F("Access-Control-Allow-Methods"), _methods.c_str());
31+
server.sendHeader(F("Access-Control-Allow-Headers"), _headers.c_str());
32+
server.sendHeader(F("Access-Control-Allow-Credentials"), _credentials ? F("true") : F("false"));
33+
server.sendHeader(F("Access-Control-Max-Age"), String(_maxAge).c_str());
34+
}
35+
36+
bool CorsMiddleware::run(WebServer &server, Middleware::Callback next) {
37+
// Origin header ? => CORS handling
38+
if (server.hasHeader(F("Origin"))) {
39+
addCORSHeaders(server);
40+
// check if this is a preflight request => handle it and return
41+
if (server.method() == HTTP_OPTIONS) {
42+
server.send(200);
43+
return true;
44+
}
45+
}
46+
return next();
47+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#include "Middlewares.h"
2+
3+
void LoggingMiddleware::setOutput(Print &output) {
4+
_out = &output;
5+
}
6+
7+
bool LoggingMiddleware::run(WebServer &server, Middleware::Callback next) {
8+
if (_out == nullptr) {
9+
return next();
10+
}
11+
12+
_out->print(F("* Connection from "));
13+
_out->print(server.client().remoteIP().toString());
14+
_out->print(F(":"));
15+
_out->println(server.client().remotePort());
16+
17+
_out->print(F("> "));
18+
const HTTPMethod method = server.method();
19+
if (method == HTTP_ANY) {
20+
_out->print(F("HTTP_ANY"));
21+
} else {
22+
_out->print(http_method_str(method));
23+
}
24+
_out->print(F(" "));
25+
_out->print(server.uri());
26+
_out->print(F(" "));
27+
_out->println(server.version());
28+
29+
int n = server.headers();
30+
for (int i = 0; i < n; i++) {
31+
String v = server.header(i);
32+
if (!v.isEmpty()) {
33+
// because these 2 are always there, eventually empty: "Authorization", "If-None-Match"
34+
_out->print(F("> "));
35+
_out->print(server.headerName(i));
36+
_out->print(F(": "));
37+
_out->println(server.header(i));
38+
}
39+
}
40+
41+
_out->println(F(">"));
42+
43+
uint32_t elapsed = millis();
44+
const bool ret = next();
45+
elapsed = millis() - elapsed;
46+
47+
if (ret) {
48+
_out->print(F("* Processed in "));
49+
_out->print(elapsed);
50+
_out->println(F(" ms"));
51+
_out->print(F("< "));
52+
_out->print(F("HTTP/1."));
53+
_out->print(server.version());
54+
_out->print(F(" "));
55+
_out->print(server.responseCode());
56+
_out->print(F(" "));
57+
_out->println(WebServer::responseCodeToString(server.responseCode()));
58+
59+
n = server.responseHeaders();
60+
for (int i = 0; i < n; i++) {
61+
_out->print(F("< "));
62+
_out->print(server.responseHeaderName(i));
63+
_out->print(F(": "));
64+
_out->println(server.responseHeader(i));
65+
}
66+
67+
_out->println(F("<"));
68+
69+
} else {
70+
_out->println(F("* Not processed!"));
71+
}
72+
73+
return ret;
74+
}

‎package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.