httpr 0.2.5__cp313-cp313t-musllinux_1_2_i686.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- httpr/__init__.py +1066 -0
- httpr/httpr.cpython-313t-i386-linux-musl.so +0 -0
- httpr/httpr.pyi +701 -0
- httpr/py.typed +0 -0
- httpr-0.2.5.dist-info/METADATA +54 -0
- httpr-0.2.5.dist-info/RECORD +9 -0
- httpr-0.2.5.dist-info/WHEEL +4 -0
- httpr-0.2.5.dist-info/licenses/LICENSE +21 -0
- httpr.libs/libgcc_s-f5fcfe20.so.1 +0 -0
httpr/__init__.py
ADDED
|
@@ -0,0 +1,1066 @@
|
|
|
1
|
+
"""
|
|
2
|
+
httpr - Blazing fast HTTP client for Python, built in Rust.
|
|
3
|
+
|
|
4
|
+
httpr is a high-performance HTTP client that can be used as a drop-in replacement
|
|
5
|
+
for `httpx` and `requests` in most cases.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
Simple GET request:
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
import httpr
|
|
12
|
+
|
|
13
|
+
response = httpr.get("https://httpbin.org/get")
|
|
14
|
+
print(response.json())
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Using a client for connection pooling:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import httpr
|
|
21
|
+
|
|
22
|
+
with httpr.Client() as client:
|
|
23
|
+
response = client.get("https://httpbin.org/get")
|
|
24
|
+
print(response.status_code)
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import asyncio
|
|
31
|
+
import sys
|
|
32
|
+
from collections.abc import AsyncIterator, Generator
|
|
33
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
34
|
+
from functools import partial
|
|
35
|
+
from typing import TYPE_CHECKING, TypedDict
|
|
36
|
+
|
|
37
|
+
if sys.version_info <= (3, 11):
|
|
38
|
+
from typing_extensions import Unpack
|
|
39
|
+
else:
|
|
40
|
+
from typing import Unpack
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
from .httpr import CaseInsensitiveHeaderMap, RClient, Response, StreamingResponse
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from .httpr import ClientRequestParams, HttpMethod, RequestParams
|
|
47
|
+
else:
|
|
48
|
+
|
|
49
|
+
class _Unpack:
|
|
50
|
+
@staticmethod
|
|
51
|
+
def __getitem__(*args, **kwargs):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
Unpack = _Unpack()
|
|
55
|
+
RequestParams = ClientRequestParams = TypedDict
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Client(RClient):
|
|
59
|
+
"""
|
|
60
|
+
A synchronous HTTP client with connection pooling.
|
|
61
|
+
|
|
62
|
+
The Client class provides a high-level interface for making HTTP requests.
|
|
63
|
+
It supports connection pooling, automatic cookie handling, and various
|
|
64
|
+
authentication methods.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
Basic usage:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import httpr
|
|
71
|
+
|
|
72
|
+
# Using context manager (recommended)
|
|
73
|
+
with httpr.Client() as client:
|
|
74
|
+
response = client.get("https://httpbin.org/get")
|
|
75
|
+
print(response.json())
|
|
76
|
+
|
|
77
|
+
# Or manually
|
|
78
|
+
client = httpr.Client()
|
|
79
|
+
response = client.get("https://httpbin.org/get")
|
|
80
|
+
client.close()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
With configuration:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import httpr
|
|
87
|
+
|
|
88
|
+
client = httpr.Client(
|
|
89
|
+
auth_bearer="your-api-token",
|
|
90
|
+
headers={"User-Agent": "my-app/1.0"},
|
|
91
|
+
timeout=30,
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
headers: Default headers sent with all requests. Excludes Cookie header.
|
|
97
|
+
cookies: Default cookies sent with all requests.
|
|
98
|
+
auth: Basic auth credentials as (username, password) tuple.
|
|
99
|
+
params: Default query parameters added to all requests.
|
|
100
|
+
timeout: Default timeout in seconds.
|
|
101
|
+
proxy: Proxy URL for requests.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
auth: tuple[str, str | None] | None = None,
|
|
107
|
+
auth_bearer: str | None = None,
|
|
108
|
+
params: dict[str, str] | None = None,
|
|
109
|
+
headers: dict[str, str] | None = None,
|
|
110
|
+
cookies: dict[str, str] | None = None,
|
|
111
|
+
cookie_store: bool | None = True,
|
|
112
|
+
referer: bool | None = True,
|
|
113
|
+
proxy: str | None = None,
|
|
114
|
+
timeout: float | None = 30,
|
|
115
|
+
follow_redirects: bool | None = True,
|
|
116
|
+
max_redirects: int | None = 20,
|
|
117
|
+
verify: bool | None = True,
|
|
118
|
+
ca_cert_file: str | None = None,
|
|
119
|
+
client_pem: str | None = None,
|
|
120
|
+
https_only: bool | None = False,
|
|
121
|
+
http2_only: bool | None = False,
|
|
122
|
+
):
|
|
123
|
+
"""
|
|
124
|
+
Initialize an HTTP client.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
auth: Basic auth credentials as (username, password). Password can be None.
|
|
128
|
+
auth_bearer: Bearer token for Authorization header.
|
|
129
|
+
params: Default query parameters to include in all requests.
|
|
130
|
+
headers: Default headers to send with all requests.
|
|
131
|
+
cookies: Default cookies to send with all requests.
|
|
132
|
+
cookie_store: Enable persistent cookie store. Cookies from responses will be
|
|
133
|
+
preserved and included in subsequent requests. Default is True.
|
|
134
|
+
referer: Automatically set Referer header. Default is True.
|
|
135
|
+
proxy: Proxy URL (e.g., "http://proxy:8080" or "socks5://127.0.0.1:1080").
|
|
136
|
+
Falls back to HTTPR_PROXY environment variable.
|
|
137
|
+
timeout: Request timeout in seconds. Default is 30.
|
|
138
|
+
follow_redirects: Follow HTTP redirects. Default is True.
|
|
139
|
+
max_redirects: Maximum redirects to follow. Default is 20.
|
|
140
|
+
verify: Verify SSL certificates. Default is True.
|
|
141
|
+
ca_cert_file: Path to CA certificate bundle (PEM format).
|
|
142
|
+
client_pem: Path to client certificate for mTLS (PEM format).
|
|
143
|
+
https_only: Only allow HTTPS requests. Default is False.
|
|
144
|
+
http2_only: Use HTTP/2 only (False uses HTTP/1.1). Default is False.
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
```python
|
|
148
|
+
import httpr
|
|
149
|
+
|
|
150
|
+
# Simple client
|
|
151
|
+
client = httpr.Client()
|
|
152
|
+
|
|
153
|
+
# Client with authentication
|
|
154
|
+
client = httpr.Client(
|
|
155
|
+
auth=("username", "password"),
|
|
156
|
+
timeout=60,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Client with bearer token
|
|
160
|
+
client = httpr.Client(
|
|
161
|
+
auth_bearer="your-api-token",
|
|
162
|
+
headers={"Accept": "application/json"},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Client with proxy
|
|
166
|
+
client = httpr.Client(proxy="http://proxy.example.com:8080")
|
|
167
|
+
|
|
168
|
+
# Client with mTLS
|
|
169
|
+
client = httpr.Client(
|
|
170
|
+
client_pem="/path/to/client.pem",
|
|
171
|
+
ca_cert_file="/path/to/ca.pem",
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
"""
|
|
175
|
+
super().__init__()
|
|
176
|
+
|
|
177
|
+
def __enter__(self) -> Client:
|
|
178
|
+
"""Enter context manager."""
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
def __exit__(self, *args):
|
|
182
|
+
"""Exit context manager and close client."""
|
|
183
|
+
del self
|
|
184
|
+
|
|
185
|
+
def close(self) -> None:
|
|
186
|
+
"""
|
|
187
|
+
Close the client and release resources.
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
```python
|
|
191
|
+
client = httpr.Client()
|
|
192
|
+
try:
|
|
193
|
+
response = client.get("https://example.com")
|
|
194
|
+
finally:
|
|
195
|
+
client.close()
|
|
196
|
+
```
|
|
197
|
+
"""
|
|
198
|
+
del self
|
|
199
|
+
|
|
200
|
+
def request(
|
|
201
|
+
self,
|
|
202
|
+
method: HttpMethod,
|
|
203
|
+
url: str,
|
|
204
|
+
**kwargs: Unpack[RequestParams],
|
|
205
|
+
) -> Response:
|
|
206
|
+
"""
|
|
207
|
+
Make an HTTP request.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
method: HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS).
|
|
211
|
+
url: Request URL.
|
|
212
|
+
**kwargs: Request parameters (see below).
|
|
213
|
+
|
|
214
|
+
Keyword Args:
|
|
215
|
+
params (dict[str, str] | None): Query parameters to append to URL.
|
|
216
|
+
headers (dict[str, str] | None): Request headers (merged with client defaults).
|
|
217
|
+
cookies (dict[str, str] | None): Request cookies (merged with client defaults).
|
|
218
|
+
auth (tuple[str, str | None] | None): Basic auth credentials (overrides client default).
|
|
219
|
+
auth_bearer (str | None): Bearer token (overrides client default).
|
|
220
|
+
timeout (float | None): Request timeout in seconds (overrides client default).
|
|
221
|
+
content (bytes | None): Raw bytes for request body.
|
|
222
|
+
data (dict[str, Any] | None): Form data for request body (application/x-www-form-urlencoded).
|
|
223
|
+
json (Any | None): JSON data for request body (application/json).
|
|
224
|
+
files (dict[str, str] | None): Files for multipart upload (dict mapping field names to file paths).
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Response object with status, headers, and body.
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
ValueError: If method is not a valid HTTP method.
|
|
231
|
+
Exception: If request fails (timeout, connection error, etc.).
|
|
232
|
+
|
|
233
|
+
Example:
|
|
234
|
+
```python
|
|
235
|
+
response = client.request("GET", "https://httpbin.org/get")
|
|
236
|
+
response = client.request("POST", "https://httpbin.org/post", json={"key": "value"})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Note:
|
|
240
|
+
Only one of `content`, `data`, `json`, or `files` can be specified per request.
|
|
241
|
+
"""
|
|
242
|
+
if method not in ["GET", "HEAD", "OPTIONS", "DELETE", "POST", "PUT", "PATCH"]:
|
|
243
|
+
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
244
|
+
if "params" in kwargs and kwargs["params"] is not None:
|
|
245
|
+
kwargs["params"] = {k: str(v) for k, v in kwargs["params"].items()}
|
|
246
|
+
|
|
247
|
+
return super().request(method=method, url=url, **kwargs)
|
|
248
|
+
|
|
249
|
+
def get(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
250
|
+
"""
|
|
251
|
+
Make a GET request.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
url: Request URL.
|
|
255
|
+
**kwargs: Request parameters (params, headers, cookies, auth, auth_bearer, timeout).
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Response object.
|
|
259
|
+
|
|
260
|
+
Example:
|
|
261
|
+
```python
|
|
262
|
+
response = client.get(
|
|
263
|
+
"https://httpbin.org/get",
|
|
264
|
+
params={"key": "value"},
|
|
265
|
+
headers={"Accept": "application/json"},
|
|
266
|
+
)
|
|
267
|
+
print(response.json())
|
|
268
|
+
```
|
|
269
|
+
"""
|
|
270
|
+
return self.request(method="GET", url=url, **kwargs)
|
|
271
|
+
|
|
272
|
+
def head(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
273
|
+
"""
|
|
274
|
+
Make a HEAD request.
|
|
275
|
+
|
|
276
|
+
Returns only headers, no response body.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
url: Request URL.
|
|
280
|
+
**kwargs: Request parameters (params, headers, cookies, auth, auth_bearer, timeout).
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Response object (body will be empty).
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
```python
|
|
287
|
+
response = client.head("https://httpbin.org/get")
|
|
288
|
+
print(response.headers["content-length"])
|
|
289
|
+
```
|
|
290
|
+
"""
|
|
291
|
+
return self.request(method="HEAD", url=url, **kwargs)
|
|
292
|
+
|
|
293
|
+
def options(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
294
|
+
"""
|
|
295
|
+
Make an OPTIONS request.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
url: Request URL.
|
|
299
|
+
**kwargs: Request parameters (params, headers, cookies, auth, auth_bearer, timeout).
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Response object.
|
|
303
|
+
|
|
304
|
+
Example:
|
|
305
|
+
```python
|
|
306
|
+
response = client.options("https://httpbin.org/get")
|
|
307
|
+
print(response.headers.get("allow"))
|
|
308
|
+
```
|
|
309
|
+
"""
|
|
310
|
+
return self.request(method="OPTIONS", url=url, **kwargs)
|
|
311
|
+
|
|
312
|
+
def delete(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
313
|
+
"""
|
|
314
|
+
Make a DELETE request.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
url: Request URL.
|
|
318
|
+
**kwargs: Request parameters (params, headers, cookies, auth, auth_bearer, timeout).
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
Response object.
|
|
322
|
+
|
|
323
|
+
Example:
|
|
324
|
+
```python
|
|
325
|
+
response = client.delete("https://httpbin.org/delete")
|
|
326
|
+
print(response.status_code)
|
|
327
|
+
```
|
|
328
|
+
"""
|
|
329
|
+
return self.request(method="DELETE", url=url, **kwargs)
|
|
330
|
+
|
|
331
|
+
def post(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
332
|
+
"""
|
|
333
|
+
Make a POST request.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
url: Request URL.
|
|
337
|
+
**kwargs: Request parameters including body options.
|
|
338
|
+
|
|
339
|
+
Keyword Args:
|
|
340
|
+
params (dict[str, str] | None): Query parameters.
|
|
341
|
+
headers (dict[str, str] | None): Request headers.
|
|
342
|
+
cookies (dict[str, str] | None): Request cookies.
|
|
343
|
+
auth (tuple[str, str | None] | None): Basic auth credentials.
|
|
344
|
+
auth_bearer (str | None): Bearer token.
|
|
345
|
+
timeout (float | None): Request timeout.
|
|
346
|
+
content (bytes | None): Raw bytes body.
|
|
347
|
+
data (dict[str, Any] | None): Form-encoded body.
|
|
348
|
+
json (Any | None): JSON body.
|
|
349
|
+
files (dict[str, str] | None): Multipart file uploads.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Response object.
|
|
353
|
+
|
|
354
|
+
Example:
|
|
355
|
+
```python
|
|
356
|
+
# JSON body
|
|
357
|
+
response = client.post(
|
|
358
|
+
"https://httpbin.org/post",
|
|
359
|
+
json={"name": "httpr", "fast": True},
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Form data
|
|
363
|
+
response = client.post(
|
|
364
|
+
"https://httpbin.org/post",
|
|
365
|
+
data={"username": "user", "password": "pass"},
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# File upload
|
|
369
|
+
response = client.post(
|
|
370
|
+
"https://httpbin.org/post",
|
|
371
|
+
files={"document": "/path/to/file.pdf"},
|
|
372
|
+
)
|
|
373
|
+
```
|
|
374
|
+
"""
|
|
375
|
+
return self.request(method="POST", url=url, **kwargs)
|
|
376
|
+
|
|
377
|
+
def put(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
378
|
+
"""
|
|
379
|
+
Make a PUT request.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
url: Request URL.
|
|
383
|
+
**kwargs: Request parameters including body options.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Response object.
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
```python
|
|
390
|
+
response = client.put(
|
|
391
|
+
"https://httpbin.org/put",
|
|
392
|
+
json={"key": "updated_value"},
|
|
393
|
+
)
|
|
394
|
+
```
|
|
395
|
+
"""
|
|
396
|
+
return self.request(method="PUT", url=url, **kwargs)
|
|
397
|
+
|
|
398
|
+
def patch(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
399
|
+
"""
|
|
400
|
+
Make a PATCH request.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
url: Request URL.
|
|
404
|
+
**kwargs: Request parameters including body options.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
Response object.
|
|
408
|
+
|
|
409
|
+
Example:
|
|
410
|
+
```python
|
|
411
|
+
response = client.patch(
|
|
412
|
+
"https://httpbin.org/patch",
|
|
413
|
+
json={"field": "new_value"},
|
|
414
|
+
)
|
|
415
|
+
```
|
|
416
|
+
"""
|
|
417
|
+
return self.request(method="PATCH", url=url, **kwargs)
|
|
418
|
+
|
|
419
|
+
@contextmanager
|
|
420
|
+
def stream(
|
|
421
|
+
self,
|
|
422
|
+
method: HttpMethod,
|
|
423
|
+
url: str,
|
|
424
|
+
**kwargs: Unpack[RequestParams],
|
|
425
|
+
) -> Generator[StreamingResponse, None, None]:
|
|
426
|
+
"""
|
|
427
|
+
Make a streaming HTTP request.
|
|
428
|
+
|
|
429
|
+
Returns a context manager that yields a StreamingResponse for iterating
|
|
430
|
+
over the response body in chunks without buffering the entire response
|
|
431
|
+
in memory.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
method: HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS).
|
|
435
|
+
url: Request URL.
|
|
436
|
+
**kwargs: Request parameters (same as request()).
|
|
437
|
+
|
|
438
|
+
Yields:
|
|
439
|
+
StreamingResponse: A response object that can be iterated to receive chunks.
|
|
440
|
+
|
|
441
|
+
Example:
|
|
442
|
+
Basic streaming:
|
|
443
|
+
|
|
444
|
+
```python
|
|
445
|
+
with client.stream("GET", "https://example.com/large-file") as response:
|
|
446
|
+
for chunk in response.iter_bytes():
|
|
447
|
+
process(chunk)
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Streaming text:
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
with client.stream("GET", "https://example.com/text") as response:
|
|
454
|
+
for text in response.iter_text():
|
|
455
|
+
print(text, end="")
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Streaming lines (e.g., Server-Sent Events):
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
with client.stream("GET", "https://example.com/events") as response:
|
|
462
|
+
for line in response.iter_lines():
|
|
463
|
+
print(line.strip())
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Conditional reading:
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
with client.stream("GET", url) as response:
|
|
470
|
+
if response.status_code == 200:
|
|
471
|
+
content = response.read() # Read all remaining content
|
|
472
|
+
else:
|
|
473
|
+
pass # Don't read the body
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Note:
|
|
477
|
+
The response body is only read when you iterate over it or call read().
|
|
478
|
+
Always use this as a context manager to ensure proper cleanup.
|
|
479
|
+
"""
|
|
480
|
+
if method not in ["GET", "HEAD", "OPTIONS", "DELETE", "POST", "PUT", "PATCH"]:
|
|
481
|
+
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
482
|
+
if "params" in kwargs and kwargs["params"] is not None:
|
|
483
|
+
kwargs["params"] = {k: str(v) for k, v in kwargs["params"].items()}
|
|
484
|
+
|
|
485
|
+
response = super()._stream(method=method, url=url, **kwargs)
|
|
486
|
+
try:
|
|
487
|
+
yield response
|
|
488
|
+
finally:
|
|
489
|
+
response.close()
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class AsyncClient(Client):
|
|
493
|
+
"""
|
|
494
|
+
An asynchronous HTTP client for use with asyncio.
|
|
495
|
+
|
|
496
|
+
AsyncClient wraps the synchronous Client using asyncio.run_in_executor(),
|
|
497
|
+
providing an async interface while leveraging the Rust implementation's
|
|
498
|
+
performance.
|
|
499
|
+
|
|
500
|
+
Example:
|
|
501
|
+
Basic usage:
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
import asyncio
|
|
505
|
+
import httpr
|
|
506
|
+
|
|
507
|
+
async def main():
|
|
508
|
+
async with httpr.AsyncClient() as client:
|
|
509
|
+
response = await client.get("https://httpbin.org/get")
|
|
510
|
+
print(response.json())
|
|
511
|
+
|
|
512
|
+
asyncio.run(main())
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Concurrent requests:
|
|
516
|
+
|
|
517
|
+
```python
|
|
518
|
+
import asyncio
|
|
519
|
+
import httpr
|
|
520
|
+
|
|
521
|
+
async def main():
|
|
522
|
+
async with httpr.AsyncClient() as client:
|
|
523
|
+
tasks = [
|
|
524
|
+
client.get("https://httpbin.org/get"),
|
|
525
|
+
client.get("https://httpbin.org/ip"),
|
|
526
|
+
]
|
|
527
|
+
responses = await asyncio.gather(*tasks)
|
|
528
|
+
for response in responses:
|
|
529
|
+
print(response.json())
|
|
530
|
+
|
|
531
|
+
asyncio.run(main())
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Note:
|
|
535
|
+
AsyncClient runs synchronous Rust code in a thread executor.
|
|
536
|
+
It provides concurrency benefits for I/O-bound tasks but is not
|
|
537
|
+
native async I/O.
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
def __init__(self, *args, **kwargs):
|
|
541
|
+
"""
|
|
542
|
+
Initialize an async HTTP client.
|
|
543
|
+
|
|
544
|
+
Accepts the same parameters as Client.
|
|
545
|
+
"""
|
|
546
|
+
super().__init__(*args, **kwargs)
|
|
547
|
+
|
|
548
|
+
async def __aenter__(self) -> AsyncClient:
|
|
549
|
+
"""Enter async context manager."""
|
|
550
|
+
return self
|
|
551
|
+
|
|
552
|
+
async def __aexit__(self, *args):
|
|
553
|
+
"""Exit async context manager and close client."""
|
|
554
|
+
del self
|
|
555
|
+
|
|
556
|
+
async def aclose(self):
|
|
557
|
+
"""
|
|
558
|
+
Close the async client.
|
|
559
|
+
|
|
560
|
+
Example:
|
|
561
|
+
```python
|
|
562
|
+
client = httpr.AsyncClient()
|
|
563
|
+
try:
|
|
564
|
+
response = await client.get("https://example.com")
|
|
565
|
+
finally:
|
|
566
|
+
await client.aclose()
|
|
567
|
+
```
|
|
568
|
+
"""
|
|
569
|
+
del self
|
|
570
|
+
return
|
|
571
|
+
|
|
572
|
+
async def _run_sync_asyncio(self, fn, *args, **kwargs):
|
|
573
|
+
"""Run a synchronous function in an executor."""
|
|
574
|
+
loop = asyncio.get_running_loop()
|
|
575
|
+
return await loop.run_in_executor(None, partial(fn, *args, **kwargs))
|
|
576
|
+
|
|
577
|
+
async def request( # type: ignore[override]
|
|
578
|
+
self,
|
|
579
|
+
method: HttpMethod,
|
|
580
|
+
url: str,
|
|
581
|
+
**kwargs: Unpack[RequestParams],
|
|
582
|
+
) -> Response:
|
|
583
|
+
"""
|
|
584
|
+
Make an async HTTP request.
|
|
585
|
+
|
|
586
|
+
Args:
|
|
587
|
+
method: HTTP method.
|
|
588
|
+
url: Request URL.
|
|
589
|
+
**kwargs: Request parameters.
|
|
590
|
+
|
|
591
|
+
Returns:
|
|
592
|
+
Response object.
|
|
593
|
+
|
|
594
|
+
Example:
|
|
595
|
+
```python
|
|
596
|
+
response = await client.request("GET", "https://httpbin.org/get")
|
|
597
|
+
```
|
|
598
|
+
"""
|
|
599
|
+
if method not in ["GET", "HEAD", "OPTIONS", "DELETE", "POST", "PUT", "PATCH"]:
|
|
600
|
+
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
601
|
+
if "params" in kwargs and kwargs["params"] is not None:
|
|
602
|
+
kwargs["params"] = {k: str(v) for k, v in kwargs["params"].items()}
|
|
603
|
+
|
|
604
|
+
return await self._run_sync_asyncio(super().request, method=method, url=url, **kwargs)
|
|
605
|
+
|
|
606
|
+
async def get( # type: ignore[override]
|
|
607
|
+
self,
|
|
608
|
+
url: str,
|
|
609
|
+
**kwargs: Unpack[RequestParams],
|
|
610
|
+
) -> Response:
|
|
611
|
+
"""
|
|
612
|
+
Make an async GET request.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
url: Request URL.
|
|
616
|
+
**kwargs: Request parameters.
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
Response object.
|
|
620
|
+
|
|
621
|
+
Example:
|
|
622
|
+
```python
|
|
623
|
+
response = await client.get("https://httpbin.org/get")
|
|
624
|
+
```
|
|
625
|
+
"""
|
|
626
|
+
return await self.request(method="GET", url=url, **kwargs)
|
|
627
|
+
|
|
628
|
+
async def head( # type: ignore[override]
|
|
629
|
+
self,
|
|
630
|
+
url: str,
|
|
631
|
+
**kwargs: Unpack[RequestParams],
|
|
632
|
+
) -> Response:
|
|
633
|
+
"""
|
|
634
|
+
Make an async HEAD request.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
url: Request URL.
|
|
638
|
+
**kwargs: Request parameters.
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
Response object.
|
|
642
|
+
"""
|
|
643
|
+
return await self.request(method="HEAD", url=url, **kwargs)
|
|
644
|
+
|
|
645
|
+
async def options( # type: ignore[override]
|
|
646
|
+
self,
|
|
647
|
+
url: str,
|
|
648
|
+
**kwargs: Unpack[RequestParams],
|
|
649
|
+
) -> Response:
|
|
650
|
+
"""
|
|
651
|
+
Make an async OPTIONS request.
|
|
652
|
+
|
|
653
|
+
Args:
|
|
654
|
+
url: Request URL.
|
|
655
|
+
**kwargs: Request parameters.
|
|
656
|
+
|
|
657
|
+
Returns:
|
|
658
|
+
Response object.
|
|
659
|
+
"""
|
|
660
|
+
return await self.request(method="OPTIONS", url=url, **kwargs)
|
|
661
|
+
|
|
662
|
+
async def delete( # type: ignore[override]
|
|
663
|
+
self,
|
|
664
|
+
url: str,
|
|
665
|
+
**kwargs: Unpack[RequestParams],
|
|
666
|
+
) -> Response:
|
|
667
|
+
"""
|
|
668
|
+
Make an async DELETE request.
|
|
669
|
+
|
|
670
|
+
Args:
|
|
671
|
+
url: Request URL.
|
|
672
|
+
**kwargs: Request parameters.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
Response object.
|
|
676
|
+
"""
|
|
677
|
+
return await self.request(method="DELETE", url=url, **kwargs)
|
|
678
|
+
|
|
679
|
+
async def post( # type: ignore[override]
|
|
680
|
+
self,
|
|
681
|
+
url: str,
|
|
682
|
+
**kwargs: Unpack[RequestParams],
|
|
683
|
+
) -> Response:
|
|
684
|
+
"""
|
|
685
|
+
Make an async POST request.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
url: Request URL.
|
|
689
|
+
**kwargs: Request parameters including body options.
|
|
690
|
+
|
|
691
|
+
Returns:
|
|
692
|
+
Response object.
|
|
693
|
+
|
|
694
|
+
Example:
|
|
695
|
+
```python
|
|
696
|
+
response = await client.post(
|
|
697
|
+
"https://httpbin.org/post",
|
|
698
|
+
json={"key": "value"},
|
|
699
|
+
)
|
|
700
|
+
```
|
|
701
|
+
"""
|
|
702
|
+
return await self.request(method="POST", url=url, **kwargs)
|
|
703
|
+
|
|
704
|
+
async def put( # type: ignore[override]
|
|
705
|
+
self,
|
|
706
|
+
url: str,
|
|
707
|
+
**kwargs: Unpack[RequestParams],
|
|
708
|
+
) -> Response:
|
|
709
|
+
"""
|
|
710
|
+
Make an async PUT request.
|
|
711
|
+
|
|
712
|
+
Args:
|
|
713
|
+
url: Request URL.
|
|
714
|
+
**kwargs: Request parameters including body options.
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
Response object.
|
|
718
|
+
"""
|
|
719
|
+
return await self.request(method="PUT", url=url, **kwargs)
|
|
720
|
+
|
|
721
|
+
async def patch( # type: ignore[override]
|
|
722
|
+
self,
|
|
723
|
+
url: str,
|
|
724
|
+
**kwargs: Unpack[RequestParams],
|
|
725
|
+
) -> Response:
|
|
726
|
+
"""
|
|
727
|
+
Make an async PATCH request.
|
|
728
|
+
|
|
729
|
+
Args:
|
|
730
|
+
url: Request URL.
|
|
731
|
+
**kwargs: Request parameters including body options.
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
Response object.
|
|
735
|
+
"""
|
|
736
|
+
return await self.request(method="PATCH", url=url, **kwargs)
|
|
737
|
+
|
|
738
|
+
@asynccontextmanager
|
|
739
|
+
async def stream( # type: ignore[override]
|
|
740
|
+
self,
|
|
741
|
+
method: HttpMethod,
|
|
742
|
+
url: str,
|
|
743
|
+
**kwargs: Unpack[RequestParams],
|
|
744
|
+
) -> AsyncIterator[StreamingResponse]:
|
|
745
|
+
"""
|
|
746
|
+
Make an async streaming HTTP request.
|
|
747
|
+
|
|
748
|
+
Returns an async context manager that yields a StreamingResponse for
|
|
749
|
+
iterating over the response body in chunks.
|
|
750
|
+
|
|
751
|
+
Args:
|
|
752
|
+
method: HTTP method.
|
|
753
|
+
url: Request URL.
|
|
754
|
+
**kwargs: Request parameters.
|
|
755
|
+
|
|
756
|
+
Yields:
|
|
757
|
+
StreamingResponse: A response object that can be iterated.
|
|
758
|
+
|
|
759
|
+
Example:
|
|
760
|
+
```python
|
|
761
|
+
async with client.stream("GET", "https://example.com/large-file") as response:
|
|
762
|
+
for chunk in response.iter_bytes():
|
|
763
|
+
process(chunk)
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
Note:
|
|
767
|
+
Iteration over the response is synchronous (uses iter_bytes, iter_text,
|
|
768
|
+
iter_lines). The async part is initiating the request and entering
|
|
769
|
+
the context manager.
|
|
770
|
+
"""
|
|
771
|
+
if method not in ["GET", "HEAD", "OPTIONS", "DELETE", "POST", "PUT", "PATCH"]:
|
|
772
|
+
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
773
|
+
if "params" in kwargs and kwargs["params"] is not None:
|
|
774
|
+
kwargs["params"] = {k: str(v) for k, v in kwargs["params"].items()}
|
|
775
|
+
|
|
776
|
+
# Run the sync _stream in executor
|
|
777
|
+
response = await self._run_sync_asyncio(super(Client, self)._stream, method=method, url=url, **kwargs)
|
|
778
|
+
try:
|
|
779
|
+
yield response
|
|
780
|
+
finally:
|
|
781
|
+
response.close()
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def request(
|
|
785
|
+
method: HttpMethod,
|
|
786
|
+
url: str,
|
|
787
|
+
verify: bool | None = True,
|
|
788
|
+
ca_cert_file: str | None = None,
|
|
789
|
+
client_pem: str | None = None,
|
|
790
|
+
**kwargs: Unpack[RequestParams],
|
|
791
|
+
) -> Response:
|
|
792
|
+
"""
|
|
793
|
+
Make an HTTP request using a temporary client.
|
|
794
|
+
|
|
795
|
+
This is a convenience function for one-off requests. For multiple requests,
|
|
796
|
+
use a Client instance for better performance (connection pooling).
|
|
797
|
+
|
|
798
|
+
Args:
|
|
799
|
+
method: HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS).
|
|
800
|
+
url: Request URL.
|
|
801
|
+
verify: Verify SSL certificates. Default is True.
|
|
802
|
+
ca_cert_file: Path to CA certificate bundle.
|
|
803
|
+
client_pem: Path to client certificate for mTLS.
|
|
804
|
+
**kwargs: Additional request parameters.
|
|
805
|
+
|
|
806
|
+
Returns:
|
|
807
|
+
Response object.
|
|
808
|
+
|
|
809
|
+
Example:
|
|
810
|
+
```python
|
|
811
|
+
import httpr
|
|
812
|
+
|
|
813
|
+
response = httpr.request("GET", "https://httpbin.org/get")
|
|
814
|
+
response = httpr.request("POST", "https://httpbin.org/post", json={"key": "value"})
|
|
815
|
+
```
|
|
816
|
+
"""
|
|
817
|
+
with Client(
|
|
818
|
+
verify=verify,
|
|
819
|
+
ca_cert_file=ca_cert_file,
|
|
820
|
+
client_pem=client_pem,
|
|
821
|
+
) as client:
|
|
822
|
+
return client.request(method, url, **kwargs)
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def get(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response:
|
|
826
|
+
"""
|
|
827
|
+
Make a GET request using a temporary client.
|
|
828
|
+
|
|
829
|
+
Args:
|
|
830
|
+
url: Request URL.
|
|
831
|
+
**kwargs: Request parameters (params, headers, cookies, auth, timeout, etc.).
|
|
832
|
+
|
|
833
|
+
Returns:
|
|
834
|
+
Response object.
|
|
835
|
+
|
|
836
|
+
Example:
|
|
837
|
+
```python
|
|
838
|
+
import httpr
|
|
839
|
+
|
|
840
|
+
response = httpr.get("https://httpbin.org/get", params={"key": "value"})
|
|
841
|
+
print(response.json())
|
|
842
|
+
```
|
|
843
|
+
"""
|
|
844
|
+
return request(method="GET", url=url, **kwargs)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def head(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response:
|
|
848
|
+
"""
|
|
849
|
+
Make a HEAD request using a temporary client.
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
url: Request URL.
|
|
853
|
+
**kwargs: Request parameters.
|
|
854
|
+
|
|
855
|
+
Returns:
|
|
856
|
+
Response object (body will be empty).
|
|
857
|
+
|
|
858
|
+
Example:
|
|
859
|
+
```python
|
|
860
|
+
import httpr
|
|
861
|
+
|
|
862
|
+
response = httpr.head("https://httpbin.org/get")
|
|
863
|
+
print(response.headers)
|
|
864
|
+
```
|
|
865
|
+
"""
|
|
866
|
+
return request(method="HEAD", url=url, **kwargs)
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def options(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response:
|
|
870
|
+
"""
|
|
871
|
+
Make an OPTIONS request using a temporary client.
|
|
872
|
+
|
|
873
|
+
Args:
|
|
874
|
+
url: Request URL.
|
|
875
|
+
**kwargs: Request parameters.
|
|
876
|
+
|
|
877
|
+
Returns:
|
|
878
|
+
Response object.
|
|
879
|
+
|
|
880
|
+
Example:
|
|
881
|
+
```python
|
|
882
|
+
import httpr
|
|
883
|
+
|
|
884
|
+
response = httpr.options("https://httpbin.org/get")
|
|
885
|
+
```
|
|
886
|
+
"""
|
|
887
|
+
return request(method="OPTIONS", url=url, **kwargs)
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def delete(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response:
|
|
891
|
+
"""
|
|
892
|
+
Make a DELETE request using a temporary client.
|
|
893
|
+
|
|
894
|
+
Args:
|
|
895
|
+
url: Request URL.
|
|
896
|
+
**kwargs: Request parameters.
|
|
897
|
+
|
|
898
|
+
Returns:
|
|
899
|
+
Response object.
|
|
900
|
+
|
|
901
|
+
Example:
|
|
902
|
+
```python
|
|
903
|
+
import httpr
|
|
904
|
+
|
|
905
|
+
response = httpr.delete("https://httpbin.org/delete")
|
|
906
|
+
```
|
|
907
|
+
"""
|
|
908
|
+
return request(method="DELETE", url=url, **kwargs)
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
def post(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response:
|
|
912
|
+
"""
|
|
913
|
+
Make a POST request using a temporary client.
|
|
914
|
+
|
|
915
|
+
Args:
|
|
916
|
+
url: Request URL.
|
|
917
|
+
**kwargs: Request parameters (json, data, content, files, etc.).
|
|
918
|
+
|
|
919
|
+
Returns:
|
|
920
|
+
Response object.
|
|
921
|
+
|
|
922
|
+
Example:
|
|
923
|
+
```python
|
|
924
|
+
import httpr
|
|
925
|
+
|
|
926
|
+
# JSON body
|
|
927
|
+
response = httpr.post("https://httpbin.org/post", json={"key": "value"})
|
|
928
|
+
|
|
929
|
+
# Form data
|
|
930
|
+
response = httpr.post("https://httpbin.org/post", data={"field": "value"})
|
|
931
|
+
```
|
|
932
|
+
"""
|
|
933
|
+
return request(method="POST", url=url, **kwargs)
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def put(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response:
|
|
937
|
+
"""
|
|
938
|
+
Make a PUT request using a temporary client.
|
|
939
|
+
|
|
940
|
+
Args:
|
|
941
|
+
url: Request URL.
|
|
942
|
+
**kwargs: Request parameters.
|
|
943
|
+
|
|
944
|
+
Returns:
|
|
945
|
+
Response object.
|
|
946
|
+
|
|
947
|
+
Example:
|
|
948
|
+
```python
|
|
949
|
+
import httpr
|
|
950
|
+
|
|
951
|
+
response = httpr.put("https://httpbin.org/put", json={"key": "value"})
|
|
952
|
+
```
|
|
953
|
+
"""
|
|
954
|
+
return request(method="PUT", url=url, **kwargs)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def patch(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response:
|
|
958
|
+
"""
|
|
959
|
+
Make a PATCH request using a temporary client.
|
|
960
|
+
|
|
961
|
+
Args:
|
|
962
|
+
url: Request URL.
|
|
963
|
+
**kwargs: Request parameters.
|
|
964
|
+
|
|
965
|
+
Returns:
|
|
966
|
+
Response object.
|
|
967
|
+
|
|
968
|
+
Example:
|
|
969
|
+
```python
|
|
970
|
+
import httpr
|
|
971
|
+
|
|
972
|
+
response = httpr.patch("https://httpbin.org/patch", json={"field": "new_value"})
|
|
973
|
+
```
|
|
974
|
+
"""
|
|
975
|
+
return request(method="PATCH", url=url, **kwargs)
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
# Import exceptions from the Rust module
|
|
979
|
+
from .httpr import ( # noqa: E402
|
|
980
|
+
CloseError,
|
|
981
|
+
# Network exceptions
|
|
982
|
+
ConnectError,
|
|
983
|
+
# Timeout exceptions
|
|
984
|
+
ConnectTimeout,
|
|
985
|
+
CookieConflict,
|
|
986
|
+
DecodingError,
|
|
987
|
+
# Base exceptions
|
|
988
|
+
HTTPError,
|
|
989
|
+
HTTPStatusError,
|
|
990
|
+
# Other exceptions
|
|
991
|
+
InvalidURL,
|
|
992
|
+
# Protocol exceptions
|
|
993
|
+
LocalProtocolError,
|
|
994
|
+
NetworkError,
|
|
995
|
+
PoolTimeout,
|
|
996
|
+
ProtocolError,
|
|
997
|
+
ProxyError,
|
|
998
|
+
ReadError,
|
|
999
|
+
ReadTimeout,
|
|
1000
|
+
RemoteProtocolError,
|
|
1001
|
+
RequestError,
|
|
1002
|
+
RequestNotRead,
|
|
1003
|
+
ResponseNotRead,
|
|
1004
|
+
StreamClosed,
|
|
1005
|
+
# Stream exceptions
|
|
1006
|
+
StreamConsumed,
|
|
1007
|
+
StreamError,
|
|
1008
|
+
TimeoutException,
|
|
1009
|
+
TooManyRedirects,
|
|
1010
|
+
TransportError,
|
|
1011
|
+
# Other transport/request exceptions
|
|
1012
|
+
UnsupportedProtocol,
|
|
1013
|
+
WriteError,
|
|
1014
|
+
WriteTimeout,
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
__all__ = [
|
|
1018
|
+
# Client and request functions
|
|
1019
|
+
"Client",
|
|
1020
|
+
"AsyncClient",
|
|
1021
|
+
"request",
|
|
1022
|
+
"get",
|
|
1023
|
+
"head",
|
|
1024
|
+
"options",
|
|
1025
|
+
"delete",
|
|
1026
|
+
"post",
|
|
1027
|
+
"put",
|
|
1028
|
+
"patch",
|
|
1029
|
+
# Response classes
|
|
1030
|
+
"Response",
|
|
1031
|
+
"StreamingResponse",
|
|
1032
|
+
"CaseInsensitiveHeaderMap",
|
|
1033
|
+
# Base exceptions
|
|
1034
|
+
"HTTPError",
|
|
1035
|
+
"RequestError",
|
|
1036
|
+
"TransportError",
|
|
1037
|
+
"NetworkError",
|
|
1038
|
+
"TimeoutException",
|
|
1039
|
+
"ProtocolError",
|
|
1040
|
+
"StreamError",
|
|
1041
|
+
# Timeout exceptions
|
|
1042
|
+
"ConnectTimeout",
|
|
1043
|
+
"ReadTimeout",
|
|
1044
|
+
"WriteTimeout",
|
|
1045
|
+
"PoolTimeout",
|
|
1046
|
+
# Network exceptions
|
|
1047
|
+
"ConnectError",
|
|
1048
|
+
"ReadError",
|
|
1049
|
+
"WriteError",
|
|
1050
|
+
"CloseError",
|
|
1051
|
+
# Protocol exceptions
|
|
1052
|
+
"LocalProtocolError",
|
|
1053
|
+
"RemoteProtocolError",
|
|
1054
|
+
# Other exceptions
|
|
1055
|
+
"UnsupportedProtocol",
|
|
1056
|
+
"ProxyError",
|
|
1057
|
+
"TooManyRedirects",
|
|
1058
|
+
"HTTPStatusError",
|
|
1059
|
+
"DecodingError",
|
|
1060
|
+
"StreamConsumed",
|
|
1061
|
+
"ResponseNotRead",
|
|
1062
|
+
"RequestNotRead",
|
|
1063
|
+
"StreamClosed",
|
|
1064
|
+
"InvalidURL",
|
|
1065
|
+
"CookieConflict",
|
|
1066
|
+
]
|