Skip to content

Commit 7861a98

Browse files
committed
First draft of bodyparser API
1 parent 5933514 commit 7861a98

6 files changed

+364
-0
lines changed

examples/HTML-Forms/HTML-Forms.ino

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/**
2+
* Example for the ESP32 HTTP(S) Webserver
3+
*
4+
* IMPORTANT NOTE:
5+
* To run this script, you need to
6+
* 1) Enter your WiFi SSID and PSK below this comment
7+
* 2) Make sure to have certificate data available. You will find a
8+
* shell script and instructions to do so in the library folder
9+
* under extras/
10+
*
11+
* This script will install an HTTPS Server on your ESP32 with the following
12+
* functionalities:
13+
* - Show simple page on web server root that includes some HTML Forms
14+
* - Define a POST handler that handles the forms using the HTTPBodyParser API
15+
* provided by the library.
16+
* - 404 for everything else
17+
*/
18+
19+
// TODO: Configure your WiFi here
20+
#define WIFI_SSID "<your ssid goes here>"
21+
#define WIFI_PSK "<your pre-shared key goes here>"
22+
23+
// Include certificate data (see note above)
24+
#include "cert.h"
25+
#include "private_key.h"
26+
27+
// We will use wifi
28+
#include <WiFi.h>
29+
30+
// Includes for the server
31+
#include <HTTPSServer.hpp>
32+
#include <SSLCert.hpp>
33+
#include <HTTPRequest.hpp>
34+
#include <HTTPResponse.hpp>
35+
36+
// The HTTPS Server comes in a separate namespace. For easier use, include it here.
37+
using namespace httpsserver;
38+
39+
// Create an SSL certificate object from the files included above
40+
SSLCert cert = SSLCert(
41+
example_crt_DER, example_crt_DER_len,
42+
example_key_DER, example_key_DER_len
43+
);
44+
45+
// Create an SSL-enabled server that uses the certificate
46+
// The contstructor takes some more parameters, but we go for default values here.
47+
HTTPSServer secureServer = HTTPSServer(&cert);
48+
49+
// Declare some handler functions for the various URLs on the server
50+
// The signature is always the same for those functions. They get two parameters,
51+
// which are pointers to the request data (read request body, headers, ...) and
52+
// to the response data (write response, set status code, ...)
53+
void handleRoot(HTTPRequest * req, HTTPResponse * res);
54+
void handleForm(HTTPRequest * req, HTTPResponse * res);
55+
void handle404(HTTPRequest * req, HTTPResponse * res);
56+
57+
void setup() {
58+
// For logging
59+
Serial.begin(115200);
60+
61+
// Connect to WiFi
62+
Serial.println("Setting up WiFi");
63+
WiFi.begin(WIFI_SSID, WIFI_PSK);
64+
while (WiFi.status() != WL_CONNECTED) {
65+
Serial.print(".");
66+
delay(500);
67+
}
68+
Serial.print("Connected. IP=");
69+
Serial.println(WiFi.localIP());
70+
71+
// For every resource available on the server, we need to create a ResourceNode
72+
// The ResourceNode links URL and HTTP method to a handler function
73+
ResourceNode * nodeRoot = new ResourceNode("/", "GET", &handleRoot);
74+
ResourceNode * nodeForm = new ResourceNode("/", "POST", &handleForm);
75+
76+
// 404 node has no URL as it is used for all requests that don't match anything else
77+
ResourceNode * node404 = new ResourceNode("", "GET", &handle404);
78+
79+
// Add the root nodes to the server
80+
secureServer.registerNode(nodeRoot);
81+
secureServer.registerNode(nodeForm);
82+
83+
// Add the 404 not found node to the server.
84+
// The path is ignored for the default node.
85+
secureServer.setDefaultNode(node404);
86+
87+
Serial.println("Starting server...");
88+
secureServer.start();
89+
if (secureServer.isRunning()) {
90+
Serial.println("Server ready.");
91+
}
92+
}
93+
94+
void loop() {
95+
// This call will let the server do its work
96+
secureServer.loop();
97+
98+
// Other code would go here...
99+
delay(1);
100+
}
101+
102+
void handleRoot(HTTPRequest * req, HTTPResponse * res) {
103+
// Status code is 200 OK by default.
104+
// We want to deliver a simple HTML page, so we send a corresponding content type:
105+
res->setHeader("Content-Type", "text/html");
106+
107+
// The response implements the Print interface, so you can use it just like
108+
// you would write to Serial etc.
109+
res->println("<!DOCTYPE html>");
110+
res->println("<html>");
111+
res->println("<head><title>Hello World!</title></head>");
112+
res->println("<body>");
113+
res->println("<h1>HTML Forms</h1>");
114+
res->println("<p>This page contains some forms to show you how form data can be evaluated on server side.");
115+
116+
// The following forms will all use the same target (/ - the server's root directory) and POST method, so
117+
// the data will go to the request body. They differ on the value of the enctype though.
118+
119+
// enctype=x-www-form-urlencoded
120+
// means that the parameters of form elements will be encoded like they would
121+
// be encoded if you would use GET as method and append them to the URL (just after a ? at the end of the
122+
// resource path). Different fields are separated by an &-character. Special characters have a specific encoding
123+
// using the %-character, so for example %20 is the representation of a space.
124+
// The body could look like this:
125+
//
126+
// foo=bar&bat=baz
127+
//
128+
// Where foo and bat are the variables and bar and baz the values.
129+
//
130+
// Advantages:
131+
// - Low overhead
132+
// Disadvantages:
133+
// - May be hard to read for humans
134+
// - Cannot be used for inputs with type=file (you will only get the filename, not the content)
135+
res->println("<form method=\"POST\" action=\"/\" enctype=\"x-www-form-urlencoded\">");
136+
res->println("<h2>enctype=x-www-form-urlencoded</h2>");
137+
res->println("</form>")
138+
139+
// enctype=multipart/form-data
140+
//
141+
// TODO: Explanatory text
142+
//
143+
// Advantages:
144+
// - Even longer text stays somewhat human readable
145+
// - Can be used for files and binary data
146+
// Disadvantages:
147+
// - Big overhead if used for some small string values
148+
res->println("<form method=\"POST\" action=\"/\" enctype=\"multipart/form-data\">");
149+
res->println("<h2>enctype=multipart/form-data</h2>");
150+
res->println("</form>")
151+
152+
res->println("</body>");
153+
res->println("</html>");
154+
}
155+
156+
void handleForm(HTTPRequest * req, HTTPResponse * res) {
157+
// First, we need to check the encoding of the form that we have received.
158+
// The browser will set the Content-Type request header, so we can use it for that purpose.
159+
// Then we select the body parser based on the encoding.
160+
// Note: This is only necessary if you expect various enctypes to be send to the same handler.
161+
// If you would have only one form on your page with a fixed enctype, just instantiate the
162+
// corresponding reader.
163+
164+
// TODO: Select Parser, instantiate it
165+
166+
// Now we iterate over the so-called fields of the BodyParser. This works in the same way for
167+
// all body parsers.
168+
// The interface is somewhat limited, so you cannot just call something like
169+
// myParser.get("fieldname"), because this would require the parser to cache the whole request
170+
// body. If you have your ESP32 attached to an external SD Card and you want to be able to upload
171+
// files that are larger than the ESP's memory to that card, this would not work.
172+
// This is why you iterate over the fields by using myParser.next() and check the name of the
173+
// current field with myParser.getFieldName(), and then process it with a buffer.
174+
// If you need random access to all fields' values, you need to parse them into a map or some similar
175+
// data structure by yourself and make sure that all fits into the memory.
176+
177+
// TODO: Iterate over fields
178+
}
179+
180+
void handle404(HTTPRequest * req, HTTPResponse * res) {
181+
// Discard request body, if we received any
182+
// We do this, as this is the default node and may also server POST/PUT requests
183+
req->discardRequestBody();
184+
185+
// Set the response status
186+
res->setStatusCode(404);
187+
res->setStatusText("Not Found");
188+
189+
// Set content type of the response
190+
res->setHeader("Content-Type", "text/html");
191+
192+
// Write a tiny HTML page
193+
res->println("<!DOCTYPE html>");
194+
res->println("<html>");
195+
res->println("<head><title>Not Found</title></head>");
196+
res->println("<body><h1>404 Not Found</h1><p>The requested resource was not found on this server.</p></body>");
197+
res->println("</html>");
198+
}

