# Clients HTTP requests are sent by using a `Client` instance. Client instances are thread safe interfaces that maintain a pool of HTTP connections.
httpx
```{ .python .httpx } >>> cli = httpx.Client() >>> cli ``` ```{ .python .ahttpx .hidden } >>> cli = ahttpx.Client() >>> cli ``` The client representation provides an indication of how many connections are currently in the pool. ```{ .python .httpx } >>> r = cli.get("https://www.example.com") >>> r = cli.get("https://www.wikipedia.com") >>> r = cli.get("https://www.theguardian.com/uk") >>> cli ``` ```{ .python .ahttpx .hidden } >>> r = await cli.get("https://www.example.com") >>> r = await cli.get("https://www.wikipedia.com") >>> r = await cli.get("https://www.theguardian.com/uk") >>> cli ``` The connections in the pool can be explicitly closed, using the `close()` method... ```{ .python .httpx } >>> cli.close() >>> cli ``` ```{ .python .ahttpx .hidden } >>> await cli.close() >>> cli ``` Client instances support being used in a context managed scope. You can use this style to enforce properly scoped resources, ensuring that the connection pool is cleanly closed when no longer required. ```{ .python .httpx } >>> with httpx.Client() as cli: ... r = cli.get("https://www.example.com") ``` ```{ .python .ahttpx .hidden } >>> async with ahttpx.Client() as cli: ... r = await cli.get("https://www.example.com") ``` It is important to scope the use of client instances as widely as possible. Typically you should have a single client instance that is used throughout the lifespan of your application. This ensures that connection pooling is maximised, and minmises unneccessary reloading of SSL certificate stores. The recommened usage is to *either* a have single global instance created at import time, *or* a single context scoped instance that is passed around wherever it is required. ## Setting a base URL Client instances can be configured with a base URL that is used when constructing requests... ```{ .python .httpx } >>> with httpx.Client(url="https://www.httpbin.org") as cli: >>> r = cli.get("/json") >>> print(r) ``` ```{ .python .ahttpx .hidden } >>> async with ahttpx.Client(url="https://www.httpbin.org") as cli: >>> r = cli.get("/json") >>> print(r) ``` ## Setting client headers Client instances include a set of headers that are used on every outgoing request. The default headers are: * `Accept: */*` - Indicates to servers that any media type may be returned. * `Accept-Encoding: gzip` - Indicates to servers that gzip compression may be used on responses. * `Connection: keep-alive` - Indicates that HTTP/1.1 connections should be reused over multiple requests. * `User-Agent: python-httpx/1.0` - Identify the client as `httpx`. You can override this behavior by explicitly specifying the default headers... ```{ .python .httpx } >>> headers = {"User-Agent": "dev", "Accept-Encoding": "gzip"} >>> with httpx.Client(headers=headers) as cli: >>> r = cli.get("https://www.example.com/") ``` ```{ .python .ahttpx .hidden } >>> headers = {"User-Agent": "dev", "Accept-Encoding": "gzip"} >>> async with ahttpx.Client(headers=headers) as cli: >>> r = await cli.get("https://www.example.com/") ``` ## Configuring the connection pool The connection pool used by the client can be configured in order to customise the SSL context, the maximum number of concurrent connections, or the network backend. ```{ .python .httpx } >>> # Setup an SSL context to allow connecting to improperly configured SSL. >>> no_verify = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) >>> no_verify.check_hostname = False >>> no_verify.verify_mode = ssl.CERT_NONE >>> # Instantiate a client with our custom SSL context. >>> pool = httpx.ConnectionPool(ssl_context=no_verify) >>> with httpx.Client(transport=pool) as cli: >>> ... ``` ```{ .python .ahttpx .hidden } >>> # Setup an SSL context to allow connecting to improperly configured SSL. >>> no_verify = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) >>> no_verify.check_hostname = False >>> no_verify.verify_mode = ssl.CERT_NONE >>> # Instantiate a client with our custom SSL context. >>> pool = ahttpx.ConnectionPool(ssl_context=no_verify) >>> async with ahttpx.Client(transport=pool) as cli: >>> ... ``` ## Sending requests * `.request()` - Send an HTTP request, reading the response to completion. * `.stream()` - Send an HTTP request, streaming the response. Shortcut methods... * `.get()` - Send an HTTP `GET` request. * `.post()` - Send an HTTP `POST` request. * `.put()` - Send an HTTP `PUT` request. * `.delete()` - Send an HTTP `DELETE` request. --- ## Transports By default requests are sent using the `ConnectionPool` class. Alternative implementations for sending requests can be created by subclassing the `Transport` interface. For example, a mock transport class that doesn't make any network requests and instead always returns a fixed response. ```{ .python .httpx } class MockTransport(httpx.Transport): def __init__(self, response): self._response = response @contextlib.contextmanager def send(self, request): yield response def close(self): pass response = httpx.Response(200, content=httpx.Text('Hello, world')) transport = MockTransport(response=response) with httpx.Client(transport=transport) as cli: r = cli.get('https://www.example.com') print(r) ``` ```{ .python .ahttpx .hidden } class MockTransport(ahttpx.Transport): def __init__(self, response): self._response = response @contextlib.contextmanager def send(self, request): yield response def close(self): pass response = ahttpx.Response(200, content=httpx.Text('Hello, world')) transport = MockTransport(response=response) async with ahttpx.Client(transport=transport) as cli: r = await cli.get('https://www.example.com') print(r) ``` --- ## Middleware In addition to maintaining an HTTP connection pool, client instances are responsible for two other pieces of functionality... * Dealing with HTTP redirects. * Maintaining an HTTP cookie store. ### `RedirectMiddleware` Wraps a transport class, adding support for HTTP redirect handling. ### `CookieMiddleware` Wraps a transport class, adding support for HTTP cookie persistence. --- ## Custom client implementations The `Client` implementation in `httpx` is intentionally lightweight. If you're working with a large codebase you might want to create a custom client implementation in order to constrain the types of request that are sent. The following example demonstrates a custom API client that only exposes `GET` and `POST` requests, and always uses JSON payloads. ```{ .python .httpx } class APIClient: def __init__(self): self.url = httpx.URL('https://www.example.com') self.headers = httpx.Headers({ 'Accept-Encoding': 'gzip', 'Connection': 'keep-alive', 'User-Agent': 'dev' }) self.via = httpx.RedirectMiddleware(httpx.ConnectionPool()) def get(self, path: str) -> Response: request = httpx.Request( method="GET", url=self.url.join(path), headers=self.headers, ) with self.via.send(request) as response: response.read() return response def post(self, path: str, payload: Any) -> httpx.Response: request = httpx.Request( method="POST", url=self.url.join(path), headers=self.headers, content=httpx.JSON(payload), ) with self.via.send(request) as response: response.read() return response ``` ```{ .python .ahttpx .hidden } class APIClient: def __init__(self): self.url = ahttpx.URL('https://www.example.com') self.headers = ahttpx.Headers({ 'Accept-Encoding': 'gzip', 'Connection': 'keep-alive', 'User-Agent': 'dev' }) self.via = ahttpx.RedirectMiddleware(ahttpx.ConnectionPool()) async def get(self, path: str) -> Response: request = ahttpx.Request( method="GET", url=self.url.join(path), headers=self.headers, ) async with self.via.send(request) as response: await response.read() return response async def post(self, path: str, payload: Any) -> ahttpx.Response: request = ahttpx.Request( method="POST", url=self.url.join(path), headers=self.headers, content=httpx.JSON(payload), ) async with self.via.send(request) as response: await response.read() return response ``` You can expand on this pattern to provide behavior such as request or response schema validation, consistent timeouts, or standardised logging and exception handling. --- ← [Quickstart](quickstart.md) [Servers](servers.md) →