From 522a61ac44fc43a30a7522a31d4b7632a3a59a12 Mon Sep 17 00:00:00 2001 From: Michael Mogenson Date: Wed, 7 Jul 2021 20:59:46 -0400 Subject: [PATCH] Add simple support for redirects Follow 3XX status redirects with a new request. Parse the `location` header for an absolute url, absolute path, or relative path. Replace or amend the url from the original request and place a new request. Convert all keys in the headers dictionary to lowercase to perform a case-insensitive search. There's no limit on the number of consecutive redirects. Since the `requets()` method is now recursive, multiple redirects may crash the stack. Especially on a CircuitPython microcontroller. Expand the requests_simpletest_cpython.py example with new requests utilizing the three supported types of redirects. Note: We're using the httpbingo.org domain for redirects until the following issue on httpbin.org is resolved: https://github.com/postmanlabs/httpbin/issues/617 This has been tested on CPython 3.8 and an ESP32-S2 based MagTag board running CircuitPython 6.0. --- adafruit_requests.py | 36 +++++++++++++++++-------- examples/requests_simpletest_cpython.py | 26 ++++++++++++++++++ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/adafruit_requests.py b/adafruit_requests.py index 3e681ac..a00670e 100644 --- a/adafruit_requests.py +++ b/adafruit_requests.py @@ -281,18 +281,12 @@ def _parse_headers(self): content = self._readto(b"\r\n") if title and content: - title = str(title, "utf-8") + # enforce that all headers are lowercase + title = str(title, "utf-8").lower() content = str(content, "utf-8") - # Check len first so we can skip the .lower allocation most of the time. - if ( - len(title) == len("content-length") - and title.lower() == "content-length" - ): + if title == "content-length": self._remaining = int(content) - if ( - len(title) == len("transfer-encoding") - and title.lower() == "transfer-encoding" - ): + if title == "transfer-encoding": self._chunked = content.strip().lower() == "chunked" self._headers[title] = content @@ -587,7 +581,27 @@ def request( resp = Response(socket, self) # our response if "location" in resp.headers and 300 <= resp.status_code <= 399: - raise NotImplementedError("Redirects not yet supported") + # a naive handler for redirects + redirect = resp.headers["location"] + + if redirect.startswith("http"): + # absolute URL + url = redirect + elif redirect[0] == "/": + # relative URL, absolute path + url = "/".join([proto, dummy, host, redirect[1:]]) + else: + # relative URL, relative path + path = path.rsplit("/", 1)[0] + + while redirect.startswith("../"): + path = path.rsplit("/", 1)[0] + redirect = redirect.split("../", 1)[1] + + url = "/".join([proto, dummy, host, path, redirect]) + + self._last_response = resp + resp = self.request(method, url, data, json, headers, stream, timeout) self._last_response = resp return resp diff --git a/examples/requests_simpletest_cpython.py b/examples/requests_simpletest_cpython.py index 923ed03..be51d57 100755 --- a/examples/requests_simpletest_cpython.py +++ b/examples/requests_simpletest_cpython.py @@ -10,6 +10,9 @@ TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html" JSON_GET_URL = "http://httpbin.org/get" JSON_POST_URL = "http://httpbin.org/post" +REDIRECT_URL = "http://httpbingo.org/redirect/1" +RELATIVE_REDIRECT_URL = "http://httpbingo.org/relative-redirect/1" +ABSOLUTE_REDIRECT_URL = "http://httpbingo.org/absolute-redirect/1" print("Fetching text from %s" % TEXT_URL) response = http.get(TEXT_URL) @@ -45,3 +48,26 @@ # Parse out the 'json' key from json_resp dict. print("JSON Data received from server:", json_resp["json"]) print("-" * 40) + +print("Fetching JSON data from redirect url %s" % REDIRECT_URL) +response = http.get(REDIRECT_URL) +print("-" * 40) + +print("JSON Response: ", response.json()) +print("-" * 40) + +print("Fetching JSON data from relative redirect url %s" % RELATIVE_REDIRECT_URL) +response = http.get(RELATIVE_REDIRECT_URL) +print("-" * 40) + +print("JSON Response: ", response.json()) +print("-" * 40) + +print("Fetching JSON data from aboslute redirect url %s" % ABSOLUTE_REDIRECT_URL) +response = http.get(ABSOLUTE_REDIRECT_URL) +print("-" * 40) + +print("JSON Response: ", response.json()) +print("-" * 40) + +response.close()