geventhttpclient 2.3.9__tar.gz → 2.4.0__tar.gz
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.
- {geventhttpclient-2.3.9/src/geventhttpclient.egg-info → geventhttpclient-2.4.0}/PKG-INFO +72 -2
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/README.md +71 -1
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/ext/_parser.c +8 -9
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/llhttp/include/llhttp.h +371 -23
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/llhttp/src/api.c +153 -13
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/llhttp/src/http.c +23 -3
- geventhttpclient-2.4.0/llhttp/src/llhttp.c +10279 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/pyproject.toml +4 -27
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/setup.py +1 -2
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/__init__.py +1 -1
- geventhttpclient-2.4.0/src/geventhttpclient/client.py +568 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/connectionpool.py +47 -22
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/header.py +2 -8
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/requests.py +3 -5
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/response.py +34 -13
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/url.py +1 -1
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/useragent.py +26 -25
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0/src/geventhttpclient.egg-info}/PKG-INFO +72 -2
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient.egg-info/SOURCES.txt +2 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_client.py +131 -7
- geventhttpclient-2.4.0/tests/test_continue.py +135 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_headers.py +2 -2
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_keep_alive.py +4 -1
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_no_module_ssl.py +4 -4
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_parser.py +25 -2
- geventhttpclient-2.4.0/tests/test_proxy.py +172 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_ssl.py +9 -3
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_useragent.py +25 -3
- geventhttpclient-2.3.9/llhttp/src/llhttp.c +0 -15954
- geventhttpclient-2.3.9/src/geventhttpclient/client.py +0 -318
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/LICENSE-MIT +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/MANIFEST.in +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/ext/Python_compat.h +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/llhttp/LICENSE-MIT +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/setup.cfg +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/api.py +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/httplib.py +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient/httplib2.py +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient.egg-info/dependency_links.txt +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient.egg-info/requires.txt +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/src/geventhttpclient.egg-info/top_level.txt +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_httplib.py +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_httplib2.py +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_network_failures.py +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_requests.py +0 -0
- {geventhttpclient-2.3.9 → geventhttpclient-2.4.0}/tests/test_url.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geventhttpclient
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: HTTP client library for gevent
|
|
5
5
|
Author-email: Antonin Amand <antonin.amand@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -76,10 +76,12 @@ simple use-cases, it can serve as a drop-in replacement.
|
|
|
76
76
|
|
|
77
77
|
```python
|
|
78
78
|
import geventhttpclient as requests
|
|
79
|
+
|
|
79
80
|
requests.get("https://github.com").text
|
|
80
81
|
requests.post("http://httpbingo.org/post", data="asdfasd").json()
|
|
81
82
|
|
|
82
83
|
from geventhttpclient import Session
|
|
84
|
+
|
|
83
85
|
s = Session()
|
|
84
86
|
s.get("http://httpbingo.org/headers").json()
|
|
85
87
|
s.get("https://github.com").content
|
|
@@ -117,6 +119,7 @@ patch before you import or the `super()` calls will fail.
|
|
|
117
119
|
|
|
118
120
|
```python
|
|
119
121
|
import geventhttpclient.httplib
|
|
122
|
+
|
|
120
123
|
geventhttpclient.httplib.patch()
|
|
121
124
|
|
|
122
125
|
import httplib2
|
|
@@ -152,6 +155,7 @@ assert response.status_code == 200
|
|
|
152
155
|
# the json parser as it's being read.
|
|
153
156
|
data = json.load(response)["data"]
|
|
154
157
|
|
|
158
|
+
|
|
155
159
|
def print_friend_username(client, friend_id):
|
|
156
160
|
friend_url = URL(f"/{friend_id}", params={"access_token": TOKEN})
|
|
157
161
|
# the greenlet will block until a connection is available
|
|
@@ -163,6 +167,7 @@ def print_friend_username(client, friend_id):
|
|
|
163
167
|
else:
|
|
164
168
|
print(f"{friend['name']} has no username.")
|
|
165
169
|
|
|
170
|
+
|
|
166
171
|
# allow to run 20 greenlet at a time, this is more than concurrency
|
|
167
172
|
# of the http client but isn't a problem since the client has its own
|
|
168
173
|
# connection pool.
|
|
@@ -192,7 +197,7 @@ client = HTTPClient.from_url(url)
|
|
|
192
197
|
response = client.get(url.query_string)
|
|
193
198
|
assert response.status_code == 200
|
|
194
199
|
|
|
195
|
-
CHUNK_SIZE = 1024 * 16
|
|
200
|
+
CHUNK_SIZE = 1024 * 16 # 16KB
|
|
196
201
|
with open("/tmp/100.dat", "w") as f:
|
|
197
202
|
data = response.read(CHUNK_SIZE)
|
|
198
203
|
while data:
|
|
@@ -200,6 +205,71 @@ with open("/tmp/100.dat", "w") as f:
|
|
|
200
205
|
data = response.read(CHUNK_SIZE)
|
|
201
206
|
```
|
|
202
207
|
|
|
208
|
+
## Chunked request bodies
|
|
209
|
+
|
|
210
|
+
Requests with `Transfer-Encoding: chunked` are supported. Add the header
|
|
211
|
+
explicitly to have the body chunk-encoded automatically:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
client = HTTPClient.from_url(url)
|
|
215
|
+
response = client.post(
|
|
216
|
+
"/upload",
|
|
217
|
+
body=data,
|
|
218
|
+
headers={"Transfer-Encoding": "chunked"},
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Bodies of unknown length are chunk-encoded automatically, so generators and
|
|
223
|
+
iterables of bytes work as request bodies as well:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
def generate_data():
|
|
227
|
+
for i in range(100):
|
|
228
|
+
yield b"some data block\n"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
response = client.post("/upload", body=generate_data())
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Chunked transfer encoding requires HTTP/1.1; a `ValueError` is raised for
|
|
235
|
+
HTTP/1.0 requests. A user-provided `Content-Length` header is dropped when
|
|
236
|
+
chunked encoding is used.
|
|
237
|
+
|
|
238
|
+
## Proxy support
|
|
239
|
+
|
|
240
|
+
`HTTPClient` can route requests through an HTTP proxy. Plain HTTP requests are
|
|
241
|
+
forwarded with an absolute request URI, HTTPS requests are tunneled with
|
|
242
|
+
`CONNECT`, optionally with `Basic` proxy authentication:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
client = HTTPClient(
|
|
246
|
+
"target.example.com",
|
|
247
|
+
port=443,
|
|
248
|
+
ssl=True,
|
|
249
|
+
proxy_host="proxy.example.com",
|
|
250
|
+
proxy_port=3128,
|
|
251
|
+
proxy_user="user",
|
|
252
|
+
proxy_password="pass",
|
|
253
|
+
)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The same keyword arguments are accepted by `UserAgent` and `HTTPClientPool`.
|
|
257
|
+
|
|
258
|
+
## Expect: 100-continue
|
|
259
|
+
|
|
260
|
+
Add an `Expect: 100-continue` header to let the server reject a request with a
|
|
261
|
+
large body before the body is sent. The client sends the headers first, waits
|
|
262
|
+
for an interim `100 Continue` response before sending the body, and returns
|
|
263
|
+
the final response directly if the server rejects the request:
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
client = HTTPClient.from_url(url)
|
|
267
|
+
response = client.post("/upload", body=data, headers={"Expect": "100-continue"})
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Note that servers not answering an `Expect: 100-continue` request block until
|
|
271
|
+
the network timeout is reached.
|
|
272
|
+
|
|
203
273
|
## Benchmarks
|
|
204
274
|
|
|
205
275
|
The benchmark runs 10000 `GET` requests against a local nginx server in the default
|
|
@@ -33,10 +33,12 @@ simple use-cases, it can serve as a drop-in replacement.
|
|
|
33
33
|
|
|
34
34
|
```python
|
|
35
35
|
import geventhttpclient as requests
|
|
36
|
+
|
|
36
37
|
requests.get("https://github.com").text
|
|
37
38
|
requests.post("http://httpbingo.org/post", data="asdfasd").json()
|
|
38
39
|
|
|
39
40
|
from geventhttpclient import Session
|
|
41
|
+
|
|
40
42
|
s = Session()
|
|
41
43
|
s.get("http://httpbingo.org/headers").json()
|
|
42
44
|
s.get("https://github.com").content
|
|
@@ -74,6 +76,7 @@ patch before you import or the `super()` calls will fail.
|
|
|
74
76
|
|
|
75
77
|
```python
|
|
76
78
|
import geventhttpclient.httplib
|
|
79
|
+
|
|
77
80
|
geventhttpclient.httplib.patch()
|
|
78
81
|
|
|
79
82
|
import httplib2
|
|
@@ -109,6 +112,7 @@ assert response.status_code == 200
|
|
|
109
112
|
# the json parser as it's being read.
|
|
110
113
|
data = json.load(response)["data"]
|
|
111
114
|
|
|
115
|
+
|
|
112
116
|
def print_friend_username(client, friend_id):
|
|
113
117
|
friend_url = URL(f"/{friend_id}", params={"access_token": TOKEN})
|
|
114
118
|
# the greenlet will block until a connection is available
|
|
@@ -120,6 +124,7 @@ def print_friend_username(client, friend_id):
|
|
|
120
124
|
else:
|
|
121
125
|
print(f"{friend['name']} has no username.")
|
|
122
126
|
|
|
127
|
+
|
|
123
128
|
# allow to run 20 greenlet at a time, this is more than concurrency
|
|
124
129
|
# of the http client but isn't a problem since the client has its own
|
|
125
130
|
# connection pool.
|
|
@@ -149,7 +154,7 @@ client = HTTPClient.from_url(url)
|
|
|
149
154
|
response = client.get(url.query_string)
|
|
150
155
|
assert response.status_code == 200
|
|
151
156
|
|
|
152
|
-
CHUNK_SIZE = 1024 * 16
|
|
157
|
+
CHUNK_SIZE = 1024 * 16 # 16KB
|
|
153
158
|
with open("/tmp/100.dat", "w") as f:
|
|
154
159
|
data = response.read(CHUNK_SIZE)
|
|
155
160
|
while data:
|
|
@@ -157,6 +162,71 @@ with open("/tmp/100.dat", "w") as f:
|
|
|
157
162
|
data = response.read(CHUNK_SIZE)
|
|
158
163
|
```
|
|
159
164
|
|
|
165
|
+
## Chunked request bodies
|
|
166
|
+
|
|
167
|
+
Requests with `Transfer-Encoding: chunked` are supported. Add the header
|
|
168
|
+
explicitly to have the body chunk-encoded automatically:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
client = HTTPClient.from_url(url)
|
|
172
|
+
response = client.post(
|
|
173
|
+
"/upload",
|
|
174
|
+
body=data,
|
|
175
|
+
headers={"Transfer-Encoding": "chunked"},
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Bodies of unknown length are chunk-encoded automatically, so generators and
|
|
180
|
+
iterables of bytes work as request bodies as well:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
def generate_data():
|
|
184
|
+
for i in range(100):
|
|
185
|
+
yield b"some data block\n"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
response = client.post("/upload", body=generate_data())
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Chunked transfer encoding requires HTTP/1.1; a `ValueError` is raised for
|
|
192
|
+
HTTP/1.0 requests. A user-provided `Content-Length` header is dropped when
|
|
193
|
+
chunked encoding is used.
|
|
194
|
+
|
|
195
|
+
## Proxy support
|
|
196
|
+
|
|
197
|
+
`HTTPClient` can route requests through an HTTP proxy. Plain HTTP requests are
|
|
198
|
+
forwarded with an absolute request URI, HTTPS requests are tunneled with
|
|
199
|
+
`CONNECT`, optionally with `Basic` proxy authentication:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
client = HTTPClient(
|
|
203
|
+
"target.example.com",
|
|
204
|
+
port=443,
|
|
205
|
+
ssl=True,
|
|
206
|
+
proxy_host="proxy.example.com",
|
|
207
|
+
proxy_port=3128,
|
|
208
|
+
proxy_user="user",
|
|
209
|
+
proxy_password="pass",
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The same keyword arguments are accepted by `UserAgent` and `HTTPClientPool`.
|
|
214
|
+
|
|
215
|
+
## Expect: 100-continue
|
|
216
|
+
|
|
217
|
+
Add an `Expect: 100-continue` header to let the server reject a request with a
|
|
218
|
+
large body before the body is sent. The client sends the headers first, waits
|
|
219
|
+
for an interim `100 Continue` response before sending the body, and returns
|
|
220
|
+
the final response directly if the server rejects the request:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
client = HTTPClient.from_url(url)
|
|
224
|
+
response = client.post("/upload", body=data, headers={"Expect": "100-continue"})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Note that servers not answering an `Expect: 100-continue` request block until
|
|
228
|
+
the network timeout is reached.
|
|
229
|
+
|
|
160
230
|
## Benchmarks
|
|
161
231
|
|
|
162
232
|
The benchmark runs 10000 `GET` requests against a local nginx server in the default
|
|
@@ -141,15 +141,14 @@ static int on_body(llhttp_t* parser, const char *at, size_t length)
|
|
|
141
141
|
return fail;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
static llhttp_settings_t _parser_settings = {
|
|
145
|
-
on_message_begin,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
on_message_complete
|
|
144
|
+
static const llhttp_settings_t _parser_settings = {
|
|
145
|
+
.on_message_begin = on_message_begin,
|
|
146
|
+
.on_status = on_status,
|
|
147
|
+
.on_header_field = on_header_field,
|
|
148
|
+
.on_header_value = on_header_value,
|
|
149
|
+
.on_headers_complete = on_headers_complete,
|
|
150
|
+
.on_body = on_body,
|
|
151
|
+
.on_message_complete = on_message_complete,
|
|
153
152
|
};
|
|
154
153
|
|
|
155
154
|
static PyObject*
|