k3http 0.1.4__tar.gz → 0.1.6__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.
- {k3http-0.1.4/k3http.egg-info → k3http-0.1.6}/PKG-INFO +14 -18
- {k3http-0.1.4 → k3http-0.1.6}/README.md +13 -17
- {k3http-0.1.4 → k3http-0.1.6}/__init__.py +8 -9
- {k3http-0.1.4 → k3http-0.1.6}/client.py +52 -48
- {k3http-0.1.4 → k3http-0.1.6/k3http.egg-info}/PKG-INFO +14 -18
- {k3http-0.1.4 → k3http-0.1.6}/pyproject.toml +1 -1
- {k3http-0.1.4 → k3http-0.1.6}/synopsis.py +4 -4
- {k3http-0.1.4 → k3http-0.1.6}/test/test_httpclient.py +97 -49
- {k3http-0.1.4 → k3http-0.1.6}/test/test_util.py +0 -3
- {k3http-0.1.4 → k3http-0.1.6}/util.py +0 -4
- {k3http-0.1.4 → k3http-0.1.6}/LICENSE +0 -0
- {k3http-0.1.4 → k3http-0.1.6}/k3http.egg-info/SOURCES.txt +0 -0
- {k3http-0.1.4 → k3http-0.1.6}/k3http.egg-info/dependency_links.txt +0 -0
- {k3http-0.1.4 → k3http-0.1.6}/k3http.egg-info/requires.txt +0 -0
- {k3http-0.1.4 → k3http-0.1.6}/k3http.egg-info/top_level.txt +0 -0
- {k3http-0.1.4 → k3http-0.1.6}/setup.cfg +0 -0
- {k3http-0.1.4 → k3http-0.1.6}/test/test_doctest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: k3http
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: HTTP/1.1 client with timeout support
|
|
5
5
|
Author-email: Zhang Yanpo <drdr.xp@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -38,7 +38,6 @@ Dynamic: license-file
|
|
|
38
38
|
# k3http
|
|
39
39
|
|
|
40
40
|
[](https://github.com/pykit3/k3http/actions/workflows/python-package.yml)
|
|
41
|
-
[](https://travis-ci.com/pykit3/k3http)
|
|
42
41
|
[](https://k3http.readthedocs.io/en/stable/?badge=stable)
|
|
43
42
|
[](https://pypi.org/project/k3http)
|
|
44
43
|
|
|
@@ -62,23 +61,22 @@ pip install k3http
|
|
|
62
61
|
# Synopsis
|
|
63
62
|
|
|
64
63
|
```python
|
|
64
|
+
import urllib
|
|
65
65
|
|
|
66
66
|
import k3http
|
|
67
|
-
import urllib
|
|
68
|
-
import socket
|
|
69
67
|
|
|
70
68
|
headers = {
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
"Host": "127.0.0.1",
|
|
70
|
+
"Accept-Language": "en, mi",
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
try:
|
|
76
|
-
h = k3http.Client(
|
|
74
|
+
h = k3http.Client("127.0.0.1", 80)
|
|
77
75
|
|
|
78
76
|
# send http reqeust without body
|
|
79
77
|
# read response status line
|
|
80
78
|
# read response headers
|
|
81
|
-
h.request(
|
|
79
|
+
h.request("/test.txt", method="GET", headers=headers)
|
|
82
80
|
|
|
83
81
|
status = h.status
|
|
84
82
|
# response code return from http server, type is int
|
|
@@ -97,23 +95,22 @@ try:
|
|
|
97
95
|
|
|
98
96
|
# get response body
|
|
99
97
|
print(h.read_body(None))
|
|
100
|
-
except (
|
|
98
|
+
except (OSError, k3http.HttpError) as e:
|
|
101
99
|
print(repr(e))
|
|
102
100
|
|
|
103
101
|
|
|
104
|
-
|
|
105
|
-
content = urllib.urlencode({'f': 'foo', 'b': 'bar'})
|
|
102
|
+
content = urllib.urlencode({"f": "foo", "b": "bar"})
|
|
106
103
|
headers = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
"Host": "www.example.com",
|
|
105
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
|
106
|
+
"Content-Length": len(content),
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
try:
|
|
113
|
-
h = k3http.Client(
|
|
110
|
+
h = k3http.Client("127.0.0.1", 80)
|
|
114
111
|
|
|
115
112
|
# send http reqeust
|
|
116
|
-
h.send_request(
|
|
113
|
+
h.send_request("http://www.example.com", method="POST", headers=headers)
|
|
117
114
|
|
|
118
115
|
# send http request body
|
|
119
116
|
h.send_body(content)
|
|
@@ -123,9 +120,8 @@ try:
|
|
|
123
120
|
|
|
124
121
|
# read response body
|
|
125
122
|
print(h.read_body(None))
|
|
126
|
-
except (
|
|
123
|
+
except (OSError, k3http.HttpError) as e:
|
|
127
124
|
print(repr(e))
|
|
128
|
-
|
|
129
125
|
```
|
|
130
126
|
|
|
131
127
|
# Author
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# k3http
|
|
2
2
|
|
|
3
3
|
[](https://github.com/pykit3/k3http/actions/workflows/python-package.yml)
|
|
4
|
-
[](https://travis-ci.com/pykit3/k3http)
|
|
5
4
|
[](https://k3http.readthedocs.io/en/stable/?badge=stable)
|
|
6
5
|
[](https://pypi.org/project/k3http)
|
|
7
6
|
|
|
@@ -25,23 +24,22 @@ pip install k3http
|
|
|
25
24
|
# Synopsis
|
|
26
25
|
|
|
27
26
|
```python
|
|
27
|
+
import urllib
|
|
28
28
|
|
|
29
29
|
import k3http
|
|
30
|
-
import urllib
|
|
31
|
-
import socket
|
|
32
30
|
|
|
33
31
|
headers = {
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
"Host": "127.0.0.1",
|
|
33
|
+
"Accept-Language": "en, mi",
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
try:
|
|
39
|
-
h = k3http.Client(
|
|
37
|
+
h = k3http.Client("127.0.0.1", 80)
|
|
40
38
|
|
|
41
39
|
# send http reqeust without body
|
|
42
40
|
# read response status line
|
|
43
41
|
# read response headers
|
|
44
|
-
h.request(
|
|
42
|
+
h.request("/test.txt", method="GET", headers=headers)
|
|
45
43
|
|
|
46
44
|
status = h.status
|
|
47
45
|
# response code return from http server, type is int
|
|
@@ -60,23 +58,22 @@ try:
|
|
|
60
58
|
|
|
61
59
|
# get response body
|
|
62
60
|
print(h.read_body(None))
|
|
63
|
-
except (
|
|
61
|
+
except (OSError, k3http.HttpError) as e:
|
|
64
62
|
print(repr(e))
|
|
65
63
|
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
content = urllib.urlencode({'f': 'foo', 'b': 'bar'})
|
|
65
|
+
content = urllib.urlencode({"f": "foo", "b": "bar"})
|
|
69
66
|
headers = {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
"Host": "www.example.com",
|
|
68
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
|
69
|
+
"Content-Length": len(content),
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
try:
|
|
76
|
-
h = k3http.Client(
|
|
73
|
+
h = k3http.Client("127.0.0.1", 80)
|
|
77
74
|
|
|
78
75
|
# send http reqeust
|
|
79
|
-
h.send_request(
|
|
76
|
+
h.send_request("http://www.example.com", method="POST", headers=headers)
|
|
80
77
|
|
|
81
78
|
# send http request body
|
|
82
79
|
h.send_body(content)
|
|
@@ -86,9 +83,8 @@ try:
|
|
|
86
83
|
|
|
87
84
|
# read response body
|
|
88
85
|
print(h.read_body(None))
|
|
89
|
-
except (
|
|
86
|
+
except (OSError, k3http.HttpError) as e:
|
|
90
87
|
print(repr(e))
|
|
91
|
-
|
|
92
88
|
```
|
|
93
89
|
|
|
94
90
|
# Author
|
|
@@ -9,30 +9,29 @@ from importlib.metadata import version
|
|
|
9
9
|
__version__ = version("k3http")
|
|
10
10
|
|
|
11
11
|
from .client import (
|
|
12
|
+
BadStatusLineError,
|
|
13
|
+
ChunkedSizeError,
|
|
14
|
+
Client,
|
|
15
|
+
HeadersError,
|
|
12
16
|
HttpError,
|
|
13
17
|
LineTooLongError,
|
|
14
|
-
ChunkedSizeError,
|
|
15
18
|
NotConnectedError,
|
|
16
19
|
ResponseNotReadyError,
|
|
17
|
-
HeadersError,
|
|
18
|
-
BadStatusLineError,
|
|
19
|
-
Client,
|
|
20
20
|
)
|
|
21
|
-
|
|
22
21
|
from .util import (
|
|
23
22
|
headers_add_host,
|
|
24
23
|
request_add_host,
|
|
25
24
|
)
|
|
26
25
|
|
|
27
26
|
__all__ = [
|
|
27
|
+
"BadStatusLineError",
|
|
28
|
+
"ChunkedSizeError",
|
|
29
|
+
"Client",
|
|
30
|
+
"HeadersError",
|
|
28
31
|
"HttpError",
|
|
29
32
|
"LineTooLongError",
|
|
30
|
-
"ChunkedSizeError",
|
|
31
33
|
"NotConnectedError",
|
|
32
34
|
"ResponseNotReadyError",
|
|
33
|
-
"HeadersError",
|
|
34
|
-
"BadStatusLineError",
|
|
35
|
-
"Client",
|
|
36
35
|
"headers_add_host",
|
|
37
36
|
"request_add_host",
|
|
38
37
|
]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# coding: utf-8
|
|
3
2
|
|
|
4
3
|
import errno
|
|
5
4
|
import logging
|
|
@@ -50,7 +49,7 @@ MAX_LINE_LENGTH = 65536
|
|
|
50
49
|
LINE_RECV_LENGTH = 1024 * 4
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
class Client
|
|
52
|
+
class Client:
|
|
54
53
|
"""
|
|
55
54
|
HTTP client class
|
|
56
55
|
|
|
@@ -125,11 +124,11 @@ class Client(object):
|
|
|
125
124
|
sw = self.get_and_end_stopwatch()
|
|
126
125
|
rst = []
|
|
127
126
|
for t in sw.get_last_trace_report():
|
|
128
|
-
ent =
|
|
129
|
-
name
|
|
130
|
-
time
|
|
131
|
-
annotation
|
|
132
|
-
|
|
127
|
+
ent = {
|
|
128
|
+
"name": t.name,
|
|
129
|
+
"time": t.end_time - t.start_time,
|
|
130
|
+
"annotation": [self._human_annotation(an) for an in t.trace_annotations],
|
|
131
|
+
}
|
|
133
132
|
|
|
134
133
|
rst.append(ent)
|
|
135
134
|
|
|
@@ -137,7 +136,7 @@ class Client(object):
|
|
|
137
136
|
|
|
138
137
|
def _human_annotation(self, annotation):
|
|
139
138
|
an = annotation
|
|
140
|
-
return "{key}:{value}"
|
|
139
|
+
return f"{an.key}:{an.value}"
|
|
141
140
|
|
|
142
141
|
def get_and_end_stopwatch(self):
|
|
143
142
|
"""stopwatch must be stopped before it can be read"""
|
|
@@ -198,7 +197,7 @@ class Client(object):
|
|
|
198
197
|
self.sock = self.https_context.wrap_socket(self.sock, server_hostname=self.host)
|
|
199
198
|
|
|
200
199
|
bufs = [
|
|
201
|
-
"{method} {uri} HTTP/1.1"
|
|
200
|
+
f"{method} {uri} HTTP/1.1",
|
|
202
201
|
]
|
|
203
202
|
|
|
204
203
|
headers = headers or {}
|
|
@@ -209,7 +208,7 @@ class Client(object):
|
|
|
209
208
|
self.request_chunked_encoded = True
|
|
210
209
|
|
|
211
210
|
for k, v in headers.items():
|
|
212
|
-
bufs.append("
|
|
211
|
+
bufs.append(f"{k}: {v}")
|
|
213
212
|
|
|
214
213
|
bufs.extend(["", ""])
|
|
215
214
|
|
|
@@ -218,20 +217,24 @@ class Client(object):
|
|
|
218
217
|
|
|
219
218
|
def send_body(self, body):
|
|
220
219
|
"""
|
|
221
|
-
Send http request body. It should be a '
|
|
220
|
+
Send http request body. It should be a 'bytes' of data to send after the headers are finished.
|
|
221
|
+
A 'str' body is encoded as UTF-8.
|
|
222
222
|
|
|
223
|
-
:param body: a 'str' of http request body.
|
|
223
|
+
:param body: a 'bytes' or 'str' of http request body.
|
|
224
224
|
:return: nothing
|
|
225
225
|
"""
|
|
226
226
|
|
|
227
227
|
if self.sock is None:
|
|
228
228
|
raise NotConnectedError("socket object is None")
|
|
229
229
|
|
|
230
|
+
if isinstance(body, str):
|
|
231
|
+
body = body.encode("utf-8")
|
|
232
|
+
|
|
230
233
|
with self.stopwatch.timer("send_body"):
|
|
231
234
|
if self.request_chunked_encoded:
|
|
232
|
-
body = "
|
|
235
|
+
body = b"%x\r\n%s\r\n" % (len(body), body)
|
|
233
236
|
|
|
234
|
-
self.sock.sendall(body
|
|
237
|
+
self.sock.sendall(body)
|
|
235
238
|
|
|
236
239
|
def read_status(self, skip_100=True):
|
|
237
240
|
"""
|
|
@@ -276,7 +279,7 @@ class Client(object):
|
|
|
276
279
|
|
|
277
280
|
kv = line.strip().split(":", 1)
|
|
278
281
|
if len(kv) < 2:
|
|
279
|
-
raise HeadersError("invalid headers param line
|
|
282
|
+
raise HeadersError(f"invalid headers param line:{line}")
|
|
280
283
|
self.headers[kv[0].lower()] = kv[1].strip()
|
|
281
284
|
|
|
282
285
|
if self.status in (204, 304) or self.method == "HEAD":
|
|
@@ -293,7 +296,7 @@ class Client(object):
|
|
|
293
296
|
try:
|
|
294
297
|
self.content_length = int(length)
|
|
295
298
|
except ValueError as e:
|
|
296
|
-
logger.error(repr(e) + " while get content-length length:{
|
|
299
|
+
logger.error(repr(e) + f" while get content-length length:{length}")
|
|
297
300
|
raise HeadersError("invalid content-length")
|
|
298
301
|
|
|
299
302
|
return self.headers
|
|
@@ -320,7 +323,7 @@ class Client(object):
|
|
|
320
323
|
Read and return the response body.
|
|
321
324
|
|
|
322
325
|
:param size: need read size, if greater than left size use the min one, if 'None' read left unread body.
|
|
323
|
-
:return: the response body.
|
|
326
|
+
:return: the response body as 'bytes'.
|
|
324
327
|
"""
|
|
325
328
|
|
|
326
329
|
with self.stopwatch.timer("recv_body"):
|
|
@@ -330,19 +333,19 @@ class Client(object):
|
|
|
330
333
|
"""
|
|
331
334
|
Read and yield one line in response body each time.
|
|
332
335
|
|
|
333
|
-
:param delimiter: specify the delimiter between each line, defalut supporting '\n'.
|
|
334
|
-
:return: a generator yileding one line including delimiter in response body each time.
|
|
336
|
+
:param delimiter: specify the 'bytes' delimiter between each line, defalut supporting b'\n'.
|
|
337
|
+
:return: a generator yileding one 'bytes' line including delimiter in response body each time.
|
|
335
338
|
"""
|
|
336
339
|
|
|
337
340
|
if delimiter is None:
|
|
338
|
-
delimiter = "\n"
|
|
341
|
+
delimiter = b"\n"
|
|
339
342
|
|
|
340
|
-
buf = ""
|
|
343
|
+
buf = b""
|
|
341
344
|
while True:
|
|
342
345
|
tmp = self._read_body(MAX_LINE_LENGTH)
|
|
343
346
|
|
|
344
|
-
if tmp == "":
|
|
345
|
-
if buf != "":
|
|
347
|
+
if tmp == b"":
|
|
348
|
+
if buf != b"":
|
|
346
349
|
yield buf
|
|
347
350
|
break
|
|
348
351
|
|
|
@@ -356,7 +359,7 @@ class Client(object):
|
|
|
356
359
|
|
|
357
360
|
def _read_body(self, size):
|
|
358
361
|
if size is not None and size <= 0:
|
|
359
|
-
return ""
|
|
362
|
+
return b""
|
|
360
363
|
|
|
361
364
|
if self.chunked:
|
|
362
365
|
buf = self._read_chunked(size)
|
|
@@ -369,7 +372,7 @@ class Client(object):
|
|
|
369
372
|
size = min(size, self.content_length - self.has_read)
|
|
370
373
|
|
|
371
374
|
if size <= 0:
|
|
372
|
-
return ""
|
|
375
|
+
return b""
|
|
373
376
|
|
|
374
377
|
buf = self._read(size)
|
|
375
378
|
self.has_read += size
|
|
@@ -380,16 +383,16 @@ class Client(object):
|
|
|
380
383
|
if self.recv_iter is not None:
|
|
381
384
|
try:
|
|
382
385
|
self.recv_iter.close()
|
|
383
|
-
except Exception
|
|
384
|
-
logger.exception(
|
|
386
|
+
except Exception:
|
|
387
|
+
logger.exception("while close recv_iter")
|
|
385
388
|
|
|
386
389
|
self.recv_iter = None
|
|
387
390
|
|
|
388
391
|
if self.sock is not None:
|
|
389
392
|
try:
|
|
390
393
|
self.sock.close()
|
|
391
|
-
except Exception
|
|
392
|
-
logger.exception(
|
|
394
|
+
except Exception:
|
|
395
|
+
logger.exception("while close sock")
|
|
393
396
|
|
|
394
397
|
self.sock = None
|
|
395
398
|
|
|
@@ -407,7 +410,8 @@ class Client(object):
|
|
|
407
410
|
return self.recv_iter.send(("block", size))
|
|
408
411
|
|
|
409
412
|
def _readline(self):
|
|
410
|
-
|
|
413
|
+
line = self.recv_iter.send(("line", None))
|
|
414
|
+
return line.decode("utf-8")
|
|
411
415
|
|
|
412
416
|
def _get_response_status(self):
|
|
413
417
|
with self.stopwatch.timer("recv_status"):
|
|
@@ -415,7 +419,7 @@ class Client(object):
|
|
|
415
419
|
|
|
416
420
|
vals = line.split(None, 2)
|
|
417
421
|
if len(vals) < 2:
|
|
418
|
-
raise BadStatusLineError("invalid status line:{
|
|
422
|
+
raise BadStatusLineError(f"invalid status line:{line}")
|
|
419
423
|
|
|
420
424
|
ver, status = vals[0], vals[1]
|
|
421
425
|
|
|
@@ -423,10 +427,10 @@ class Client(object):
|
|
|
423
427
|
status = int(status)
|
|
424
428
|
except ValueError as e:
|
|
425
429
|
logger.error(repr(e) + " while get response status")
|
|
426
|
-
raise BadStatusLineError("status is not int:{
|
|
430
|
+
raise BadStatusLineError(f"status is not int:{line}")
|
|
427
431
|
|
|
428
432
|
if not ver.startswith("HTTP/") or status < 100 or status > 999:
|
|
429
|
-
raise BadStatusLineError("invalid status line:{
|
|
433
|
+
raise BadStatusLineError(f"invalid status line:{line}")
|
|
430
434
|
|
|
431
435
|
return status
|
|
432
436
|
|
|
@@ -441,7 +445,7 @@ class Client(object):
|
|
|
441
445
|
try:
|
|
442
446
|
chunk_size = int(line, 16)
|
|
443
447
|
except ValueError as e:
|
|
444
|
-
logger.error(repr(e) + " while get chunk size line:{
|
|
448
|
+
logger.error(repr(e) + f" while get chunk size line:{line}")
|
|
445
449
|
raise ChunkedSizeError("invalid chunk size")
|
|
446
450
|
|
|
447
451
|
return chunk_size
|
|
@@ -450,7 +454,7 @@ class Client(object):
|
|
|
450
454
|
buf = []
|
|
451
455
|
|
|
452
456
|
if self.chunk_left == 0:
|
|
453
|
-
return ""
|
|
457
|
+
return b""
|
|
454
458
|
|
|
455
459
|
while size is None or size > 0:
|
|
456
460
|
if self.chunk_left is None:
|
|
@@ -478,7 +482,7 @@ class Client(object):
|
|
|
478
482
|
if line == "":
|
|
479
483
|
break
|
|
480
484
|
|
|
481
|
-
return "".join(buf)
|
|
485
|
+
return b"".join(buf)
|
|
482
486
|
|
|
483
487
|
def set_timeout(self, timeout):
|
|
484
488
|
self.timeout = timeout
|
|
@@ -488,20 +492,20 @@ class Client(object):
|
|
|
488
492
|
|
|
489
493
|
|
|
490
494
|
def _recv_loop(sock, timeout):
|
|
491
|
-
bufs = [""]
|
|
495
|
+
bufs = [b""]
|
|
492
496
|
mode, size = yield
|
|
493
497
|
|
|
494
498
|
while True:
|
|
495
499
|
if mode == "line":
|
|
496
500
|
buf = bufs[0]
|
|
497
|
-
if "\r\n" in buf:
|
|
498
|
-
rst, buf = buf.split("\r\n", 1)
|
|
501
|
+
if b"\r\n" in buf:
|
|
502
|
+
rst, buf = buf.split(b"\r\n", 1)
|
|
499
503
|
bufs[0] = buf
|
|
500
504
|
mode, size = yield rst
|
|
501
505
|
continue
|
|
502
506
|
else:
|
|
503
507
|
if len(buf) >= MAX_LINE_LENGTH:
|
|
504
|
-
raise LineTooLongError("line length greater than max_len:{
|
|
508
|
+
raise LineTooLongError(f"line length greater than max_len:{len(buf)}")
|
|
505
509
|
else:
|
|
506
510
|
buf += _recv(sock, timeout, LINE_RECV_LENGTH)
|
|
507
511
|
bufs[0] = buf
|
|
@@ -512,31 +516,31 @@ def _recv_loop(sock, timeout):
|
|
|
512
516
|
bufs.append(_recv(sock, timeout, size - total))
|
|
513
517
|
total += len(bufs[-1])
|
|
514
518
|
|
|
515
|
-
rst = "".join(bufs)
|
|
519
|
+
rst = b"".join(bufs)
|
|
516
520
|
if size < len(rst):
|
|
517
521
|
bufs = [rst[size:]]
|
|
518
522
|
rst = rst[:size]
|
|
519
523
|
else:
|
|
520
|
-
bufs = [""]
|
|
524
|
+
bufs = [b""]
|
|
521
525
|
mode, size = yield rst
|
|
522
526
|
|
|
523
527
|
|
|
524
528
|
def _recv(sock, timeout, size):
|
|
525
|
-
buf = ""
|
|
529
|
+
buf = b""
|
|
526
530
|
for _ in range(2):
|
|
527
531
|
try:
|
|
528
|
-
buf =
|
|
532
|
+
buf = sock.recv(size)
|
|
529
533
|
break
|
|
530
|
-
except
|
|
534
|
+
except OSError as e:
|
|
531
535
|
if len(e.args) <= 0 or e.args[0] != errno.EAGAIN:
|
|
532
536
|
raise
|
|
533
537
|
|
|
534
|
-
evin,
|
|
538
|
+
evin, _evout, _everr = select.select([sock.fileno()], [], [], timeout)
|
|
535
539
|
|
|
536
540
|
if len(evin) <= 0:
|
|
537
|
-
raise socket.timeout("{
|
|
541
|
+
raise socket.timeout(f"{timeout}s timeout while recv")
|
|
538
542
|
|
|
539
543
|
if len(buf) <= 0:
|
|
540
|
-
raise
|
|
544
|
+
raise OSError(f"got empty when recv {size} bytes")
|
|
541
545
|
|
|
542
546
|
return buf
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: k3http
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: HTTP/1.1 client with timeout support
|
|
5
5
|
Author-email: Zhang Yanpo <drdr.xp@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -38,7 +38,6 @@ Dynamic: license-file
|
|
|
38
38
|
# k3http
|
|
39
39
|
|
|
40
40
|
[](https://github.com/pykit3/k3http/actions/workflows/python-package.yml)
|
|
41
|
-
[](https://travis-ci.com/pykit3/k3http)
|
|
42
41
|
[](https://k3http.readthedocs.io/en/stable/?badge=stable)
|
|
43
42
|
[](https://pypi.org/project/k3http)
|
|
44
43
|
|
|
@@ -62,23 +61,22 @@ pip install k3http
|
|
|
62
61
|
# Synopsis
|
|
63
62
|
|
|
64
63
|
```python
|
|
64
|
+
import urllib
|
|
65
65
|
|
|
66
66
|
import k3http
|
|
67
|
-
import urllib
|
|
68
|
-
import socket
|
|
69
67
|
|
|
70
68
|
headers = {
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
"Host": "127.0.0.1",
|
|
70
|
+
"Accept-Language": "en, mi",
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
try:
|
|
76
|
-
h = k3http.Client(
|
|
74
|
+
h = k3http.Client("127.0.0.1", 80)
|
|
77
75
|
|
|
78
76
|
# send http reqeust without body
|
|
79
77
|
# read response status line
|
|
80
78
|
# read response headers
|
|
81
|
-
h.request(
|
|
79
|
+
h.request("/test.txt", method="GET", headers=headers)
|
|
82
80
|
|
|
83
81
|
status = h.status
|
|
84
82
|
# response code return from http server, type is int
|
|
@@ -97,23 +95,22 @@ try:
|
|
|
97
95
|
|
|
98
96
|
# get response body
|
|
99
97
|
print(h.read_body(None))
|
|
100
|
-
except (
|
|
98
|
+
except (OSError, k3http.HttpError) as e:
|
|
101
99
|
print(repr(e))
|
|
102
100
|
|
|
103
101
|
|
|
104
|
-
|
|
105
|
-
content = urllib.urlencode({'f': 'foo', 'b': 'bar'})
|
|
102
|
+
content = urllib.urlencode({"f": "foo", "b": "bar"})
|
|
106
103
|
headers = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
"Host": "www.example.com",
|
|
105
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
|
106
|
+
"Content-Length": len(content),
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
try:
|
|
113
|
-
h = k3http.Client(
|
|
110
|
+
h = k3http.Client("127.0.0.1", 80)
|
|
114
111
|
|
|
115
112
|
# send http reqeust
|
|
116
|
-
h.send_request(
|
|
113
|
+
h.send_request("http://www.example.com", method="POST", headers=headers)
|
|
117
114
|
|
|
118
115
|
# send http request body
|
|
119
116
|
h.send_body(content)
|
|
@@ -123,9 +120,8 @@ try:
|
|
|
123
120
|
|
|
124
121
|
# read response body
|
|
125
122
|
print(h.read_body(None))
|
|
126
|
-
except (
|
|
123
|
+
except (OSError, k3http.HttpError) as e:
|
|
127
124
|
print(repr(e))
|
|
128
|
-
|
|
129
125
|
```
|
|
130
126
|
|
|
131
127
|
# Author
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import k3http
|
|
2
1
|
import urllib
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
import k3http
|
|
4
4
|
|
|
5
5
|
headers = {
|
|
6
6
|
"Host": "127.0.0.1",
|
|
@@ -32,7 +32,7 @@ try:
|
|
|
32
32
|
|
|
33
33
|
# get response body
|
|
34
34
|
print(h.read_body(None))
|
|
35
|
-
except (
|
|
35
|
+
except (OSError, k3http.HttpError) as e:
|
|
36
36
|
print(repr(e))
|
|
37
37
|
|
|
38
38
|
|
|
@@ -57,5 +57,5 @@ try:
|
|
|
57
57
|
|
|
58
58
|
# read response body
|
|
59
59
|
print(h.read_body(None))
|
|
60
|
-
except (
|
|
60
|
+
except (OSError, k3http.HttpError) as e:
|
|
61
61
|
print(repr(e))
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# coding: utf-8
|
|
3
|
-
|
|
4
1
|
import gc
|
|
5
2
|
import os
|
|
6
3
|
import socket
|
|
@@ -8,12 +5,12 @@ import ssl
|
|
|
8
5
|
import threading
|
|
9
6
|
import time
|
|
10
7
|
import unittest
|
|
11
|
-
from http.server import BaseHTTPRequestHandler
|
|
12
|
-
from
|
|
8
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
9
|
+
from typing import ClassVar
|
|
13
10
|
|
|
14
|
-
import k3http
|
|
15
11
|
import k3ut
|
|
16
12
|
|
|
13
|
+
import k3http
|
|
17
14
|
|
|
18
15
|
dd = k3ut.dd
|
|
19
16
|
|
|
@@ -26,22 +23,34 @@ HOME_PATH = os.path.dirname(os.path.abspath(__file__))
|
|
|
26
23
|
|
|
27
24
|
|
|
28
25
|
class TestHttpClient(unittest.TestCase):
|
|
29
|
-
special_cases = {
|
|
30
|
-
"test_recving_server_close": (0, 1, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n"),
|
|
31
|
-
"test_server_delay_response": (0.5, 1, "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nabcd"),
|
|
32
|
-
"test_raise_chunked_size_error": (0, 10, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nfoo\r\n"),
|
|
33
|
-
"test_raise_socket_timeout": (3, 1, "H"),
|
|
34
|
-
"test_raise_line_too_long_error": (0, KB, "a" * 65536),
|
|
26
|
+
special_cases: ClassVar[dict] = {
|
|
27
|
+
"test_recving_server_close": (0, 1, b"HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n"),
|
|
28
|
+
"test_server_delay_response": (0.5, 1, b"HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nabcd"),
|
|
29
|
+
"test_raise_chunked_size_error": (0, 10, b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nfoo\r\n"),
|
|
30
|
+
"test_raise_socket_timeout": (3, 1, b"H"),
|
|
31
|
+
"test_raise_line_too_long_error": (0, KB, b"a" * 65536),
|
|
35
32
|
"test_request_chunked": (),
|
|
33
|
+
"test_send_body_chunked_utf8": (),
|
|
36
34
|
"test_readlines": (
|
|
37
35
|
0,
|
|
38
36
|
10,
|
|
39
|
-
"HTTP/1.1 200 OK\r\nContent-Length: 131086\r\n\r\n" + "a" * 65540 + "\r\nbb\r\n" + "c" * 65540,
|
|
37
|
+
b"HTTP/1.1 200 OK\r\nContent-Length: 131086\r\n\r\n" + b"a" * 65540 + b"\r\nbb\r\n" + b"c" * 65540,
|
|
38
|
+
),
|
|
39
|
+
"test_readlines_delimiter": (0, 10, b"HTTP/1.1 200 OK\r\nContent-Length: 15\r\n\r\nabcd\rbcde\rcdef\r"),
|
|
40
|
+
# The first send ends inside the 3-byte "中", the second send completes it.
|
|
41
|
+
"test_read_body_utf8_split_across_recv": (
|
|
42
|
+
0.1,
|
|
43
|
+
39,
|
|
44
|
+
b"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\n" + "中".encode(),
|
|
45
|
+
),
|
|
46
|
+
"test_read_body_chunked_binary": (
|
|
47
|
+
0,
|
|
48
|
+
KB,
|
|
49
|
+
b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n8\r\n\x89PNG\r\n\x1a\n\r\n0\r\n\r\n",
|
|
40
50
|
),
|
|
41
|
-
"test_readlines_delimiter": (0, 10, "HTTP/1.1 200 OK\r\nContent-Length: 15\r\n\r\nabcd\rbcde\rcdef\r"),
|
|
42
51
|
}
|
|
43
|
-
request_headers = {}
|
|
44
|
-
request_body =
|
|
52
|
+
request_headers: ClassVar[dict] = {}
|
|
53
|
+
request_body = b""
|
|
45
54
|
|
|
46
55
|
def test_raise_connnect_error(self):
|
|
47
56
|
h = k3http.Client(HOST, PORT)
|
|
@@ -125,7 +134,7 @@ class TestHttpClient(unittest.TestCase):
|
|
|
125
134
|
for uri, each_read_size, expected_res, content_range, chunked in cases:
|
|
126
135
|
h.request(uri)
|
|
127
136
|
|
|
128
|
-
bufs = ""
|
|
137
|
+
bufs = b""
|
|
129
138
|
if each_read_size is None:
|
|
130
139
|
bufs = h.read_body(None)
|
|
131
140
|
self.assertEqual(h.has_read, len(bufs))
|
|
@@ -141,7 +150,8 @@ class TestHttpClient(unittest.TestCase):
|
|
|
141
150
|
if len(content_range) >= 2:
|
|
142
151
|
start, end = content_range[0], content_range[1] + 1
|
|
143
152
|
|
|
144
|
-
|
|
153
|
+
expected = expected_res[start:end].encode("utf-8")
|
|
154
|
+
self.assertEqual(expected, bufs)
|
|
145
155
|
self.assertEqual(None if chunked else len(bufs), h.content_length)
|
|
146
156
|
self.assertEqual(chunked, h.chunked)
|
|
147
157
|
|
|
@@ -169,6 +179,17 @@ class TestHttpClient(unittest.TestCase):
|
|
|
169
179
|
h.send_body(body)
|
|
170
180
|
self.assertEqual(h.read_status(False), status)
|
|
171
181
|
|
|
182
|
+
def test_send_body_chunked_utf8(self):
|
|
183
|
+
# The chunk size must count the 3 UTF-8 bytes of "中", not 1 char.
|
|
184
|
+
h = k3http.Client(HOST, PORT)
|
|
185
|
+
h.send_request("", "PUT", {"Transfer-Encoding": "chunked"})
|
|
186
|
+
h.send_body("中")
|
|
187
|
+
h.send_body("")
|
|
188
|
+
h.read_response()
|
|
189
|
+
|
|
190
|
+
chunks = self.request_body.split(b"\r\n\r\n", 1)[1]
|
|
191
|
+
self.assertEqual(b"3\r\n\xe4\xb8\xad\r\n0\r\n\r\n", chunks)
|
|
192
|
+
|
|
172
193
|
def test_request_headers(self):
|
|
173
194
|
cases = (
|
|
174
195
|
("/header_1", {"host": "example.com"}),
|
|
@@ -198,9 +219,10 @@ class TestHttpClient(unittest.TestCase):
|
|
|
198
219
|
|
|
199
220
|
def test_send_body(self):
|
|
200
221
|
cases = (
|
|
201
|
-
("/put_1b", "a", {"Content-Length": 1}),
|
|
202
|
-
("/put_10k", "bc" * 5 * KB, {"Content-Length": 10 * KB}),
|
|
203
|
-
("/put_30m", "cde" * 10 * MB, {"Content-Length": 30 * MB}),
|
|
222
|
+
("/put_1b", b"a", {"Content-Length": 1}),
|
|
223
|
+
("/put_10k", b"bc" * 5 * KB, {"Content-Length": 10 * KB}),
|
|
224
|
+
("/put_30m", b"cde" * 10 * MB, {"Content-Length": 30 * MB}),
|
|
225
|
+
("/put_binary", b"\x89PNG\r\n\x1a\n", {"Content-Length": 8}),
|
|
204
226
|
)
|
|
205
227
|
|
|
206
228
|
h = k3http.Client(HOST, PORT)
|
|
@@ -216,7 +238,7 @@ class TestHttpClient(unittest.TestCase):
|
|
|
216
238
|
h = k3http.Client(HOST, PORT)
|
|
217
239
|
h.request("")
|
|
218
240
|
|
|
219
|
-
expected_body = ("a" * 65540 + "\r\n", "bb\r\n", "c" * 65540)
|
|
241
|
+
expected_body = (b"a" * 65540 + b"\r\n", b"bb\r\n", b"c" * 65540)
|
|
220
242
|
for idx, line in enumerate(h.readlines()):
|
|
221
243
|
self.assertEqual(expected_body[idx], line)
|
|
222
244
|
|
|
@@ -224,10 +246,24 @@ class TestHttpClient(unittest.TestCase):
|
|
|
224
246
|
h = k3http.Client(HOST, PORT)
|
|
225
247
|
h.request("")
|
|
226
248
|
|
|
227
|
-
expected_body = ("abcd\r", "bcde\r", "cdef\r")
|
|
228
|
-
for idx, line in enumerate(h.readlines("\r")):
|
|
249
|
+
expected_body = (b"abcd\r", b"bcde\r", b"cdef\r")
|
|
250
|
+
for idx, line in enumerate(h.readlines(b"\r")):
|
|
229
251
|
self.assertEqual(expected_body[idx], line)
|
|
230
252
|
|
|
253
|
+
def test_read_body_utf8_split_across_recv(self):
|
|
254
|
+
h = k3http.Client(HOST, PORT)
|
|
255
|
+
h.request("")
|
|
256
|
+
|
|
257
|
+
body = h.read_body(None)
|
|
258
|
+
self.assertEqual("中".encode(), body)
|
|
259
|
+
|
|
260
|
+
def test_read_body_chunked_binary(self):
|
|
261
|
+
h = k3http.Client(HOST, PORT)
|
|
262
|
+
h.request("")
|
|
263
|
+
|
|
264
|
+
body = h.read_body(None)
|
|
265
|
+
self.assertEqual(b"\x89PNG\r\n\x1a\n", body)
|
|
266
|
+
|
|
231
267
|
def test_recving_server_close(self):
|
|
232
268
|
h = k3http.Client(HOST, PORT, 3)
|
|
233
269
|
succ = False
|
|
@@ -235,16 +271,14 @@ class TestHttpClient(unittest.TestCase):
|
|
|
235
271
|
try:
|
|
236
272
|
h.request("")
|
|
237
273
|
h.read_body(1024)
|
|
238
|
-
except
|
|
274
|
+
except OSError as e:
|
|
239
275
|
dd(repr(e) + " while recv server close")
|
|
240
276
|
succ = True
|
|
241
|
-
except Exception as e:
|
|
242
|
-
dd(repr(e) + " unexpected exception")
|
|
243
277
|
|
|
244
278
|
self.assertTrue(succ)
|
|
245
279
|
|
|
246
280
|
def test_server_delay_response(self):
|
|
247
|
-
case = ({"content-length": "4"}, "abcd")
|
|
281
|
+
case = ({"content-length": "4"}, b"abcd")
|
|
248
282
|
expected_headers, expected_body = case
|
|
249
283
|
|
|
250
284
|
h = k3http.Client(HOST, PORT, 1)
|
|
@@ -255,7 +289,7 @@ class TestHttpClient(unittest.TestCase):
|
|
|
255
289
|
self.assertEqual(expected_body, body)
|
|
256
290
|
|
|
257
291
|
def test_client_delay_send_data(self):
|
|
258
|
-
case = ("/client_delay", {"Content-Length": 10}, "abcde" * 2)
|
|
292
|
+
case = ("/client_delay", {"Content-Length": 10}, b"abcde" * 2)
|
|
259
293
|
uri, headers, body = case
|
|
260
294
|
|
|
261
295
|
h = k3http.Client(HOST, PORT, 3)
|
|
@@ -290,7 +324,7 @@ class TestHttpClient(unittest.TestCase):
|
|
|
290
324
|
try:
|
|
291
325
|
with h.stopwatch.timer("exception"):
|
|
292
326
|
raise FakeErrorDuringHTTP(3)
|
|
293
|
-
except
|
|
327
|
+
except FakeErrorDuringHTTP:
|
|
294
328
|
pass
|
|
295
329
|
|
|
296
330
|
trace = h.get_trace()
|
|
@@ -306,7 +340,7 @@ class TestHttpClient(unittest.TestCase):
|
|
|
306
340
|
|
|
307
341
|
for i, k in enumerate(ks):
|
|
308
342
|
self.assertEqual(k, trace[i]["name"])
|
|
309
|
-
self.assertEqual(
|
|
343
|
+
self.assertEqual(float, type(trace[i]["time"]))
|
|
310
344
|
|
|
311
345
|
names = [x["name"] for x in trace]
|
|
312
346
|
self.assertEqual(
|
|
@@ -340,9 +374,9 @@ class TestHttpClient(unittest.TestCase):
|
|
|
340
374
|
|
|
341
375
|
def test_https(self):
|
|
342
376
|
cases = (
|
|
343
|
-
("/get_1b", "a"),
|
|
344
|
-
("/get_10k", "bc" * 5 * KB),
|
|
345
|
-
("/get_30m", "cde" * 10 * MB),
|
|
377
|
+
("/get_1b", b"a"),
|
|
378
|
+
("/get_10k", b"bc" * 5 * KB),
|
|
379
|
+
("/get_30m", b"cde" * 10 * MB),
|
|
346
380
|
)
|
|
347
381
|
|
|
348
382
|
context = ssl._create_unverified_context()
|
|
@@ -355,7 +389,7 @@ class TestHttpClient(unittest.TestCase):
|
|
|
355
389
|
self.assertEqual(expected_res, body)
|
|
356
390
|
|
|
357
391
|
def __init__(self, *args, **kwargs):
|
|
358
|
-
super(
|
|
392
|
+
super().__init__(*args, **kwargs)
|
|
359
393
|
self.server_thread = None
|
|
360
394
|
self.http_server = None
|
|
361
395
|
|
|
@@ -406,6 +440,18 @@ class TestHttpClient(unittest.TestCase):
|
|
|
406
440
|
res = "HTTP/1.1 200 OK\r\n\r\n"
|
|
407
441
|
conn.sendall(res.encode("utf-8"))
|
|
408
442
|
|
|
443
|
+
elif self._testMethodName == "test_send_body_chunked_utf8":
|
|
444
|
+
conn, _ = sock.accept()
|
|
445
|
+
data = b""
|
|
446
|
+
while not data.endswith(b"\r\n0\r\n\r\n"):
|
|
447
|
+
buf = conn.recv(1024)
|
|
448
|
+
if buf == b"":
|
|
449
|
+
break
|
|
450
|
+
data += buf
|
|
451
|
+
|
|
452
|
+
TestHttpClient.request_body = data
|
|
453
|
+
conn.sendall(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
|
|
454
|
+
|
|
409
455
|
else:
|
|
410
456
|
conn, _ = sock.accept()
|
|
411
457
|
data = (conn.recv(1024)).decode("utf-8")
|
|
@@ -417,10 +463,10 @@ class TestHttpClient(unittest.TestCase):
|
|
|
417
463
|
sleep_time, each_send_size, content = res
|
|
418
464
|
try:
|
|
419
465
|
while len(content) > 0:
|
|
420
|
-
conn.sendall(
|
|
466
|
+
conn.sendall(content[:each_send_size])
|
|
421
467
|
content = content[each_send_size:]
|
|
422
468
|
time.sleep(sleep_time)
|
|
423
|
-
except
|
|
469
|
+
except OSError as e:
|
|
424
470
|
dd(repr(e) + " while response")
|
|
425
471
|
|
|
426
472
|
time.sleep(1)
|
|
@@ -429,7 +475,7 @@ class TestHttpClient(unittest.TestCase):
|
|
|
429
475
|
|
|
430
476
|
|
|
431
477
|
class Handle(BaseHTTPRequestHandler):
|
|
432
|
-
all_responses = {
|
|
478
|
+
all_responses: ClassVar[dict] = {
|
|
433
479
|
"/invalid_content_len": (200, {"content-length": "abc"}, (0, "")),
|
|
434
480
|
"/invalid_header": (200, {}, (0, "")),
|
|
435
481
|
"/get_1b": (200, {"content-length": 1}, (1, "a")),
|
|
@@ -438,15 +484,15 @@ class Handle(BaseHTTPRequestHandler):
|
|
|
438
484
|
"/get_10b_chunked": (200, {"Transfer-Encoding": "chunked"}, (5, "f" * 10)),
|
|
439
485
|
"/get_10k_chunked": (200, {"Transfer-Encoding": "chunked"}, (KB, "gh" * 5 * KB)),
|
|
440
486
|
"/get_30m_chunked": (200, {"Transfer-Encoding": "chunked"}, (10 * MB, "ijk" * 10 * MB)),
|
|
441
|
-
"/get_10b_range": (206, {"Content-Range": "bytes
|
|
487
|
+
"/get_10b_range": (206, {"Content-Range": "bytes 2-8/10", "Content-Length": 7}, (5, "l" * 10)),
|
|
442
488
|
"/get_10k_range": (
|
|
443
489
|
206,
|
|
444
|
-
{"Content-Range": "bytes
|
|
490
|
+
{"Content-Range": f"bytes {KB}-{8 * KB}/{10 * KB}", "Content-Length": 7 * KB + 1},
|
|
445
491
|
(KB, "mn" * 5 * KB),
|
|
446
492
|
),
|
|
447
493
|
"/get_30m_range": (
|
|
448
494
|
206,
|
|
449
|
-
{"Content-Range": "bytes
|
|
495
|
+
{"Content-Range": f"bytes {2 * MB}-{25 * MB}/{30 * MB}", "Content-Length": 23 * MB + 1},
|
|
450
496
|
(10 * MB, "opq" * 10 * MB),
|
|
451
497
|
),
|
|
452
498
|
"/get_200": (200, {"content-length": 1}, (0, "")),
|
|
@@ -481,9 +527,9 @@ class Handle(BaseHTTPRequestHandler):
|
|
|
481
527
|
return
|
|
482
528
|
else:
|
|
483
529
|
protocol = self.protocol_version
|
|
484
|
-
self.wfile.write(
|
|
530
|
+
self.wfile.write(f"{protocol} {code} {message}\r\n".encode())
|
|
485
531
|
if self.path == "/invalid_header":
|
|
486
|
-
self.wfile.write(
|
|
532
|
+
self.wfile.write(b"foo\r\n")
|
|
487
533
|
|
|
488
534
|
def do_PUT(self):
|
|
489
535
|
try:
|
|
@@ -493,18 +539,18 @@ class Handle(BaseHTTPRequestHandler):
|
|
|
493
539
|
return
|
|
494
540
|
|
|
495
541
|
read_bytes = 0
|
|
496
|
-
bufs = ""
|
|
542
|
+
bufs = b""
|
|
497
543
|
|
|
498
544
|
try:
|
|
499
545
|
while read_bytes < length:
|
|
500
|
-
bufs +=
|
|
546
|
+
bufs += self.rfile.read(length - read_bytes)
|
|
501
547
|
read_bytes = len(bufs)
|
|
502
548
|
|
|
503
549
|
TestHttpClient.request_body = bufs
|
|
504
550
|
self.send_response(200)
|
|
505
551
|
self.send_header("Content-Length", 0)
|
|
506
552
|
self.end_headers()
|
|
507
|
-
except
|
|
553
|
+
except OSError as e:
|
|
508
554
|
dd(repr(e) + " while parse put request")
|
|
509
555
|
|
|
510
556
|
def do_GET(self):
|
|
@@ -525,7 +571,9 @@ class Handle(BaseHTTPRequestHandler):
|
|
|
525
571
|
self.end_headers()
|
|
526
572
|
|
|
527
573
|
self._send_body(headers, body)
|
|
528
|
-
except
|
|
574
|
+
except (OSError, AttributeError) as e:
|
|
575
|
+
# The malformed responses, such as /invalid_line, send no header,
|
|
576
|
+
# so end_headers() raises AttributeError.
|
|
529
577
|
dd(repr(e) + " while parse get request")
|
|
530
578
|
|
|
531
579
|
def _send_body(self, headers, body):
|
|
@@ -538,13 +586,13 @@ class Handle(BaseHTTPRequestHandler):
|
|
|
538
586
|
ext = ""
|
|
539
587
|
else:
|
|
540
588
|
ext = ";extname"
|
|
541
|
-
send_buf = "
|
|
589
|
+
send_buf = f"{len(send_buf):x}{ext}\r\n{send_buf}\r\n"
|
|
542
590
|
|
|
543
591
|
self.wfile.write(send_buf.encode("utf-8"))
|
|
544
592
|
data = data[each_send_size:]
|
|
545
593
|
|
|
546
594
|
if "Transfer-Encoding" in headers:
|
|
547
|
-
self.wfile.write(
|
|
595
|
+
self.wfile.write(b"0\r\n\r\n")
|
|
548
596
|
|
|
549
597
|
def _get_body(self, headers, body):
|
|
550
598
|
each_send_size, data = body
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|