1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-19 23:22:16 +03:00

Web server refactoring

This commit is contained in:
Ivan Grokhotkov 2015-05-14 13:33:12 +03:00
parent eb37830238
commit 36d0968ada
5 changed files with 574 additions and 507 deletions

View File

@ -34,7 +34,6 @@
#include <SPI.h> #include <SPI.h>
#include <SD.h> #include <SD.h>
#define WWW_BUF_SIZE 1460
#define DBG_OUTPUT_PORT Serial #define DBG_OUTPUT_PORT Serial
const char* ssid = "**********"; const char* ssid = "**********";
@ -47,28 +46,17 @@ ESP8266WebServer server(80);
static bool hasSD = false; static bool hasSD = false;
File uploadFile; File uploadFile;
void returnOK(){
WiFiClient client = server.client(); void returnOK() {
String message = "HTTP/1.1 200 OK\r\n"; server.sendHeader("Connection", "close");
message += "Content-Type: text/plain\r\n"; server.sendHeader("Access-Control-Allow-Origin", "*");
message += "Connection: close\r\n"; server.send(200, "text/plain", "");
message += "Access-Control-Allow-Origin: *\r\n";
message += "\r\n";
client.print(message);
client.stop();
} }
void returnFail(String msg){ void returnFail(String msg) {
WiFiClient client = server.client(); server.sendHeader("Connection", "close");
String message = "HTTP/1.1 500 Fail\r\n"; server.sendHeader("Access-Control-Allow-Origin", "*");
message += "Content-Type: text/plain\r\n"; server.send(500, "text/plain", msg + "\r\n");
message += "Connection: close\r\n";
message += "Access-Control-Allow-Origin: *\r\n";
message += "\r\n";
message += msg;
message += "\r\n";
client.print(message);
client.stop();
} }
bool loadFromSdCard(String path){ bool loadFromSdCard(String path){
@ -94,58 +82,39 @@ bool loadFromSdCard(String path){
dataFile = SD.open(path.c_str()); dataFile = SD.open(path.c_str());
} }
if (!dataFile)
return false;
if(server.hasArg("download")) dataType = "application/octet-stream"; if(server.hasArg("download")) dataType = "application/octet-stream";
if (dataFile) { server.sendHeader("Content-Length", String(dataFile.size()));
WiFiClient client = server.client(); server.sendHeader("Connection", "close");
String head = "HTTP/1.1 200 OK\r\nContent-Type: "; server.sendHeader("Access-Control-Allow-Origin", "*");
head += dataType; server.send(200, dataType.c_str(), "");
head += "\r\nContent-Length: ";
head += dataFile.size();
head += "\r\nConnection: close";
head += "\r\nAccess-Control-Allow-Origin: *";
head += "\r\n\r\n";
client.print(head);
dataType = String();
path = String();
uint8_t obuf[WWW_BUF_SIZE]; WiFiClient client = server.client();
size_t totalSize = dataFile.size();
while (dataFile.available() > WWW_BUF_SIZE){ if (client.write(dataFile, PAYLOAD_UNIT_SIZE) != totalSize) {
dataFile.read(obuf, WWW_BUF_SIZE); DBG_OUTPUT_PORT.println("Sent less data than expected!");
if(client.write(obuf, WWW_BUF_SIZE) != WWW_BUF_SIZE){
DBG_OUTPUT_PORT.println("Sent less data than expected!");
dataFile.close();
return true;
}
}
uint16_t leftLen = dataFile.available();
dataFile.read(obuf, leftLen);
if(client.write(obuf, leftLen) != leftLen){
DBG_OUTPUT_PORT.println("Sent less data than expected!");
dataFile.close();
return true;
}
dataFile.close();
client.stop();
return true;
} }
return false;
dataFile.close();
return true;
} }
void handleFileUpload(){ void handleFileUpload(){
if(server.uri() != "/edit") return; if(server.uri() != "/edit") return;
HTTPUpload upload = server.upload(); HTTPUpload& upload = server.upload();
if(upload.status == UPLOAD_FILE_START){ if(upload.status == UPLOAD_FILE_START){
if(SD.exists((char *)upload.filename.c_str())) SD.remove((char *)upload.filename.c_str()); if(SD.exists((char *)upload.filename.c_str())) SD.remove((char *)upload.filename.c_str());
uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE); uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE);
DBG_OUTPUT_PORT.print("Upload: START, filename: "); DBG_OUTPUT_PORT.println(upload.filename); DBG_OUTPUT_PORT.print("Upload: START, filename: "); DBG_OUTPUT_PORT.println(upload.filename);
} else if(upload.status == UPLOAD_FILE_WRITE){ } else if(upload.status == UPLOAD_FILE_WRITE){
if(uploadFile) uploadFile.write(upload.buf, upload.buflen); if(uploadFile) uploadFile.write(upload.buf, upload.currentSize);
DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: "); DBG_OUTPUT_PORT.println(upload.buflen); DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: "); DBG_OUTPUT_PORT.println(upload.currentSize);
} else if(upload.status == UPLOAD_FILE_END){ } else if(upload.status == UPLOAD_FILE_END){
if(uploadFile) uploadFile.close(); if(uploadFile) uploadFile.close();
DBG_OUTPUT_PORT.print("Upload: END, Size: "); DBG_OUTPUT_PORT.println(upload.size); DBG_OUTPUT_PORT.print("Upload: END, Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
} }
} }
@ -156,13 +125,12 @@ void deleteRecursive(String path){
SD.remove((char *)path.c_str()); SD.remove((char *)path.c_str());
return; return;
} }
file.rewindDirectory(); file.rewindDirectory();
File entry;
String entryPath;
while(true) { while(true) {
entry = file.openNextFile(); File entry = file.openNextFile();
if (!entry) break; if (!entry) break;
entryPath = path + "/" +entry.name(); String entryPath = path + "/" +entry.name();
if(entry.isDirectory()){ if(entry.isDirectory()){
entry.close(); entry.close();
deleteRecursive(entryPath); deleteRecursive(entryPath);
@ -170,27 +138,32 @@ void deleteRecursive(String path){
entry.close(); entry.close();
SD.remove((char *)entryPath.c_str()); SD.remove((char *)entryPath.c_str());
} }
entryPath = String();
yield(); yield();
} }
SD.rmdir((char *)path.c_str()); SD.rmdir((char *)path.c_str());
path = String();
file.close(); file.close();
} }
void handleDelete(){ void handleDelete(){
if(server.args() == 0) return returnFail("BAD ARGS"); if(server.args() == 0) return returnFail("BAD ARGS");
String path = server.arg(0); String path = server.arg(0);
if(path == "/" || !SD.exists((char *)path.c_str())) return returnFail("BAD PATH"); if(path == "/" || !SD.exists((char *)path.c_str())) {
returnFail("BAD PATH");
return;
}
deleteRecursive(path); deleteRecursive(path);
returnOK(); returnOK();
path = String();
} }
void handleCreate(){ void handleCreate(){
if(server.args() == 0) return returnFail("BAD ARGS"); if(server.args() == 0) return returnFail("BAD ARGS");
String path = server.arg(0); String path = server.arg(0);
if(path == "/" || SD.exists((char *)path.c_str())) return returnFail("BAD PATH"); if(path == "/" || SD.exists((char *)path.c_str())) {
returnFail("BAD PATH");
return;
}
if(path.indexOf('.') > 0){ if(path.indexOf('.') > 0){
File file = SD.open((char *)path.c_str(), FILE_WRITE); File file = SD.open((char *)path.c_str(), FILE_WRITE);
if(file){ if(file){
@ -201,7 +174,6 @@ void handleCreate(){
SD.mkdir((char *)path.c_str()); SD.mkdir((char *)path.c_str());
} }
returnOK(); returnOK();
path = String();
} }
void printDirectory() { void printDirectory() {
@ -216,31 +188,31 @@ void printDirectory() {
} }
dir.rewindDirectory(); dir.rewindDirectory();
File entry; server.send(200, "text/json", "");
WiFiClient client = server.client(); WiFiClient client = server.client();
client.print("HTTP/1.1 200 OK\r\nContent-Type: text/json\r\n\r\n");
String output = "["; for (int cnt = 0; true; ++cnt) {
while(true) { File entry = dir.openNextFile();
entry = dir.openNextFile(); if (!entry)
if (!entry) break; break;
if(output != "[") output += ',';
output += "{\"type\":\""; String output;
output += (entry.isDirectory())?"dir":"file"; if (cnt == 0)
output += "\",\"name\":\""; output = '[';
output += entry.name(); else
output += "\""; output = ',';
output += "}";
entry.close(); output += "{\"type\":\"";
if(output.length() > 1460){ output += (entry.isDirectory()) ? "dir" : "file";
client.write(output.substring(0, 1460).c_str(), 1460); output += "\",\"name\":\"";
output = output.substring(1460); output += entry.name();
} output += "\"";
output += "}";
server.sendContent(output);
entry.close();
} }
server.sendContent("]");
dir.close(); dir.close();
output += "]";
client.write(output.c_str(), output.length());
client.stop();
output = String();
} }
void handleNotFound(){ void handleNotFound(){
@ -280,14 +252,14 @@ void setup(void){
} }
DBG_OUTPUT_PORT.print("Connected! IP address: "); DBG_OUTPUT_PORT.print("Connected! IP address: ");
DBG_OUTPUT_PORT.println(WiFi.localIP()); DBG_OUTPUT_PORT.println(WiFi.localIP());
/*
if (mdns.begin(hostname, WiFi.localIP())) { if (mdns.begin(hostname, WiFi.localIP())) {
DBG_OUTPUT_PORT.println("MDNS responder started"); DBG_OUTPUT_PORT.println("MDNS responder started");
DBG_OUTPUT_PORT.print("You can now connect to http://"); DBG_OUTPUT_PORT.print("You can now connect to http://");
DBG_OUTPUT_PORT.print(hostname); DBG_OUTPUT_PORT.print(hostname);
DBG_OUTPUT_PORT.println(".local"); DBG_OUTPUT_PORT.println(".local");
} }
*/
server.on("/list", HTTP_GET, printDirectory); server.on("/list", HTTP_GET, printDirectory);
server.on("/edit", HTTP_DELETE, handleDelete); server.on("/edit", HTTP_DELETE, handleDelete);

View File

@ -26,8 +26,8 @@
#include "WiFiClient.h" #include "WiFiClient.h"
#include "ESP8266WebServer.h" #include "ESP8266WebServer.h"
//#define DEBUG // #define DEBUG
#define DEBUG_OUTPUT Serial1 #define DEBUG_OUTPUT Serial
struct ESP8266WebServer::RequestHandler { struct ESP8266WebServer::RequestHandler {
RequestHandler(ESP8266WebServer::THandlerFunction fn, const char* uri, HTTPMethod method) RequestHandler(ESP8266WebServer::THandlerFunction fn, const char* uri, HTTPMethod method)
@ -99,110 +99,32 @@ void ESP8266WebServer::handleClient()
#ifdef DEBUG #ifdef DEBUG
DEBUG_OUTPUT.println("New client"); DEBUG_OUTPUT.println("New client");
#endif #endif
// Wait for data from client to become available // Wait for data from client to become available
while(client.connected() && !client.available()){ while(client.connected() && !client.available()){
delay(1); delay(1);
} }
// Read the first line of HTTP request if (!_parseRequest(client)) {
String req = client.readStringUntil('\r');
client.readStringUntil('\n');
// 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
DEBUG_OUTPUT.print("Invalid request: ");
DEBUG_OUTPUT.println(req);
#endif
return; return;
} }
String methodStr = req.substring(0, addr_start); _currentClient = client;
String url = req.substring(addr_start + 1, addr_end); _handleRequest();
String searchStr = ""; }
int hasSearch = url.indexOf('?');
if(hasSearch != -1){ void ESP8266WebServer::sendHeader(String name, String value, bool first) {
searchStr = url.substring(hasSearch + 1); String headerLine = name;
url = url.substring(0, hasSearch); headerLine += ": ";
headerLine += value;
headerLine += "\r\n";
if (first) {
_responseHeaders = headerLine + _responseHeaders;
} }
_currentUri = url; else {
_responseHeaders += headerLine;
HTTPMethod method = HTTP_GET;
if (methodStr == "POST") {
method = HTTP_POST;
} else if (methodStr == "DELETE") {
method = HTTP_DELETE;
} else if (methodStr == "PUT") {
method = HTTP_PUT;
} else if (methodStr == "PATCH") {
method = HTTP_PATCH;
} }
#ifdef DEBUG
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
String formData;
//bellow 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;
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 + 2);
if(headerName == "Content-Type"){
if(headerValue.startsWith("text/plain")){
isForm = false;
} else if(headerValue.startsWith("multipart/form-data")){
boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
isForm = true;
}
} else if(headerName == "Content-Length"){
contentLength = headerValue.toInt();
}
}
if(!isForm){
if(searchStr != "") searchStr += '&';
searchStr += client.readStringUntil('\r');
client.readStringUntil('\n');
}
_parseArguments(searchStr);
if(isForm){
_parseForm(client, boundaryStr, contentLength);
}
} else {
_parseArguments(searchStr);
}
client.flush();
#ifdef DEBUG
DEBUG_OUTPUT.print("Request: ");
DEBUG_OUTPUT.println(url);
DEBUG_OUTPUT.print(" Arguments: ");
DEBUG_OUTPUT.println(searchStr);
#endif
_handleRequest(client, url, method);
} }
void ESP8266WebServer::send(int code, const char* content_type, String content) { void ESP8266WebServer::send(int code, const char* content_type, String content) {
@ -214,11 +136,28 @@ void ESP8266WebServer::send(int code, const char* content_type, String content)
if (!content_type) if (!content_type)
content_type = "text/html"; content_type = "text/html";
_appendHeader(response, "Content-Type", content_type); sendHeader("Content-Type", content_type, true);
response += _responseHeaders;
response += "\r\n"; response += "\r\n";
response += content; response += content;
_currentClient.print(response); _responseHeaders = String();
sendContent(response);
}
void ESP8266WebServer::sendContent(String content) {
size_t size_to_send = content.length();
size_t size_sent = 0;
while(size_to_send) {
const size_t unit_size = PAYLOAD_UNIT_SIZE;
size_t will_send = (size_to_send < unit_size) ? size_to_send : unit_size;
size_t sent = _currentClient.write(content.c_str() + size_sent, will_send);
size_to_send -= sent;
size_sent += sent;
if (sent == 0) {
break;
}
}
} }
String ESP8266WebServer::arg(const char* name) { String ESP8266WebServer::arg(const char* name) {
@ -253,298 +192,6 @@ bool ESP8266WebServer::hasArg(const char* name) {
return false; return false;
} }
void ESP8266WebServer::_parseArguments(String data) {
#ifdef DEBUG
DEBUG_OUTPUT.print("args: ");
DEBUG_OUTPUT.println(data);
#endif
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = 0;
if (data.length() == 0) {
_currentArgCount = 0;
return;
}
_currentArgCount = 1;
for (int i = 0; i < data.length(); ) {
i = data.indexOf('&', i);
if (i == -1)
break;
++i;
++_currentArgCount;
}
#ifdef DEBUG
DEBUG_OUTPUT.print("args count: ");
DEBUG_OUTPUT.println(_currentArgCount);
#endif
_currentArgs = new RequestArgument[_currentArgCount];
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
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
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 = data.substring(equal_sign_index + 1, next_arg_index);
#ifdef DEBUG
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
DEBUG_OUTPUT.print("args count: ");
DEBUG_OUTPUT.println(_currentArgCount);
#endif
}
void ESP8266WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){
#ifdef DEBUG
DEBUG_OUTPUT.print("Parse Form: Boundary: ");
DEBUG_OUTPUT.print(boundary);
DEBUG_OUTPUT.print("Length: ");
DEBUG_OUTPUT.println(len);
#endif
String line;
line = client.readStringUntil('\r');
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.startsWith("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
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
DEBUG_OUTPUT.print("PostArg Name: ");
DEBUG_OUTPUT.println(argName);
#endif
argType = "text/plain";
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if(line.startsWith("Content-Type")){
argType = line.substring(line.indexOf(':')+2);
//skip next line
client.readStringUntil('\r');
client.readStringUntil('\n');
}
#ifdef DEBUG
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
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
DEBUG_OUTPUT.println("Done Parsing POST");
#endif
break;
}
} else {
_currentUpload.status = UPLOAD_FILE_START;
_currentUpload.name = argName;
_currentUpload.filename = argFilename;
_currentUpload.type = argType;
_currentUpload.size = 0;
_currentUpload.buflen = 0;
#ifdef DEBUG
DEBUG_OUTPUT.print("Start File: ");
DEBUG_OUTPUT.print(_currentUpload.filename);
DEBUG_OUTPUT.print(" Type: ");
DEBUG_OUTPUT.println(_currentUpload.type);
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.status = UPLOAD_FILE_WRITE;
uint8_t argByte = client.read();
readfile:
while(argByte != 0x0D){
_currentUpload.buf[_currentUpload.buflen++] = argByte;
if(_currentUpload.buflen == 1460){
#ifdef DEBUG
DEBUG_OUTPUT.println("Write File: 1460");
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.size += _currentUpload.buflen;
_currentUpload.buflen = 0;
}
argByte = client.read();
}
argByte = client.read();
if(argByte == 0x0A){
#ifdef DEBUG
DEBUG_OUTPUT.print("Write File: ");
DEBUG_OUTPUT.println(_currentUpload.buflen);
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.size += _currentUpload.buflen;
_currentUpload.buflen = 0;
argByte = client.read();
if((char)argByte != '-'){
//continue reading the file
_currentUpload.buf[_currentUpload.buflen++] = 0x0D;
_currentUpload.buf[_currentUpload.buflen++] = 0x0A;
goto readfile;
} else {
argByte = client.read();
if((char)argByte != '-'){
//continue reading the file
_currentUpload.buf[_currentUpload.buflen++] = 0x0D;
_currentUpload.buf[_currentUpload.buflen++] = 0x0A;
_currentUpload.buf[_currentUpload.buflen++] = (uint8_t)('-');
goto readfile;
}
}
uint8_t endBuf[boundary.length()];
client.readBytes(endBuf, boundary.length());
if(strstr((const char*)endBuf, (const char*)(boundary.c_str())) != NULL){
_currentUpload.status = UPLOAD_FILE_END;
#ifdef DEBUG
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.size);
#endif
if(_fileUploadHandler) _fileUploadHandler();
line = client.readStringUntil(0x0D);
client.readStringUntil(0x0A);
if(line == "--"){
#ifdef DEBUG
DEBUG_OUTPUT.println("Done Parsing POST");
#endif
break;
}
continue;
} else {
_currentUpload.buf[_currentUpload.buflen++] = 0x0D;
_currentUpload.buf[_currentUpload.buflen++] = 0x0A;
uint32_t i = 0;
while(i < boundary.length()){
_currentUpload.buf[_currentUpload.buflen++] = endBuf[i++];
if(_currentUpload.buflen == 1460){
#ifdef DEBUG
DEBUG_OUTPUT.println("Write File: 1460");
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.size += _currentUpload.buflen;
_currentUpload.buflen = 0;
}
}
argByte = client.read();
goto readfile;
}
} else {
_currentUpload.buf[_currentUpload.buflen++] = 0x0D;
if(_currentUpload.buflen == 1460){
#ifdef DEBUG
DEBUG_OUTPUT.println("Write File: 1460");
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.size += _currentUpload.buflen;
_currentUpload.buflen = 0;
}
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;
}
}
void ESP8266WebServer::onFileUpload(THandlerFunction fn) { void ESP8266WebServer::onFileUpload(THandlerFunction fn) {
_fileUploadHandler = fn; _fileUploadHandler = fn;
} }
@ -553,18 +200,14 @@ void ESP8266WebServer::onNotFound(THandlerFunction fn) {
_notFoundHandler = fn; _notFoundHandler = fn;
} }
void ESP8266WebServer::_handleRequest(WiFiClient& client, String uri, HTTPMethod method) { void ESP8266WebServer::_handleRequest() {
_currentClient = client;
_currentUri = uri;
_currentMethod = method;
RequestHandler* handler; RequestHandler* handler;
for (handler = _firstHandler; handler; handler = handler->next) for (handler = _firstHandler; handler; handler = handler->next)
{ {
if (handler->method != HTTP_ANY && handler->method != method) if (handler->method != HTTP_ANY && handler->method != _currentMethod)
continue; continue;
if (handler->uri != uri) if (handler->uri != _currentUri)
continue; continue;
handler->fn(); handler->fn();
@ -580,26 +223,19 @@ void ESP8266WebServer::_handleRequest(WiFiClient& client, String uri, HTTPMethod
_notFoundHandler(); _notFoundHandler();
} }
else { else {
send(404, "text/plain", String("Not found: ") + uri); send(404, "text/plain", String("Not found: ") + _currentUri);
} }
} }
_currentClient = WiFiClient(); _currentClient = WiFiClient();
_currentUri = String(); _currentUri = String();
} }
const char* ESP8266WebServer::_responseCodeToString(int code) { const char* ESP8266WebServer::_responseCodeToString(int code) {
switch (code) { switch (code) {
case 200: return "OK"; case 200: return "OK";
case 404: return "Not found"; case 404: return "Not found";
case 500: return "Fail";
default: return ""; default: return "";
} }
} }
void ESP8266WebServer::_appendHeader(String& response, const char* name, const char* value) {
response += name;
response += ": ";
response += value;
response += "\r\n";
}

