Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,20 +141,6 @@ make modifications before sending the request.
<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
Expand Down Expand Up @@ -367,7 +353,7 @@ More specifically, if a tuple is used as a value, it must have between 2 and 3 e
- 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".
Expand Down
60 changes: 60 additions & 0 deletions docs/http2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 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".
```
5 changes: 3 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ HTTPX is a high performance asynchronous HTTP client, that builds on the
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.
Expand Down Expand Up @@ -92,7 +92,8 @@ Plus all the standard features of `requests`...

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.

Expand Down
4 changes: 0 additions & 4 deletions httpx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
from .config import (
USER_AGENT,
CertTypes,
HTTPVersionConfig,
HTTPVersionTypes,
PoolLimits,
SSLConfig,
TimeoutConfig,
Expand Down Expand Up @@ -118,8 +116,6 @@
"StatusCode",
"codes",
"TimeoutTypes",
"HTTPVersionTypes",
"HTTPVersionConfig",
"AuthTypes",
"Cookies",
"CookieTypes",
Expand Down
6 changes: 1 addition & 5 deletions httpx/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,7 @@ async def request(
```
"""
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,
Expand Down
17 changes: 8 additions & 9 deletions httpx/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
DEFAULT_POOL_LIMITS,
DEFAULT_TIMEOUT_CONFIG,
CertTypes,
HTTPVersionTypes,
PoolLimits,
TimeoutTypes,
VerifyTypes,
Expand Down Expand Up @@ -79,8 +78,8 @@ class Client:
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
Expand Down Expand Up @@ -111,7 +110,7 @@ def __init__(
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,
Expand All @@ -134,7 +133,7 @@ def __init__(
verify=verify,
cert=cert,
timeout=timeout,
http_versions=http_versions,
http_2=http_2,
pool_limits=pool_limits,
backend=backend,
trust_env=trust_env,
Expand Down Expand Up @@ -167,7 +166,7 @@ def __init__(
verify=verify,
cert=cert,
timeout=timeout,
http_versions=http_versions,
http_2=http_2,
pool_limits=pool_limits,
backend=backend,
trust_env=trust_env,
Expand Down Expand Up @@ -833,13 +832,13 @@ def _proxies_to_dispatchers(
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(
Expand All @@ -850,7 +849,7 @@ def _proxy_from_url(url: URLTypes) -> Dispatcher:
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}")

Expand Down
82 changes: 11 additions & 71 deletions httpx/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,10 @@
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",
Expand Down Expand Up @@ -91,43 +86,35 @@ def with_overrides(
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.
"""
Expand All @@ -146,7 +133,7 @@ def load_ssl_context_verify(
"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

Expand Down Expand Up @@ -175,9 +162,7 @@ def load_ssl_context_verify(

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.
Expand All @@ -191,7 +176,8 @@ def _create_default_ssl_context(
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")
Expand Down Expand Up @@ -288,52 +274,6 @@ def __repr__(self) -> str:
)


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.
Expand Down
10 changes: 3 additions & 7 deletions httpx/dispatch/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
from ..config import (
DEFAULT_TIMEOUT_CONFIG,
CertTypes,
HTTPVersionConfig,
HTTPVersionTypes,
SSLConfig,
TimeoutConfig,
TimeoutTypes,
Expand All @@ -35,15 +33,15 @@ def __init__(
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
Expand Down Expand Up @@ -119,9 +117,7 @@ async def get_ssl_context(self, ssl: SSLConfig) -> typing.Optional[ssl.SSLContex
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")
Expand Down
Loading