mirror of
https://github.com/luc-github/ESP3D.git
synced 2026-03-25 17:16:51 +01:00
Merge Development version to master
This commit is contained in:
29
libraries/WebServer/src/ESP8266WebServer.h
Normal file
29
libraries/WebServer/src/ESP8266WebServer.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
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 <WebServer.h>
|
||||
|
||||
#endif //ESP8266WEBSERVER_H
|
||||
610
libraries/WebServer/src/Parsing.cpp
Normal file
610
libraries/WebServer/src/Parsing.cpp
Normal file
@@ -0,0 +1,610 @@
|
||||
/*
|
||||
Parsing.cpp - HTTP request parsing.
|
||||
|
||||
Copyright (c) 2015 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)
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "WiFiServer.h"
|
||||
#include "WiFiClient.h"
|
||||
#include "WebServer.h"
|
||||
|
||||
//#define DEBUG_ESP_HTTP_SERVER
|
||||
#ifdef DEBUG_ESP_PORT
|
||||
#define DEBUG_OUTPUT DEBUG_ESP_PORT
|
||||
#else
|
||||
#define DEBUG_OUTPUT Serial
|
||||
#endif
|
||||
|
||||
static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms)
|
||||
{
|
||||
char *buf = nullptr;
|
||||
dataLength = 0;
|
||||
while (dataLength < maxLength) {
|
||||
int tries = timeout_ms;
|
||||
size_t newLength;
|
||||
while (!(newLength = client.available()) && tries--) delay(1);
|
||||
if (!newLength) {
|
||||
break;
|
||||
}
|
||||
if (!buf) {
|
||||
buf = (char *) malloc(newLength + 1);
|
||||
if (!buf) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
char* newBuf = (char *) realloc(buf, dataLength + newLength + 1);
|
||||
if (!newBuf) {
|
||||
free(buf);
|
||||
return nullptr;
|
||||
}
|
||||
buf = newBuf;
|
||||
}
|
||||
client.readBytes(buf + dataLength, newLength);
|
||||
dataLength += newLength;
|
||||
buf[dataLength] = '\0';
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool WebServer::_parseRequest(WiFiClient& client) {
|
||||
// Read the first line of HTTP request
|
||||
String req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
//reset header value
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
_currentHeaders[i].value =String();
|
||||
}
|
||||
|
||||
// First line of HTTP request looks like "GET /path HTTP/1.1"
|
||||
// Retrieve the "/path" part by finding the spaces
|
||||
int addr_start = req.indexOf(' ');
|
||||
int addr_end = req.indexOf(' ', addr_start + 1);
|
||||
if (addr_start == -1 || addr_end == -1) {
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("Invalid request: ");
|
||||
DEBUG_OUTPUT.println(req);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
String methodStr = req.substring(0, addr_start);
|
||||
String url = req.substring(addr_start + 1, addr_end);
|
||||
String versionEnd = req.substring(addr_end + 8);
|
||||
_currentVersion = atoi(versionEnd.c_str());
|
||||
String searchStr = "";
|
||||
int hasSearch = url.indexOf('?');
|
||||
if (hasSearch != -1){
|
||||
searchStr = url.substring(hasSearch + 1);
|
||||
url = url.substring(0, hasSearch);
|
||||
}
|
||||
_currentUri = url;
|
||||
_chunked = false;
|
||||
|
||||
HTTPMethod method = HTTP_GET;
|
||||
if (methodStr == "POST") {
|
||||
method = HTTP_POST;
|
||||
} else if (methodStr == "DELETE") {
|
||||
method = HTTP_DELETE;
|
||||
} else if (methodStr == "OPTIONS") {
|
||||
method = HTTP_OPTIONS;
|
||||
} else if (methodStr == "PUT") {
|
||||
method = HTTP_PUT;
|
||||
} else if (methodStr == "PATCH") {
|
||||
method = HTTP_PATCH;
|
||||
}
|
||||
_currentMethod = method;
|
||||
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("method: ");
|
||||
DEBUG_OUTPUT.print(methodStr);
|
||||
DEBUG_OUTPUT.print(" url: ");
|
||||
DEBUG_OUTPUT.print(url);
|
||||
DEBUG_OUTPUT.print(" search: ");
|
||||
DEBUG_OUTPUT.println(searchStr);
|
||||
#endif
|
||||
|
||||
//attach handler
|
||||
RequestHandler* handler;
|
||||
for (handler = _firstHandler; handler; handler = handler->next()) {
|
||||
if (handler->canHandle(_currentMethod, _currentUri))
|
||||
break;
|
||||
}
|
||||
_currentHandler = handler;
|
||||
|
||||
String formData;
|
||||
// below is needed only when POST type request
|
||||
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){
|
||||
String boundaryStr;
|
||||
String headerName;
|
||||
String headerValue;
|
||||
bool isForm = false;
|
||||
bool isEncoded = false;
|
||||
uint32_t contentLength = 0;
|
||||
//parse headers
|
||||
while(1){
|
||||
req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (req == "") break;//no moar headers
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1){
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 1);
|
||||
headerValue.trim();
|
||||
_collectHeader(headerName.c_str(),headerValue.c_str());
|
||||
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("headerName: ");
|
||||
DEBUG_OUTPUT.println(headerName);
|
||||
DEBUG_OUTPUT.print("headerValue: ");
|
||||
DEBUG_OUTPUT.println(headerValue);
|
||||
#endif
|
||||
|
||||
if (headerName.equalsIgnoreCase("Content-Type")){
|
||||
if (headerValue.startsWith("text/plain")){
|
||||
isForm = false;
|
||||
} else if (headerValue.startsWith("application/x-www-form-urlencoded")){
|
||||
isForm = false;
|
||||
isEncoded = true;
|
||||
} else if (headerValue.startsWith("multipart/")){
|
||||
boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
|
||||
isForm = true;
|
||||
}
|
||||
} else if (headerName.equalsIgnoreCase("Content-Length")){
|
||||
contentLength = headerValue.toInt();
|
||||
} else if (headerName.equalsIgnoreCase("Host")){
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isForm){
|
||||
size_t plainLength;
|
||||
char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
|
||||
if (plainLength < contentLength) {
|
||||
free(plainBuf);
|
||||
return false;
|
||||
}
|
||||
if (contentLength > 0) {
|
||||
if (searchStr != "") searchStr += '&';
|
||||
if(isEncoded){
|
||||
//url encoded form
|
||||
String decoded = urlDecode(plainBuf);
|
||||
size_t decodedLen = decoded.length();
|
||||
memcpy(plainBuf, decoded.c_str(), decodedLen);
|
||||
plainBuf[decodedLen] = 0;
|
||||
searchStr += plainBuf;
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
if(!isEncoded){
|
||||
//plain post json or other data
|
||||
RequestArgument& arg = _currentArgs[_currentArgCount++];
|
||||
arg.key = "plain";
|
||||
arg.value = String(plainBuf);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("Plain: ");
|
||||
DEBUG_OUTPUT.println(plainBuf);
|
||||
#endif
|
||||
free(plainBuf);
|
||||
} else {
|
||||
// No content - but we can still have arguments in the URL.
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
}
|
||||
|
||||
if (isForm){
|
||||
_parseArguments(searchStr);
|
||||
if (!_parseForm(client, boundaryStr, contentLength)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String headerName;
|
||||
String headerValue;
|
||||
//parse headers
|
||||
while(1){
|
||||
req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (req == "") break;//no moar headers
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1){
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 2);
|
||||
_collectHeader(headerName.c_str(),headerValue.c_str());
|
||||
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("headerName: ");
|
||||
DEBUG_OUTPUT.println(headerName);
|
||||
DEBUG_OUTPUT.print("headerValue: ");
|
||||
DEBUG_OUTPUT.println(headerValue);
|
||||
#endif
|
||||
|
||||
if (headerName.equalsIgnoreCase("Host")){
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
client.flush();
|
||||
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("Request: ");
|
||||
DEBUG_OUTPUT.println(url);
|
||||
DEBUG_OUTPUT.print(" Arguments: ");
|
||||
DEBUG_OUTPUT.println(searchStr);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebServer::_collectHeader(const char* headerName, const char* headerValue) {
|
||||
for (int i = 0; i < _headerKeysCount; i++) {
|
||||
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
|
||||
_currentHeaders[i].value=headerValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebServer::_parseArguments(String data) {
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("args: ");
|
||||
DEBUG_OUTPUT.println(data);
|
||||
#endif
|
||||
if (_currentArgs)
|
||||
delete[] _currentArgs;
|
||||
_currentArgs = 0;
|
||||
if (data.length() == 0) {
|
||||
_currentArgCount = 0;
|
||||
_currentArgs = new RequestArgument[1];
|
||||
return;
|
||||
}
|
||||
_currentArgCount = 1;
|
||||
|
||||
for (int i = 0; i < (int)data.length(); ) {
|
||||
i = data.indexOf('&', i);
|
||||
if (i == -1)
|
||||
break;
|
||||
++i;
|
||||
++_currentArgCount;
|
||||
}
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("args count: ");
|
||||
DEBUG_OUTPUT.println(_currentArgCount);
|
||||
#endif
|
||||
|
||||
_currentArgs = new RequestArgument[_currentArgCount+1];
|
||||
int pos = 0;
|
||||
int iarg;
|
||||
for (iarg = 0; iarg < _currentArgCount;) {
|
||||
int equal_sign_index = data.indexOf('=', pos);
|
||||
int next_arg_index = data.indexOf('&', pos);
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("pos ");
|
||||
DEBUG_OUTPUT.print(pos);
|
||||
DEBUG_OUTPUT.print("=@ ");
|
||||
DEBUG_OUTPUT.print(equal_sign_index);
|
||||
DEBUG_OUTPUT.print(" &@ ");
|
||||
DEBUG_OUTPUT.println(next_arg_index);
|
||||
#endif
|
||||
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("arg missing value: ");
|
||||
DEBUG_OUTPUT.println(iarg);
|
||||
#endif
|
||||
if (next_arg_index == -1)
|
||||
break;
|
||||
pos = next_arg_index + 1;
|
||||
continue;
|
||||
}
|
||||
RequestArgument& arg = _currentArgs[iarg];
|
||||
arg.key = data.substring(pos, equal_sign_index);
|
||||
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("arg ");
|
||||
DEBUG_OUTPUT.print(iarg);
|
||||
DEBUG_OUTPUT.print(" key: ");
|
||||
DEBUG_OUTPUT.print(arg.key);
|
||||
DEBUG_OUTPUT.print(" value: ");
|
||||
DEBUG_OUTPUT.println(arg.value);
|
||||
#endif
|
||||
++iarg;
|
||||
if (next_arg_index == -1)
|
||||
break;
|
||||
pos = next_arg_index + 1;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("args count: ");
|
||||
DEBUG_OUTPUT.println(_currentArgCount);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void WebServer::_uploadWriteByte(uint8_t b){
|
||||
if (_currentUpload.currentSize == HTTP_UPLOAD_BUFLEN){
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, _currentUpload);
|
||||
_currentUpload.totalSize += _currentUpload.currentSize;
|
||||
_currentUpload.currentSize = 0;
|
||||
}
|
||||
_currentUpload.buf[_currentUpload.currentSize++] = b;
|
||||
}
|
||||
|
||||
uint8_t WebServer::_uploadReadByte(WiFiClient& client){
|
||||
int res = client.read();
|
||||
if(res == -1){
|
||||
while(!client.available() && client.connected())
|
||||
yield();
|
||||
res = client.read();
|
||||
}
|
||||
return (uint8_t)res;
|
||||
}
|
||||
|
||||
bool WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){
|
||||
(void) len;
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("Parse Form: Boundary: ");
|
||||
DEBUG_OUTPUT.print(boundary);
|
||||
DEBUG_OUTPUT.print(" Length: ");
|
||||
DEBUG_OUTPUT.println(len);
|
||||
#endif
|
||||
String line;
|
||||
int retry = 0;
|
||||
do {
|
||||
line = client.readStringUntil('\r');
|
||||
++retry;
|
||||
} while (line.length() == 0 && retry < 3);
|
||||
|
||||
client.readStringUntil('\n');
|
||||
//start reading the form
|
||||
if (line == ("--"+boundary)){
|
||||
RequestArgument* postArgs = new RequestArgument[32];
|
||||
int postArgsLen = 0;
|
||||
while(1){
|
||||
String argName;
|
||||
String argValue;
|
||||
String argType;
|
||||
String argFilename;
|
||||
bool argIsFile = false;
|
||||
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase("Content-Disposition")){
|
||||
int nameStart = line.indexOf('=');
|
||||
if (nameStart != -1){
|
||||
argName = line.substring(nameStart+2);
|
||||
nameStart = argName.indexOf('=');
|
||||
if (nameStart == -1){
|
||||
argName = argName.substring(0, argName.length() - 1);
|
||||
} else {
|
||||
argFilename = argName.substring(nameStart+2, argName.length() - 1);
|
||||
argName = argName.substring(0, argName.indexOf('"'));
|
||||
argIsFile = true;
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("PostArg FileName: ");
|
||||
DEBUG_OUTPUT.println(argFilename);
|
||||
#endif
|
||||
//use GET to set the filename if uploading using blob
|
||||
if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename");
|
||||
}
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("PostArg Name: ");
|
||||
DEBUG_OUTPUT.println(argName);
|
||||
#endif
|
||||
argType = "text/plain";
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase("Content-Type")){
|
||||
argType = line.substring(line.indexOf(':')+2);
|
||||
//skip next line
|
||||
client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
}
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("PostArg Type: ");
|
||||
DEBUG_OUTPUT.println(argType);
|
||||
#endif
|
||||
if (!argIsFile){
|
||||
while(1){
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.startsWith("--"+boundary)) break;
|
||||
if (argValue.length() > 0) argValue += "\n";
|
||||
argValue += line;
|
||||
}
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("PostArg Value: ");
|
||||
DEBUG_OUTPUT.println(argValue);
|
||||
DEBUG_OUTPUT.println();
|
||||
#endif
|
||||
|
||||
RequestArgument& arg = postArgs[postArgsLen++];
|
||||
arg.key = argName;
|
||||
arg.value = argValue;
|
||||
|
||||
if (line == ("--"+boundary+"--")){
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.println("Done Parsing POST");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
_currentUpload.status = UPLOAD_FILE_START;
|
||||
_currentUpload.name = argName;
|
||||
_currentUpload.filename = argFilename;
|
||||
_currentUpload.type = argType;
|
||||
_currentUpload.totalSize = 0;
|
||||
_currentUpload.currentSize = 0;
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("Start File: ");
|
||||
DEBUG_OUTPUT.print(_currentUpload.filename);
|
||||
DEBUG_OUTPUT.print(" Type: ");
|
||||
DEBUG_OUTPUT.println(_currentUpload.type);
|
||||
#endif
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, _currentUpload);
|
||||
_currentUpload.status = UPLOAD_FILE_WRITE;
|
||||
uint8_t argByte = _uploadReadByte(client);
|
||||
readfile:
|
||||
while(argByte != 0x0D){
|
||||
if (!client.connected()) return _parseFormUploadAborted();
|
||||
_uploadWriteByte(argByte);
|
||||
argByte = _uploadReadByte(client);
|
||||
}
|
||||
|
||||
argByte = _uploadReadByte(client);
|
||||
if (!client.connected()) return _parseFormUploadAborted();
|
||||
if (argByte == 0x0A){
|
||||
argByte = _uploadReadByte(client);
|
||||
if (!client.connected()) return _parseFormUploadAborted();
|
||||
if ((char)argByte != '-'){
|
||||
//continue reading the file
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
goto readfile;
|
||||
} else {
|
||||
argByte = _uploadReadByte(client);
|
||||
if (!client.connected()) return _parseFormUploadAborted();
|
||||
if ((char)argByte != '-'){
|
||||
//continue reading the file
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
goto readfile;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t endBuf[boundary.length()];
|
||||
client.readBytes(endBuf, boundary.length());
|
||||
|
||||
if (strstr((const char*)endBuf, boundary.c_str()) != NULL){
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, _currentUpload);
|
||||
_currentUpload.totalSize += _currentUpload.currentSize;
|
||||
_currentUpload.status = UPLOAD_FILE_END;
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, _currentUpload);
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("End File: ");
|
||||
DEBUG_OUTPUT.print(_currentUpload.filename);
|
||||
DEBUG_OUTPUT.print(" Type: ");
|
||||
DEBUG_OUTPUT.print(_currentUpload.type);
|
||||
DEBUG_OUTPUT.print(" Size: ");
|
||||
DEBUG_OUTPUT.println(_currentUpload.totalSize);
|
||||
#endif
|
||||
line = client.readStringUntil(0x0D);
|
||||
client.readStringUntil(0x0A);
|
||||
if (line == "--"){
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.println("Done Parsing POST");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
uint32_t i = 0;
|
||||
while(i < boundary.length()){
|
||||
_uploadWriteByte(endBuf[i++]);
|
||||
}
|
||||
argByte = _uploadReadByte(client);
|
||||
goto readfile;
|
||||
}
|
||||
} else {
|
||||
_uploadWriteByte(0x0D);
|
||||
goto readfile;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int iarg;
|
||||
int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount;
|
||||
for (iarg = 0; iarg < totalArgs; iarg++){
|
||||
RequestArgument& arg = postArgs[postArgsLen++];
|
||||
arg.key = _currentArgs[iarg].key;
|
||||
arg.value = _currentArgs[iarg].value;
|
||||
}
|
||||
if (_currentArgs) delete[] _currentArgs;
|
||||
_currentArgs = new RequestArgument[postArgsLen];
|
||||
for (iarg = 0; iarg < postArgsLen; iarg++){
|
||||
RequestArgument& arg = _currentArgs[iarg];
|
||||
arg.key = postArgs[iarg].key;
|
||||
arg.value = postArgs[iarg].value;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
if (postArgs) delete[] postArgs;
|
||||
return true;
|
||||
}
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.print("Error: line: ");
|
||||
DEBUG_OUTPUT.println(line);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::urlDecode(const String& text)
|
||||
{
|
||||
String decoded = "";
|
||||
char temp[] = "0x00";
|
||||
unsigned int len = text.length();
|
||||
unsigned int i = 0;
|
||||
while (i < len)
|
||||
{
|
||||
char decodedChar;
|
||||
char encodedChar = text.charAt(i++);
|
||||
if ((encodedChar == '%') && (i + 1 < len))
|
||||
{
|
||||
temp[2] = text.charAt(i++);
|
||||
temp[3] = text.charAt(i++);
|
||||
|
||||
decodedChar = strtol(temp, NULL, 16);
|
||||
}
|
||||
else {
|
||||
if (encodedChar == '+')
|
||||
{
|
||||
decodedChar = ' ';
|
||||
}
|
||||
else {
|
||||
decodedChar = encodedChar; // normal ascii char
|
||||
}
|
||||
}
|
||||
decoded += decodedChar;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
bool WebServer::_parseFormUploadAborted(){
|
||||
_currentUpload.status = UPLOAD_FILE_ABORTED;
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, _currentUpload);
|
||||
return false;
|
||||
}
|
||||
530
libraries/WebServer/src/WebServer.cpp
Normal file
530
libraries/WebServer/src/WebServer.cpp
Normal file
@@ -0,0 +1,530 @@
|
||||
/*
|
||||
WebServer.cpp - 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)
|
||||
*/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <libb64/cencode.h>
|
||||
#include "WiFiServer.h"
|
||||
#include "WiFiClient.h"
|
||||
#include "WebServer.h"
|
||||
#include "FS.h"
|
||||
#include "detail/RequestHandlersImpl.h"
|
||||
|
||||
//#define DEBUG_ESP_HTTP_SERVER
|
||||
#ifdef DEBUG_ESP_PORT
|
||||
#define DEBUG_OUTPUT DEBUG_ESP_PORT
|
||||
#else
|
||||
#define DEBUG_OUTPUT Serial
|
||||
#endif
|
||||
|
||||
const char * AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
WebServer::WebServer(IPAddress addr, int port)
|
||||
: _server(addr, port)
|
||||
, _currentMethod(HTTP_ANY)
|
||||
, _currentVersion(0)
|
||||
, _currentStatus(HC_NONE)
|
||||
, _statusChange(0)
|
||||
, _currentHandler(0)
|
||||
, _firstHandler(0)
|
||||
, _lastHandler(0)
|
||||
, _currentArgCount(0)
|
||||
, _currentArgs(0)
|
||||
, _headerKeysCount(0)
|
||||
, _currentHeaders(0)
|
||||
, _contentLength(0)
|
||||
, _chunked(false)
|
||||
{
|
||||
}
|
||||
|
||||
WebServer::WebServer(int port)
|
||||
: _server(port)
|
||||
, _currentMethod(HTTP_ANY)
|
||||
, _currentVersion(0)
|
||||
, _currentStatus(HC_NONE)
|
||||
, _statusChange(0)
|
||||
, _currentHandler(0)
|
||||
, _firstHandler(0)
|
||||
, _lastHandler(0)
|
||||
, _currentArgCount(0)
|
||||
, _currentArgs(0)
|
||||
, _headerKeysCount(0)
|
||||
, _currentHeaders(0)
|
||||
, _contentLength(0)
|
||||
, _chunked(false)
|
||||
{
|
||||
}
|
||||
|
||||
WebServer::~WebServer() {
|
||||
if (_currentHeaders)
|
||||
delete[]_currentHeaders;
|
||||
_headerKeysCount = 0;
|
||||
RequestHandler* handler = _firstHandler;
|
||||
while (handler) {
|
||||
RequestHandler* next = handler->next();
|
||||
delete handler;
|
||||
handler = next;
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
void WebServer::begin() {
|
||||
_currentStatus = HC_NONE;
|
||||
_server.begin();
|
||||
if(!_headerKeysCount)
|
||||
collectHeaders(0, 0);
|
||||
}
|
||||
|
||||
bool WebServer::authenticate(const char * username, const char * password){
|
||||
if(hasHeader(AUTHORIZATION_HEADER)){
|
||||
String authReq = header(AUTHORIZATION_HEADER);
|
||||
if(authReq.startsWith("Basic")){
|
||||
authReq = authReq.substring(6);
|
||||
authReq.trim();
|
||||
char toencodeLen = strlen(username)+strlen(password)+1;
|
||||
char *toencode = new char[toencodeLen + 1];
|
||||
if(toencode == NULL){
|
||||
authReq = String();
|
||||
return false;
|
||||
}
|
||||
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
||||
if(encoded == NULL){
|
||||
authReq = String();
|
||||
delete[] toencode;
|
||||
return false;
|
||||
}
|
||||
sprintf(toencode, "%s:%s", username, password);
|
||||
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){
|
||||
authReq = String();
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
return true;
|
||||
}
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
}
|
||||
authReq = String();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebServer::requestAuthentication(){
|
||||
sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
|
||||
send(401);
|
||||
}
|
||||
|
||||
void WebServer::on(const String &uri, WebServer::THandlerFunction handler) {
|
||||
on(uri, HTTP_ANY, handler);
|
||||
}
|
||||
|
||||
void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
|
||||
on(uri, method, fn, _fileUploadHandler);
|
||||
}
|
||||
|
||||
void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
|
||||
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
|
||||
}
|
||||
|
||||
void WebServer::addHandler(RequestHandler* handler) {
|
||||
_addRequestHandler(handler);
|
||||
}
|
||||
|
||||
void WebServer::_addRequestHandler(RequestHandler* handler) {
|
||||
if (!_lastHandler) {
|
||||
_firstHandler = handler;
|
||||
_lastHandler = handler;
|
||||
}
|
||||
else {
|
||||
_lastHandler->next(handler);
|
||||
_lastHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
|
||||
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
|
||||
}
|
||||
|
||||
void WebServer::handleClient() {
|
||||
if (_currentStatus == HC_NONE) {
|
||||
WiFiClient client = _server.available();
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.println("New client");
|
||||
#endif
|
||||
|
||||
_currentClient = client;
|
||||
_currentStatus = HC_WAIT_READ;
|
||||
_statusChange = millis();
|
||||
}
|
||||
|
||||
if (!_currentClient.connected()) {
|
||||
_currentClient = WiFiClient();
|
||||
_currentStatus = HC_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for data from client to become available
|
||||
if (_currentStatus == HC_WAIT_READ) {
|
||||
if (!_currentClient.available()) {
|
||||
if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) {
|
||||
_currentClient = WiFiClient();
|
||||
_currentStatus = HC_NONE;
|
||||
}
|
||||
yield();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_parseRequest(_currentClient)) {
|
||||
_currentClient = WiFiClient();
|
||||
_currentStatus = HC_NONE;
|
||||
return;
|
||||
}
|
||||
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT);
|
||||
_contentLength = CONTENT_LENGTH_NOT_SET;
|
||||
_handleRequest();
|
||||
|
||||
if (!_currentClient.connected()) {
|
||||
_currentClient = WiFiClient();
|
||||
_currentStatus = HC_NONE;
|
||||
return;
|
||||
} else {
|
||||
_currentStatus = HC_WAIT_CLOSE;
|
||||
_statusChange = millis();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentStatus == HC_WAIT_CLOSE) {
|
||||
if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) {
|
||||
_currentClient = WiFiClient();
|
||||
_currentStatus = HC_NONE;
|
||||
} else {
|
||||
yield();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::close() {
|
||||
#ifdef ESP8266
|
||||
_server.stop();
|
||||
#else
|
||||
// TODO add ESP32 WiFiServer::stop()
|
||||
_server.end();
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebServer::stop() {
|
||||
close();
|
||||
}
|
||||
|
||||
void WebServer::sendHeader(const String& name, const String& value, bool first) {
|
||||
String headerLine = name;
|
||||
headerLine += ": ";
|
||||
headerLine += value;
|
||||
headerLine += "\r\n";
|
||||
|
||||
if (first) {
|
||||
_responseHeaders = headerLine + _responseHeaders;
|
||||
}
|
||||
else {
|
||||
_responseHeaders += headerLine;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::setContentLength(size_t contentLength) {
|
||||
_contentLength = contentLength;
|
||||
}
|
||||
|
||||
void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
|
||||
response = "HTTP/1."+String(_currentVersion)+" ";
|
||||
response += String(code);
|
||||
response += " ";
|
||||
response += _responseCodeToString(code);
|
||||
response += "\r\n";
|
||||
|
||||
if (!content_type)
|
||||
content_type = "text/html";
|
||||
|
||||
sendHeader("Content-Type", content_type, true);
|
||||
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
|
||||
sendHeader("Content-Length", String(contentLength));
|
||||
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
|
||||
sendHeader("Content-Length", String(_contentLength));
|
||||
} else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client
|
||||
//let's do chunked
|
||||
_chunked = true;
|
||||
sendHeader("Accept-Ranges","none");
|
||||
sendHeader("Transfer-Encoding","chunked");
|
||||
}
|
||||
sendHeader("Connection", "close");
|
||||
|
||||
response += _responseHeaders;
|
||||
response += "\r\n";
|
||||
_responseHeaders = String();
|
||||
}
|
||||
|
||||
void WebServer::send(int code, const char* content_type, const String& content) {
|
||||
String header;
|
||||
// Can we asume the following?
|
||||
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
|
||||
// _contentLength = CONTENT_LENGTH_UNKNOWN;
|
||||
_prepareHeader(header, code, content_type, content.length());
|
||||
_currentClient.write(header.c_str(), header.length());
|
||||
if(content.length())
|
||||
sendContent(content);
|
||||
}
|
||||
|
||||
void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
|
||||
size_t contentLength = 0;
|
||||
|
||||
if (content != NULL) {
|
||||
contentLength = strlen_P(content);
|
||||
}
|
||||
|
||||
String header;
|
||||
char type[64];
|
||||
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||
_prepareHeader(header, code, (const char* )type, contentLength);
|
||||
_currentClient.write(header.c_str(), header.length());
|
||||
sendContent_P(content);
|
||||
}
|
||||
|
||||
void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
|
||||
String header;
|
||||
char type[64];
|
||||
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||
_prepareHeader(header, code, (const char* )type, contentLength);
|
||||
sendContent(header);
|
||||
sendContent_P(content, contentLength);
|
||||
}
|
||||
|
||||
void WebServer::send(int code, char* content_type, const String& content) {
|
||||
send(code, (const char*)content_type, content);
|
||||
}
|
||||
|
||||
void WebServer::send(int code, const String& content_type, const String& content) {
|
||||
send(code, (const char*)content_type.c_str(), content);
|
||||
}
|
||||
|
||||
void WebServer::sendContent(const String& content) {
|
||||
const char * footer = "\r\n";
|
||||
size_t len = content.length();
|
||||
if(_chunked) {
|
||||
char * chunkSize = (char *)malloc(11);
|
||||
if(chunkSize){
|
||||
sprintf(chunkSize, "%x%s", len, footer);
|
||||
_currentClient.write(chunkSize, strlen(chunkSize));
|
||||
free(chunkSize);
|
||||
}
|
||||
}
|
||||
_currentClient.write(content.c_str(), len);
|
||||
if(_chunked){
|
||||
_currentClient.write(footer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::sendContent_P(PGM_P content) {
|
||||
sendContent_P(content, strlen_P(content));
|
||||
}
|
||||
|
||||
void WebServer::sendContent_P(PGM_P content, size_t size) {
|
||||
const char * footer = "\r\n";
|
||||
if(_chunked) {
|
||||
char * chunkSize = (char *)malloc(11);
|
||||
if(chunkSize){
|
||||
sprintf(chunkSize, "%x%s", size, footer);
|
||||
_currentClient.write(chunkSize, strlen(chunkSize));
|
||||
free(chunkSize);
|
||||
}
|
||||
}
|
||||
_currentClient.write_P(content, size);
|
||||
if(_chunked){
|
||||
_currentClient.write(footer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String WebServer::arg(String name) {
|
||||
for (int i = 0; i < _currentArgCount; ++i) {
|
||||
if ( _currentArgs[i].key == name )
|
||||
return _currentArgs[i].value;
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
String WebServer::arg(int i) {
|
||||
if (i < _currentArgCount)
|
||||
return _currentArgs[i].value;
|
||||
return String();
|
||||
}
|
||||
|
||||
String WebServer::argName(int i) {
|
||||
if (i < _currentArgCount)
|
||||
return _currentArgs[i].key;
|
||||
return String();
|
||||
}
|
||||
|
||||
int WebServer::args() {
|
||||
return _currentArgCount;
|
||||
}
|
||||
|
||||
bool WebServer::hasArg(String name) {
|
||||
for (int i = 0; i < _currentArgCount; ++i) {
|
||||
if (_currentArgs[i].key == name)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
String WebServer::header(String name) {
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
if (_currentHeaders[i].key.equalsIgnoreCase(name))
|
||||
return _currentHeaders[i].value;
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
void WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
|
||||
_headerKeysCount = headerKeysCount + 1;
|
||||
if (_currentHeaders)
|
||||
delete[]_currentHeaders;
|
||||
_currentHeaders = new RequestArgument[_headerKeysCount];
|
||||
_currentHeaders[0].key = AUTHORIZATION_HEADER;
|
||||
for (int i = 1; i < _headerKeysCount; i++){
|
||||
_currentHeaders[i].key = headerKeys[i-1];
|
||||
}
|
||||
}
|
||||
|
||||
String WebServer::header(int i) {
|
||||
if (i < _headerKeysCount)
|
||||
return _currentHeaders[i].value;
|
||||
return String();
|
||||
}
|
||||
|
||||
String WebServer::headerName(int i) {
|
||||
if (i < _headerKeysCount)
|
||||
return _currentHeaders[i].key;
|
||||
return String();
|
||||
}
|
||||
|
||||
int WebServer::headers() {
|
||||
return _headerKeysCount;
|
||||
}
|
||||
|
||||
bool WebServer::hasHeader(String name) {
|
||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::hostHeader() {
|
||||
return _hostHeader;
|
||||
}
|
||||
|
||||
void WebServer::onFileUpload(THandlerFunction fn) {
|
||||
_fileUploadHandler = fn;
|
||||
}
|
||||
|
||||
void WebServer::onNotFound(THandlerFunction fn) {
|
||||
_notFoundHandler = fn;
|
||||
}
|
||||
|
||||
void WebServer::_handleRequest() {
|
||||
bool handled = false;
|
||||
if (!_currentHandler){
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.println("request handler not found");
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
if (!handled) {
|
||||
DEBUG_OUTPUT.println("request handler failed to handle request");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
if(_notFoundHandler) {
|
||||
_notFoundHandler();
|
||||
}
|
||||
else {
|
||||
send(404, "text/plain", String("Not found: ") + _currentUri);
|
||||
}
|
||||
}
|
||||
|
||||
_currentUri = String();
|
||||
}
|
||||
|
||||
String WebServer::_responseCodeToString(int code) {
|
||||
switch (code) {
|
||||
case 100: return F("Continue");
|
||||
case 101: return F("Switching Protocols");
|
||||
case 200: return F("OK");
|
||||
case 201: return F("Created");
|
||||
case 202: return F("Accepted");
|
||||
case 203: return F("Non-Authoritative Information");
|
||||
case 204: return F("No Content");
|
||||
case 205: return F("Reset Content");
|
||||
case 206: return F("Partial Content");
|
||||
case 300: return F("Multiple Choices");
|
||||
case 301: return F("Moved Permanently");
|
||||
case 302: return F("Found");
|
||||
case 303: return F("See Other");
|
||||
case 304: return F("Not Modified");
|
||||
case 305: return F("Use Proxy");
|
||||
case 307: return F("Temporary Redirect");
|
||||
case 400: return F("Bad Request");
|
||||
case 401: return F("Unauthorized");
|
||||
case 402: return F("Payment Required");
|
||||
case 403: return F("Forbidden");
|
||||
case 404: return F("Not Found");
|
||||
case 405: return F("Method Not Allowed");
|
||||
case 406: return F("Not Acceptable");
|
||||
case 407: return F("Proxy Authentication Required");
|
||||
case 408: return F("Request Time-out");
|
||||
case 409: return F("Conflict");
|
||||
case 410: return F("Gone");
|
||||
case 411: return F("Length Required");
|
||||
case 412: return F("Precondition Failed");
|
||||
case 413: return F("Request Entity Too Large");
|
||||
case 414: return F("Request-URI Too Large");
|
||||
case 415: return F("Unsupported Media Type");
|
||||
case 416: return F("Requested range not satisfiable");
|
||||
case 417: return F("Expectation Failed");
|
||||
case 500: return F("Internal Server Error");
|
||||
case 501: return F("Not Implemented");
|
||||
case 502: return F("Bad Gateway");
|
||||
case 503: return F("Service Unavailable");
|
||||
case 504: return F("Gateway Time-out");
|
||||
case 505: return F("HTTP Version not supported");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
236
libraries/WebServer/src/WebServer.h
Normal file
236
libraries/WebServer/src/WebServer.h
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
WebServer.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 WEBSERVER_H
|
||||
#define WEBSERVER_H
|
||||
|
||||
#include <functional>
|
||||
#ifdef ESP8266
|
||||
#define WebServer ESP8266WebServer
|
||||
#include <ESP8266WiFi.h>
|
||||
#else
|
||||
#include <WiFi.h>
|
||||
#define write_P write
|
||||
#endif
|
||||
|
||||
enum HTTPMethod { HTTP_ANY, HTTP_GET, 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 };
|
||||
|
||||
#define HTTP_DOWNLOAD_UNIT_SIZE 1460
|
||||
|
||||
#ifndef HTTP_UPLOAD_BUFLEN
|
||||
#define HTTP_UPLOAD_BUFLEN 2048
|
||||
#endif
|
||||
|
||||
#define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request
|
||||
#define HTTP_MAX_POST_WAIT 1000 //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)
|
||||
|
||||
class WebServer;
|
||||
|
||||
typedef struct {
|
||||
HTTPUploadStatus status;
|
||||
String filename;
|
||||
String name;
|
||||
String type;
|
||||
size_t totalSize; // file size
|
||||
size_t currentSize; // size of data currently in buf
|
||||
uint8_t buf[HTTP_UPLOAD_BUFLEN];
|
||||
} HTTPUpload;
|
||||
|
||||
#include "detail/RequestHandler.h"
|
||||
|
||||
namespace fs {
|
||||
class FS;
|
||||
}
|
||||
|
||||
class WebServer
|
||||
{
|
||||
public:
|
||||
WebServer(IPAddress addr, int port = 80);
|
||||
WebServer(int port = 80);
|
||||
~WebServer();
|
||||
|
||||
void begin();
|
||||
void handleClient();
|
||||
|
||||
void close();
|
||||
void stop();
|
||||
|
||||
bool authenticate(const char * username, const char * password);
|
||||
void requestAuthentication();
|
||||
|
||||
typedef std::function<void(void)> THandlerFunction;
|
||||
void on(const String &uri, THandlerFunction handler);
|
||||
void on(const String &uri, HTTPMethod method, THandlerFunction fn);
|
||||
void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
|
||||
void addHandler(RequestHandler* 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
|
||||
|
||||
String uri() { return _currentUri; }
|
||||
HTTPMethod method() { return _currentMethod; }
|
||||
WiFiClient client() { return _currentClient; }
|
||||
HTTPUpload& upload() { return _currentUpload; }
|
||||
|
||||
String arg(String name); // get request argument value by name
|
||||
String arg(int i); // get request argument value by number
|
||||
String argName(int i); // get request argument name by number
|
||||
int args(); // get arguments count
|
||||
bool hasArg(String name); // check if argument exists
|
||||
void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
|
||||
String header(String name); // get request header value by name
|
||||
String header(int i); // get request header value by number
|
||||
String headerName(int i); // get request header name by number
|
||||
int headers(); // get header count
|
||||
bool hasHeader(String name); // check if header exists
|
||||
|
||||
String hostHeader(); // 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 = String(""));
|
||||
void send(int code, char* content_type, const String& content);
|
||||
void send(int code, const String& content_type, const String& content);
|
||||
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 setContentLength(size_t contentLength);
|
||||
void sendHeader(const String& name, const String& value, bool first = false);
|
||||
void sendContent(const String& content);
|
||||
void sendContent_P(PGM_P content);
|
||||
void sendContent_P(PGM_P content, size_t size);
|
||||
|
||||
static String urlDecode(const String& text);
|
||||
|
||||
#ifdef ESP8266
|
||||
template<typename T> size_t streamFile(T &file, const String& contentType){
|
||||
setContentLength(file.size());
|
||||
if (String(file.name()).endsWith(".gz") &&
|
||||
contentType != "application/x-gzip" &&
|
||||
contentType != "application/octet-stream"){
|
||||
sendHeader("Content-Encoding", "gzip");
|
||||
}
|
||||
send(200, contentType, "");
|
||||
return _currentClient.write(file);
|
||||
}
|
||||
#else
|
||||
template<typename T> size_t streamFile(T &file, const String& contentType){
|
||||
#define STREAMFILE_BUFSIZE 2*1460
|
||||
setContentLength(file.size());
|
||||
if (String(file.name()).endsWith(".gz") &&
|
||||
contentType != "application/x-gzip" &&
|
||||
contentType != "application/octet-stream") {
|
||||
sendHeader("Content-Encoding", "gzip");
|
||||
}
|
||||
send(200, contentType, "");
|
||||
uint8_t *buf = (uint8_t *)malloc(STREAMFILE_BUFSIZE);
|
||||
if (buf == NULL) {
|
||||
//DBG_OUTPUT_PORT.printf("streamFile malloc failed");
|
||||
return 0;
|
||||
}
|
||||
size_t totalBytesOut = 0;
|
||||
while (client().connected() && (file.available() > 0)) {
|
||||
int bytesOut;
|
||||
int bytesIn = file.read(buf, STREAMFILE_BUFSIZE);
|
||||
if (bytesIn <= 0) break;
|
||||
while (1) {
|
||||
bytesOut = 0;
|
||||
if (!client().connected()) break;
|
||||
bytesOut = client().write(buf, bytesIn);
|
||||
if (bytesIn == bytesOut) break;
|
||||
|
||||
//DBG_OUTPUT_PORT.printf("bytesIn %d != bytesOut %d\r\n",
|
||||
//bytesIn, bytesOut);
|
||||
delay(1);
|
||||
}
|
||||
totalBytesOut += bytesOut;
|
||||
yield();
|
||||
}
|
||||
if (totalBytesOut != file.size()) {
|
||||
//DBG_OUTPUT_PORT.printf("file size %d bytes out %d\r\n",
|
||||
// file.size(), totalBytesOut);
|
||||
}
|
||||
free(buf);
|
||||
return totalBytesOut;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void _addRequestHandler(RequestHandler* handler);
|
||||
void _handleRequest();
|
||||
bool _parseRequest(WiFiClient& client);
|
||||
void _parseArguments(String data);
|
||||
static String _responseCodeToString(int code);
|
||||
bool _parseForm(WiFiClient& client, String boundary, uint32_t len);
|
||||
bool _parseFormUploadAborted();
|
||||
void _uploadWriteByte(uint8_t b);
|
||||
uint8_t _uploadReadByte(WiFiClient& client);
|
||||
void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
|
||||
bool _collectHeader(const char* headerName, const char* headerValue);
|
||||
|
||||
struct RequestArgument {
|
||||
String key;
|
||||
String value;
|
||||
};
|
||||
|
||||
WiFiServer _server;
|
||||
|
||||
WiFiClient _currentClient;
|
||||
HTTPMethod _currentMethod;
|
||||
String _currentUri;
|
||||
uint8_t _currentVersion;
|
||||
HTTPClientStatus _currentStatus;
|
||||
unsigned long _statusChange;
|
||||
|
||||
RequestHandler* _currentHandler;
|
||||
RequestHandler* _firstHandler;
|
||||
RequestHandler* _lastHandler;
|
||||
THandlerFunction _notFoundHandler;
|
||||
THandlerFunction _fileUploadHandler;
|
||||
|
||||
int _currentArgCount;
|
||||
RequestArgument* _currentArgs;
|
||||
HTTPUpload _currentUpload;
|
||||
|
||||
int _headerKeysCount;
|
||||
RequestArgument* _currentHeaders;
|
||||
size_t _contentLength;
|
||||
String _responseHeaders;
|
||||
|
||||
String _hostHeader;
|
||||
bool _chunked;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //WEBSERVER_H
|
||||
19
libraries/WebServer/src/detail/RequestHandler.h
Normal file
19
libraries/WebServer/src/detail/RequestHandler.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef REQUESTHANDLER_H
|
||||
#define REQUESTHANDLER_H
|
||||
|
||||
class RequestHandler {
|
||||
public:
|
||||
virtual ~RequestHandler() { }
|
||||
virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; }
|
||||
virtual bool canUpload(String uri) { (void) uri; return false; }
|
||||
virtual bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; }
|
||||
virtual void upload(WebServer& server, String requestUri, HTTPUpload& upload) { (void) server; (void) requestUri; (void) upload; }
|
||||
|
||||
RequestHandler* next() { return _next; }
|
||||
void next(RequestHandler* r) { _next = r; }
|
||||
|
||||
private:
|
||||
RequestHandler* _next = nullptr;
|
||||
};
|
||||
|
||||
#endif //REQUESTHANDLER_H
|
||||
191
libraries/WebServer/src/detail/RequestHandlersImpl.h
Normal file
191
libraries/WebServer/src/detail/RequestHandlersImpl.h
Normal file
@@ -0,0 +1,191 @@
|
||||
#ifndef REQUESTHANDLERSIMPL_H
|
||||
#define REQUESTHANDLERSIMPL_H
|
||||
|
||||
#include "RequestHandler.h"
|
||||
|
||||
#ifdef ESP8266
|
||||
// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules
|
||||
static const struct {const char endsWith[16]; const char mimeType[32];} mimeTable[] ICACHE_RODATA_ATTR = {
|
||||
#else
|
||||
static const struct {const char endsWith[16]; const char mimeType[32];} mimeTable[] = {
|
||||
#endif
|
||||
{ ".html", "text/html" },
|
||||
{ ".htm", "text/html" },
|
||||
{ ".css", "text/css" },
|
||||
{ ".txt", "text/plain" },
|
||||
{ ".js", "application/javascript" },
|
||||
{ ".json", "application/json" },
|
||||
{ ".png", "image/png" },
|
||||
{ ".gif", "image/gif" },
|
||||
{ ".jpg", "image/jpeg" },
|
||||
{ ".ico", "image/x-icon" },
|
||||
{ ".svg", "image/svg+xml" },
|
||||
{ ".ttf", "application/x-font-ttf" },
|
||||
{ ".otf", "application/x-font-opentype" },
|
||||
{ ".woff", "application/font-woff" },
|
||||
{ ".woff2", "application/font-woff2" },
|
||||
{ ".eot", "application/vnd.ms-fontobject" },
|
||||
{ ".sfnt", "application/font-sfnt" },
|
||||
{ ".xml", "text/xml" },
|
||||
{ ".pdf", "application/pdf" },
|
||||
{ ".zip", "application/zip" },
|
||||
{ ".gz", "application/x-gzip" },
|
||||
{ ".appcache", "text/cache-manifest" },
|
||||
{ "", "application/octet-stream" } };
|
||||
|
||||
class FunctionRequestHandler : public RequestHandler {
|
||||
public:
|
||||
FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const String &uri, HTTPMethod method)
|
||||
: _fn(fn)
|
||||
, _ufn(ufn)
|
||||
, _uri(uri)
|
||||
, _method(method)
|
||||
{
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||
if (_method != HTTP_ANY && _method != requestMethod)
|
||||
return false;
|
||||
|
||||
if (requestUri != _uri)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canUpload(String requestUri) override {
|
||||
if (!_ufn || !canHandle(HTTP_POST, requestUri))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override {
|
||||
(void) server;
|
||||
if (!canHandle(requestMethod, requestUri))
|
||||
return false;
|
||||
|
||||
_fn();
|
||||
return true;
|
||||
}
|
||||
|
||||
void upload(WebServer& server, String requestUri, HTTPUpload& upload) override {
|
||||
(void) server;
|
||||
(void) upload;
|
||||
if (canUpload(requestUri))
|
||||
_ufn();
|
||||
}
|
||||
|
||||
protected:
|
||||
WebServer::THandlerFunction _fn;
|
||||
WebServer::THandlerFunction _ufn;
|
||||
String _uri;
|
||||
HTTPMethod _method;
|
||||
};
|
||||
|
||||
class StaticRequestHandler : public RequestHandler {
|
||||
public:
|
||||
StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
|
||||
: _fs(fs)
|
||||
, _uri(uri)
|
||||
, _path(path)
|
||||
, _cache_header(cache_header)
|
||||
{
|
||||
_isFile = fs.exists(path);
|
||||
#ifdef ESP8266
|
||||
DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
|
||||
#else
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.printf("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
|
||||
#endif
|
||||
#endif
|
||||
_baseUriLength = _uri.length();
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||
if (requestMethod != HTTP_GET)
|
||||
return false;
|
||||
|
||||
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override {
|
||||
if (!canHandle(requestMethod, requestUri))
|
||||
return false;
|
||||
|
||||
#ifdef ESP8266
|
||||
DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
||||
#else
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.printf("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
String path(_path);
|
||||
|
||||
if (!_isFile) {
|
||||
// Base URI doesn't point to a file.
|
||||
// If a directory is requested, look for index file.
|
||||
if (requestUri.endsWith("/")) requestUri += "index.htm";
|
||||
|
||||
// Append whatever follows this URI in request to get the file path.
|
||||
path += requestUri.substring(_baseUriLength);
|
||||
}
|
||||
#ifdef ESP8266
|
||||
DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
||||
#else
|
||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
||||
DEBUG_OUTPUT.printf("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
String contentType = getContentType(path);
|
||||
|
||||
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
|
||||
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
|
||||
if (!path.endsWith(".gz") && !_fs.exists(path)) {
|
||||
String pathWithGz = path + ".gz";
|
||||
if(_fs.exists(pathWithGz))
|
||||
path += ".gz";
|
||||
}
|
||||
|
||||
File f = _fs.open(path, "r");
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
if (_cache_header.length() != 0)
|
||||
server.sendHeader("Cache-Control", _cache_header);
|
||||
|
||||
server.streamFile(f, contentType);
|
||||
return true;
|
||||
}
|
||||
|
||||
static String getContentType(const String& path) {
|
||||
char buff[sizeof(mimeTable[0].mimeType)];
|
||||
// Check all entries but last one for match, return if found
|
||||
for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) {
|
||||
strcpy_P(buff, mimeTable[i].endsWith);
|
||||
if (path.endsWith(buff)) {
|
||||
strcpy_P(buff, mimeTable[i].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
}
|
||||
// Fall-through and just return default type
|
||||
strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
|
||||
protected:
|
||||
FS _fs;
|
||||
String _uri;
|
||||
String _path;
|
||||
String _cache_header;
|
||||
bool _isFile;
|
||||
size_t _baseUriLength;
|
||||
};
|
||||
|
||||
|
||||
#endif //REQUESTHANDLERSIMPL_H
|
||||
Reference in New Issue
Block a user