Advanced Usage

Client Instances

Using a Client instance to make requests will give you HTTP connection pooling, will provide cookie persistence, and allows you to apply configuration across all outgoing requests.

Hint

A Client instance is equivalent to a Session instance in requests.

Note

Starting from httpx==0.10.0, the default and recommended Client class is AsyncClient. This should help prevent breaking changes once sync support is reintroduced.

A Client synonym remains for compatibility with httpx==0.9.* releases, but you are encouraged to migrate to AsyncClient as soon as possible.

Usage

The recommended way to use a Client is as a context manager. This will ensure that connections are properly cleaned up when leaving the with block:

>>> with httpx.Client() as client:
...     r = client.get('https://example.com')
...
>>> r
<Response [200 OK]>

Alternatively, you can explicitly close the connection pool without block-usage using .close():

>>> client = httpx.Client()
>>> try:
...     r = client.get('https://example.com')
... finally:
...     client.close()
...
>>> r
<Response [200 OK]>

Once you have a Client, you can use all the features documented in the Quickstart guide.

Configuration

Clients allow you to apply configuration to all outgoing requests by passing parameters to the Client constructor.

For example, to apply a set of custom headers on every request:

>>> url = 'http://httpbin.org/headers'
>>> headers = {'user-agent': 'my-app/0.0.1'}
>>> with httpx.Client(headers=headers) as client:
...     r = client.get(url)
...
>>> r.json()['headers']['User-Agent']
'my-app/0.0.1'

Note

When you provide a parameter at both the client and request levels, one of two things can happen:

  • For headers, query parameters and cookies, the values are merged into one.
  • For all other parameters, the request-level value is used.

Additionally, Client accepts some parameters that aren't available at the request level.

One particularly useful parameter is base_url, which allows you to define a base URL to prepend to all outgoing requests:

>>> with httpx.Client(base_url='http://httpbin.org') as client:
...     r = client.get('/headers')
...
>>> r.request.url
URL('http://httpbin.org/headers')

For a list of all available client-level parameters, see the Client API reference.

Calling into Python Web Apps

You can configure an httpx client to call directly into a Python web application using the WSGI protocol.

This is particularly useful for two main use-cases:

  • Using httpx as a client inside test cases.
  • Mocking out external services during tests or in dev/staging environments.

Here's an example of integrating against a Flask application:

from flask import Flask
import httpx


app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

with httpx.Client(app=app, base_url="http://testserver") as client:
    r = client.get("/")
    assert r.status_code == 200
    assert r.text == "Hello World!"

For some more complex cases you might need to customize the WSGI dispatch. This allows you to:

  • Inspect 500 error responses rather than raise exceptions by setting raise_app_exceptions=False.
  • Mount the WSGI application at a subpath by setting script_name (WSGI).
  • Use a given client address for requests by setting remote_addr (WSGI).

For example:

# Instantiate a client that makes WSGI requests with a client IP of "1.2.3.4".
dispatch = httpx.WSGIDispatch(app=app, remote_addr="1.2.3.4")
with httpx.Client(dispatch=dispatch, base_url="http://testserver") as client:
    ...

Build Request

You can use Client.build_request() to build a request and make modifications before sending the request.

>>> with httpx.Client() as client:
...     req = client.build_request("OPTIONS", "https://example.com")
...     req.url.full_path = "*"  # Build an 'OPTIONS *' request for CORS
...     r = client.send(req)
...
>>> r
<Response [200 OK]>

.netrc Support

HTTPX supports .netrc file. In trust_env=True cases, if auth parameter is not defined, HTTPX tries to add auth into request's header from .netrc file.

Note

The NETRC file is cached across requests made by a client. If you need to refresh the cache (e.g. because the NETRC file has changed), you should create a new client or restart the interpreter.

As default trust_env is true. To set false:

>>> httpx.get('https://example.org/', trust_env=False)

If NETRC environment is empty, HTTPX tries to use default files. (~/.netrc, ~/_netrc)

To change NETRC environment:

>>> import os
>>> os.environ["NETRC"] = "my_default_folder/.my_netrc"

.netrc file content example:

machine netrcexample.org
login example-username
password example-password

...

When using Client instances, trust_env should be set on the client itself, rather that on the request methods:

client = httpx.Client(trust_env=False)

HTTP Proxying

HTTPX supports setting up HTTP proxies the same way that Requests does via the proxies parameter. For example to forward all HTTP traffic to http://127.0.0.1:3080 and all HTTPS traffic to http://127.0.0.1:3081 your proxies config would look like this:

