-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Reduce code duplications in _parseRequest #6951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,6 @@ | |
#endif | ||
|
||
static const char Content_Type[] PROGMEM = "Content-Type"; | ||
static const char filename[] PROGMEM = "filename"; | ||
|
||
template <typename ServerType> | ||
static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t maxLength, String& data, int timeout_ms) | ||
|
@@ -62,216 +61,188 @@ static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t | |
|
||
template <typename ServerType> | ||
bool ESP8266WebServerTemplate<ServerType>::_parseRequest(ClientType& client) { | ||
// Read the first line of HTTP request | ||
String req = client.readStringUntil('\r'); | ||
// Read the first line of HTTP request | ||
String req = client.readStringUntil('\r'); | ||
#ifdef DEBUG_ESP_HTTP_SERVER | ||
DEBUG_OUTPUT.print("request: "); | ||
DEBUG_OUTPUT.println(req); | ||
#endif | ||
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) { | ||
client.readStringUntil('\n'); | ||
//reset header value | ||
for (size_t i = 0; i < _headerKeysCount; ++i) { | ||
_currentHeaders[i].value.clear(); | ||
} | ||
|
||
// 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.println("Invalid request"); | ||
DEBUG_OUTPUT.println("Invalid request"); | ||
#endif | ||
return false; | ||
} | ||
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 == F("HEAD")) { | ||
method = HTTP_HEAD; | ||
} else if (methodStr == F("POST")) { | ||
method = HTTP_POST; | ||
} else if (methodStr == F("DELETE")) { | ||
method = HTTP_DELETE; | ||
} else if (methodStr == F("OPTIONS")) { | ||
method = HTTP_OPTIONS; | ||
} else if (methodStr == F("PUT")) { | ||
method = HTTP_PUT; | ||
} else if (methodStr == F("PATCH")) { | ||
method = HTTP_PATCH; | ||
} | ||
_currentMethod = method; | ||
String methodStr = req.substring(0, addr_start); | ||
String url = req.substring(addr_start + 1, addr_end); | ||
_currentVersion = req.substring(addr_end + 8).toInt(); | ||
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 == F("HEAD")) { | ||
method = HTTP_HEAD; | ||
} else if (methodStr == F("POST")) { | ||
method = HTTP_POST; | ||
} else if (methodStr == F("DELETE")) { | ||
method = HTTP_DELETE; | ||
} else if (methodStr == F("OPTIONS")) { | ||
method = HTTP_OPTIONS; | ||
} else if (methodStr == F("PUT")) { | ||
method = HTTP_PUT; | ||
} else if (methodStr == F("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); | ||
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 | ||
RequestHandlerType* handler; | ||
for (handler = _firstHandler; handler; handler = handler->next()) { | ||
if (handler->canHandle(_currentMethod, _currentUri)) | ||
break; | ||
} | ||
_currentHandler = handler; | ||
//attach handler | ||
RequestHandlerType* 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){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you test that moving this condition still results in correct function as before for all methods? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not test all methods. I did test GET and POST though in the scope of my application and it worked fine. Another way to put it is however that this "if condition" didn't actually move. what happened is that the header parsing part that was used in the POST/PUT/PATCH/DELETE code was moved up front, and then the rest of the code remained the same. this allowed to remove the header parsing that was done for GET/HEAD case as it was duplicate now. Personally I think thats a relatively low risk change because the two header parsing loops were doing mostly the same, except that POST/PUT/PATCH/DELETE was checking for additional headers and parsed them. It now does so in all cases, but since it is extremely unlikely to get the additional header fields send by GET/HEAD requests, it will not matter in practice. and even if we get them, the parsed result is ignored as the if() condition remained the same and the parsed resulting values are not further evaluated. |
||
String boundaryStr; | ||
String headerName; | ||
String headerValue; | ||
String boundaryStr; | ||
bool isForm = false; | ||
bool isEncoded = false; | ||
uint32_t contentLength = 0; | ||
//parse headers | ||
while(1){ | ||
req = client.readStringUntil('\r'); | ||
client.readStringUntil('\n'); | ||
if (req.isEmpty()) 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(FPSTR(Content_Type))){ | ||
using namespace mime; | ||
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))){ | ||
isForm = false; | ||
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))){ | ||
isForm = false; | ||
isEncoded = true; | ||
} else if (headerValue.startsWith(F("multipart/"))){ | ||
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1); | ||
boundaryStr.replace("\"",""); | ||
isForm = true; | ||
} | ||
} else if (headerName.equalsIgnoreCase(F("Content-Length"))){ | ||
contentLength = headerValue.toInt(); | ||
} else if (headerName.equalsIgnoreCase(F("Host"))){ | ||
_hostHeader = headerValue; | ||
} | ||
} | ||
// parse headers | ||
while (true) { | ||
dirkmueller marked this conversation as resolved.
Show resolved
Hide resolved
|
||
req = client.readStringUntil('\r'); | ||
client.readStringUntil('\n'); | ||
|
||
String plainBuf; | ||
if ( !isForm | ||
&& // read content into plainBuf | ||
( !readBytesWithTimeout<ServerType>(client, contentLength, plainBuf, HTTP_MAX_POST_WAIT) | ||
|| (plainBuf.length() < contentLength) | ||
) | ||
) | ||
{ | ||
return false; | ||
} | ||
int headerDiv = req.indexOf(':'); | ||
if (headerDiv < 0) { | ||
break; | ||
} | ||
headerName = req.substring(0, headerDiv); | ||
headerValue = req.substring(headerDiv + 1); | ||
headerValue.trim(); | ||
_collectHeader(headerName, headerValue); | ||
|
||
if (isEncoded) { | ||
// isEncoded => !isForm => plainBuf is not empty | ||
// add plainBuf in search str | ||
if (searchStr.length()) | ||
searchStr += '&'; | ||
searchStr += plainBuf; | ||
#ifdef DEBUG_ESP_HTTP_SERVER | ||
DEBUG_OUTPUT.print(F("headerName: ")); | ||
DEBUG_OUTPUT.println(headerName); | ||
DEBUG_OUTPUT.print(F("headerValue: ")); | ||
DEBUG_OUTPUT.println(headerValue); | ||
#endif | ||
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) { | ||
if (headerValue.startsWith(FPSTR(::mime::mimeTable[::mime::txt].mimeType))) { | ||
isForm = false; | ||
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) { | ||
isForm = false; | ||
isEncoded = true; | ||
} else if (headerValue.startsWith(F("multipart/"))) { | ||
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1); | ||
boundaryStr.replace("\"", ""); | ||
isForm = true; | ||
} | ||
} else if (headerName.equalsIgnoreCase(F("Content-Length"))) { | ||
contentLength = headerValue.toInt(); | ||
} else if (headerName.equalsIgnoreCase(F("Host"))) { | ||
_hostHeader = headerValue; | ||
} | ||
} | ||
|
||
// parse searchStr for key/value pairs | ||
_parseArguments(searchStr); | ||
|
||
if (!isForm) { | ||
if (contentLength) { | ||
// add key=value: plain={body} (post json or other data) | ||
RequestArgument& arg = _currentArgs[_currentArgCount++]; | ||
arg.key = F("plain"); | ||
arg.value = plainBuf; | ||
_currentArgsHavePlain = 1; | ||
} | ||
} else { // isForm is true | ||
// here: content is not yet read (plainBuf is still empty) | ||
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.isEmpty()) 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()); | ||
// below is needed only when POST type request | ||
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE) { | ||
String plainBuf; | ||
if (!isForm && | ||
// read content into plainBuf | ||
(!readBytesWithTimeout<ServerType>(client, contentLength, plainBuf, HTTP_MAX_POST_WAIT) | ||
|| (plainBuf.length() < contentLength))) | ||
{ | ||
return false; | ||
} | ||
|
||
#ifdef DEBUG_ESP_HTTP_SERVER | ||
DEBUG_OUTPUT.print(F("headerName: ")); | ||
DEBUG_OUTPUT.println(headerName); | ||
DEBUG_OUTPUT.print(F("headerValue: ")); | ||
DEBUG_OUTPUT.println(headerValue); | ||
#endif | ||
if (isEncoded) { | ||
// isEncoded => !isForm => plainBuf is not empty | ||
// add plainBuf in search str | ||
if (searchStr.length()) | ||
searchStr += '&'; | ||
searchStr += plainBuf; | ||
} | ||
|
||
if (headerName.equalsIgnoreCase(F("Host"))){ | ||
_hostHeader = headerValue; | ||
} | ||
// parse searchStr for key/value pairs | ||
_parseArguments(searchStr); | ||
|
||
if (!isForm) { | ||
if (contentLength) { | ||
// add key=value: plain={body} (post json or other data) | ||
RequestArgument& arg = _currentArgs[_currentArgCount++]; | ||
arg.key = F("plain"); | ||
arg.value = plainBuf; | ||
_currentArgsHavePlain = 1; | ||
} | ||
} else { // isForm is true | ||
// here: content is not yet read (plainBuf is still empty) | ||
if (!_parseForm(client, boundaryStr, contentLength)) { | ||
return false; | ||
} | ||
} | ||
} | ||
_parseArguments(searchStr); | ||
} | ||
client.flush(); | ||
client.flush(); | ||
|
||
#ifdef DEBUG_ESP_HTTP_SERVER | ||
DEBUG_OUTPUT.print(F("Request: ")); | ||
DEBUG_OUTPUT.println(url); | ||
DEBUG_OUTPUT.print(F("Arguments: ")); | ||
DEBUG_OUTPUT.println(searchStr); | ||
|
||
DEBUG_OUTPUT.println(F("final list of key/value pairs:")); | ||
for (int i = 0; i < _currentArgCount; i++) | ||
DEBUG_OUTPUT.printf(" key:'%s' value:'%s'\r\n", | ||
_currentArgs[i].key.c_str(), | ||
_currentArgs[i].value.c_str()); | ||
DEBUG_OUTPUT.print(F("Request: ")); | ||
DEBUG_OUTPUT.println(url); | ||
DEBUG_OUTPUT.print(F("Arguments: ")); | ||
DEBUG_OUTPUT.println(searchStr); | ||
|
||
DEBUG_OUTPUT.println(F("final list of key/value pairs:")); | ||
for (int i = 0; i < _currentArgCount; i++) | ||
DEBUG_OUTPUT.printf(" key:'%s' value:'%s'\r\n", | ||
_currentArgs[i].key.c_str(), | ||
_currentArgs[i].value.c_str()); | ||
#endif | ||
|
||
return true; | ||
return true; | ||
} | ||
|
||
template <typename ServerType> | ||
bool ESP8266WebServerTemplate<ServerType>::_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 _collectHeader(String(headerName), String(headerValue)); | ||
} | ||
|
||
template <typename ServerType> | ||
bool ESP8266WebServerTemplate<ServerType>::_collectHeader(const String& headerName, const String& headerValue) { | ||
for (int i = 0; i < _headerKeysCount; i++) { | ||
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) { | ||
_currentHeaders[i].value = headerValue; | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
return false; | ||
} | ||
|
||
template <typename ServerType> | ||
|
@@ -359,9 +330,9 @@ int ESP8266WebServerTemplate<ServerType>::_parseArgumentsPrivate(const String& d | |
} | ||
|
||
template <typename ServerType> | ||
void ESP8266WebServerTemplate<ServerType>::_uploadWriteByte(uint8_t b){ | ||
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN){ | ||
if(_currentHandler && _currentHandler->canUpload(_currentUri)) | ||
void ESP8266WebServerTemplate<ServerType>::_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; | ||
|
@@ -427,8 +398,9 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const | |
DEBUG_OUTPUT.println(argFilename); | ||
#endif | ||
//use GET to set the filename if uploading using blob | ||
if (argFilename == F("blob") && hasArg(FPSTR(filename))) | ||
argFilename = arg(FPSTR(filename)); | ||
String filenameArg = arg(F("filename")); | ||
if (argFilename == F("blob") && !filenameArg.isEmpty()) | ||
argFilename = filenameArg; | ||
} | ||
#ifdef DEBUG_ESP_HTTP_SERVER | ||
DEBUG_OUTPUT.print("PostArg Name: "); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a signature change for a non-private method, and therefore breaking. We can wait to merge for v3, or you could add a compatibility method that forwards from one to the other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is source compatible (String(const char*) constructor is implicit). I don't see the need to add a forwarder method - it will just work in any case as far as I can see.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're thinking of method call, I'm referring to inheritance. This change can cause a derived class to no longer compile. Trivial example with std::string instead of String, but the principle is the same: