|
| 1 | +--- |
| 2 | +id: custom-http-clients |
| 3 | +title: Custom HTTP clients |
| 4 | +--- |
| 5 | + |
| 6 | +import Tabs from '@theme/Tabs'; |
| 7 | +import TabItem from '@theme/TabItem'; |
| 8 | +import CodeBlock from '@theme/CodeBlock'; |
| 9 | +import ApiLink from '@site/src/components/ApiLink'; |
| 10 | + |
| 11 | +import DefaultHttpClientAsyncExample from '!!raw-loader!./code/10_default_http_client_async.py'; |
| 12 | +import DefaultHttpClientSyncExample from '!!raw-loader!./code/10_default_http_client_sync.py'; |
| 13 | + |
| 14 | +import ArchitectureImportsExample from '!!raw-loader!./code/10_architecture_imports.py'; |
| 15 | + |
| 16 | +import PluggingInAsyncExample from '!!raw-loader!./code/10_plugging_in_async.py'; |
| 17 | +import PluggingInSyncExample from '!!raw-loader!./code/10_plugging_in_sync.py'; |
| 18 | + |
| 19 | +The Apify API client uses a pluggable HTTP client architecture. By default, it ships with an [Impit](https://github.com/apify/impit)-based HTTP client that handles retries, timeouts, passing headers, and more. You can replace it with your own implementation for use cases like custom logging, proxying, request modification, or integrating with a different HTTP library. |
| 20 | + |
| 21 | +## Default HTTP client |
| 22 | + |
| 23 | +When you create an <ApiLink to="class/ApifyClient">`ApifyClient`</ApiLink> or <ApiLink to="class/ApifyClientAsync">`ApifyClientAsync`</ApiLink> instance, it automatically uses the built-in <ApiLink to="class/ImpitHttpClient">`ImpitHttpClient`</ApiLink> (or <ApiLink to="class/ImpitHttpClientAsync">`ImpitHttpClientAsync`</ApiLink>). This default client provides: |
| 24 | + |
| 25 | +- Automatic retries with exponential backoff for network errors, HTTP 429, and HTTP 5xx responses. |
| 26 | +- Configurable timeouts. |
| 27 | +- Preparing request data and headers according to the API requirements, including authentication. |
| 28 | +- Collecting requests statistics for monitoring and debugging. |
| 29 | + |
| 30 | +You can configure the default client through the <ApiLink to="class/ApifyClient">`ApifyClient`</ApiLink> or <ApiLink to="class/ApifyClientAsync">`ApifyClientAsync`</ApiLink> constructor: |
| 31 | + |
| 32 | +<Tabs> |
| 33 | + <TabItem value="AsyncExample" label="Async client" default> |
| 34 | + <CodeBlock className="language-python"> |
| 35 | + {DefaultHttpClientAsyncExample} |
| 36 | + </CodeBlock> |
| 37 | + </TabItem> |
| 38 | + <TabItem value="SyncExample" label="Sync client"> |
| 39 | + <CodeBlock className="language-python"> |
| 40 | + {DefaultHttpClientSyncExample} |
| 41 | + </CodeBlock> |
| 42 | + </TabItem> |
| 43 | +</Tabs> |
| 44 | + |
| 45 | +## Architecture |
| 46 | + |
| 47 | +The HTTP client system is built on two key abstractions: |
| 48 | + |
| 49 | +- <ApiLink to="class/HttpClient">`HttpClient`</ApiLink> / <ApiLink to="class/HttpClientAsync">`HttpClientAsync`</ApiLink> - Abstract base classes that define the interface. Extend one of these to create a custom HTTP client by implementing the `call` method. |
| 50 | +- <ApiLink to="class/HttpResponse">`HttpResponse`</ApiLink> - A [runtime-checkable protocol](https://docs.python.org/3/library/typing.html#typing.runtime_checkable) that defines the expected response shape. Any object with the required attributes and methods satisfies the protocol — no inheritance needed. |
| 51 | + |
| 52 | +To plug in your custom implementation, use the <ApiLink to="class/ApifyClient#with_custom_http_client">`ApifyClient.with_custom_http_client`</ApiLink> class method. |
| 53 | + |
| 54 | +All of these are available as top-level imports from the `apify_client` package: |
| 55 | + |
| 56 | +<CodeBlock className="language-python"> |
| 57 | + {ArchitectureImportsExample} |
| 58 | +</CodeBlock> |
| 59 | + |
| 60 | +### The call method |
| 61 | + |
| 62 | +The `call` method receives all the information needed to make an HTTP request: |
| 63 | + |
| 64 | +- `method` - HTTP method (`GET`, `POST`, `PUT`, `DELETE`, etc.). |
| 65 | +- `url` - Full URL to make the request to. |
| 66 | +- `headers` - Additional headers to include. |
| 67 | +- `params` - Query parameters to append to the URL. |
| 68 | +- `data` - Raw request body (mutually exclusive with `json`). |
| 69 | +- `json` - JSON-serializable request body (mutually exclusive with `data`). |
| 70 | +- `stream` - Whether to stream the response body. |
| 71 | +- `timeout` - Timeout for the request as a `timedelta`. |
| 72 | + |
| 73 | +It must return an object satisfying the <ApiLink to="class/HttpResponse">`HttpResponse`</ApiLink> protocol. |
| 74 | + |
| 75 | +### The HTTP response protocol |
| 76 | + |
| 77 | +<ApiLink to="class/HttpResponse">`HttpResponse`</ApiLink> is not a concrete class. Any object with the following attributes and methods will work: |
| 78 | + |
| 79 | +| Property / Method | Description | |
| 80 | +|---|---| |
| 81 | +| `status_code: int` | HTTP status code | |
| 82 | +| `text: str` | Response body as text | |
| 83 | +| `content: bytes` | Raw response body | |
| 84 | +| `headers: Mapping[str, str]` | Response headers | |
| 85 | +| `json() -> Any` | Parse body as JSON | |
| 86 | +| `read() -> bytes` | Read entire response body | |
| 87 | +| `aread() -> bytes` | Read entire response body (async) | |
| 88 | +| `close() -> None` | Close the response | |
| 89 | +| `aclose() -> None` | Close the response (async) | |
| 90 | +| `iter_bytes() -> Iterator[bytes]` | Iterate body in chunks | |
| 91 | +| `aiter_bytes() -> AsyncIterator[bytes]` | Iterate body in chunks (async) | |
| 92 | + |
| 93 | +:::note |
| 94 | +Many HTTP libraries, including our default [Impit](https://github.com/apify/impit) or for example [HTTPX](https://www.python-httpx.org/) already satisfy this protocol out of the box. |
| 95 | +::: |
| 96 | + |
| 97 | +### Plugging it in |
| 98 | + |
| 99 | +Use the <ApiLink to="class/ApifyClient#with_custom_http_client">`ApifyClient.with_custom_http_client`</ApiLink> (or <ApiLink to="class/ApifyClientAsync#with_custom_http_client">`ApifyClientAsync.with_custom_http_client`</ApiLink>) class method to create a client with your custom implementation: |
| 100 | + |
| 101 | +<Tabs> |
| 102 | + <TabItem value="AsyncExample" label="Async client" default> |
| 103 | + <CodeBlock className="language-python"> |
| 104 | + {PluggingInAsyncExample} |
| 105 | + </CodeBlock> |
| 106 | + </TabItem> |
| 107 | + <TabItem value="SyncExample" label="Sync client"> |
| 108 | + <CodeBlock className="language-python"> |
| 109 | + {PluggingInSyncExample} |
| 110 | + </CodeBlock> |
| 111 | + </TabItem> |
| 112 | +</Tabs> |
| 113 | + |
| 114 | +After that, all API calls made through the client will go through your custom HTTP client. |
| 115 | + |
| 116 | +:::warning |
| 117 | +When using a custom HTTP client, you are responsible for constructing the request, handling retries, timeouts, and errors yourself. The default retry logic is not applied. |
| 118 | +::: |
| 119 | + |
| 120 | +## Use cases |
| 121 | + |
| 122 | +Custom HTTP clients might be useful when you need to: |
| 123 | + |
| 124 | +- **Use a different HTTP library** - Swap Impit for [httpx](https://www.python-httpx.org/), [requests](https://requests.readthedocs.io/), or [aiohttp](https://docs.aiohttp.org/). |
| 125 | +- **Route through a proxy** - Add proxy support or request routing. |
| 126 | +- **Implement custom retry logic** - Use different backoff strategies or retry conditions. |
| 127 | +- **Log requests and responses** - Track API calls for debugging or auditing. |
| 128 | +- **Modify requests** - Add custom fields, modify the body, or change headers. |
| 129 | +- **Collect custom metrics** - Measure request latency, track error rates, or count API calls. |
| 130 | + |
| 131 | +For a step-by-step walkthrough of building a custom HTTP client, see the [Using HTTPX as the HTTP client](/api/client/python/docs/guides/custom-http-client-httpx) guide. |
0 commit comments