Skip to content

Commit bbcab23

Browse files
committed
Reproduce RustPython#7891 locally
1 parent e35dbef commit bbcab23

1 file changed

Lines changed: 242 additions & 0 deletions

File tree

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import os
2+
import socket
3+
import ssl
4+
import sys
5+
import threading
6+
import time
7+
import urllib.request
8+
9+
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10+
CERTFILE = os.path.join(ROOT_DIR, "Lib/test/certdata/keycert.pem")
11+
BODY = b"x" * 407_676
12+
13+
# TLS record body sizes observed from https://crates.io/api/v1/crates/tokio.
14+
TLS_RECORD_BODY_SIZES = [
15+
2855,
16+
281,
17+
53,
18+
218,
19+
1095,
20+
1395,
21+
1395,
22+
483,
23+
1395,
24+
1395,
25+
1395,
26+
1395,
27+
48,
28+
1360,
29+
1354,
30+
1395,
31+
1395,
32+
1395,
33+
1367,
34+
1395,
35+
1395,
36+
1395,
37+
1395,
38+
1326,
39+
1395,
40+
1395,
41+
1395,
42+
47,
43+
1395,
44+
1395,
45+
1395,
46+
1395,
47+
95,
48+
1395,
49+
1332,
50+
1287,
51+
1388,
52+
1395,
53+
1395,
54+
1374,
55+
1395,
56+
1380,
57+
794,
58+
791,
59+
1395,
60+
1381,
61+
1395,
62+
1395,
63+
1395,
64+
1333,
65+
1395,
66+
1395,
67+
1395,
68+
1395,
69+
1395,
70+
1395,
71+
965,
72+
16401,
73+
3914,
74+
2526,
75+
1041,
76+
8209,
77+
9233,
78+
16401,
79+
11650,
80+
10262,
81+
7486,
82+
3468,
83+
692,
84+
1041,
85+
16401,
86+
12242,
87+
9466,
88+
1041,
89+
8209,
90+
9233,
91+
8209,
92+
9233,
93+
16401,
94+
1041,
95+
8209,
96+
9233,
97+
6161,
98+
2065,
99+
9233,
100+
16401,
101+
16358,
102+
10806,
103+
1041,
104+
8209,
105+
16401,
106+
3914,
107+
16401,
108+
16401,
109+
3089,
110+
9233,
111+
4642,
112+
478,
113+
8209,
114+
3140,
115+
1752,
116+
9233,
117+
8209,
118+
8209,
119+
16401,
120+
16064,
121+
14676,
122+
13288,
123+
2065,
124+
16401,
125+
1041,
126+
8209,
127+
16401,
128+
1041,
129+
6374,
130+
1007,
131+
]
132+
133+
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
134+
server_context.load_cert_chain(CERTFILE)
135+
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
136+
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
137+
listener.bind(("127.0.0.1", 0))
138+
listener.listen(1)
139+
port = listener.getsockname()[1]
140+
server_errors = []
141+
finished = False
142+
143+
144+
def guard_timeout():
145+
time.sleep(20)
146+
if not finished:
147+
print("stdlib_urllib_https_short_recv.py timed out", file=sys.stderr, flush=True)
148+
os.abort()
149+
150+
151+
threading.Thread(target=guard_timeout, daemon=True).start()
152+
153+
154+
def drain_outgoing(outgoing, conn):
155+
while True:
156+
try:
157+
data = outgoing.read()
158+
except ssl.SSLWantReadError:
159+
return
160+
if not data:
161+
return
162+
conn.sendall(data)
163+
164+
165+
def run_server():
166+
try:
167+
conn, _ = listener.accept()
168+
conn.settimeout(5.0)
169+
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
170+
171+
incoming = ssl.MemoryBIO()
172+
outgoing = ssl.MemoryBIO()
173+
tls = server_context.wrap_bio(incoming, outgoing, server_side=True)
174+
175+
while True:
176+
try:
177+
tls.do_handshake()
178+
break
179+
except ssl.SSLWantReadError:
180+
drain_outgoing(outgoing, conn)
181+
incoming.write(conn.recv(65536))
182+
except ssl.SSLWantWriteError:
183+
pass
184+
drain_outgoing(outgoing, conn)
185+
186+
request = b""
187+
while b"\r\n\r\n" not in request:
188+
try:
189+
request += tls.read(65536)
190+
except ssl.SSLWantReadError:
191+
drain_outgoing(outgoing, conn)
192+
incoming.write(conn.recv(65536))
193+
drain_outgoing(outgoing, conn)
194+
195+
response = (
196+
b"HTTP/1.1 200 OK\r\n"
197+
b"Connection: close\r\n"
198+
+ b"Content-Length: "
199+
+ str(len(BODY)).encode()
200+
+ b"\r\n"
201+
+ b"Content-Type: application/json\r\n"
202+
+ b"\r\n"
203+
+ BODY
204+
)
205+
plaintext_sizes = [max(1, n - 17) for n in TLS_RECORD_BODY_SIZES]
206+
pos = 0
207+
while pos < len(response):
208+
size = plaintext_sizes.pop(0) if plaintext_sizes else 16384
209+
end = min(len(response), pos + size)
210+
while pos < end:
211+
try:
212+
pos += tls.write(response[pos:end])
213+
except ssl.SSLWantWriteError:
214+
pass
215+
drain_outgoing(outgoing, conn)
216+
conn.close()
217+
except BaseException as exc:
218+
server_errors.append(exc)
219+
finally:
220+
listener.close()
221+
222+
223+
thread = threading.Thread(target=run_server)
224+
thread.start()
225+
226+
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
227+
client_context.check_hostname = False
228+
client_context.verify_mode = ssl.CERT_NONE
229+
opener = urllib.request.build_opener(
230+
urllib.request.ProxyHandler({}),
231+
urllib.request.HTTPSHandler(context=client_context),
232+
)
233+
try:
234+
with opener.open("https://127.0.0.1:%d/" % port, timeout=5.0) as response:
235+
body = response.read()
236+
237+
thread.join(10.0)
238+
assert not thread.is_alive(), "server thread did not stop"
239+
assert not server_errors, server_errors
240+
assert body == BODY
241+
finally:
242+
finished = True

0 commit comments

Comments
 (0)