Skip to content

Commit 1805ee0

Browse files
authored
Graceful upgrade path for 0.28. (#3394)
1 parent 41597ad commit 1805ee0

File tree

11 files changed

+155
-434
lines changed

11 files changed

+155
-434
lines changed

CHANGELOG.md

+16-4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## [Unreleased]
88

9-
This release introduces an `httpx.SSLContext()` class and `ssl_context` parameter.
9+
The 0.28 release includes a limited set of backwards incompatible changes.
1010

11-
* Added `httpx.SSLContext` class and `ssl_context` parameter. (#3022, #3335)
12-
* The `verify` and `cert` arguments have been deprecated and will now raise warnings. (#3022, #3335)
11+
**Backwards incompatible changes**:
12+
13+
SSL configuration has been significantly simplified.
14+
15+
* The `verify` argument no longer accepts string arguments.
16+
* The `cert` argument has now been removed.
17+
* The `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables are no longer automatically used.
18+
19+
For users of the standard `verify=True` or `verify=False` cases this should require no changes.
20+
21+
For information on configuring more complex SSL cases, please see the [SSL documentation](docs/advanced/ssl.md).
22+
23+
**The following changes are also included**:
24+
25+
* The undocumented `URL.raw` property has now been deprecated, and will raise warnings.
1326
* The deprecated `proxies` argument has now been removed.
1427
* The deprecated `app` argument has now been removed.
15-
* The `URL.raw` property has now been removed.
1628
* Ensure JSON request bodies are compact. (#3363)
1729
* Review URL percent escape sets, based on WHATWG spec. (#3371, #3373)
1830
* Ensure `certifi` and `httpcore` are only imported if required. (#3377)

docs/advanced/ssl.md

+42-140
Original file line numberDiff line numberDiff line change
@@ -9,191 +9,93 @@ By default httpx will verify HTTPS connections, and raise an error for invalid S
99
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997)
1010
```
1111

12-
Verification is configured through [the SSL Context API](https://docs.python.org/3/library/ssl.html#ssl-contexts).
12+
You can disable SSL verification completely and allow insecure requests...
1313

1414
```pycon
15-
>>> context = httpx.SSLContext()
16-
>>> context
17-
<SSLContext(verify=True)>
18-
>>> httpx.get("https://www.example.com", ssl_context=context)
19-
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997)
20-
```
21-
22-
You can use this to disable verification completely and allow insecure requests...
23-
24-
```pycon
25-
>>> context = httpx.SSLContext(verify=False)
26-
>>> context
27-
<SSLContext(verify=False)>
28-
>>> httpx.get("https://expired.badssl.com/", ssl_context=context)
15+
>>> httpx.get("https://expired.badssl.com/", verify=False)
2916
<Response [200 OK]>
3017
```
3118

3219
### Configuring client instances
3320

34-
If you're using a `Client()` instance you should pass any SSL context when instantiating the client.
35-
36-
```pycon
37-
>>> context = httpx.SSLContext()
38-
>>> client = httpx.Client(ssl_context=context)
39-
```
40-
41-
The `client.get(...)` method and other request methods on a `Client` instance *do not* support changing the SSL settings on a per-request basis.
42-
43-
If you need different SSL settings in different cases you should use more than one client instance, with different settings on each. Each client will then be using an isolated connection pool with a specific fixed SSL configuration on all connections within that pool.
44-
45-
### Configuring certificate stores
46-
47-
By default, HTTPX uses the CA bundle provided by [Certifi](https://pypi.org/project/certifi/).
48-
49-
You can load additional certificate verification using the [`.load_verify_locations()`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations) API:
50-
51-
```pycon
52-
>>> context = httpx.SSLContext()
53-
>>> context.load_verify_locations(cafile="path/to/certs.pem")
54-
>>> client = httpx.Client(ssl_context=context)
55-
>>> client.get("https://www.example.com")
56-
<Response [200 OK]>
57-
```
58-
59-
Or by providing an certificate directory:
60-
61-
```pycon
62-
>>> context = httpx.SSLContext()
63-
>>> context.load_verify_locations(capath="path/to/certs")
64-
>>> client = httpx.Client(ssl_context=context)
65-
>>> client.get("https://www.example.com")
66-
<Response [200 OK]>
67-
```
21+
If you're using a `Client()` instance you should pass any `verify=<...>` configuration when instantiating the client.
6822

69-
### Client side certificates
23+
By default the [certifi CA bundle](https://certifiio.readthedocs.io/en/latest/) is used for SSL verification.
7024

71-
You can also specify a local cert to use as a client-side certificate, using the [`.load_cert_chain()`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain) API:
25+
For more complex configurations you can pass an [SSL Context](https://docs.python.org/3/library/ssl.html) instance...
7226

73-
```pycon
74-
>>> context = httpx.SSLContext()
75-
>>> context.load_cert_chain(certfile="path/to/client.pem")
76-
>>> httpx.get("https://example.org", ssl_context=ssl_context)
77-
<Response [200 OK]>
78-
```
79-
80-
Or including a keyfile...
81-
82-
```pycon
83-
>>> context = httpx.SSLContext()
84-
>>> context.load_cert_chain(
85-
certfile="path/to/client.pem",
86-
keyfile="path/to/client.key"
87-
)
88-
>>> httpx.get("https://example.org", ssl_context=context)
89-
<Response [200 OK]>
90-
```
91-
92-
Or including a keyfile and password...
27+
```python
28+
import certifi
29+
import httpx
30+
import ssl
9331

94-
```pycon
95-
>>> context = httpx.SSLContext(cert=cert)
96-
>>> context = httpx.SSLContext()
97-
>>> context.load_cert_chain(
98-
certfile="path/to/client.pem",
99-
keyfile="path/to/client.key"
100-
password="password"
101-
)
102-
>>> httpx.get("https://example.org", ssl_context=context)
103-
<Response [200 OK]>
32+
# This SSL context is equivelent to the default `verify=True`.
33+
ctx = ssl.create_default_context(cafile=certifi.where())
34+
client = httpx.Client(verify=ctx)
10435
```
10536

106-
### Using alternate SSL contexts
107-
108-
You can also use an alternate `ssl.SSLContext` instances.
109-
110-
For example, [using the `truststore` package](https://truststore.readthedocs.io/)...
37+
Using [the `truststore` package](https://truststore.readthedocs.io/) to support system certificate stores...
11138

11239
```python
11340
import ssl
11441
import truststore
11542
import httpx
11643

117-
ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
118-
client = httpx.Client(ssl_context=ssl_context)
44+
# Use system certificate stores.
45+
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
46+
client = httpx.Client(verify=ctx)
11947
```
12048

121-
Or working [directly with Python's standard library](https://docs.python.org/3/library/ssl.html)...
49+
Loding an alternative certificate verification store using [the standard SSL context API](https://docs.python.org/3/library/ssl.html)...
12250

12351
```python
124-
import ssl
12552
import httpx
53+
import ssl
12654

127-
ssl_context = ssl.create_default_context()
128-
client = httpx.Client(ssl_context=ssl_context)
55+
# Use an explicitly configured certificate store.
56+
ctx = ssl.create_default_context(cafile="path/to/certs.pem") # Either cafile or capath.
57+
client = httpx.Client(verify=ctx)
12958
```
13059

131-
### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR`
60+
### Client side certificates
13261

133-
Unlike `requests`, the `httpx` package does not automatically pull in [the environment variables `SSL_CERT_FILE` or `SSL_CERT_DIR`](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_default_verify_paths.html). If you want to use these they need to be enabled explicitly.
62+
Client side certificates allow a remote server to verify the client. They tend to be used within private organizations to authenticate requests to remote servers.
13463

135-
For example...
64+
You can specify client-side certificates, using the [`.load_cert_chain()`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain) API...
13665

13766
```python
138-
context = httpx.SSLContext()
139-
140-
# Use `SSL_CERT_FILE` or `SSL_CERT_DIR` if configured.
141-
if os.environ.get("SSL_CERT_FILE") or os.environ.get("SSL_CERT_DIR"):
142-
context.load_verify_locations(
143-
cafile=os.environ.get("SSL_CERT_FILE"),
144-
capath=os.environ.get("SSL_CERT_DIR"),
145-
)
67+
ctx = ssl.create_default_context()
68+
ctx.load_cert_chain(certfile="path/to/client.pem") # Optionally also keyfile or password.
69+
client = httpx.Client(verify=ctx)
14670
```
14771

148-
## `SSLKEYLOGFILE`
149-
150-
Valid values: a filename
151-
152-
If this environment variable is set, TLS keys will be appended to the specified file, creating it if it doesn't exist, whenever key material is generated or received. The keylog file is designed for debugging purposes only.
72+
### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR`
15373

154-
Support for `SSLKEYLOGFILE` requires Python 3.8 and OpenSSL 1.1.1 or newer.
74+
Unlike `requests`, the `httpx` package does not automatically pull in [the environment variables `SSL_CERT_FILE` or `SSL_CERT_DIR`](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_default_verify_paths.html). If you want to use these they need to be enabled explicitly.
15575

156-
Example:
76+
For example...
15777

15878
```python
159-
# test_script.py
160-
import httpx
161-
162-
with httpx.Client() as client:
163-
r = client.get("https://google.com")
164-
```
165-
166-
```console
167-
SSLKEYLOGFILE=test.log python test_script.py
168-
cat test.log
169-
# TLS secrets log file, generated by OpenSSL / Python
170-
SERVER_HANDSHAKE_TRAFFIC_SECRET XXXX
171-
EXPORTER_SECRET XXXX
172-
SERVER_TRAFFIC_SECRET_0 XXXX
173-
CLIENT_HANDSHAKE_TRAFFIC_SECRET XXXX
174-
CLIENT_TRAFFIC_SECRET_0 XXXX
175-
SERVER_HANDSHAKE_TRAFFIC_SECRET XXXX
176-
EXPORTER_SECRET XXXX
177-
SERVER_TRAFFIC_SECRET_0 XXXX
178-
CLIENT_HANDSHAKE_TRAFFIC_SECRET XXXX
179-
CLIENT_TRAFFIC_SECRET_0 XXXX
79+
# Use `SSL_CERT_FILE` or `SSL_CERT_DIR` if configured.
80+
# Otherwise default to certifi.
81+
ctx = ssl.create_default_context(
82+
cafile=os.environ.get("SSL_CERT_FILE", certifi.where()),
83+
capath=os.environ.get("SSL_CERT_DIR"),
84+
)
85+
client = httpx.Client(verify=ctx)
18086
```
18187

18288
### Making HTTPS requests to a local server
18389

18490
When making requests to local servers, such as a development server running on `localhost`, you will typically be using unencrypted HTTP connections.
18591

186-
If you do need to make HTTPS connections to a local server, for example to test an HTTPS-only service, you will need to create and use your own certificates. Here's one way to do it:
92+
If you do need to make HTTPS connections to a local server, for example to test an HTTPS-only service, you will need to create and use your own certificates. Here's one way to do it...
18793

18894
1. Use [trustme](https://github.com/python-trio/trustme) to generate a pair of server key/cert files, and a client cert file.
18995
2. Pass the server key/cert files when starting your local server. (This depends on the particular web server you're using. For example, [Uvicorn](https://www.uvicorn.org) provides the `--ssl-keyfile` and `--ssl-certfile` options.)
190-
3. Tell HTTPX to use the certificates stored in `client.pem`:
96+
3. Configure `httpx` to use the certificates stored in `client.pem`.
19197

192-
```pycon
193-
>>> import httpx
194-
>>> context = httpx.SSLContext()
195-
>>> context.load_verify_locations(cafile="/tmp/client.pem")
196-
>>> r = httpx.get("https://localhost:8000", ssl_context=context)
197-
>>> r
198-
Response <200 OK>
98+
```python
99+
ctx = ssl.create_default_context(cafile="client.pem")
100+
client = httpx.Client(verify=ctx)
199101
```

docs/contributing.md

+3-6
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,9 @@ configure HTTPX as described in the
210210
the [SSL certificates section](https://www.python-httpx.org/advanced/ssl/),
211211
this is where our previously generated `client.pem` comes in:
212212

213-
```
214-
import httpx
215-
216-
with httpx.Client(proxy="http://127.0.0.1:8080/", verify="/path/to/client.pem") as client:
217-
response = client.get("https://example.org")
218-
print(response.status_code) # should print 200
213+
```python
214+
ctx = ssl.create_default_context(cafile="/path/to/client.pem")
215+
client = httpx.Client(proxy="http://127.0.0.1:8080/", verify=ctx)
219216
```
220217

221218
Note, however, that HTTPS requests will only succeed to the host specified

httpx/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ def main() -> None: # type: ignore
8181
"RequestNotRead",
8282
"Response",
8383
"ResponseNotRead",
84-
"SSLContext",
8584
"stream",
8685
"StreamClosed",
8786
"StreamConsumed",

0 commit comments

Comments
 (0)