moyreq 0.1__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.
- moyreq-0.1/LICENSE.txt +2 -0
- moyreq-0.1/PKG-INFO +16 -0
- moyreq-0.1/moyreq/__init__.py +369 -0
- moyreq-0.1/moyreq.egg-info/PKG-INFO +16 -0
- moyreq-0.1/moyreq.egg-info/SOURCES.txt +8 -0
- moyreq-0.1/moyreq.egg-info/dependency_links.txt +1 -0
- moyreq-0.1/moyreq.egg-info/requires.txt +2 -0
- moyreq-0.1/moyreq.egg-info/top_level.txt +1 -0
- moyreq-0.1/setup.cfg +4 -0
- moyreq-0.1/setup.py +18 -0
moyreq-0.1/LICENSE.txt
ADDED
moyreq-0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moyreq
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: god not generous
|
|
5
|
+
Author: god generous
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
License-File: LICENSE.txt
|
|
10
|
+
Requires-Dist: httpx
|
|
11
|
+
Requires-Dist: aioquic
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
Dynamic: requires-dist
|
|
16
|
+
Dynamic: summary
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json as json_module
|
|
3
|
+
from urllib.parse import urlparse, urlencode
|
|
4
|
+
import httpx
|
|
5
|
+
from aioquic.asyncio import connect
|
|
6
|
+
from aioquic.asyncio.protocol import QuicConnectionProtocol
|
|
7
|
+
from aioquic.h3.connection import H3Connection
|
|
8
|
+
from aioquic.h3.events import HeadersReceived, DataReceived
|
|
9
|
+
from aioquic.quic.configuration import QuicConfiguration
|
|
10
|
+
|
|
11
|
+
# HTTP/3
|
|
12
|
+
|
|
13
|
+
class HTTP3Protocol(QuicConnectionProtocol):
|
|
14
|
+
|
|
15
|
+
def __init__(self, *args, **kwargs):
|
|
16
|
+
super().__init__(*args, **kwargs)
|
|
17
|
+
|
|
18
|
+
self.h3 = H3Connection(self._quic)
|
|
19
|
+
self.events = asyncio.Queue()
|
|
20
|
+
|
|
21
|
+
def quic_event_received(self, event):
|
|
22
|
+
|
|
23
|
+
for http_event in self.h3.handle_event(event):
|
|
24
|
+
self.events.put_nowait(http_event)
|
|
25
|
+
|
|
26
|
+
async def request(self, method, url, headers=None, body=None):
|
|
27
|
+
|
|
28
|
+
parsed = urlparse(url)
|
|
29
|
+
|
|
30
|
+
stream_id = self._quic.get_next_available_stream_id()
|
|
31
|
+
|
|
32
|
+
path = parsed.path or "/"
|
|
33
|
+
|
|
34
|
+
if parsed.query:
|
|
35
|
+
path += "?" + parsed.query
|
|
36
|
+
|
|
37
|
+
request_headers = [
|
|
38
|
+
(b":method", method.encode()),
|
|
39
|
+
(b":scheme", parsed.scheme.encode()),
|
|
40
|
+
(b":authority", parsed.netloc.encode()),
|
|
41
|
+
(b":path", path.encode()),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
for key, value in (headers or {}).items():
|
|
45
|
+
|
|
46
|
+
key = key.lower()
|
|
47
|
+
|
|
48
|
+
# لا نرسل pseudo headers يدويًا
|
|
49
|
+
if key.startswith(":"):
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
request_headers.append(
|
|
53
|
+
(
|
|
54
|
+
key.encode(),
|
|
55
|
+
str(value).encode()
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
self.h3.send_headers(
|
|
60
|
+
stream_id=stream_id,
|
|
61
|
+
headers=request_headers,
|
|
62
|
+
end_stream=body is None,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if body is not None:
|
|
66
|
+
|
|
67
|
+
self.h3.send_data(
|
|
68
|
+
stream_id=stream_id,
|
|
69
|
+
data=body,
|
|
70
|
+
end_stream=True,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
self.transmit()
|
|
74
|
+
|
|
75
|
+
status_code = 0
|
|
76
|
+
response_headers = []
|
|
77
|
+
response_body = bytearray()
|
|
78
|
+
|
|
79
|
+
while True:
|
|
80
|
+
|
|
81
|
+
event = await self.events.get()
|
|
82
|
+
|
|
83
|
+
if isinstance(event, HeadersReceived):
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
for key, value in event.headers:
|
|
87
|
+
|
|
88
|
+
if key == b":status":
|
|
89
|
+
|
|
90
|
+
status_code = int(value)
|
|
91
|
+
|
|
92
|
+
elif not key.startswith(b":"):
|
|
93
|
+
|
|
94
|
+
response_headers.append(
|
|
95
|
+
(
|
|
96
|
+
key.decode(
|
|
97
|
+
errors="replace"
|
|
98
|
+
),
|
|
99
|
+
value.decode(
|
|
100
|
+
errors="replace"
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
elif isinstance(event, DataReceived):
|
|
106
|
+
if event.stream_id == stream_id:
|
|
107
|
+
response_body.extend(event.data)
|
|
108
|
+
|
|
109
|
+
if event.stream_ended:
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
response = httpx.Response(
|
|
114
|
+
status_code=status_code,
|
|
115
|
+
headers=response_headers,
|
|
116
|
+
content=bytes(response_body),
|
|
117
|
+
request=httpx.Request(
|
|
118
|
+
method,
|
|
119
|
+
url
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# تسجيل البروتوكول
|
|
124
|
+
response.extensions["http_version"] = b"HTTP/3"
|
|
125
|
+
|
|
126
|
+
return response
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def _http3_request(
|
|
130
|
+
method,
|
|
131
|
+
url,
|
|
132
|
+
headers=None,
|
|
133
|
+
params=None,
|
|
134
|
+
data=None,
|
|
135
|
+
json=None,
|
|
136
|
+
cookies=None,
|
|
137
|
+
):
|
|
138
|
+
|
|
139
|
+
parsed = urlparse(url)
|
|
140
|
+
|
|
141
|
+
if parsed.scheme != "https":
|
|
142
|
+
raise ValueError(
|
|
143
|
+
"HTTP/3 requires HTTPS"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Parameters
|
|
147
|
+
if params:
|
|
148
|
+
|
|
149
|
+
query = urlencode(
|
|
150
|
+
params,
|
|
151
|
+
doseq=True
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
url += (
|
|
155
|
+
"&" if parsed.query else "?"
|
|
156
|
+
) + query
|
|
157
|
+
|
|
158
|
+
headers = dict(headers or {})
|
|
159
|
+
|
|
160
|
+
# Cookies
|
|
161
|
+
if cookies:
|
|
162
|
+
|
|
163
|
+
headers["cookie"] = "; ".join(
|
|
164
|
+
f"{key}={value}"
|
|
165
|
+
for key, value in cookies.items()
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# JSON
|
|
169
|
+
body = data
|
|
170
|
+
|
|
171
|
+
if json is not None:
|
|
172
|
+
|
|
173
|
+
body = json_module.dumps(
|
|
174
|
+
json
|
|
175
|
+
).encode()
|
|
176
|
+
|
|
177
|
+
headers.setdefault(
|
|
178
|
+
"content-type",
|
|
179
|
+
"application/json"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# String → bytes
|
|
183
|
+
if isinstance(body, str):
|
|
184
|
+
body = body.encode()
|
|
185
|
+
|
|
186
|
+
configuration = QuicConfiguration(
|
|
187
|
+
is_client=True,
|
|
188
|
+
alpn_protocols=["h3"],
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
async with connect(
|
|
192
|
+
parsed.hostname,
|
|
193
|
+
parsed.port or 443,
|
|
194
|
+
configuration=configuration,
|
|
195
|
+
create_protocol=HTTP3Protocol,
|
|
196
|
+
) as protocol:
|
|
197
|
+
return await protocol.request(
|
|
198
|
+
method,
|
|
199
|
+
url,
|
|
200
|
+
headers,
|
|
201
|
+
body
|
|
202
|
+
)
|
|
203
|
+
# HTTP/2 + HTTP/1.1
|
|
204
|
+
def _httpx_request(
|
|
205
|
+
method,
|
|
206
|
+
url,
|
|
207
|
+
headers=None,
|
|
208
|
+
params=None,
|
|
209
|
+
data=None,
|
|
210
|
+
json=None,
|
|
211
|
+
cookies=None,
|
|
212
|
+
):
|
|
213
|
+
|
|
214
|
+
with httpx.Client(
|
|
215
|
+
http2=True,
|
|
216
|
+
follow_redirects=True
|
|
217
|
+
) as client:
|
|
218
|
+
|
|
219
|
+
return client.request(
|
|
220
|
+
method,
|
|
221
|
+
url,
|
|
222
|
+
headers=headers,
|
|
223
|
+
params=params,
|
|
224
|
+
content=data,
|
|
225
|
+
json=json,
|
|
226
|
+
cookies=cookies,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# MAIN REQUEST
|
|
231
|
+
|
|
232
|
+
def request(
|
|
233
|
+
method,
|
|
234
|
+
url,
|
|
235
|
+
headers=None,
|
|
236
|
+
params=None,
|
|
237
|
+
data=None,
|
|
238
|
+
json=None,
|
|
239
|
+
cookies=None,
|
|
240
|
+
):
|
|
241
|
+
|
|
242
|
+
# HTTP/3
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
|
|
246
|
+
return asyncio.run(
|
|
247
|
+
_http3_request(
|
|
248
|
+
method=method,
|
|
249
|
+
url=url,
|
|
250
|
+
headers=headers,
|
|
251
|
+
params=params,
|
|
252
|
+
data=data,
|
|
253
|
+
json=json,
|
|
254
|
+
cookies=cookies,
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
|
|
261
|
+
# HTTP/2 → HTTP/1.1
|
|
262
|
+
return _httpx_request(
|
|
263
|
+
method=method,
|
|
264
|
+
url=url,
|
|
265
|
+
headers=headers,
|
|
266
|
+
params=params,
|
|
267
|
+
data=data,
|
|
268
|
+
json=json,
|
|
269
|
+
cookies=cookies,
|
|
270
|
+
)
|
|
271
|
+
# HTTP METHODS
|
|
272
|
+
def get(
|
|
273
|
+
url,
|
|
274
|
+
headers=None,
|
|
275
|
+
params=None,
|
|
276
|
+
data=None,
|
|
277
|
+
json=None,
|
|
278
|
+
cookies=None
|
|
279
|
+
):
|
|
280
|
+
|
|
281
|
+
return request(
|
|
282
|
+
"GET",
|
|
283
|
+
url,
|
|
284
|
+
headers,
|
|
285
|
+
params,
|
|
286
|
+
data,
|
|
287
|
+
json,
|
|
288
|
+
cookies
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def post(
|
|
293
|
+
url,
|
|
294
|
+
headers=None,
|
|
295
|
+
params=None,
|
|
296
|
+
data=None,
|
|
297
|
+
json=None,
|
|
298
|
+
cookies=None
|
|
299
|
+
):
|
|
300
|
+
|
|
301
|
+
return request(
|
|
302
|
+
"POST",
|
|
303
|
+
url,
|
|
304
|
+
headers,
|
|
305
|
+
params,
|
|
306
|
+
data,
|
|
307
|
+
json,
|
|
308
|
+
cookies
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def put(
|
|
313
|
+
url,
|
|
314
|
+
headers=None,
|
|
315
|
+
params=None,
|
|
316
|
+
data=None,
|
|
317
|
+
json=None,
|
|
318
|
+
cookies=None
|
|
319
|
+
):
|
|
320
|
+
|
|
321
|
+
return request(
|
|
322
|
+
"PUT",
|
|
323
|
+
url,
|
|
324
|
+
headers,
|
|
325
|
+
params,
|
|
326
|
+
data,
|
|
327
|
+
json,
|
|
328
|
+
cookies
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def patch(
|
|
333
|
+
url,
|
|
334
|
+
headers=None,
|
|
335
|
+
params=None,
|
|
336
|
+
data=None,
|
|
337
|
+
json=None,
|
|
338
|
+
cookies=None
|
|
339
|
+
):
|
|
340
|
+
|
|
341
|
+
return request(
|
|
342
|
+
"PATCH",
|
|
343
|
+
url,
|
|
344
|
+
headers,
|
|
345
|
+
params,
|
|
346
|
+
data,
|
|
347
|
+
json,
|
|
348
|
+
cookies
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def delete(
|
|
353
|
+
url,
|
|
354
|
+
headers=None,
|
|
355
|
+
params=None,
|
|
356
|
+
data=None,
|
|
357
|
+
json=None,
|
|
358
|
+
cookies=None
|
|
359
|
+
):
|
|
360
|
+
|
|
361
|
+
return request(
|
|
362
|
+
"DELETE",
|
|
363
|
+
url,
|
|
364
|
+
headers,
|
|
365
|
+
params,
|
|
366
|
+
data,
|
|
367
|
+
json,
|
|
368
|
+
cookies
|
|
369
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moyreq
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: god not generous
|
|
5
|
+
Author: god generous
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
License-File: LICENSE.txt
|
|
10
|
+
Requires-Dist: httpx
|
|
11
|
+
Requires-Dist: aioquic
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
Dynamic: requires-dist
|
|
16
|
+
Dynamic: summary
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
moyreq
|
moyreq-0.1/setup.cfg
ADDED
moyreq-0.1/setup.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import setuptools
|
|
2
|
+
|
|
3
|
+
setuptools.setup(
|
|
4
|
+
name="moyreq",
|
|
5
|
+
version="0.1",
|
|
6
|
+
author="god generous",
|
|
7
|
+
description="god not generous",
|
|
8
|
+
packages=setuptools.find_packages(),
|
|
9
|
+
install_requires=[
|
|
10
|
+
"httpx",
|
|
11
|
+
"aioquic",
|
|
12
|
+
],
|
|
13
|
+
classifiers=[
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
],
|
|
18
|
+
)
|