Skip to content

Commit 521bbaa

Browse files
committed
feat(webserver): Middleware with default middleware for cors, authc, curl-like logging
1 parent ae052f4 commit 521bbaa

16 files changed

+1011
-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: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
This example shows how to load all request headers and use middleware.
2+
3+
### CORS Middleware
4+
5+
```bash
6+
❯ curl -i -X OPTIONS http://192.168.4.1
7+
HTTP/1.1 200 OK
8+
Content-Type: text/html
9+
Access-Control-Allow-Origin: http://192.168.4.1
10+
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
11+
Access-Control-Allow-Headers: X-Custom-Header
12+
Access-Control-Allow-Credentials: false
13+
Access-Control-Max-Age: 600
14+
Content-Length: 0
15+
Connection: close
16+
```
17+
18+
Output of logger middleware:
19+
20+
```
21+
* Connection from 192.168.4.2:57597
22+
< OPTIONS / HTTP/1.1
23+
< Host: 192.168.4.1
24+
< User-Agent: curl/8.9.1
25+
< Accept: */*
26+
<
27+
* Processed!
28+
> HTTP/1.HTTP/1.1 200 OK
29+
> Content-Type: text/html
30+
> Access-Control-Allow-Origin: http://192.168.4.1
31+
> Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
32+
> Access-Control-Allow-Headers: X-Custom-Header
33+
> Access-Control-Allow-Credentials: false
34+
> Access-Control-Max-Age: 600
35+
> Content-Length: 0
36+
> Connection: close
37+
>
38+
```
39+
40+
### Authentication Middleware
41+
42+
```bash
43+
❯ curl -i -X GET http://192.168.4.1
44+
HTTP/1.1 401 Unauthorized
45+
Content-Type: text/html
46+
WWW-Authenticate: Basic realm=""
47+
Content-Length: 0
48+
Connection: close
49+
```
50+
51+
Output of logger middleware:
52+
53+
```
54+
* Connection from 192.168.4.2:57705
55+
< GET / HTTP/1.1
56+
< Host: 192.168.4.1
57+
< User-Agent: curl/8.9.1
58+
< Accept: */*
59+
<
60+
* Processed!
61+
> HTTP/1.HTTP/1.1 401 Unauthorized
62+
> Content-Type: text/html
63+
> WWW-Authenticate: Basic realm=""
64+
> Content-Length: 0
65+
> Connection: close
66+
>
67+
```
68+
69+
Sending auth...
70+
71+
```bash
72+
Note: Unnecessary use of -X or --request, GET is already inferred.
73+
* Trying 192.168.4.1:80...
74+
* Connected to 192.168.4.1 (192.168.4.1) port 80
75+
* Server auth using Basic with user 'admin'
76+
> GET / HTTP/1.1
77+
> Host: 192.168.4.1
78+
> Authorization: Basic YWRtaW46YWRtaW4=
79+
> User-Agent: curl/8.9.1
80+
> Accept: */*
81+
>
82+
* Request completely sent off
83+
< HTTP/1.1 200 OK
84+
< Content-Type: text/plain
85+
< Content-Length: 4
86+
< Connection: close
87+
<
88+
* shutting down connection #0
89+
Home
90+
91+
```
92+
93+
Output of logger middleware:
94+
95+
```
96+
* Connection from 192.168.4.2:62099
97+
< GET / HTTP/1.1
98+
< Authorization: Basic YWRtaW46YWRtaW4=
99+
< Host: 192.168.4.1
100+
< User-Agent: curl/8.9.1
101+
< Accept: */*
102+
<
103+
* Processed!
104+
> HTTP/1.HTTP/1.1 200 OK
105+
> Content-Type: text/plain
106+
> Content-Length: 4
107+
> Connection: close
108+
>
109+
```
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+
}
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

0 commit comments

Comments
 (0)