Skip to content

Commit ee88c42

Browse files
authored
Add support for following redirects in HTTPClient (#4240)
1 parent 837cc3d commit ee88c42

File tree

2 files changed

+172
-17
lines changed

2 files changed

+172
-17
lines changed

Diff for: libraries/HTTPClient/src/HTTPClient.cpp

+145-17
Original file line numberDiff line numberDiff line change
@@ -548,29 +548,106 @@ int HTTPClient::sendRequest(const char * type, String payload)
548548
*/
549549
int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
550550
{
551-
// connect to server
552-
if(!connect()) {
553-
return returnError(HTTPC_ERROR_CONNECTION_REFUSED);
554-
}
551+
int code;
552+
bool redirect = false;
553+
uint16_t redirectCount = 0;
554+
do {
555+
// wipe out any existing headers from previous request
556+
for(size_t i = 0; i < _headerKeysCount; i++) {
557+
if (_currentHeaders[i].value.length() > 0) {
558+
_currentHeaders[i].value.clear();
559+
}
560+
}
555561

556-
if(payload && size > 0) {
557-
addHeader(F("Content-Length"), String(size));
558-
}
562+
log_d("request type: '%s' redirCount: %d\n", type, redirectCount);
563+
564+
// connect to server
565+
if(!connect()) {
566+
return returnError(HTTPC_ERROR_CONNECTION_REFUSED);
567+
}
559568

560-
// send Header
561-
if(!sendHeader(type)) {
562-
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
563-
}
569+
if(payload && size > 0) {
570+
addHeader(F("Content-Length"), String(size));
571+
}
564572

565-
// send Payload if needed
566-
if(payload && size > 0) {
567-
if(_client->write(&payload[0], size) != size) {
568-
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
573+
// send Header
574+
if(!sendHeader(type)) {
575+
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
576+
}
577+
578+
// send Payload if needed
579+
if(payload && size > 0) {
580+
if(_client->write(&payload[0], size) != size) {
581+
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
582+
}
583+
}
584+
585+
code = handleHeaderResponse();
586+
Serial.printf("sendRequest code=%d\n", code);
587+
588+
// Handle redirections as stated in RFC document:
589+
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
590+
//
591+
// Implementing HTTP_CODE_FOUND as redirection with GET method,
592+
// to follow most of existing user agent implementations.
593+
//
594+
redirect = false;
595+
if (
596+
_followRedirects != HTTPC_DISABLE_FOLLOW_REDIRECTS &&
597+
redirectCount < _redirectLimit &&
598+
_location.length() > 0
599+
) {
600+
switch (code) {
601+
// redirecting using the same method
602+
case HTTP_CODE_MOVED_PERMANENTLY:
603+
case HTTP_CODE_TEMPORARY_REDIRECT: {
604+
if (
605+
// allow to force redirections on other methods
606+
// (the RFC require user to accept the redirection)
607+
_followRedirects == HTTPC_FORCE_FOLLOW_REDIRECTS ||
608+
// allow GET and HEAD methods without force
609+
!strcmp(type, "GET") ||
610+
!strcmp(type, "HEAD")
611+
) {
612+
redirectCount += 1;
613+
log_d("following redirect (the same method): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
614+
if (!setURL(_location)) {
615+
log_d("failed setting URL for redirection\n");
616+
// no redirection
617+
break;
618+
}
619+
// redirect using the same request method and payload, diffrent URL
620+
redirect = true;
621+
}
622+
break;
623+
}
624+
// redirecting with method dropped to GET or HEAD
625+
// note: it does not need `HTTPC_FORCE_FOLLOW_REDIRECTS` for any method
626+
case HTTP_CODE_FOUND:
627+
case HTTP_CODE_SEE_OTHER: {
628+
redirectCount += 1;
629+
log_d("following redirect (dropped to GET/HEAD): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
630+
if (!setURL(_location)) {
631+
log_d("failed setting URL for redirection\n");
632+
// no redirection
633+
break;
634+
}
635+
// redirect after changing method to GET/HEAD and dropping payload
636+
type = "GET";
637+
payload = nullptr;
638+
size = 0;
639+
redirect = true;
640+
break;
641+
}
642+
643+
default:
644+
break;
645+
}
569646
}
570-
}
571647

648+
} while (redirect);
572649
// handle Server Response (Header)
573-
return returnError(handleHeaderResponse());
650+
return returnError(code);
574651
}
575652

576653
/**
@@ -1143,6 +1220,10 @@ int HTTPClient::handleHeaderResponse()
11431220
transferEncoding = headerValue;
11441221
}
11451222

1223+
if (headerName.equalsIgnoreCase("Location")) {
1224+
_location = headerValue;
1225+
}
1226+
11461227
for(size_t i = 0; i < _headerKeysCount; i++) {
11471228
if(_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
11481229
_currentHeaders[i].value = headerValue;
@@ -1320,3 +1401,50 @@ int HTTPClient::returnError(int error)
13201401
}
13211402
return error;
13221403
}
1404+
1405+
void HTTPClient::setFollowRedirects(followRedirects_t follow)
1406+
{
1407+
_followRedirects = follow;
1408+
}
1409+
1410+
void HTTPClient::setRedirectLimit(uint16_t limit)
1411+
{
1412+
_redirectLimit = limit;
1413+
}
1414+
1415+
/**
1416+
* set the URL to a new value. Handy for following redirects.
1417+
* @param url
1418+
*/
1419+
bool HTTPClient::setURL(const String& url)
1420+
{
1421+
// if the new location is only a path then only update the URI
1422+
if (url && url[0] == '/') {
1423+
_uri = url;
1424+
clear();
1425+
return true;
1426+
}
1427+
1428+
if (!url.startsWith(_protocol + ':')) {
1429+
log_d("new URL not the same protocol, expected '%s', URL: '%s'\n", _protocol.c_str(), url.c_str());
1430+
return false;
1431+
}
1432+
1433+
// check if the port is specified
1434+
int indexPort = url.indexOf(':', 6); // find the first ':' excluding the one from the protocol
1435+
int indexURI = url.indexOf('/', 7); // find where the URI starts to make sure the ':' is not part of it
1436+
if (indexPort == -1 || indexPort > indexURI) {
1437+
// the port is not specified
1438+
_port = (_protocol == "https" ? 443 : 80);
1439+
}
1440+
1441+
// disconnect but preserve _client (clear _canReuse so disconnect will close the connection)
1442+
_canReuse = false;
1443+
disconnect(true);
1444+
return beginInternal(url, _protocol.c_str());
1445+
}
1446+
1447+
const String &HTTPClient::getLocation(void)
1448+
{
1449+
return _location;
1450+
}

Diff for: libraries/HTTPClient/src/HTTPClient.h

+27
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,24 @@ typedef enum {
119119
HTTPC_TE_CHUNKED
120120
} transferEncoding_t;
121121

122+
/**
123+
* redirection follow mode.
124+
* + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed.
125+
* + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using
126+
* GET or HEAD methods will be redirected (using the same method),
127+
* since the RFC requires end-user confirmation in other cases.
128+
* + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed,
129+
* regardless of a used method. New request will use the same method,
130+
* and they will include the same body data and the same headers.
131+
* In the sense of the RFC, it's just like every redirection is confirmed.
132+
*/
133+
typedef enum {
134+
HTTPC_DISABLE_FOLLOW_REDIRECTS,
135+
HTTPC_STRICT_FOLLOW_REDIRECTS,
136+
HTTPC_FORCE_FOLLOW_REDIRECTS
137+
} followRedirects_t;
138+
139+
122140
#ifdef HTTPCLIENT_1_1_COMPATIBLE
123141
class TransportTraits;
124142
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
@@ -156,6 +174,11 @@ class HTTPClient
156174
void setConnectTimeout(int32_t connectTimeout);
157175
void setTimeout(uint16_t timeout);
158176

177+
// Redirections
178+
void setFollowRedirects(followRedirects_t follow);
179+
void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
180+
181+
bool setURL(const String &url);
159182
void useHTTP10(bool usehttp10 = true);
160183

161184
/// request handling
@@ -182,6 +205,7 @@ class HTTPClient
182205

183206

184207
int getSize(void);
208+
const String &getLocation(void);
185209

186210
WiFiClient& getStream(void);
187211
WiFiClient* getStreamPtr(void);
@@ -235,6 +259,9 @@ class HTTPClient
235259
int _returnCode = 0;
236260
int _size = -1;
237261
bool _canReuse = false;
262+
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
263+
uint16_t _redirectLimit = 10;
264+
String _location;
238265
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
239266
};
240267

0 commit comments

Comments
 (0)