]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Improve 'Custom transports' docs (#3081)
authorTom Christie <tom@tomchristie.com>
Wed, 14 Feb 2024 11:14:02 +0000 (11:14 +0000)
committerGitHub <noreply@github.com>
Wed, 14 Feb 2024 11:14:02 +0000 (11:14 +0000)
docs/advanced/transports.md

index 7e0e21c6f9644caf0da8d1a4291463b4d3422af9..d4e7615d380b94a269babb492ac2dbfe2e39d57e 100644 (file)
@@ -2,7 +2,7 @@ HTTPX's `Client` also accepts a `transport` argument. This argument allows you
 to provide a custom Transport object that will be used to perform the actual
 sending of the requests.
 
-## HTTPTransport
+## HTTP Transport
 
 For some advanced configuration you might need to instantiate a transport
 class directly, and pass it to the client instance. One example is the
@@ -83,7 +83,7 @@ with httpx.Client(transport=transport, base_url="http://testserver") as client:
     ...
 ```
 
-## ASGITransport
+## ASGI Transport
 
 You can configure an `httpx` client to call directly into an async Python web application using the ASGI protocol.
 
@@ -148,7 +148,7 @@ However it is suggested to use `LifespanManager` from [asgi-lifespan](https://gi
 
 ## Custom transports
 
-A transport instance must implement the low-level Transport API, which deals
+A transport instance must implement the low-level Transport API which deals
 with sending a single request, and returning a response. You should either
 subclass `httpx.BaseTransport` to implement a transport to use with `Client`,
 or subclass `httpx.AsyncBaseTransport` to implement a transport to
@@ -166,28 +166,81 @@ A complete example of a custom transport implementation would be:
 import json
 import httpx
 
-
 class HelloWorldTransport(httpx.BaseTransport):
     """
     A mock transport that always returns a JSON "Hello, world!" response.
     """
 
     def handle_request(self, request):
-        message = {"text": "Hello, world!"}
-        content = json.dumps(message).encode("utf-8")
-        stream = httpx.ByteStream(content)
-        headers = [(b"content-type", b"application/json")]
-        return httpx.Response(200, headers=headers, stream=stream)
+        return httpx.Response(200, json={"text": "Hello, world!"})
 ```
 
-Which we can use in the same way:
+Or this example, which uses a custom transport and `httpx.Mounts` to always redirect `http://` requests.
 
-```pycon
->>> import httpx
->>> client = httpx.Client(transport=HelloWorldTransport())
->>> response = client.get("https://example.org/")
->>> response.json()
-{"text": "Hello, world!"}
+```python
+class HTTPSRedirect(httpx.BaseTransport):
+    """
+    A transport that always redirects to HTTPS.
+    """
+    def handle_request(self, request):
+        url = request.url.copy_with(scheme="https")
+        return httpx.Response(303, headers={"Location": str(url)})
+
+# A client where any `http` requests are always redirected to `https`
+transport = httpx.Mounts({
+    'http://': HTTPSRedirect()
+    'https://': httpx.HTTPTransport()
+})
+client = httpx.Client(transport=transport)
+```
+
+A useful pattern here is custom transport classes that wrap the default HTTP implementation. For example...
+
+```python
+class DebuggingTransport(httpx.BaseTransport):
+    def __init__(self, **kwargs):
+        self._wrapper = httpx.HTTPTransport(**kwargs)
+
+    def handle_request(self, request):
+        print(f">>> {request}")
+        response = self._wrapper.handle_request(request)
+        print(f"<<< {response}")
+        return response
+
+    def close(self):
+        self._wrapper.close()
+
+transport = DebuggingTransport()
+client = httpx.Client(transport=transport)
+```
+
+Here's another case, where we're using a round-robin across a number of different proxies...
+
+```python
+class ProxyRoundRobin(httpx.BaseTransport):
+    def __init__(self, proxies, **kwargs):
+        self._transports = [
+            httpx.HTTPTransport(proxy=proxy, **kwargs)
+            for proxy in proxies
+        ]
+        self._idx = 0
+
+    def handle_request(self, request):
+        transport = self._transports[self._idx]
+        self._idx = (self._idx + 1) % len(self._transports)
+        return transport.handle_request(request)
+
+    def close(self):
+        for transport in self._transports:
+            transport.close()
+
+proxies = [
+    httpx.Proxy("http://127.0.0.1:8081"),
+    httpx.Proxy("http://127.0.0.1:8082"),
+    httpx.Proxy("http://127.0.0.1:8083"),
+]
+transport = ProxyRoundRobin(proxies=proxies)
+client = httpx.Client(transport=transport)
 ```
 
 ## Mock transports