From a924ba1336910c2b8419d9c54e3240cd3aa4945b Mon Sep 17 00:00:00 2001 From: ficeto Date: Fri, 8 May 2015 02:44:59 +0300 Subject: [PATCH] add proper POST support and more methods GET params are always added plain POST is added to the GET arguments Uploads are handled by separate handler --- .../examples/SDWebServer/SDWebServer.ino | 33 ++ .../ESP8266WebServer/src/ESP8266WebServer.cpp | 378 ++++++++++++++---- .../ESP8266WebServer/src/ESP8266WebServer.h | 18 +- 3 files changed, 350 insertions(+), 79 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino b/libraries/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino index d7e2863af..bdc945d33 100644 --- a/libraries/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino +++ b/libraries/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino @@ -43,6 +43,26 @@ MDNSResponder mdns; ESP8266WebServer server(80); static bool hasSD = false; +File uploadFile; + +void handleFileUpload(){ + if(server.uri() != "/upload") return; + HTTPUpload upload = server.upload(); + if(upload.status == UPLOAD_FILE_START){ + Serial.print("Upload: START, filename:"); + Serial.println(upload.filename); + if(SD.exists((char *)upload.filename.c_str())) SD.remove((char *)upload.filename.c_str()); + uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE); + } else if(upload.status == UPLOAD_FILE_WRITE){ + Serial.print("Upload: WRITE, Bytes:"); + Serial.println(upload.buflen); + if(uploadFile) uploadFile.write(upload.buf, upload.buflen); + } else if(upload.status == UPLOAD_FILE_END){ + Serial.print("Upload: END, Size:"); + Serial.println(upload.size); + if(uploadFile) uploadFile.close(); + } +} bool loadFromSdCard(String path){ String dataType = "text/plain"; @@ -152,6 +172,19 @@ void setup(void){ //Attach handler server.onNotFound(tryLoadFromSdCard); + //Attach Upload handler + server.onFileUpload(handleFileUpload); + + //Attach handler for the Upload location + server.on("/upload", HTTP_POST, [](){ + WiFiClient client = server.client(); + String message = "HTTP/1.1 200 OK\r\n"; + message += "Content-Type: text/plain\r\n"; + message += "Access-Control-Allow-Origin: *\r\n"; + message += "\r\n"; + client.print(message); + }); + //start server server.begin(); Serial.println("HTTP server started"); diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp index 6097e20e1..360876991 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp @@ -17,6 +17,7 @@ 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) */ @@ -25,7 +26,8 @@ #include "WiFiClient.h" #include "ESP8266WebServer.h" -// #define DEBUG +//#define DEBUG +#define DEBUG_OUTPUT Serial1 struct ESP8266WebServer::RequestHandler { RequestHandler(ESP8266WebServer::THandlerFunction fn, const char* uri, HTTPMethod method) @@ -95,7 +97,7 @@ void ESP8266WebServer::handleClient() } #ifdef DEBUG - Serial.println("New client"); + DEBUG_OUTPUT.println("New client"); #endif // Wait for data from client to become available while(client.connected() && !client.available()){ @@ -106,86 +108,101 @@ void ESP8266WebServer::handleClient() String req = client.readStringUntil('\r'); client.readStringUntil('\n'); - HTTPMethod method = HTTP_GET; - if (req.startsWith("POST")) { - method = HTTP_POST; - } - // 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 - Serial.print("Invalid request: "); - Serial.println(req); + DEBUG_OUTPUT.print("Invalid request: "); + DEBUG_OUTPUT.println(req); #endif return; } - - req = req.substring(addr_start + 1, addr_end); + + 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; + } + +#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; - if (method == HTTP_POST) { - int contentLength = -1; - int headerCount = 0; - while(headerCount < 1024) { // there shouldn't be that much really - String line = client.readStringUntil('\r'); + //bellow is needed only when POST type request + if(method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH){ + 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 (line.length() > 0) { // this is a header - ++headerCount; - if (contentLength < 0 && line.startsWith("Content-Length")) { - // get content length from the header - int valuePos = line.indexOf(' ', 14); - if (valuePos > 0) { - String valueStr = line.substring(valuePos+1); - contentLength = valueStr.toInt(); -#ifdef DEBUG - Serial.print("Content-Length: "); - Serial.println(contentLength); -#endif - } - } - } - else { + 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(); + } } -#ifdef DEBUG - Serial.print("headerCount="); - Serial.println(headerCount); -#endif - if (contentLength >= 0) { - formData = ""; - int n = 0; // timeout counter - while (formData.length() < contentLength && ++n < 3) - formData += client.readString(); + + if(!isForm){ + if(searchStr != "") searchStr += '&'; + searchStr += client.readStringUntil('\r'); + client.readStringUntil('\n'); } - else { - formData = client.readStringUntil('\r'); // will return after timing out once + _parseArguments(searchStr); + if(isForm){ + _parseForm(client, boundaryStr, contentLength); } + } else { + _parseArguments(searchStr); } - else if (method == HTTP_GET) { - int args_start = req.indexOf('?'); - if (args_start != -1) { - formData = req.substring(args_start + 1); - req = req.substring(0, args_start); - } - } - client.flush(); #ifdef DEBUG - Serial.print("Request: "); - Serial.println(req); - Serial.print("Args: "); - Serial.println(formData); + DEBUG_OUTPUT.print("Request: "); + DEBUG_OUTPUT.println(url); + DEBUG_OUTPUT.print(" Arguments: "); + DEBUG_OUTPUT.println(searchStr); #endif - _parseArguments(formData); - _handleRequest(client, req, method); - + _handleRequest(client, url, method); } void ESP8266WebServer::send(int code, const char* content_type, String content) { @@ -237,6 +254,10 @@ bool ESP8266WebServer::hasArg(const char* name) { } void ESP8266WebServer::_parseArguments(String data) { +#ifdef DEBUG + DEBUG_OUTPUT.print("args: "); + DEBUG_OUTPUT.println(data); +#endif if (_currentArgs) delete[] _currentArgs; _currentArgs = 0; @@ -254,8 +275,8 @@ void ESP8266WebServer::_parseArguments(String data) { ++_currentArgCount; } #ifdef DEBUG - Serial.print("args count: "); - Serial.println(_currentArgCount); + DEBUG_OUTPUT.print("args count: "); + DEBUG_OUTPUT.println(_currentArgCount); #endif _currentArgs = new RequestArgument[_currentArgCount]; @@ -265,17 +286,17 @@ void ESP8266WebServer::_parseArguments(String data) { int equal_sign_index = data.indexOf('=', pos); int next_arg_index = data.indexOf('&', pos); #ifdef DEBUG - Serial.print("pos "); - Serial.print(pos); - Serial.print("=@ "); - Serial.print(equal_sign_index); - Serial.print(" &@ "); - Serial.println(next_arg_index); + 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 - Serial.print("arg missing value: "); - Serial.println(iarg); + DEBUG_OUTPUT.print("arg missing value: "); + DEBUG_OUTPUT.println(iarg); #endif if (next_arg_index == -1) break; @@ -286,12 +307,12 @@ void ESP8266WebServer::_parseArguments(String data) { arg.key = data.substring(pos, equal_sign_index); arg.value = data.substring(equal_sign_index + 1, next_arg_index); #ifdef DEBUG - Serial.print("arg "); - Serial.print(iarg); - Serial.print(" key: "); - Serial.print(arg.key); - Serial.print(" value: "); - Serial.println(arg.value); + 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) @@ -300,12 +321,213 @@ void ESP8266WebServer::_parseArguments(String data) { } _currentArgCount = iarg; #ifdef DEBUG - Serial.print("args count: "); - Serial.println(_currentArgCount); + 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; + argValue += line+"\n"; + } + #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){ + line = client.readStringUntil(0x0D); + client.readStringUntil(0x0A); +#ifdef DEBUG + DEBUG_OUTPUT.print("Write File: "); + DEBUG_OUTPUT.println(_currentUpload.buflen); +#endif + if(_fileUploadHandler) _fileUploadHandler(); + _currentUpload.size += _currentUpload.buflen; + _currentUpload.buflen = 0; + if(line.startsWith("--"+boundary)){ + _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(); + if(line == ("--"+boundary+"--")){ +#ifdef DEBUG + DEBUG_OUTPUT.println("Done Parsing POST"); +#endif + break; + } + continue; + } else { + _currentUpload.buf[_currentUpload.buflen++] = 0x0D; + _currentUpload.buf[_currentUpload.buflen++] = 0x0A; + const char * lineChars = line.c_str(); + uint32_t i = 0; + while(i < os_strlen(lineChars)){ + _currentUpload.buf[_currentUpload.buflen++] = lineChars[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; + + } +} + +void ESP8266WebServer::onFileUpload(THandlerFunction fn) { + _fileUploadHandler = fn; +} + void ESP8266WebServer::onNotFound(THandlerFunction fn) { _notFoundHandler = fn; } @@ -330,7 +552,7 @@ void ESP8266WebServer::_handleRequest(WiFiClient& client, String uri, HTTPMethod if (!handler){ #ifdef DEBUG - Serial.println("request handler not found"); + DEBUG_OUTPUT.println("request handler not found"); #endif if(_notFoundHandler) { diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 3b31eab5a..423fc6173 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -17,6 +17,7 @@ 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) */ @@ -25,8 +26,18 @@ #include -enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST }; +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 }; +typedef struct { + HTTPUploadStatus status; + String filename; + String name; + String type; + size_t size; + size_t buflen; + uint8_t buf[1460]; +} HTTPUpload; class ESP8266WebServer { @@ -42,10 +53,12 @@ public: void on(const char* uri, THandlerFunction handler); void on(const char* uri, HTTPMethod method, THandlerFunction fn); 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(const char* name); // get request argument value by name String arg(int i); // get request argument value by number @@ -64,6 +77,7 @@ protected: void _parseArguments(String data); 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); struct RequestHandler; struct RequestArgument { @@ -79,10 +93,12 @@ protected: size_t _currentArgCount; RequestArgument* _currentArgs; + HTTPUpload _currentUpload; RequestHandler* _firstHandler; RequestHandler* _lastHandler; THandlerFunction _notFoundHandler; + THandlerFunction _fileUploadHandler; };