Skip to content

Commit 3faa4a8

Browse files
authored
Improve 'Custom transports' docs (#3081)
1 parent c51af4b commit 3faa4a8

File tree

1 file changed

+69
-16
lines changed

1 file changed

+69
-16
lines changed

docs/advanced/transports.md

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ HTTPX's `Client` also accepts a `transport` argument. This argument allows you
22
to provide a custom Transport object that will be used to perform the actual
33
sending of the requests.
44

5-
## HTTPTransport
5+
## HTTP Transport
66

77
For some advanced configuration you might need to instantiate a transport
88
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:
8383
...
8484
```
8585

86-
## ASGITransport
86+
## ASGI Transport
8787

8888
You can configure an `httpx` client to call directly into an async Python web application using the ASGI protocol.
8989

@@ -148,7 +148,7 @@ However it is suggested to use `LifespanManager` from [asgi-lifespan](https://gi
148148

149149
## Custom transports
150150

151-
A transport instance must implement the low-level Transport API, which deals
151+
A transport instance must implement the low-level Transport API which deals
152152
with sending a single request, and returning a response. You should either
153153
subclass `httpx.BaseTransport` to implement a transport to use with `Client`,
154154
or subclass `httpx.AsyncBaseTransport` to implement a transport to
@@ -166,28 +166,81 @@ A complete example of a custom transport implementation would be:
166166
import json
167167
import httpx
168168

169-
170169
class HelloWorldTransport(httpx.BaseTransport):
171170
"""
172171
A mock transport that always returns a JSON "Hello, world!" response.
173172
"""
174173

175174
def handle_request(self, request):
176-
message = {"text": "Hello, world!"}
177-
content = json.dumps(message).encode("utf-8")
178-
stream = httpx.ByteStream(content)
179-
headers = [(b"content-type", b"application/json")]
180-
return httpx.Response(200, headers=headers, stream=stream)
175+
return httpx.Response(200, json={"text": "Hello, world!"})
181176
```
182177

183-
Which we can use in the same way:
178+
Or this example, which uses a custom transport and `httpx.Mounts` to always redirect `http://` requests.
184179

185-
```pycon
186-
>>> import httpx
187-
>>> client = httpx.Client(transport=HelloWorldTransport())
188-
>>> response = client.get("https://example.org/")
189-
>>> response.json()
190-
{"text": "Hello, world!"}
180+
```python
181+
class HTTPSRedirect(httpx.BaseTransport):
182+
"""
183+
A transport that always redirects to HTTPS.
184+
"""
185+
def handle_request(self, request):
186+
url = request.url.copy_with(scheme="https")
187+
return httpx.Response(303, headers={"Location": str(url)})
188+
189+
# A client where any `http` requests are always redirected to `https`
190+
transport = httpx.Mounts({
191+
'http://': HTTPSRedirect()
192+
'https://': httpx.HTTPTransport()
193+
})
194+
client = httpx.Client(transport=transport)
195+
```
196+
197+
A useful pattern here is custom transport classes that wrap the default HTTP implementation. For example...
198+
199+
```python
200+
class DebuggingTransport(httpx.BaseTransport):
201+
def __init__(self, **kwargs):
202+
self._wrapper = httpx.HTTPTransport(**kwargs)
203+
204+
def handle_request(self, request):
205+
print(f">>> {request}")
206+
response = self._wrapper.handle_request(request)
207+
print(f"<<< {response}")
208+
return response
209+
210+
def close(self):
211+
self._wrapper.close()
212+
213+
transport = DebuggingTransport()
214+
client = httpx.Client(transport=transport)
215+
```
216+
217+
Here's another case, where we're using a round-robin across a number of different proxies...
218+
219+
```python
220+
class ProxyRoundRobin(httpx.BaseTransport):
221+
def __init__(self, proxies, **kwargs):
222+
self._transports = [
223+
httpx.HTTPTransport(proxy=proxy, **kwargs)
224+
for proxy in proxies
225+
]
226+
self._idx = 0
227+
228+
def handle_request(self, request):
229+
transport = self._transports[self._idx]
230+
self._idx = (self._idx + 1) % len(self._transports)
231+
return transport.handle_request(request)
232+
233+
def close(self):
234+
for transport in self._transports:
235+
transport.close()
236+
237+
proxies = [
238+
httpx.Proxy("http://127.0.0.1:8081"),
239+
httpx.Proxy("http://127.0.0.1:8082"),
240+
httpx.Proxy("http://127.0.0.1:8083"),
241+
]
242+
transport = ProxyRoundRobin(proxies=proxies)
243+
client = httpx.Client(transport=transport)
191244
```
192245

193246
## Mock transports

0 commit comments

Comments
 (0)