<Response [200 OK]>
```
-## Specify the version of the HTTP protocol
-
-One can set the version of the HTTP protocol for the client in case you want to make the requests using a specific version.
-
-For example:
-
-```python
-async with httpx.Client(http_versions=["HTTP/1.1"]) as h11_client:
- h11_response = await h11_client.get("https://myserver.com")
-
-async with httpx.Client(http_versions=["HTTP/2"]) as h2_client:
- h2_response = await h2_client.get("https://myserver.com")
-```
-
## .netrc Support
HTTPX supports .netrc file. In `trust_env=True` cases, if auth parameter is
- The first element is an optional file name which can be set to `None`.
- The second element may be a file-like object or a string which will be automatically
encoded in UTF-8.
-- An optional third element can be used to specify the
+- An optional third element can be used to specify the
[MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_Types)
of the file being uploaded. If not specified HTTPX will attempt to guess the MIME type based
on the file name, with unknown file extensions defaulting to "application/octet-stream".
--- /dev/null
+# HTTP/2
+
+HTTP/2 is a major new iteration of the HTTP protocol, that provides a far more
+efficient transport, with potential performance benefits. HTTP/2 does not change
+the core semantics of the request or response, but alters the way that data is
+sent to and from the server.
+
+Rather that the text format that HTTP/1.1 uses, HTTP/2 is a binary format.
+The binary format provides full request and response multiplexing, and efficient
+compression of HTTP headers. The stream multiplexing means that where HTTP/1.1
+requires one TCP stream for each concurrent request, HTTP/2 allows a single TCP
+stream to handle multiple concurrent requests.
+
+HTTP/2 also provides support for functionality such as response prioritization,
+and server push.
+
+For a comprehensive guide to HTTP/2 you may want to check out "[HTTP2 Explained](https://http2-explained.haxx.se/content/en/)".
+
+## Enabling HTTP/2
+
+The HTTPX client provides provisional HTTP/2 support.
+
+HTTP/2 support is not enabled by default, because HTTP/1.1 is a mature,
+battle-hardened transport layer. With HTTP/2 being newer and significantly more
+complex, our implementation should be considered a less robust option at this
+point in time.
+
+However, if you're issuing highly concurrent requests you might want to consider
+trying out our HTTP/2 support. You can do so by instantiating a client with
+HTTP/2 support enabled:
+
+```python
+client = httpx.Client(http_2=True)
+...
+```
+
+You can also instantiate a client as a context manager, to ensure that all
+HTTP connections are nicely scoped, and will be closed once the context block
+is exited.
+
+```python
+async with httpx.Client(http_2=True) as client:
+ ...
+```
+
+## Inspecting the HTTP version
+
+Enabling HTTP/2 support on the client does not *necessarily* mean that your
+requests and responses will be transported over HTTP/2, since both the client
+*and* the server need to support HTTP/2. If you connect to a server that only
+supports HTTP/1.1 the client will use a standard HTTP/1.1 connection instead.
+
+You can determine which version of the HTTP protocol was used by examining
+the `.http_version` property on the response.
+
+```python
+client = httpx.Client(http_2=True)
+response = await client.get(...)
+print(response.http_version) # "HTTP/1.0", "HTTP/1.1", or "HTTP/2".
+```
well-established usability of `requests`, and gives you:
* A broadly requests-compatible API.
-* HTTP/2 and HTTP/1.1 support.
+* HTTP/1.1 and [HTTP/2 support](http2.md).
* Ability to [make requests directly to ASGI applications](advanced.md#calling-into-python-web-apps).
* Strict timeouts everywhere.
* Fully type annotated.
For a run-through of all the basics, head over to the [QuickStart](quickstart.md).
-For more advanced topics, see the [Advanced Usage](advanced.md) section.
+For more advanced topics, see the [Advanced Usage](advanced.md) section,
+or the [HTTP/2](http2.md) section.
The [Developer Interface](api.md) provides a comprehensive API reference.
from .config import (
USER_AGENT,
CertTypes,
- HTTPVersionConfig,
- HTTPVersionTypes,
PoolLimits,
SSLConfig,
TimeoutConfig,
"StatusCode",
"codes",
"TimeoutTypes",
- "HTTPVersionTypes",
- "HTTPVersionConfig",
"AuthTypes",
"Cookies",
"CookieTypes",
```
"""
async with Client(
- http_versions=["HTTP/1.1"],
- cert=cert,
- verify=verify,
- timeout=timeout,
- trust_env=trust_env,
+ cert=cert, verify=verify, timeout=timeout, trust_env=trust_env,
) as client:
return await client.request(
method=method,
DEFAULT_POOL_LIMITS,
DEFAULT_TIMEOUT_CONFIG,
CertTypes,
- HTTPVersionTypes,
PoolLimits,
TimeoutTypes,
VerifyTypes,
to authenticate the client. Either a path to an SSL certificate file, or
two-tuple of (certificate file, key file), or a three-tuple of (certificate
file, key file, password).
- * **http_versions** - *(optional)* A list of strings of HTTP protocol
- versions to use when sending requests. eg. `http_versions=["HTTP/1.1"]`
+ * **http_2** - *(optional)* A boolean indicating if HTTP/2 support should be
+ enabled. Defaults to `False`.
* **proxies** - *(optional)* A dictionary mapping HTTP protocols to proxy
URLs.
* **timeout** - *(optional)* The timeout configuration to use when sending
cookies: CookieTypes = None,
verify: VerifyTypes = True,
cert: CertTypes = None,
- http_versions: HTTPVersionTypes = None,
+ http_2: bool = False,
proxies: ProxiesTypes = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,
verify=verify,
cert=cert,
timeout=timeout,
- http_versions=http_versions,
+ http_2=http_2,
pool_limits=pool_limits,
backend=backend,
trust_env=trust_env,
verify=verify,
cert=cert,
timeout=timeout,
- http_versions=http_versions,
+ http_2=http_2,
pool_limits=pool_limits,
backend=backend,
trust_env=trust_env,
verify: VerifyTypes,
cert: typing.Optional[CertTypes],
timeout: TimeoutTypes,
- http_versions: typing.Optional[HTTPVersionTypes],
+ http_2: bool,
pool_limits: PoolLimits,
backend: ConcurrencyBackend,
trust_env: bool,
) -> typing.Dict[str, Dispatcher]:
def _proxy_from_url(url: URLTypes) -> Dispatcher:
- nonlocal verify, cert, timeout, http_versions, pool_limits, backend, trust_env
+ nonlocal verify, cert, timeout, http_2, pool_limits, backend, trust_env
url = URL(url)
if url.scheme in ("http", "https"):
return HTTPProxy(
pool_limits=pool_limits,
backend=backend,
trust_env=trust_env,
- http_versions=http_versions,
+ http_2=http_2,
)
raise ValueError(f"Unknown proxy for {url!r}")
TimeoutTypes = typing.Union[
float, typing.Tuple[float, float, float, float], "TimeoutConfig"
]
-HTTPVersionTypes = typing.Union[
- str, typing.List[str], typing.Tuple[str], "HTTPVersionConfig"
-]
USER_AGENT = f"python-httpx/{__version__}"
-HTTP_VERSIONS_TO_ALPN_IDENTIFIERS = {"HTTP/1.1": "http/1.1", "HTTP/2": "h2"}
-
DEFAULT_CIPHERS = ":".join(
[
"ECDHE+AESGCM",
return self
return SSLConfig(cert=cert, verify=verify)
- def load_ssl_context(
- self, http_versions: "HTTPVersionConfig" = None
- ) -> ssl.SSLContext:
- http_versions = HTTPVersionConfig() if http_versions is None else http_versions
-
+ def load_ssl_context(self, http_2: bool = False) -> ssl.SSLContext:
logger.trace(
f"load_ssl_context "
f"verify={self.verify!r} "
f"cert={self.cert!r} "
f"trust_env={self.trust_env!r} "
- f"http_versions={http_versions!r}"
+ f"http_2={http_2!r}"
)
if self.ssl_context is None:
self.ssl_context = (
- self.load_ssl_context_verify(http_versions=http_versions)
+ self.load_ssl_context_verify(http_2=http_2)
if self.verify
- else self.load_ssl_context_no_verify(http_versions=http_versions)
+ else self.load_ssl_context_no_verify(http_2=http_2)
)
assert self.ssl_context is not None
return self.ssl_context
- def load_ssl_context_no_verify(
- self, http_versions: "HTTPVersionConfig"
- ) -> ssl.SSLContext:
+ def load_ssl_context_no_verify(self, http_2: bool = False) -> ssl.SSLContext:
"""
Return an SSL context for unverified connections.
"""
- context = self._create_default_ssl_context(http_versions=http_versions)
+ context = self._create_default_ssl_context(http_2=http_2)
context.verify_mode = ssl.CERT_NONE
context.check_hostname = False
return context
- def load_ssl_context_verify(
- self, http_versions: "HTTPVersionConfig"
- ) -> ssl.SSLContext:
+ def load_ssl_context_verify(self, http_2: bool = False) -> ssl.SSLContext:
"""
Return an SSL context for verified connections.
"""
"invalid path: {}".format(self.verify)
)
- context = self._create_default_ssl_context(http_versions=http_versions)
+ context = self._create_default_ssl_context(http_2=http_2)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
return context
- def _create_default_ssl_context(
- self, http_versions: "HTTPVersionConfig"
- ) -> ssl.SSLContext:
+ def _create_default_ssl_context(self, http_2: bool) -> ssl.SSLContext:
"""
Creates the default SSLContext object that's used for both verified
and unverified connections.
context.set_ciphers(DEFAULT_CIPHERS)
if ssl.HAS_ALPN:
- context.set_alpn_protocols(http_versions.alpn_identifiers)
+ alpn_idents = ["http/1.1", "h2"] if http_2 else ["http/1.1"]
+ context.set_alpn_protocols(alpn_idents)
if hasattr(context, "keylog_filename"):
keylogfile = os.environ.get("SSLKEYLOGFILE")
)
-class HTTPVersionConfig:
- """
- Configure which HTTP protocol versions are supported.
- """
-
- def __init__(self, http_versions: HTTPVersionTypes = None):
- if http_versions is None:
- http_versions = ["HTTP/1.1", "HTTP/2"]
-
- if isinstance(http_versions, str):
- self.http_versions = {http_versions.upper()}
- elif isinstance(http_versions, HTTPVersionConfig):
- self.http_versions = http_versions.http_versions
- elif isinstance(http_versions, typing.Iterable):
- self.http_versions = {
- version.upper() if isinstance(version, str) else version
- for version in http_versions
- }
- else:
- raise TypeError(
- "HTTP version should be a string or list of strings, "
- f"but got {type(http_versions)}"
- )
-
- for version in self.http_versions:
- if version not in ("HTTP/1.1", "HTTP/2"):
- raise ValueError(f"Unsupported HTTP version {version!r}.")
-
- if not self.http_versions:
- raise ValueError("HTTP versions cannot be an empty list.")
-
- @property
- def alpn_identifiers(self) -> typing.List[str]:
- """
- Returns a list of supported ALPN identifiers. (One or more of "http/1.1", "h2").
- """
- return [
- HTTP_VERSIONS_TO_ALPN_IDENTIFIERS[version] for version in self.http_versions
- ]
-
- def __repr__(self) -> str:
- class_name = self.__class__.__name__
- value = sorted(list(self.http_versions))
- return f"{class_name}({value!r})"
-
-
class PoolLimits:
"""
Limits on the number of connections in a connection pool.
from ..config import (
DEFAULT_TIMEOUT_CONFIG,
CertTypes,
- HTTPVersionConfig,
- HTTPVersionTypes,
SSLConfig,
TimeoutConfig,
TimeoutTypes,
cert: CertTypes = None,
trust_env: bool = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
- http_versions: HTTPVersionTypes = None,
+ http_2: bool = False,
backend: ConcurrencyBackend = None,
release_func: typing.Optional[ReleaseCallback] = None,
uds: typing.Optional[str] = None,
self.origin = Origin(origin) if isinstance(origin, str) else origin
self.ssl = SSLConfig(cert=cert, verify=verify, trust_env=trust_env)
self.timeout = TimeoutConfig(timeout)
- self.http_versions = HTTPVersionConfig(http_versions)
+ self.http_2 = http_2
self.backend = AsyncioBackend() if backend is None else backend
self.release_func = release_func
self.uds = uds
return None
# Run the SSL loading in a threadpool, since it may make disk accesses.
- return await self.backend.run_in_threadpool(
- ssl.load_ssl_context, self.http_versions
- )
+ return await self.backend.run_in_threadpool(ssl.load_ssl_context, self.http_2)
async def close(self) -> None:
logger.trace("close_connection")
DEFAULT_POOL_LIMITS,
DEFAULT_TIMEOUT_CONFIG,
CertTypes,
- HTTPVersionTypes,
PoolLimits,
TimeoutConfig,
TimeoutTypes,
trust_env: bool = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,
- http_versions: HTTPVersionTypes = None,
+ http_2: bool = False,
backend: ConcurrencyBackend = None,
uds: typing.Optional[str] = None,
):
self.cert = cert
self.timeout = TimeoutConfig(timeout)
self.pool_limits = pool_limits
- self.http_versions = http_versions
+ self.http_2 = http_2
self.is_closed = False
self.trust_env = trust_env
self.uds = uds
verify=self.verify,
cert=self.cert,
timeout=self.timeout,
- http_versions=self.http_versions,
+ http_2=self.http_2,
backend=self.backend,
release_func=self.release_connection,
trust_env=self.trust_env,
DEFAULT_POOL_LIMITS,
DEFAULT_TIMEOUT_CONFIG,
CertTypes,
- HTTPVersionTypes,
PoolLimits,
SSLConfig,
TimeoutTypes,
trust_env: bool = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,
- http_versions: HTTPVersionTypes = None,
+ http_2: bool = False,
backend: ConcurrencyBackend = None,
):
pool_limits=pool_limits,
backend=backend,
trust_env=trust_env,
- http_versions=http_versions,
+ http_2=http_2,
)
self.proxy_url = URL(proxy_url)
cert=self.cert,
timeout=self.timeout,
backend=self.backend,
- http_versions=["HTTP/1.1"], # Short-lived 'connection'
+ http_2=False, # Short-lived 'connection'
trust_env=self.trust_env,
release_func=self.release_connection,
)
- Introduction: 'index.md'
- QuickStart: 'quickstart.md'
- Advanced Usage: 'advanced.md'
+ - HTTP/2 Support: 'http2.md'
- Environment Variables: 'environment_variables.md'
- Requests Compatibility: 'compatibility.md'
- Developer Interface: 'api.md'
cert="/path/to/cert",
trust_env=False,
timeout=30,
- http_versions=["HTTP/1.1"],
)
pool = client.dispatch
proxy = client.proxies["all"]
"cert",
"timeout",
"pool_limits",
- "http_versions",
"backend",
]:
assert getattr(pool, prop) == getattr(proxy, prop)
async def test_http2_get_request():
backend = MockHTTP2Backend(app=app)
- async with Client(backend=backend) as client:
+ async with Client(backend=backend, http_2=True) as client:
response = await client.get("http://example.org")
assert response.status_code == 200
async def test_http2_post_request():
backend = MockHTTP2Backend(app=app)
- async with Client(backend=backend) as client:
+ async with Client(backend=backend, http_2=True) as client:
response = await client.post("http://example.org", data=b"<data>")
assert response.status_code == 200
backend = MockHTTP2Backend(app=app)
data = b"a" * 100000
- async with Client(backend=backend) as client:
+ async with Client(backend=backend, http_2=True) as client:
response = await client.post("http://example.org", data=data)
assert response.status_code == 200
assert json.loads(response.content) == {
async def test_http2_multiple_requests():
backend = MockHTTP2Backend(app=app)
- async with Client(backend=backend) as client:
+ async with Client(backend=backend, http_2=True) as client:
response_1 = await client.get("http://example.org/1")
response_2 = await client.get("http://example.org/2")
response_3 = await client.get("http://example.org/3")
"""
backend = MockHTTP2Backend(app=app)
- async with Client(backend=backend) as client:
+ async with Client(backend=backend, http_2=True) as client:
response_1 = await client.get("http://example.org/1")
backend.server.close_connection = True
response_2 = await client.get("http://example.org/2")
async def test_http2_settings_in_handshake(backend):
backend = MockHTTP2Backend(app=app, backend=backend)
- async with Client(backend=backend) as client:
+ async with Client(backend=backend, http_2=True) as client:
await client.get("http://example.org")
h2_conn = backend.server.conn
async def test_http2_live_request(backend):
- async with Client(backend=backend) as client:
+ async with Client(backend=backend, http_2=True) as client:
try:
resp = await client.get("https://nghttp2.org/httpbin/anything")
except Timeout:
import pytest
import trio
-from httpx import AsyncioBackend, HTTPVersionConfig, SSLConfig, TimeoutConfig
+from httpx import AsyncioBackend, SSLConfig, TimeoutConfig
from httpx.concurrency.trio import TrioBackend
from tests.concurrency import run_concurrently
],
)
async def test_start_tls_on_tcp_socket_stream(https_server, backend, get_cipher):
- ctx = SSLConfig().load_ssl_context_no_verify(HTTPVersionConfig())
+ ctx = SSLConfig().load_ssl_context_no_verify()
timeout = TimeoutConfig(5)
stream = await backend.open_tcp_stream(
],
)
async def test_start_tls_on_uds_socket_stream(https_uds_server, backend, get_cipher):
- ctx = SSLConfig().load_ssl_context_no_verify(HTTPVersionConfig())
+ ctx = SSLConfig().load_ssl_context_no_verify()
timeout = TimeoutConfig(5)
stream = await backend.open_uds_stream(
assert repr(ssl) == "SSLConfig(cert=None, verify=False)"
-def test_http_versions_repr():
- http_versions = httpx.HTTPVersionConfig()
- assert repr(http_versions) == "HTTPVersionConfig(['HTTP/1.1', 'HTTP/2'])"
-
-
-def test_http_versions_from_string():
- http_versions = httpx.HTTPVersionConfig("HTTP/1.1")
- assert repr(http_versions) == "HTTPVersionConfig(['HTTP/1.1'])"
-
-
-def test_http_versions_from_list():
- http_versions = httpx.HTTPVersionConfig(["http/1.1"])
- assert repr(http_versions) == "HTTPVersionConfig(['HTTP/1.1'])"
-
-
-def test_http_versions_from_config():
- http_versions = httpx.HTTPVersionConfig(httpx.HTTPVersionConfig("HTTP/1.1"))
- assert repr(http_versions) == "HTTPVersionConfig(['HTTP/1.1'])"
-
-
-def test_invalid_http_version():
- with pytest.raises(ValueError):
- httpx.HTTPVersionConfig("HTTP/9")
-
-
-def test_invalid_http_version_type():
- with pytest.raises(TypeError):
- httpx.HTTPVersionConfig(123)
-
-
-def test_invalid_http_version_list_type():
- with pytest.raises(ValueError):
- httpx.HTTPVersionConfig([123])
-
-
-def test_empty_http_version():
- with pytest.raises(ValueError):
- httpx.HTTPVersionConfig([])
-
-
def test_limits_repr():
limits = httpx.PoolLimits(hard_limit=100)
assert repr(limits) == "PoolLimits(soft_limit=None, hard_limit=100)"
CertTypes,
Client,
Dispatcher,
- HTTPVersionTypes,
Request,
Response,
TimeoutTypes,
verify: VerifyTypes = None,
cert: CertTypes = None,
timeout: TimeoutTypes = None,
- http_versions: HTTPVersionTypes = None,
) -> Response:
content = await request.read()
return Response(200, content=content)