>>> proxies = {
...     "http": "http://127.0.0.1:3080",
...     "https": "http://127.0.0.1:3081"
... }
>>> with httpx.Client(proxies=proxies) as client:
...     ...

Credentials may be passed in as part of the URL in the standard way, i.e. http://username:password@127.0.0.1:3080.

Proxies can be configured for a specific scheme and host, all schemes of a host, all hosts for a scheme, or for all requests. When determining which proxy configuration to use for a given request this same order is used.

>>> proxies = {
...     "http://example.com":  "...",  # Host+Scheme
...     "all://example.com":  "...",  # Host
...     "http": "...",  # Scheme
...     "all": "...",  # All
... }
>>> with httpx.Client(proxies=proxies) as client:
...     ...
...
>>> proxy = "..."  # Shortcut for {'all': '...'}
>>> with httpx.Client(proxies=proxy) as client:
...     ...

Warning

To make sure that proxies cannot read your traffic, and even if the proxy_url uses HTTPS, it is recommended to use HTTPS and tunnel requests if possible.

By default httpx.Proxy will operate as a forwarding proxy for http://... requests and will establish a CONNECT TCP tunnel for https:// requests. This doesn't change regardless of the proxy url being http or https.

Proxies can be configured to have different behavior such as forwarding or tunneling all requests:

proxy = httpx.Proxy(
    url="https://127.0.0.1",
    mode="TUNNEL_ONLY"  # May be "TUNNEL_ONLY" or "FORWARD_ONLY". Defaults to "DEFAULT".
)
with httpx.Client(proxies=proxy) as client:
    # This request will be tunneled instead of forwarded.
    r = client.get("http://example.com")

Note

To use proxies you must pass the proxy information at Client initialization, rather than on the .get(...) call or other request methods.

SOCKS proxies are not supported yet.

Timeout Configuration

HTTPX is careful to enforce timeouts everywhere by default.

The default behavior is to raise a TimeoutException after 5 seconds of network inactivity.

Setting and disabling timeouts

You can set timeouts for an individual request:

# Using the top-level API:
httpx.get('http://example.com/api/v1/example', timeout=10.0)

# Using a client instance:
with httpx.Client() as client:
    client.get("http://example.com/api/v1/example", timeout=10.0)

Or disable timeouts for an individual request:

# Using the top-level API:
httpx.get('http://example.com/api/v1/example', timeout=None)

# Using a client instance:
with httpx.Client() as client:
    client.get("http://example.com/api/v1/example", timeout=None)

Setting a default timeout on a client

You can set a timeout on a client instance, which results in the given timeout being used as the default for requests made with this client:

client = httpx.Client()              # Use a default 5s timeout everywhere.
client = httpx.Client(timeout=10.0)  # Use a default 10s timeout everywhere.
client = httpx.Client(timeout=None)  # Disable all timeouts by default.

Fine tuning the configuration

HTTPX also allows you to specify the timeout behavior in more fine grained detail.

There are four different types of timeouts that may occur. These are connect, read, write, and pool timeouts.

  • The connect timeout specifies the maximum amount of time to wait until a connection to the requested host is established. If HTTPX is unable to connect within this time frame, a ConnectTimeout exception is raised.
  • The read timeout specifies the maximum duration to wait for a chunk of data to be received (for example, a chunk of the response body). If HTTPX is unable to receive data within this time frame, a ReadTimeout exception is raised.
  • The write timeout specifies the maximum duration to wait for a chunk of data to be sent (for example, a chunk of the request body). If HTTPX is unable to send data within this time frame, a WriteTimeout exception is raised.
  • The pool timeout specifies the maximum duration to wait for acquiring a connection from the connection pool. If HTTPX is unable to acquire a connection within this time frame, a PoolTimeout exception is raised. A related configuration here is the maximum number of allowable connections in the connection pool, which is configured by the pool_limits.

You can configure the timeout behavior for any of these values...

# A client with a 60s timeout for connecting, and a 10s timeout elsewhere.
timeout = httpx.Timeout(10.0, connect_timeout=60.0)
client = httpx.Client(timeout=timeout)

response = client.get('http://example.com/')

Multipart file encoding

As mentioned in the quickstart multipart file encoding is available by passing a dictionary with the name of the payloads as keys and either tuple of elements or a file-like object or a string as values.

>>> files = {'upload-file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel')}
>>> r = httpx.post("https://httpbin.org/post", files=files)
>>> print(r.text)
{
  ...
  "files": {
    "upload-file": "<... binary content ...>"
  },
  ...
}

More specifically, if a tuple is used as a value, it must have between 2 and 3 elements:

  • 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 MIME type 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". If the file name is explicitly set to None then HTTPX will not include a content-type MIME header field.