View File

@ -29,14 +29,17 @@
enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE }; enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE };
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END }; enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END };
#define PAYLOAD_UNIT_SIZE 1460
typedef struct { typedef struct {
HTTPUploadStatus status; HTTPUploadStatus status;
String filename; String filename;
String name; String name;
String type; String type;
size_t size; size_t totalSize; // file size
size_t buflen; size_t currentSize; // size of data currently in buf
uint8_t buf[1460]; uint8_t buf[PAYLOAD_UNIT_SIZE];
} HTTPUpload; } HTTPUpload;
class ESP8266WebServer class ESP8266WebServer
@ -58,7 +61,7 @@ public:
String uri() { return _currentUri; } String uri() { return _currentUri; }
HTTPMethod method() { return _currentMethod; } HTTPMethod method() { return _currentMethod; }
WiFiClient client() { return _currentClient; } WiFiClient client() { return _currentClient; }
HTTPUpload upload() { return _currentUpload; } HTTPUpload& upload() { return _currentUpload; }
String arg(const char* name); // get request argument value by name String arg(const char* name); // get request argument value by name
String arg(int i); // get request argument value by number String arg(int i); // get request argument value by number
@ -72,11 +75,13 @@ public:
// content - actual content body // content - actual content body
void send(int code, const char* content_type = NULL, String content = String("")); void send(int code, const char* content_type = NULL, String content = String(""));
void sendHeader(String name, String value, bool first = false);
void sendContent(String content);
protected: protected:
void _handleRequest(WiFiClient& client, String uri, HTTPMethod method); void _handleRequest();
bool _parseRequest(WiFiClient& client);
void _parseArguments(String data); void _parseArguments(String data);
static const char* _responseCodeToString(int code); static const char* _responseCodeToString(int code);
static void _appendHeader(String& response, const char* name, const char* value);
void _parseForm(WiFiClient& client, String boundary, uint32_t len); void _parseForm(WiFiClient& client, String boundary, uint32_t len);
struct RequestHandler; struct RequestHandler;
@ -95,6 +100,8 @@ protected:
RequestArgument* _currentArgs; RequestArgument* _currentArgs;
HTTPUpload _currentUpload; HTTPUpload _currentUpload;
String _responseHeaders;
RequestHandler* _firstHandler; RequestHandler* _firstHandler;
RequestHandler* _lastHandler; RequestHandler* _lastHandler;
THandlerFunction _notFoundHandler; THandlerFunction _notFoundHandler;

