@@ -548,29 +548,106 @@ int HTTPClient::sendRequest(const char * type, String payload)
548548 */
549549int 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+ }
0 commit comments