>>> files = {'upload-file': (None, 'text content', 'text/plain')}
>>> r = httpx.post("https://httpbin.org/post", files=files)
>>> print(r.text)
{
  ...
  "files": {},
  "form": {
    "upload-file": "text-content"
  },
  ...
}

Customizing authentication

When issuing requests or instantiating a client, the auth argument can be used to pass an authentication scheme to use. The auth argument may be one of the following...

  • A two-tuple of username/password, to be used with basic authentication.
  • An instance of httpx.BasicAuth() or httpx.DigestAuth().
  • A callable, accepting a request and returning an authenticated request instance.
  • A subclass of httpx.Auth.

The most involved of these is the last, which allows you to create authentication flows involving one or more requests. A subclass of httpx.Auth should implement def auth_flow(request), and yield any requests that need to be made...

class MyCustomAuth(httpx.Auth):
    def __init__(self, token):
        self.token = token

    def auth_flow(self, request):
        # Send the request, with a custom `X-Authentication` header.
        request.headers['X-Authentication'] = self.token
        yield request

If the auth flow requires more that one request, you can issue multiple yields, and obtain the response in each case...

class MyCustomAuth(httpx.Auth):
    def __init__(self, token):
        self.token = token

    def auth_flow(self, request):
      response = yield request
      if response.status_code == 401:
          # If the server issues a 401 response then resend the request,
          # with a custom `X-Authentication` header.
          request.headers['X-Authentication'] = self.token
          yield request

Custom authentication classes are designed to not perform any I/O, so that they may be used with both sync and async client instances. If you are implementing an authentication scheme that requires the request body, then you need to indicate this on the class using a requires_request_body property.

You will then be able to access request.content inside the .auth_flow() method.

class MyCustomAuth(httpx.Auth):
    requires_request_body = True

    def __init__(self, token):
        self.token = token

    def auth_flow(self, request):
      response = yield request
      if response.status_code == 401:
          # If the server issues a 401 response then resend the request,
          # with a custom `X-Authentication` header.
          request.headers['X-Authentication'] = self.sign_request(...)
          yield request

    def sign_request(self, request):
        # Create a request signature, based on `request.method`, `request.url`,
        # `request.headers`, and `request.content`.
        ...

Similarly, if you are implementing a scheme that requires access to the response body, then use the requires_response_body property. You will then be able to access response body properties and methods such as response.content, response.text, response.json(), etc.

class MyCustomAuth(httpx.Auth):
    requires_response_body = True

    def __init__(self, access_token, refresh_token, refresh_url):
        self.access_token = access_token
        self.refresh_token = refresh_token
        self.refresh_url = refresh_url

    def auth_flow(self, request):
        request.headers["X-Authentication"] = self.access_token
        response = yield request

        if response.status_code == 401:
            # If the server issues a 401 response, then issue a request to
            # refresh tokens, and resend the request.
            refresh_response = yield self.build_refresh_request()
            self.update_tokens(refresh_response)

            request.headers["X-Authentication"] = self.access_token
            yield request

    def build_refresh_request(self):
        # Return an `httpx.Request` for refreshing tokens.
        ...

    def update_tokens(self, response):
        # Update the `.access_token` and `.refresh_token` tokens
        # based on a refresh response.
        data = response.json()
        ...

SSL certificates

When making a request over HTTPS, HTTPX needs to verify the identity of the requested host. To do this, it uses a bundle of SSL certificates (a.k.a. CA bundle) delivered by a trusted certificate authority (CA).

Changing the verification defaults

By default, HTTPX uses the CA bundle provided by Certifi. This is what you want in most cases, even though some advanced situations may require you to use a different set of certificates.

If you'd like to use a custom CA bundle, you can use the verify parameter.

import httpx

r = httpx.get("https://example.org", verify="path/to/client.pem")

You can also disable the SSL verification...

import httpx

r = httpx.get("https://example.org", verify=False)

SSL configuration on client instances

If you're using a Client() instance, then you should pass any SSL settings when instantiating the client.

client = httpx.Client(verify=False)

The client.get(...) method and other request methods do not support changing the SSL settings on a per-request basis. If you need different SSL settings in different cases you should use more that 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.

Making HTTPS requests to a local server

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

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:

  1. Use trustme-cli to generate a pair of server key/cert files, and a client cert file.
  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 provides the --ssl-keyfile and --ssl-certfile options.)
  3. Tell HTTPX to use the certificates stored in client.pem:
>>> import httpx
>>> r = httpx.get("https://localhost:8000", verify="/tmp/client.pem")
>>> r
Response <200 OK>