src/HTTPBodyParser.hpp

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#ifndef SRC_HTTPBODYPARSER_HPP_
2+
#define SRC_HTTPBODYPARSER_HPP_
3+
4+
#include <Arduino.h>
5+
#include <string>
6+
#include "HTTPRequest.hpp"
7+
8+
namespace httpsserver {
9+
10+
/**
11+
* Superclass for various body parser implementations that can be used to
12+
* interpret http-specific bodies (like x-www-form-urlencoded or multipart/form-data)
13+
*
14+
* To allow for arbitrary body length, the interface of the body parser provides access
15+
* to one underlying "field" at a time. A field may be a value of the urlencoded string
16+
* or a part of a multipart message.
17+
*
18+
* Using next() proceeds to the next field.
19+
*/
20+
class HTTPBodyParser {
21+
public:
22+
HTTPBodyParser(HTTPRequest * req): _request(req) {};
23+
24+
/**
25+
* Proceeds to the next field of the body
26+
*
27+
* If a field has not been read completely, the remaining content is discarded.
28+
*
29+
* Returns true iff proceeding to the next field succeeded (ie there was a next field)
30+
*/
31+
virtual bool nextField() = 0;
32+
33+
/** Returns the name of the current field */
34+
virtual std::string getFieldName() = 0;
35+
36+
/**
37+
* Returns the mime type of the current field.
38+
*
39+
* Note: This value is set by the client. It can be altered maliciously. Do NOT rely on it
40+
* for anything that affects the security of your device or other clients connected to it!
41+
*
42+
* Not every BodyParser might provide this value, usually it's set to something like text/plain then
43+
*/
44+
virtual std::string getFieldMimeType() = 0;
45+
46+
/** Returns the total length of the field */
47+
virtual size_t getLength() = 0;
48+
49+
/** Returns the remaining length of the field. 0 means the field has been read completely */
50+
virtual size_t getRemainingLength() = 0;
51+
52+
/** Reads a maximum of bufferSize bytes into buffer and returns the actual amount of bytes that have been read */
53+
virtual size_t read(byte* buffer, size_t bufferSize) = 0;
54+
55+
protected:
56+
/** The underlying request */
57+
HTTPRequest * _request;
58+
};
59+
60+
} // namespace httpserver
61+
62+
#endif

