@@ -548,29 +548,106 @@ int HTTPClient::sendRequest(const char * type, String payload)
548
548
*/
549
549
int HTTPClient::sendRequest (const char * type, uint8_t * payload, size_t size)
550
550
{
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
+ }
555
561
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
+ }
559
568
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
+ }
564
572
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
+ }
569
646
}
570
- }
571
647
648
+ } while (redirect);
572
649
// handle Server Response (Header)
573
- return returnError (handleHeaderResponse () );
650
+ return returnError (code );
574
651
}
575
652
576
653
/* *
@@ -1143,6 +1220,10 @@ int HTTPClient::handleHeaderResponse()
1143
1220
transferEncoding = headerValue;
1144
1221
}
1145
1222
1223
+ if (headerName.equalsIgnoreCase (" Location" )) {
1224
+ _location = headerValue;
1225
+ }
1226
+
1146
1227
for (size_t i = 0 ; i < _headerKeysCount; i++) {
1147
1228
if (_currentHeaders[i].key .equalsIgnoreCase (headerName)) {
1148
1229
_currentHeaders[i].value = headerValue;
@@ -1320,3 +1401,50 @@ int HTTPClient::returnError(int error)
1320
1401
}
1321
1402
return error;
1322
1403
}
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