-
Notifications
You must be signed in to change notification settings - Fork 13.3k
/
Copy pathESP8266WebServer.h
365 lines (302 loc) · 14.2 KB
/
ESP8266WebServer.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/*
ESP8266WebServer.h - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#ifndef ESP8266WEBSERVER_H
#define ESP8266WEBSERVER_H
#include <functional>
#include <memory>
#include <functional>
#include <list>
#include <ESP8266WiFi.h>
#include <FS.h>
#include <StreamDev.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 };
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
#define WEBSERVER_HAS_HOOK 1
#define HTTP_DOWNLOAD_UNIT_SIZE 1460
#ifndef HTTP_UPLOAD_BUFLEN
#define HTTP_UPLOAD_BUFLEN 2048
#endif
#define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request
#define HTTP_MAX_DATA_AVAILABLE_WAIT 30 //ms to wait for the client to send the request when there is another client with data available
#define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive
#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed
#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection
#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
#define CONTENT_LENGTH_NOT_SET ((size_t) -2)
typedef struct {
HTTPUploadStatus status;
String filename;
String name;
String type;
size_t totalSize; // total size of uploaded file so far
size_t currentSize; // size of data currently in buf
size_t contentLength; // size of entire post request, file size + headers and other request data.
uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;
namespace esp8266webserver {
template<typename ServerType>
class ESP8266WebServerTemplate;
}
#include "detail/RequestHandler.h"
namespace esp8266webserver {
template<typename ServerType>
class ESP8266WebServerTemplate
{
public:
ESP8266WebServerTemplate(IPAddress addr, int port = 80);
ESP8266WebServerTemplate(int port = 80);
~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);
void handleClient();
void close();
void stop();
bool authenticate(const char * username, const char * password);
bool authenticateDigest(const String& username, const String& H1);
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") );
typedef std::function<void(void)> THandlerFunction;
typedef std::function<String(FS &fs, const String &fName)> ETagFunction;
void on(const Uri &uri, THandlerFunction handler);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
void addHandler(RequestHandlerType* handler);
void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL );
void onNotFound(THandlerFunction fn); //called when handler is not assigned
void onFileUpload(THandlerFunction fn); //handle file uploads
void enableCORS(bool enable);
void enableETag(bool enable, ETagFunction fn = nullptr);
const String& uri() const { return _currentUri; }
HTTPMethod method() const { return _currentMethod; }
ClientType& client() { return _currentClient; }
HTTPUpload& upload() { return *_currentUpload; }
// Allows setting server options (i.e. SSL keys) by the instantiator
ServerType &getServer() { return _server; }
const String& pathArg(unsigned int i) const; // get request path argument by number
const String& arg(const String& name) const; // get request argument value by name
const String& arg(int i) const; // get request argument value by number
const String& argName(int i) const; // get request argument name by number
int args() const; // get arguments count
bool hasArg(const String& name) const; // check if argument exists
void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
template<typename... Args>
void collectHeaders(const Args&... args); // set the request headers to collect (variadic template version)
const String& header(const String& name) const; // get request header value by name
const String& header(int i) const; // get request header value by number
const String& headerName(int i) const; // get request header name by number
int headers() const; // get header count
bool hasHeader(const String& name) const; // check if header exists
const String& hostHeader() const; // get request host header if available or empty String if not
// send response to the client
// code - HTTP response code, can be 200 or 404
// content_type - HTTP content type, like "text/plain" or "image/png"
// content - actual content body
void send(int code, const char* content_type = NULL, const String& content = emptyString);
void send(int code, char* content_type, const String& content);
void send(int code, const String& content_type, const String& content);
void send(int code, const char *content_type, const char *content) {
send_P(code, content_type, content);
}
void send(int code, const char *content_type, const char *content, size_t content_length) {
send_P(code, content_type, content, content_length);
}
void send(int code, const char *content_type, const uint8_t *content, size_t content_length) {
send_P(code, content_type, (const char *)content, content_length);
}
void send_P(int code, PGM_P content_type, PGM_P content);
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
void send(int code, const char* content_type, Stream* stream, size_t content_length = 0);
void send(int code, const char* content_type, Stream& stream, size_t content_length = 0) {
send(code, content_type, &stream, content_length);
}
void setContentLength(const size_t contentLength);
template <typename S1, typename S2>
void sendHeader(S1 name, S2 value, bool first = false);
void sendContent(const String& content);
void sendContent(String& content) {
sendContent((const String&)content);
}
void sendContent_P(PGM_P content);
void sendContent_P(PGM_P content, size_t size);
void sendContent(const char *content) { sendContent_P(content); }
void sendContent(const char *content, size_t size) { sendContent_P(content, size); }
void sendContent(Stream* content, ssize_t content_length = 0);
void sendContent(Stream& content, ssize_t content_length = 0) { sendContent(&content, content_length); }
bool chunkedResponseModeStart_P (int code, PGM_P content_type) {
if (_currentVersion == 0)
// no chunk mode in HTTP/1.0
return false;
setContentLength(CONTENT_LENGTH_UNKNOWN);
send_P(code, content_type, "");
return true;
}
bool chunkedResponseModeStart (int code, const char* content_type) {
return chunkedResponseModeStart_P(code, content_type);
}
bool chunkedResponseModeStart (int code, const String& content_type) {
return chunkedResponseModeStart_P(code, content_type.c_str());
}
void chunkedResponseFinalize () {
sendContent(emptyString);
}
// Whether other requests should be accepted from the client on the
// same socket after a response is sent.
// This will automatically configure the "Connection" header of the response.
// Defaults to true when the client's HTTP version is 1.1 or above, otherwise it defaults to false.
// If the client sends the "Connection" header, the value given by the header is used.
void keepAlive(bool keepAlive) { _keepAlive = keepAlive; }
bool keepAlive() { return _keepAlive; }
static String credentialHash(const String& username, const String& realm, const String& password);
static String urlDecode(const String& text);
// Handle a GET request by sending a response header and stream file content to response body
template<typename T>
size_t streamFile(T &file, const String& contentType) {
return streamFile(file, contentType, HTTP_GET);
}
// Implement GET and HEAD requests for files.
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
template<typename T>
size_t streamFile(T &file, const String& contentType, HTTPMethod requestMethod) {
size_t contentLength = 0;
_streamFileCore(file.size(), file.name(), contentType);
if (requestMethod == HTTP_GET) {
contentLength = file.sendAll(_currentClient);
}
return contentLength;
}
// Implement GET and HEAD requests for stream
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
template<typename T>
size_t stream(T &aStream, const String& contentType, HTTPMethod requestMethod, ssize_t size) {
setContentLength(size);
send(200, contentType, emptyString);
if (requestMethod == HTTP_GET)
size = aStream.sendSize(_currentClient, size);
return size;
}
// Implement GET and HEAD requests for stream
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
template<typename T>
size_t stream(T& aStream, const String& contentType, HTTPMethod requestMethod = HTTP_GET) {
ssize_t size = aStream.size();
if (size < 0)
{
send(500, F("text/html"), F("input stream: undetermined size"));
return 0;
}
return stream(aStream, contentType, requestMethod, size);
}
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;
}
}
bool _eTagEnabled = false;
ETagFunction _eTagFunction = nullptr;
protected:
void _addRequestHandler(RequestHandlerType* handler);
void _handleRequest();
void _finalizeResponse();
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);
bool _parseFormUploadAborted();
void _uploadWriteByte(uint8_t b);
int _uploadReadByte(ClientType& client);
bool _sendHeader(int code, const char* content_type, size_t contentLength);
bool _collectHeader(const char* headerName, const char* headerValue);
void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType);
static String _getRandomHexString();
// for extracting Auth parameters
String _extractParam(String& authReq,const String& param,const char delimit = '"') const;
bool _streamIt (const String& s) { StreamConstPtr c(s.c_str(), s.length()); return _streamIt(c); }
bool _streamItC (StreamConstPtr&& s) { return _streamIt(s); }
bool _streamIt (Stream& s);
template <typename K, typename V>
bool _streamHeader (K name, V value);
struct RequestArgument {
String key;
String value;
};
ServerType _server;
ClientType _currentClient;
HTTPMethod _currentMethod = HTTP_ANY;
String _currentUri;
uint8_t _currentVersion = 0;
HTTPClientStatus _currentStatus = HC_NONE;
unsigned long _statusChange = 0;
RequestHandlerType* _currentHandler = nullptr;
RequestHandlerType* _firstHandler = nullptr;
RequestHandlerType* _lastHandler = nullptr;
THandlerFunction _notFoundHandler;
THandlerFunction _fileUploadHandler;
int _currentArgCount = 0;
RequestArgument* _currentArgs = nullptr;
int _currentArgsHavePlain = 0;
std::unique_ptr<HTTPUpload> _currentUpload;
int _postArgsLen = 0;
RequestArgument* _postArgs = nullptr;
int _headerKeysCount = 0;
RequestArgument* _currentHeaders = nullptr;
size_t _contentLength = 0;
std::list<std::pair<String,String>> _userHeaders;
String _hostHeader;
bool _chunked = false;
bool _corsEnabled = false;
bool _keepAlive = false;
String _snonce; // Store noance and opaque for future comparison
String _sopaque;
String _srealm; // Store the Auth realm between Calls
HookFunction _hook;
};
} // namespace
#include "ESP8266WebServer-impl.h"
#include "Parsing-impl.h"
using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate<WiFiServer>;
using RequestHandler = esp8266webserver::RequestHandler<WiFiServer>;
#endif //ESP8266WEBSERVER_H