src/HTTPMultipartBodyParser.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include "HTTPMultipartBodyParser.hpp"
2+
3+
namespace httpsserver {
4+
5+
bool HTTPMultipartBodyParser::nextField() {
6+
return false;
7+
}
8+
9+
std::string HTTPMultipartBodyParser::getFieldName() {
10+
return std::string("foo");
11+
}
12+
13+
std::string HTTPMultipartBodyParser::getFieldMimeType() {
14+
return std::string("text/plain");
15+
}
16+
17+
size_t HTTPMultipartBodyParser::getLength() {
18+
return 0;
19+
}
20+
21+
size_t HTTPMultipartBodyParser::getRemainingLength() {
22+
return 0;
23+
}
24+
25+
size_t HTTPMultipartBodyParser::read(byte* buffer, size_t bufferSize) {
26+
return 0;
27+
}
28+
29+
30+
}

src/HTTPMultipartBodyParser.hpp

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#ifndef SRC_HTTPMULTIPARTBODYPARSER_HPP_
2+
#define SRC_HTTPMULTIPARTBODYPARSER_HPP_
3+
4+
#include <Arduino.h>
5+
#include "HTTPBodyParser.hpp"
6+
7+
namespace httpsserver {
8+
9+
class HTTPMultipartBodyParser : public HTTPBodyParser {
10+
public:
11+
// From HTTPBodyParser
12+
virtual bool nextField();
13+
virtual std::string getFieldName();
14+
virtual std::string getFieldMimeType();
15+
virtual size_t getLength();
16+
virtual size_t getRemainingLength();
17+
virtual size_t read(byte* buffer, size_t bufferSize);
18+
};
19+
20+
} // namespace httpserver
21+
22+
#endif

src/HTTPURLEncodedBodyParser.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include "HTTPURLEncodedBodyParser.hpp"
2+
3+
namespace httpsserver {
4+
5+
bool HTTPURLEncodedBodyParser::nextField() {
6+
return false;
7+
}
8+
9+
std::string HTTPURLEncodedBodyParser::getFieldName() {
10+
return std::string("foo");
11+
}
12+
13+
std::string HTTPURLEncodedBodyParser::getFieldMimeType() {
14+
return std::string("text/plain");
15+
}
16+
17+
size_t HTTPURLEncodedBodyParser::getLength() {
18+
return 0;
19+
}
20+
21+
size_t HTTPURLEncodedBodyParser::getRemainingLength() {
22+
return 0;
23+
}
24+
25+
size_t HTTPURLEncodedBodyParser::read(byte* buffer, size_t bufferSize) {
26+
return 0;
27+
}
28+
29+
30+
}

src/HTTPURLEncodedBodyParser.hpp

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#ifndef SRC_HTTPURLENCODEDBODYPARSER_HPP_
2+
#define SRC_HTTPURLENCODEDBODYPARSER_HPP_
3+
4+
#include <Arduino.h>
5+
#include "HTTPBodyParser.hpp"
6+
7+
namespace httpsserver {
8+
9+
class HTTPURLEncodedBodyParser : public HTTPBodyParser {
10+
public:
11+
// From HTTPBodyParser
12+
virtual bool nextField();
13+
virtual std::string getFieldName();
14+
virtual std::string getFieldMimeType();
15+
virtual size_t getLength();
16+
virtual size_t getRemainingLength();
17+
virtual size_t read(byte* buffer, size_t bufferSize);
18+
};
19+
20+
} // namespace httpserver
21+
22+
#endif

0 commit comments

Comments
 (0)