View File

@ -0,0 +1,428 @@
/*
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 "ESP8266WebServer.h"
// #define DEBUG
#define DEBUG_OUTPUT Serial1
bool ESP8266WebServer::_parseRequest(WiFiClient& client) {
// Read the first line of HTTP request
String req = client.readStringUntil('\r');
client.readStringUntil('\n');
// 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
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 searchStr = "";
int hasSearch = url.indexOf('?');
if(hasSearch != -1){
searchStr = url.substring(hasSearch + 1);
url = url.substring(0, hasSearch);
}
_currentUri = url;
HTTPMethod method = HTTP_GET;
if (methodStr == "POST") {
method = HTTP_POST;
} else if (methodStr == "DELETE") {
method = HTTP_DELETE;
} else if (methodStr == "PUT") {
method = HTTP_PUT;
} else if (methodStr == "PATCH") {
method = HTTP_PATCH;
}
_currentMethod = method;
#ifdef DEBUG
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
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;
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 + 2);
if(headerName == "Content-Type"){
if(headerValue.startsWith("text/plain")){
isForm = false;
} else if(headerValue.startsWith("multipart/form-data")){
boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
isForm = true;
}
} else if(headerName == "Content-Length"){
contentLength = headerValue.toInt();
}
}
if(!isForm){
if(searchStr != "") searchStr += '&';
searchStr += client.readStringUntil('\r');
client.readStringUntil('\n');
}
_parseArguments(searchStr);
if(isForm){
_parseForm(client, boundaryStr, contentLength);
}
} else {
_parseArguments(searchStr);
}
client.flush();
#ifdef DEBUG
DEBUG_OUTPUT.print("Request: ");
DEBUG_OUTPUT.println(url);
DEBUG_OUTPUT.print(" Arguments: ");
DEBUG_OUTPUT.println(searchStr);
#endif
return true;
}
void ESP8266WebServer::_parseArguments(String data) {
#ifdef DEBUG
DEBUG_OUTPUT.print("args: ");
DEBUG_OUTPUT.println(data);
#endif
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = 0;
if (data.length() == 0) {
_currentArgCount = 0;
return;
}
_currentArgCount = 1;
for (int i = 0; i < data.length(); ) {
i = data.indexOf('&', i);
if (i == -1)
break;
++i;
++_currentArgCount;
}
#ifdef DEBUG
DEBUG_OUTPUT.print("args count: ");
DEBUG_OUTPUT.println(_currentArgCount);
#endif
_currentArgs = new RequestArgument[_currentArgCount];
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
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
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 = data.substring(equal_sign_index + 1, next_arg_index);
#ifdef DEBUG
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
DEBUG_OUTPUT.print("args count: ");
DEBUG_OUTPUT.println(_currentArgCount);
#endif
}
void ESP8266WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){
#ifdef DEBUG
DEBUG_OUTPUT.print("Parse Form: Boundary: ");
DEBUG_OUTPUT.print(boundary);
DEBUG_OUTPUT.print("Length: ");
DEBUG_OUTPUT.println(len);
#endif
String line;
line = client.readStringUntil('\r');
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.startsWith("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
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
DEBUG_OUTPUT.print("PostArg Name: ");
DEBUG_OUTPUT.println(argName);
#endif
argType = "text/plain";
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if(line.startsWith("Content-Type")){
argType = line.substring(line.indexOf(':')+2);
//skip next line
client.readStringUntil('\r');
client.readStringUntil('\n');
}
#ifdef DEBUG
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
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
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
DEBUG_OUTPUT.print("Start File: ");
DEBUG_OUTPUT.print(_currentUpload.filename);
DEBUG_OUTPUT.print(" Type: ");
DEBUG_OUTPUT.println(_currentUpload.type);
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.status = UPLOAD_FILE_WRITE;
uint8_t argByte = client.read();
readfile:
while(argByte != 0x0D){
_currentUpload.buf[_currentUpload.currentSize++] = argByte;
if(_currentUpload.currentSize == PAYLOAD_UNIT_SIZE){
#ifdef DEBUG
DEBUG_OUTPUT.print("Write File: ");
DEBUG_OUTPUT.println(PAYLOAD_UNIT_SIZE);
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.totalSize += _currentUpload.currentSize;
_currentUpload.currentSize = 0;
}
argByte = client.read();
}
argByte = client.read();
if(argByte == 0x0A){
#ifdef DEBUG
DEBUG_OUTPUT.print("Write File: ");
DEBUG_OUTPUT.println(_currentUpload.currentSize);
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.totalSize += _currentUpload.currentSize;
_currentUpload.currentSize = 0;
argByte = client.read();
if((char)argByte != '-'){
//continue reading the file
_currentUpload.buf[_currentUpload.currentSize++] = 0x0D;
_currentUpload.buf[_currentUpload.currentSize++] = 0x0A;
goto readfile;
} else {
argByte = client.read();
if((char)argByte != '-'){
//continue reading the file
_currentUpload.buf[_currentUpload.currentSize++] = 0x0D;
_currentUpload.buf[_currentUpload.currentSize++] = 0x0A;
_currentUpload.buf[_currentUpload.currentSize++] = (uint8_t)('-');
goto readfile;
}
}
uint8_t endBuf[boundary.length()];
client.readBytes(endBuf, boundary.length());
if(strstr((const char*)endBuf, (const char*)(boundary.c_str())) != NULL){
_currentUpload.status = UPLOAD_FILE_END;
#ifdef DEBUG
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
if(_fileUploadHandler) _fileUploadHandler();
line = client.readStringUntil(0x0D);
client.readStringUntil(0x0A);
if(line == "--"){
#ifdef DEBUG
DEBUG_OUTPUT.println("Done Parsing POST");
#endif
break;
}
continue;
} else {
_currentUpload.buf[_currentUpload.currentSize++] = 0x0D;
_currentUpload.buf[_currentUpload.currentSize++] = 0x0A;
uint32_t i = 0;
while(i < boundary.length()){
_currentUpload.buf[_currentUpload.currentSize++] = endBuf[i++];
if(_currentUpload.currentSize == PAYLOAD_UNIT_SIZE){
#ifdef DEBUG
DEBUG_OUTPUT.print("Write File: ");
DEBUG_OUTPUT.println(PAYLOAD_UNIT_SIZE);
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.totalSize += _currentUpload.currentSize;
_currentUpload.currentSize = 0;
}
}
argByte = client.read();
goto readfile;
}
} else {
_currentUpload.buf[_currentUpload.currentSize++] = 0x0D;
if(_currentUpload.currentSize == PAYLOAD_UNIT_SIZE){
#ifdef DEBUG
DEBUG_OUTPUT.print("Write File: ");
DEBUG_OUTPUT.println(PAYLOAD_UNIT_SIZE);
#endif
if(_fileUploadHandler) _fileUploadHandler();
_currentUpload.totalSize += _currentUpload.currentSize;
_currentUpload.currentSize = 0;
}
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;
}
}

View File

@ -25,6 +25,7 @@
#include "Print.h" #include "Print.h"
#include "Client.h" #include "Client.h"
#include "IPAddress.h" #include "IPAddress.h"
#include <memory>
class ClientContext; class ClientContext;
class WiFiServer; class WiFiServer;
@ -44,6 +45,9 @@ public:
virtual int connect(const char *host, uint16_t port); virtual int connect(const char *host, uint16_t port);
virtual size_t write(uint8_t); virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buf, size_t size); virtual size_t write(const uint8_t *buf, size_t size);
template <typename T>
size_t write(T& source, size_t unitSize);
virtual int available(); virtual int available();
virtual int read(); virtual int read();
virtual int read(uint8_t *buf, size_t size); virtual int read(uint8_t *buf, size_t size);
@ -72,4 +76,24 @@ private:
}; };
template <typename T>
inline size_t WiFiClient::write(T& source, size_t unitSize) {
std::unique_ptr<uint8_t[]> buffer(new uint8_t[unitSize]);
size_t size_sent = 0;
while(true) {
size_t left = source.available();
if (!left)
break;
size_t will_send = (left < unitSize) ? left : unitSize;
source.read(buffer.get(), will_send);
size_t cb = write(buffer.get(), will_send);
size_sent += cb;
if (cb != will_send) {
break;
}
}
return size_sent;
}
#endif #endif