Add support for following redirects in HTTPClient (#4240)

This commit is contained in:
Clickau 2020-10-01 14:41:54 +03:00 committed by GitHub
parent 837cc3d271
commit ee88c42c3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 175 additions and 20 deletions

View File

@ -548,29 +548,106 @@ int HTTPClient::sendRequest(const char * type, String payload)
*/ */
int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size) int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
{ {
// connect to server int code;
if(!connect()) { bool redirect = false;
return returnError(HTTPC_ERROR_CONNECTION_REFUSED); uint16_t redirectCount = 0;
} do {
// wipe out any existing headers from previous request
if(payload && size > 0) { for(size_t i = 0; i < _headerKeysCount; i++) {
addHeader(F("Content-Length"), String(size)); if (_currentHeaders[i].value.length() > 0) {
} _currentHeaders[i].value.clear();
}
// send Header
if(!sendHeader(type)) {
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
}
// send Payload if needed
if(payload && size > 0) {
if(_client->write(&payload[0], size) != size) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
} }
}
log_d("request type: '%s' redirCount: %d\n", type, redirectCount);
// connect to server
if(!connect()) {
return returnError(HTTPC_ERROR_CONNECTION_REFUSED);
}
if(payload && size > 0) {
addHeader(F("Content-Length"), String(size));
}
// send Header
if(!sendHeader(type)) {
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
}
// send Payload if needed
if(payload && size > 0) {
if(_client->write(&payload[0], size) != size) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
}
code = handleHeaderResponse();
Serial.printf("sendRequest code=%d\n", code);
// Handle redirections as stated in RFC document:
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
//
// Implementing HTTP_CODE_FOUND as redirection with GET method,
// to follow most of existing user agent implementations.
//
redirect = false;
if (
_followRedirects != HTTPC_DISABLE_FOLLOW_REDIRECTS &&
redirectCount < _redirectLimit &&
_location.length() > 0
) {
switch (code) {
// redirecting using the same method
case HTTP_CODE_MOVED_PERMANENTLY:
case HTTP_CODE_TEMPORARY_REDIRECT: {
if (
// allow to force redirections on other methods
// (the RFC require user to accept the redirection)
_followRedirects == HTTPC_FORCE_FOLLOW_REDIRECTS ||
// allow GET and HEAD methods without force
!strcmp(type, "GET") ||
!strcmp(type, "HEAD")
) {
redirectCount += 1;
log_d("following redirect (the same method): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
if (!setURL(_location)) {
log_d("failed setting URL for redirection\n");
// no redirection
break;
}
// redirect using the same request method and payload, diffrent URL
redirect = true;
}
break;
}
// redirecting with method dropped to GET or HEAD
// note: it does not need `HTTPC_FORCE_FOLLOW_REDIRECTS` for any method
case HTTP_CODE_FOUND:
case HTTP_CODE_SEE_OTHER: {
redirectCount += 1;
log_d("following redirect (dropped to GET/HEAD): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
if (!setURL(_location)) {
log_d("failed setting URL for redirection\n");
// no redirection
break;
}
// redirect after changing method to GET/HEAD and dropping payload
type = "GET";
payload = nullptr;
size = 0;
redirect = true;
break;
}
default:
break;
}
}
} while (redirect);
// handle Server Response (Header) // handle Server Response (Header)
return returnError(handleHeaderResponse()); return returnError(code);
} }
/** /**
@ -1143,6 +1220,10 @@ int HTTPClient::handleHeaderResponse()
transferEncoding = headerValue; transferEncoding = headerValue;
} }
if (headerName.equalsIgnoreCase("Location")) {
_location = headerValue;
}
for(size_t i = 0; i < _headerKeysCount; i++) { for(size_t i = 0; i < _headerKeysCount; i++) {
if(_currentHeaders[i].key.equalsIgnoreCase(headerName)) { if(_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
_currentHeaders[i].value = headerValue; _currentHeaders[i].value = headerValue;
@ -1320,3 +1401,50 @@ int HTTPClient::returnError(int error)
} }
return error; return error;
} }
void HTTPClient::setFollowRedirects(followRedirects_t follow)
{
_followRedirects = follow;
}
void HTTPClient::setRedirectLimit(uint16_t limit)
{
_redirectLimit = limit;
}
/**
* set the URL to a new value. Handy for following redirects.
* @param url
*/
bool HTTPClient::setURL(const String& url)
{
// if the new location is only a path then only update the URI
if (url && url[0] == '/') {
_uri = url;
clear();
return true;
}
if (!url.startsWith(_protocol + ':')) {
log_d("new URL not the same protocol, expected '%s', URL: '%s'\n", _protocol.c_str(), url.c_str());
return false;
}
// check if the port is specified
int indexPort = url.indexOf(':', 6); // find the first ':' excluding the one from the protocol
int indexURI = url.indexOf('/', 7); // find where the URI starts to make sure the ':' is not part of it
if (indexPort == -1 || indexPort > indexURI) {
// the port is not specified
_port = (_protocol == "https" ? 443 : 80);
}
// disconnect but preserve _client (clear _canReuse so disconnect will close the connection)
_canReuse = false;
disconnect(true);
return beginInternal(url, _protocol.c_str());
}
const String &HTTPClient::getLocation(void)
{
return _location;
}

View File

@ -119,6 +119,24 @@ typedef enum {
HTTPC_TE_CHUNKED HTTPC_TE_CHUNKED
} transferEncoding_t; } transferEncoding_t;
/**
* redirection follow mode.
* + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed.
* + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using
* GET or HEAD methods will be redirected (using the same method),
* since the RFC requires end-user confirmation in other cases.
* + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed,
* regardless of a used method. New request will use the same method,
* and they will include the same body data and the same headers.
* In the sense of the RFC, it's just like every redirection is confirmed.
*/
typedef enum {
HTTPC_DISABLE_FOLLOW_REDIRECTS,
HTTPC_STRICT_FOLLOW_REDIRECTS,
HTTPC_FORCE_FOLLOW_REDIRECTS
} followRedirects_t;
#ifdef HTTPCLIENT_1_1_COMPATIBLE #ifdef HTTPCLIENT_1_1_COMPATIBLE
class TransportTraits; class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr; typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
@ -156,6 +174,11 @@ public:
void setConnectTimeout(int32_t connectTimeout); void setConnectTimeout(int32_t connectTimeout);
void setTimeout(uint16_t timeout); void setTimeout(uint16_t timeout);
// Redirections
void setFollowRedirects(followRedirects_t follow);
void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
bool setURL(const String &url);
void useHTTP10(bool usehttp10 = true); void useHTTP10(bool usehttp10 = true);
/// request handling /// request handling
@ -182,6 +205,7 @@ public:
int getSize(void); int getSize(void);
const String &getLocation(void);
WiFiClient& getStream(void); WiFiClient& getStream(void);
WiFiClient* getStreamPtr(void); WiFiClient* getStreamPtr(void);
@ -235,6 +259,9 @@ protected:
int _returnCode = 0; int _returnCode = 0;
int _size = -1; int _size = -1;
bool _canReuse = false; bool _canReuse = false;
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
uint16_t _redirectLimit = 10;
String _location;
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY; transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
}; };