Add support for following redirects in HTTPClient (#4240)
This commit is contained in:
parent
837cc3d271
commit
ee88c42c3b
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user