Pyjoyment 0.1.0__py3-none-any.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.
- Pyjo/App/ASGI.py +735 -0
- Pyjo/App/CGI.py +773 -0
- Pyjo/App/FastCGI.py +631 -0
- Pyjo/App/Lambda.py +987 -0
- Pyjo/App/WSGI.py +427 -0
- Pyjo/App/__init__.py +409 -0
- Pyjo/Asset/File.py +377 -0
- Pyjo/Asset/Memory.py +235 -0
- Pyjo/Asset/__init__.py +200 -0
- Pyjo/Base.py +215 -0
- Pyjo/Cache.py +119 -0
- Pyjo/Collection.py +570 -0
- Pyjo/Content/MultiPart.py +325 -0
- Pyjo/Content/Single.py +193 -0
- Pyjo/Content/__init__.py +806 -0
- Pyjo/Cookie/Request.py +90 -0
- Pyjo/Cookie/Response.py +294 -0
- Pyjo/Cookie/__init__.py +143 -0
- Pyjo/DOM/CSS.py +1019 -0
- Pyjo/DOM/HTML.py +550 -0
- Pyjo/DOM/__init__.py +1329 -0
- Pyjo/Date.py +294 -0
- Pyjo/EventEmitter.py +522 -0
- Pyjo/Exception.py +494 -0
- Pyjo/File.py +959 -0
- Pyjo/GetOpt.py +605 -0
- Pyjo/Headers.py +760 -0
- Pyjo/HelloWorld.py +77 -0
- Pyjo/Home.py +109 -0
- Pyjo/IOLoop/ChildProcess.py +591 -0
- Pyjo/IOLoop/Client.py +736 -0
- Pyjo/IOLoop/Datagram.py +982 -0
- Pyjo/IOLoop/Messages.py +664 -0
- Pyjo/IOLoop/Server.py +458 -0
- Pyjo/IOLoop/Stdio.py +446 -0
- Pyjo/IOLoop/Stream.py +644 -0
- Pyjo/IOLoop/Subprocess.py +419 -0
- Pyjo/IOLoop/TLS.py +360 -0
- Pyjo/IOLoop/__init__.py +1036 -0
- Pyjo/IOLoop/certs/README.md +41 -0
- Pyjo/IOLoop/certs/ca.crt +20 -0
- Pyjo/IOLoop/certs/server.crt +21 -0
- Pyjo/IOLoop/certs/server.key +28 -0
- Pyjo/JSON/Pointer.py +135 -0
- Pyjo/JSON/RPC.py +772 -0
- Pyjo/JSON/Schema.py +2506 -0
- Pyjo/JSON/__init__.py +573 -0
- Pyjo/Loader.py +342 -0
- Pyjo/Log.py +723 -0
- Pyjo/Message/Request.py +725 -0
- Pyjo/Message/Response.py +476 -0
- Pyjo/Message/__init__.py +830 -0
- Pyjo/Parameters.py +472 -0
- Pyjo/Path.py +419 -0
- Pyjo/Promise.py +894 -0
- Pyjo/Prove.py +1305 -0
- Pyjo/Reactor/Asyncio.py +854 -0
- Pyjo/Reactor/Poll.py +439 -0
- Pyjo/Reactor/__init__.py +525 -0
- Pyjo/SSE.py +119 -0
- Pyjo/Server/ASGI.py +486 -0
- Pyjo/Server/CGI.py +259 -0
- Pyjo/Server/Daemon.py +1007 -0
- Pyjo/Server/FastCGI.py +517 -0
- Pyjo/Server/Hot/__init__.py +687 -0
- Pyjo/Server/Hot/__main__.py +5 -0
- Pyjo/Server/Lambda.py +641 -0
- Pyjo/Server/Prefork.py +917 -0
- Pyjo/Server/Prethread.py +394 -0
- Pyjo/Server/Reload/Backend/Poll.py +114 -0
- Pyjo/Server/Reload/Backend/__init__.py +126 -0
- Pyjo/Server/Reload/__init__.py +482 -0
- Pyjo/Server/Reload/__main__.py +5 -0
- Pyjo/Server/Stdio.py +228 -0
- Pyjo/Server/WSGI.py +349 -0
- Pyjo/Server/__init__.py +396 -0
- Pyjo/String/Bytes.py +532 -0
- Pyjo/String/HTML.py +210 -0
- Pyjo/String/Mixin.py +333 -0
- Pyjo/String/Template.py +470 -0
- Pyjo/String/Unicode.py +553 -0
- Pyjo/String/__init__.py +0 -0
- Pyjo/TOML.py +232 -0
- Pyjo/Template.py +1290 -0
- Pyjo/Test/Pyjo.py +1454 -0
- Pyjo/Test/__init__.py +1233 -0
- Pyjo/Test/pytest_plugin.py +305 -0
- Pyjo/Transaction/HTTP.py +374 -0
- Pyjo/Transaction/WebSocket.py +709 -0
- Pyjo/Transaction/__init__.py +429 -0
- Pyjo/URL/Template.py +459 -0
- Pyjo/URL/__init__.py +651 -0
- Pyjo/Upload.py +139 -0
- Pyjo/UserAgent/CookieJar.py +391 -0
- Pyjo/UserAgent/Proxy.py +138 -0
- Pyjo/UserAgent/Server.py +239 -0
- Pyjo/UserAgent/Transactor.py +766 -0
- Pyjo/UserAgent/__init__.py +1243 -0
- Pyjo/Util.py +3567 -0
- Pyjo/WebSocket.py +332 -0
- Pyjo/YAML.py +1167 -0
- Pyjo/__init__.py +441 -0
- Pyjo/__main__.py +35 -0
- Pyjo/py.typed +0 -0
- Pyjoyment/Command/Author/__init__.py +20 -0
- Pyjoyment/Command/Author/generate/__init__.py +106 -0
- Pyjoyment/Command/Author/generate/app.py +299 -0
- Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
- Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
- Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
- Pyjoyment/Command/Author/generate/lite_app.py +113 -0
- Pyjoyment/Command/Author/generate/plugin.py +267 -0
- Pyjoyment/Command/Author/generate/pyproject.py +123 -0
- Pyjoyment/Command/Author/inflate.py +101 -0
- Pyjoyment/Command/__init__.py +332 -0
- Pyjoyment/Command/asgi.py +92 -0
- Pyjoyment/Command/cgi.py +90 -0
- Pyjoyment/Command/daemon.py +160 -0
- Pyjoyment/Command/eval.py +128 -0
- Pyjoyment/Command/fastcgi.py +99 -0
- Pyjoyment/Command/get.py +305 -0
- Pyjoyment/Command/lambda.py +117 -0
- Pyjoyment/Command/prefork.py +181 -0
- Pyjoyment/Command/prethread.py +126 -0
- Pyjoyment/Command/routes.py +133 -0
- Pyjoyment/Command/stdio.py +92 -0
- Pyjoyment/Command/version.py +141 -0
- Pyjoyment/Command/wsgi.py +93 -0
- Pyjoyment/Commands.py +546 -0
- Pyjoyment/Controller.py +1307 -0
- Pyjoyment/HelloWorld.py +56 -0
- Pyjoyment/Lite.py +494 -0
- Pyjoyment/Plugin/AllowedHosts.py +205 -0
- Pyjoyment/Plugin/Config.py +248 -0
- Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
- Pyjoyment/Plugin/EPLRenderer.py +134 -0
- Pyjoyment/Plugin/EPRenderer.py +212 -0
- Pyjoyment/Plugin/HeaderCondition.py +98 -0
- Pyjoyment/Plugin/Health.py +325 -0
- Pyjoyment/Plugin/JSONConfig.py +172 -0
- Pyjoyment/Plugin/Mount.py +223 -0
- Pyjoyment/Plugin/PyRenderer.py +209 -0
- Pyjoyment/Plugin/TOMLConfig.py +141 -0
- Pyjoyment/Plugin/TagHelpers.py +959 -0
- Pyjoyment/Plugin/YAMLConfig.py +178 -0
- Pyjoyment/Plugin/__init__.py +80 -0
- Pyjoyment/Plugins.py +215 -0
- Pyjoyment/Renderer.py +673 -0
- Pyjoyment/Routes/Match.py +251 -0
- Pyjoyment/Routes/Pattern.py +443 -0
- Pyjoyment/Routes/Route.py +827 -0
- Pyjoyment/Routes/__init__.py +419 -0
- Pyjoyment/Sessions.py +255 -0
- Pyjoyment/Static.py +421 -0
- Pyjoyment/Types.py +238 -0
- Pyjoyment/Validator/Validation.py +395 -0
- Pyjoyment/Validator/__init__.py +304 -0
- Pyjoyment/__init__.py +1083 -0
- Pyjoyment/__main__.py +5 -0
- Pyjoyment/py.typed +0 -0
- Pyjoyment/resources/public/favicon.ico +0 -0
- Pyjoyment/resources/public/favicon.svg +345 -0
- Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
- Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
- Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
- Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
- Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo.png +0 -0
- Pyjoyment/resources/public/pyjo/logo.svg +346 -0
- Pyjoyment/resources/public/pyjo/notfound.png +0 -0
- Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
- Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
- Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
- Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
- Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
- Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
- Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
- pyjoyment-0.1.0.dist-info/METADATA +376 -0
- pyjoyment-0.1.0.dist-info/RECORD +200 -0
- pyjoyment-0.1.0.dist-info/WHEEL +4 -0
- pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
- pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
Pyjo/Message/Request.py
ADDED
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Pyjo.Message.Request - HTTP request
|
|
3
|
+
===================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.Message.Request import Request
|
|
7
|
+
|
|
8
|
+
# Parse
|
|
9
|
+
req = Request()
|
|
10
|
+
req.parse(b'GET /foo HTTP/1.0\x0d\x0a')
|
|
11
|
+
req.parse(b'Content-Length: 12\x0d\x0a')
|
|
12
|
+
req.parse(b'Content-Type: text/plain\x0d\x0a\x0d\x0a')
|
|
13
|
+
req.parse(b'Hello World!')
|
|
14
|
+
print(req.method)
|
|
15
|
+
print(req.headers.content_type)
|
|
16
|
+
print(req.body)
|
|
17
|
+
|
|
18
|
+
# Or import the module, as in Google Python Style Guide
|
|
19
|
+
|
|
20
|
+
import Pyjo.Message.Request
|
|
21
|
+
|
|
22
|
+
# Parse
|
|
23
|
+
req = Pyjo.Message.Request.new()
|
|
24
|
+
req.parse(b'GET /foo HTTP/1.0\x0d\x0a')
|
|
25
|
+
req.parse(b'Content-Length: 12\x0d\x0a')
|
|
26
|
+
req.parse(b'Content-Type: text/plain\x0d\x0a\x0d\x0a')
|
|
27
|
+
req.parse(b'Hello World!')
|
|
28
|
+
print(req.method)
|
|
29
|
+
print(req.headers.content_type)
|
|
30
|
+
print(req.body)
|
|
31
|
+
|
|
32
|
+
# Build
|
|
33
|
+
req = Pyjo.Message.Request.new()
|
|
34
|
+
req.url.parse('http://127.0.0.1/foo/bar')
|
|
35
|
+
req.method = 'GET'
|
|
36
|
+
print(req.to_bytes())
|
|
37
|
+
|
|
38
|
+
:mod:`Pyjo.Message.Request` is a container for HTTP requests, based on
|
|
39
|
+
`RFC 7230 <https://tools.ietf.org/html/rfc7230>`_,
|
|
40
|
+
`RFC 7231 <https://tools.ietf.org/html/rfc7231>`_,
|
|
41
|
+
`RFC 7235 <https://tools.ietf.org/html/rfc7235>`_ and
|
|
42
|
+
`RFC 2817 <https://tools.ietf.org/html/rfc2817>`_.
|
|
43
|
+
|
|
44
|
+
Events
|
|
45
|
+
------
|
|
46
|
+
|
|
47
|
+
:mod:`Pyjo.Message.Request` inherits all events from :mod:`Pyjo.Message`.
|
|
48
|
+
|
|
49
|
+
See also
|
|
50
|
+
--------
|
|
51
|
+
|
|
52
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
53
|
+
|
|
54
|
+
Ported from `Mojo::Message::Request <https://github.com/mojolicious/mojo/blob/v9.51/lib/Mojo/Message/Request.pm>`_
|
|
55
|
+
of Mojolicious 9.51.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
import Pyjo.Cookie.Request
|
|
59
|
+
import Pyjo.Message
|
|
60
|
+
import Pyjo.Parameters
|
|
61
|
+
import Pyjo.URL
|
|
62
|
+
|
|
63
|
+
from Pyjo.Util import b64_decode, b64_encode, decode, url_unescape
|
|
64
|
+
|
|
65
|
+
import base64
|
|
66
|
+
import builtins
|
|
67
|
+
import hashlib
|
|
68
|
+
import os
|
|
69
|
+
import random
|
|
70
|
+
import re
|
|
71
|
+
import time
|
|
72
|
+
import urllib.request
|
|
73
|
+
|
|
74
|
+
from collections.abc import Iterable, Mapping
|
|
75
|
+
from typing import TYPE_CHECKING, Any, Self, cast, overload
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if TYPE_CHECKING:
|
|
79
|
+
import http.server
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
type Anything = builtins.object
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Pyjo_Message_Request(Pyjo.Message.object):
|
|
86
|
+
"""
|
|
87
|
+
:mod:`Pyjo.Message.Request` inherits all methods from :mod:`Pyjo.Message`
|
|
88
|
+
and implements the following new ones.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
request: 'urllib.request.Request | http.server.BaseHTTPRequestHandler | None' = None,
|
|
94
|
+
/,
|
|
95
|
+
*,
|
|
96
|
+
env: Mapping[str, Any] | None = None,
|
|
97
|
+
method: str = 'GET',
|
|
98
|
+
proxy: Pyjo.URL.object | None = None,
|
|
99
|
+
request_id: str | None = None,
|
|
100
|
+
reverse_proxy: bool | None = None,
|
|
101
|
+
trusted_proxies: list[str] | None = None,
|
|
102
|
+
url: Pyjo.URL.object | None = None,
|
|
103
|
+
via_proxy: bool = True,
|
|
104
|
+
**kwargs: Any,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""::
|
|
107
|
+
|
|
108
|
+
req = Pyjo.Message.Request.new()
|
|
109
|
+
req = Pyjo.Message.Request.new(method='POST', url=Pyjo.URL.new('http://127.0.0.1/'))
|
|
110
|
+
req = Pyjo.Message.Request.new(urllib.request.Request('http://127.0.0.1/', data=b'Hi!'))
|
|
111
|
+
req = Pyjo.Message.Request.new(handler)
|
|
112
|
+
|
|
113
|
+
Construct a new :mod:`Pyjo.Message.Request` object, from a request of
|
|
114
|
+
the standard library if it is given: the method, URL, headers and data
|
|
115
|
+
of a :class:`urllib.request.Request`, or the request that a
|
|
116
|
+
:class:`http.server.BaseHTTPRequestHandler` has parsed, which is parsed
|
|
117
|
+
again, as :meth:`parse` does, with the body that it reads from the
|
|
118
|
+
``rfile`` of the handler, of the ``Content-Length`` or chunked, and not
|
|
119
|
+
further. ::
|
|
120
|
+
|
|
121
|
+
# A request in a handler of http.server
|
|
122
|
+
class Handler(http.server.BaseHTTPRequestHandler):
|
|
123
|
+
def do_GET(self):
|
|
124
|
+
req = Pyjo.Message.Request.new(self)
|
|
125
|
+
res = Pyjo.Message.Response.new(code=200).set(body=bytes(req.url.path))
|
|
126
|
+
self.wfile.write(res.to_bytes())
|
|
127
|
+
"""
|
|
128
|
+
super().__init__(**kwargs)
|
|
129
|
+
|
|
130
|
+
self.env: Mapping[str, Any] = env if env is not None else {}
|
|
131
|
+
"""::
|
|
132
|
+
|
|
133
|
+
env = req.env
|
|
134
|
+
req.env = {'PATH_INFO': '/'}
|
|
135
|
+
|
|
136
|
+
Direct access to the ``CGI`` or ``WSGI`` environment dict if
|
|
137
|
+
available. ::
|
|
138
|
+
|
|
139
|
+
# Check CGI version
|
|
140
|
+
version = req.env['GATEWAY_INTERFACE']
|
|
141
|
+
|
|
142
|
+
# Check WSGI version
|
|
143
|
+
version = req.env['wsgi.version']
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
self.method = method
|
|
147
|
+
"""::
|
|
148
|
+
|
|
149
|
+
method = req.method
|
|
150
|
+
req.method = 'POST'
|
|
151
|
+
|
|
152
|
+
HTTP request method, defaults to ``GET``.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
self.proxy = proxy
|
|
156
|
+
"""::
|
|
157
|
+
|
|
158
|
+
url = req.proxy
|
|
159
|
+
req.proxy = Pyjo.URL.new('http://127.0.0.1:3000')
|
|
160
|
+
|
|
161
|
+
Proxy URL for request.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
self.reverse_proxy = reverse_proxy
|
|
165
|
+
"""::
|
|
166
|
+
|
|
167
|
+
boolean = req.reverse_proxy
|
|
168
|
+
req.reverse_proxy = boolean
|
|
169
|
+
|
|
170
|
+
Request has been performed through a reverse proxy.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
self.trusted_proxies: list[str] = trusted_proxies if trusted_proxies is not None else []
|
|
174
|
+
"""::
|
|
175
|
+
|
|
176
|
+
proxies = req.trusted_proxies
|
|
177
|
+
req.trusted_proxies = ['10.0.0.0/8', '127.0.0.1', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7']
|
|
178
|
+
|
|
179
|
+
Trusted reverse proxies, addresses or networks in CIDR form.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
self.via_proxy = via_proxy
|
|
183
|
+
"""::
|
|
184
|
+
|
|
185
|
+
boolean = req.via_proxy
|
|
186
|
+
req.via_proxy = boolean
|
|
187
|
+
|
|
188
|
+
Request can be performed through a proxy server.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
self._params: Pyjo.Parameters.object | None = None
|
|
192
|
+
self._request_id = request_id
|
|
193
|
+
self._start_buffer: bytes | None = None
|
|
194
|
+
self._url = url
|
|
195
|
+
|
|
196
|
+
# Not in Mojolicious: a request of urllib or http.server
|
|
197
|
+
if isinstance(request, urllib.request.Request):
|
|
198
|
+
self._from_urllib(request)
|
|
199
|
+
elif request is not None:
|
|
200
|
+
self._from_http_server(request)
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def request_id(self) -> str:
|
|
204
|
+
"""::
|
|
205
|
+
|
|
206
|
+
request_id = req.request_id
|
|
207
|
+
req.request_id = 'aee7d5d8'
|
|
208
|
+
|
|
209
|
+
Request ID, defaults to a reasonably unique value.
|
|
210
|
+
"""
|
|
211
|
+
if self._request_id is None:
|
|
212
|
+
self._request_id = _request_id()
|
|
213
|
+
return self._request_id
|
|
214
|
+
|
|
215
|
+
@request_id.setter
|
|
216
|
+
def request_id(self, value: str) -> None:
|
|
217
|
+
self._request_id = value
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def url(self) -> Pyjo.URL.object:
|
|
221
|
+
"""::
|
|
222
|
+
|
|
223
|
+
url = req.url
|
|
224
|
+
req.url = Pyjo.URL.new()
|
|
225
|
+
|
|
226
|
+
HTTP request URL, defaults to a :mod:`Pyjo.URL` object. ::
|
|
227
|
+
|
|
228
|
+
# Get request information
|
|
229
|
+
info = req.url.to_abs().userinfo
|
|
230
|
+
host = req.url.to_abs().host
|
|
231
|
+
path = req.url.to_abs().path
|
|
232
|
+
"""
|
|
233
|
+
if self._url is None:
|
|
234
|
+
self._url = Pyjo.URL.new()
|
|
235
|
+
return self._url
|
|
236
|
+
|
|
237
|
+
@url.setter
|
|
238
|
+
def url(self, value: Pyjo.URL.object) -> None:
|
|
239
|
+
self._url = value
|
|
240
|
+
|
|
241
|
+
def clone(self) -> Self | None:
|
|
242
|
+
"""::
|
|
243
|
+
|
|
244
|
+
clone = req.clone()
|
|
245
|
+
|
|
246
|
+
Return a new :mod:`Pyjo.Message.Request` object cloned from this
|
|
247
|
+
request if possible, otherwise return ``None``.
|
|
248
|
+
"""
|
|
249
|
+
# Dynamic requests cannot be cloned
|
|
250
|
+
if (content := self.content.clone()) is None:
|
|
251
|
+
return None
|
|
252
|
+
clone = self.new(content=content, method=self.method, url=self.url.clone(), version=self.version)
|
|
253
|
+
if self.proxy:
|
|
254
|
+
clone.proxy = self.proxy.clone()
|
|
255
|
+
|
|
256
|
+
return clone
|
|
257
|
+
|
|
258
|
+
@overload
|
|
259
|
+
def cookies(self) -> list[Pyjo.Cookie.Request.object]: ...
|
|
260
|
+
|
|
261
|
+
@overload
|
|
262
|
+
def cookies(self, cookie: Pyjo.Cookie.object | Mapping[str, Any], /, *cookies: Anything) -> Self: ...
|
|
263
|
+
|
|
264
|
+
def cookies(self, *cookies: Anything) -> list[Pyjo.Cookie.Request.object] | Self:
|
|
265
|
+
"""::
|
|
266
|
+
|
|
267
|
+
cookies = req.cookies()
|
|
268
|
+
req = req.cookies(Pyjo.Cookie.Request.new())
|
|
269
|
+
req = req.cookies({'name': 'foo', 'value': 'bar'})
|
|
270
|
+
|
|
271
|
+
Access request cookies, usually :mod:`Pyjo.Cookie.Request` objects. ::
|
|
272
|
+
|
|
273
|
+
# Names of all cookies
|
|
274
|
+
for cookie in req.cookies():
|
|
275
|
+
print(cookie.name)
|
|
276
|
+
"""
|
|
277
|
+
# Parse cookies
|
|
278
|
+
headers = self.headers
|
|
279
|
+
if not cookies:
|
|
280
|
+
return Pyjo.Cookie.Request.parse(headers.cookie)
|
|
281
|
+
|
|
282
|
+
# Add cookies
|
|
283
|
+
values = [headers.cookie] if headers.cookie else []
|
|
284
|
+
for cookie in cookies:
|
|
285
|
+
values.append(
|
|
286
|
+
str(
|
|
287
|
+
Pyjo.Cookie.Request.new(**cast(Mapping[str, Any], cookie))
|
|
288
|
+
if isinstance(cookie, Mapping)
|
|
289
|
+
else cookie
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
headers.cookie = '; '.join(values)
|
|
293
|
+
|
|
294
|
+
return self
|
|
295
|
+
|
|
296
|
+
def every_param(self, name: str) -> list[str]:
|
|
297
|
+
"""::
|
|
298
|
+
|
|
299
|
+
values = req.every_param('foo')
|
|
300
|
+
|
|
301
|
+
Similar to :meth:`param`, but returns all values sharing the same name
|
|
302
|
+
as a list. ::
|
|
303
|
+
|
|
304
|
+
# Get first value
|
|
305
|
+
print(req.every_param('foo')[0])
|
|
306
|
+
"""
|
|
307
|
+
return self.params.every_param(name)
|
|
308
|
+
|
|
309
|
+
def extract_start_line(self, buffer: bytearray) -> bool | None:
|
|
310
|
+
"""::
|
|
311
|
+
|
|
312
|
+
boolean = req.extract_start_line(buffer)
|
|
313
|
+
|
|
314
|
+
Extract request-line from a :class:`bytearray`, which loses it.
|
|
315
|
+
"""
|
|
316
|
+
# Ignore any leading empty lines
|
|
317
|
+
if not (m := re.match(rb'\s*(.*?)\x0d?\x0a', bytes(buffer))):
|
|
318
|
+
return None
|
|
319
|
+
del buffer[: m.end()]
|
|
320
|
+
|
|
321
|
+
# We have a (hopefully) full request-line
|
|
322
|
+
if not (m := re.fullmatch(rb'(\S+)\s+(\S+)\s+HTTP/(\d\.\d)', m[1])):
|
|
323
|
+
self.error = {'message': 'Bad request start-line'}
|
|
324
|
+
return False
|
|
325
|
+
self.method, self.version = m[1].decode('latin-1'), m[3].decode('latin-1')
|
|
326
|
+
url = self.url
|
|
327
|
+
target = m[2].decode('latin-1')
|
|
328
|
+
if self.method == 'CONNECT':
|
|
329
|
+
url.host_port = target
|
|
330
|
+
elif re.match(r'[^:/?#]+:', target):
|
|
331
|
+
url.parse(target).fragment = None
|
|
332
|
+
else:
|
|
333
|
+
url.path_query = target
|
|
334
|
+
return True
|
|
335
|
+
|
|
336
|
+
def fix_headers(self) -> Self:
|
|
337
|
+
"""::
|
|
338
|
+
|
|
339
|
+
req = req.fix_headers()
|
|
340
|
+
|
|
341
|
+
Make sure request has all required headers.
|
|
342
|
+
"""
|
|
343
|
+
if self._fix:
|
|
344
|
+
return self
|
|
345
|
+
super().fix_headers()
|
|
346
|
+
|
|
347
|
+
# Empty
|
|
348
|
+
headers = self.headers
|
|
349
|
+
if headers.content_length == '0' and self.method == 'GET':
|
|
350
|
+
headers.remove('Content-Length')
|
|
351
|
+
|
|
352
|
+
# Host
|
|
353
|
+
url = self.url
|
|
354
|
+
if headers.host is None:
|
|
355
|
+
headers.host = url.host_port or ''
|
|
356
|
+
|
|
357
|
+
# Basic authentication
|
|
358
|
+
if (info := url.userinfo) and not headers.authorization:
|
|
359
|
+
headers.authorization = 'Basic ' + _basic_encode(info)
|
|
360
|
+
|
|
361
|
+
# Basic proxy authentication
|
|
362
|
+
if not ((proxy := self.proxy) and self.via_proxy):
|
|
363
|
+
return self
|
|
364
|
+
if not (info := proxy.userinfo):
|
|
365
|
+
return self
|
|
366
|
+
if not headers.proxy_authorization:
|
|
367
|
+
headers.proxy_authorization = 'Basic ' + _basic_encode(info)
|
|
368
|
+
return self
|
|
369
|
+
|
|
370
|
+
def get_start_line_chunk(self, offset: int) -> bytes:
|
|
371
|
+
"""::
|
|
372
|
+
|
|
373
|
+
data = req.get_start_line_chunk(offset)
|
|
374
|
+
|
|
375
|
+
Get a chunk of request-line data starting from a specific position.
|
|
376
|
+
Note that this method finalizes the request.
|
|
377
|
+
"""
|
|
378
|
+
buffer = self._start_line()
|
|
379
|
+
self.emit('progress', 'start_line', offset)
|
|
380
|
+
return buffer[offset : offset + 131072]
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def is_handshake(self) -> bool:
|
|
384
|
+
"""::
|
|
385
|
+
|
|
386
|
+
boolean = req.is_handshake
|
|
387
|
+
|
|
388
|
+
Check ``Upgrade`` header for ``websocket`` value.
|
|
389
|
+
"""
|
|
390
|
+
return (self.headers.upgrade or '').lower() == 'websocket'
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def is_secure(self) -> bool:
|
|
394
|
+
"""::
|
|
395
|
+
|
|
396
|
+
boolean = req.is_secure
|
|
397
|
+
|
|
398
|
+
Check if connection is secure.
|
|
399
|
+
"""
|
|
400
|
+
url = self.url
|
|
401
|
+
return (url.protocol or url.base.protocol) == 'https'
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def is_xhr(self) -> bool:
|
|
405
|
+
"""::
|
|
406
|
+
|
|
407
|
+
boolean = req.is_xhr
|
|
408
|
+
|
|
409
|
+
Check ``X-Requested-With`` header for ``XMLHttpRequest`` value.
|
|
410
|
+
"""
|
|
411
|
+
return bool(re.search(r'XMLHttpRequest', self.headers.header('X-Requested-With') or '', re.I))
|
|
412
|
+
|
|
413
|
+
@overload
|
|
414
|
+
def param(self, name: str, /) -> str | None: ...
|
|
415
|
+
|
|
416
|
+
@overload
|
|
417
|
+
def param(self, name: str, value: Anything, /, *values: Anything) -> Pyjo.Parameters.object: ...
|
|
418
|
+
|
|
419
|
+
def param(self, name: str, /, *values: Anything) -> str | None | Pyjo.Parameters.object:
|
|
420
|
+
"""::
|
|
421
|
+
|
|
422
|
+
value = req.param('foo')
|
|
423
|
+
|
|
424
|
+
Access ``GET`` and ``POST`` parameters extracted from the query string
|
|
425
|
+
and ``application/x-www-form-urlencoded`` or ``multipart/form-data``
|
|
426
|
+
message body. If there are multiple values sharing the same name, and
|
|
427
|
+
you want to access more than just the last one, you can use
|
|
428
|
+
:meth:`every_param`. Note that this method caches all data, so it
|
|
429
|
+
should not be called before the entire request body has been received.
|
|
430
|
+
Parts of the request body need to be loaded into memory to parse
|
|
431
|
+
``POST`` parameters, so you have to make sure it is not excessively
|
|
432
|
+
large. There's a 16MiB limit for requests by default.
|
|
433
|
+
"""
|
|
434
|
+
return self.params.param(name, *values)
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
def params(self) -> Pyjo.Parameters.object:
|
|
438
|
+
"""::
|
|
439
|
+
|
|
440
|
+
params = req.params
|
|
441
|
+
|
|
442
|
+
All ``GET`` and ``POST`` parameters extracted from the query string and
|
|
443
|
+
``application/x-www-form-urlencoded`` or ``multipart/form-data``
|
|
444
|
+
message body, usually a :mod:`Pyjo.Parameters` object. Note that this
|
|
445
|
+
method caches all data, so it should not be called before the entire
|
|
446
|
+
request body has been received. Parts of the request body need to be
|
|
447
|
+
loaded into memory to parse ``POST`` parameters, so you have to make
|
|
448
|
+
sure it is not excessively large. There's a 16MiB limit for requests by
|
|
449
|
+
default. ::
|
|
450
|
+
|
|
451
|
+
# Get parameter names and values
|
|
452
|
+
params = req.params.to_dict()
|
|
453
|
+
"""
|
|
454
|
+
if self._params is None:
|
|
455
|
+
self._params = self.body_params.clone().append(self.query_params)
|
|
456
|
+
return self._params
|
|
457
|
+
|
|
458
|
+
def parse(self, chunk: bytes | Mapping[str, Any]) -> Self:
|
|
459
|
+
"""::
|
|
460
|
+
|
|
461
|
+
req = req.parse(b'GET /foo/bar HTTP/1.1')
|
|
462
|
+
req = req.parse({'PATH_INFO': '/'})
|
|
463
|
+
|
|
464
|
+
Parse HTTP request chunks or environment dict.
|
|
465
|
+
"""
|
|
466
|
+
env, chunk = (chunk, b'') if isinstance(chunk, Mapping) else (None, chunk)
|
|
467
|
+
|
|
468
|
+
# Parse CGI environment
|
|
469
|
+
if env is not None:
|
|
470
|
+
self.env = env
|
|
471
|
+
self._parse_env(env)
|
|
472
|
+
|
|
473
|
+
# Parse normal message
|
|
474
|
+
if (self._state or '') != 'cgi':
|
|
475
|
+
super().parse(chunk)
|
|
476
|
+
|
|
477
|
+
# Parse CGI content
|
|
478
|
+
else:
|
|
479
|
+
if env is None:
|
|
480
|
+
self._raw_size += len(chunk)
|
|
481
|
+
self.content = self.content.parse_body(chunk)
|
|
482
|
+
super().parse(b'')
|
|
483
|
+
|
|
484
|
+
# Check if we can fix things that require all headers
|
|
485
|
+
if not self.is_finished:
|
|
486
|
+
return self
|
|
487
|
+
|
|
488
|
+
# Base URL
|
|
489
|
+
base = self.url.base
|
|
490
|
+
if not base.scheme:
|
|
491
|
+
base.scheme = 'http'
|
|
492
|
+
headers = self.headers
|
|
493
|
+
if not base.host and (host := headers.host):
|
|
494
|
+
base.host_port = host
|
|
495
|
+
|
|
496
|
+
# Basic authentication
|
|
497
|
+
if basic := _basic(headers.authorization):
|
|
498
|
+
base.userinfo = basic
|
|
499
|
+
|
|
500
|
+
# Basic proxy authentication
|
|
501
|
+
if basic := _basic(headers.proxy_authorization):
|
|
502
|
+
self.proxy = Pyjo.URL.new().set(userinfo=basic)
|
|
503
|
+
|
|
504
|
+
# "X-Forwarded-Proto"
|
|
505
|
+
if self.reverse_proxy and headers.header('X-Forwarded-Proto') == 'https':
|
|
506
|
+
base.scheme = 'https'
|
|
507
|
+
|
|
508
|
+
return self
|
|
509
|
+
|
|
510
|
+
@property
|
|
511
|
+
def query_params(self) -> Pyjo.Parameters.object:
|
|
512
|
+
"""::
|
|
513
|
+
|
|
514
|
+
params = req.query_params
|
|
515
|
+
|
|
516
|
+
All ``GET`` parameters, usually a :mod:`Pyjo.Parameters` object. ::
|
|
517
|
+
|
|
518
|
+
# Turn GET parameters to dict and extract value
|
|
519
|
+
print(req.query_params.to_dict()['foo'])
|
|
520
|
+
"""
|
|
521
|
+
return self.url.query
|
|
522
|
+
|
|
523
|
+
@property
|
|
524
|
+
def start_line_size(self) -> int:
|
|
525
|
+
"""::
|
|
526
|
+
|
|
527
|
+
size = req.start_line_size
|
|
528
|
+
|
|
529
|
+
Size of the request-line in bytes. Note that this method finalizes the
|
|
530
|
+
request.
|
|
531
|
+
"""
|
|
532
|
+
return len(self._start_line())
|
|
533
|
+
|
|
534
|
+
# Not in Mojolicious: a request of urllib
|
|
535
|
+
def to_urllib(self) -> urllib.request.Request:
|
|
536
|
+
"""::
|
|
537
|
+
|
|
538
|
+
request = req.to_urllib()
|
|
539
|
+
|
|
540
|
+
Turn request into a :class:`urllib.request.Request`, with the absolute
|
|
541
|
+
:attr:`url`, the headers, the body and the :attr:`proxy`. urllib takes
|
|
542
|
+
no userinfo in a URL, so the userinfo of :attr:`url` and :attr:`proxy`
|
|
543
|
+
becomes the ``Authorization`` and ``Proxy-Authorization`` headers, as
|
|
544
|
+
:meth:`fix_headers` does. ::
|
|
545
|
+
|
|
546
|
+
# Send the request with urllib
|
|
547
|
+
with urllib.request.urlopen(req.to_urllib()) as response:
|
|
548
|
+
res = Pyjo.Message.Response.new(response)
|
|
549
|
+
"""
|
|
550
|
+
url = self.url.to_abs()
|
|
551
|
+
headers = self.headers
|
|
552
|
+
body = self.build_body() if self.content.is_multipart else self.body
|
|
553
|
+
request = urllib.request.Request(
|
|
554
|
+
str(url),
|
|
555
|
+
data=body or None,
|
|
556
|
+
headers={name: value for name in headers.names() if (value := headers.header(name)) is not None},
|
|
557
|
+
method=self.method,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
# Basic authentication
|
|
561
|
+
if (info := url.userinfo) and not headers.authorization:
|
|
562
|
+
request.add_header('Authorization', 'Basic ' + _basic_encode(info))
|
|
563
|
+
|
|
564
|
+
# Proxy with basic authentication
|
|
565
|
+
if (proxy := self.proxy) and self.via_proxy:
|
|
566
|
+
request.set_proxy(proxy.host_port or '', proxy.scheme or 'http')
|
|
567
|
+
if (info := proxy.userinfo) and not headers.proxy_authorization:
|
|
568
|
+
request.add_header('Proxy-Authorization', 'Basic ' + _basic_encode(info))
|
|
569
|
+
|
|
570
|
+
return request
|
|
571
|
+
|
|
572
|
+
# Not in Mojolicious: a request of http.server
|
|
573
|
+
def _from_http_server(self, handler: 'http.server.BaseHTTPRequestHandler') -> None:
|
|
574
|
+
start = f'{handler.command} {handler.path} {handler.request_version}\x0d\x0a'
|
|
575
|
+
headers = ''.join(f'{name}: {value}\x0d\x0a' for name, value in handler.headers.items())
|
|
576
|
+
self.parse(f'{start}{headers}\x0d\x0a'.encode('latin-1'))
|
|
577
|
+
|
|
578
|
+
# The body of the Content-Length, or chunked line by line, so that nothing of the next request is read
|
|
579
|
+
rfile = handler.rfile
|
|
580
|
+
if not self.is_finished and (length := self.headers.content_length or '').isdigit():
|
|
581
|
+
self.parse(rfile.read(int(length)))
|
|
582
|
+
while not self.is_finished and (line := rfile.readline(131072)):
|
|
583
|
+
self.parse(line)
|
|
584
|
+
|
|
585
|
+
# Not in Mojolicious: a request of urllib
|
|
586
|
+
def _from_urllib(self, request: urllib.request.Request) -> None:
|
|
587
|
+
self.method = request.get_method()
|
|
588
|
+
self.url = Pyjo.URL.new(request.full_url)
|
|
589
|
+
headers = self.headers
|
|
590
|
+
for name, value in request.header_items():
|
|
591
|
+
headers.add(name, value)
|
|
592
|
+
|
|
593
|
+
# Data as bytes, a file or an iterable of bytes
|
|
594
|
+
data = request.data
|
|
595
|
+
if data is None:
|
|
596
|
+
return
|
|
597
|
+
if isinstance(data, bytes | bytearray):
|
|
598
|
+
self.body = bytes(data)
|
|
599
|
+
elif isinstance(data, memoryview):
|
|
600
|
+
self.body = data.tobytes()
|
|
601
|
+
elif (read := getattr(data, 'read', None)) is not None:
|
|
602
|
+
self.body = bytes(read())
|
|
603
|
+
else:
|
|
604
|
+
self.body = b''.join(bytes(chunk) for chunk in cast(Iterable[bytes], data))
|
|
605
|
+
|
|
606
|
+
def _parse_env(self, env: Mapping[str, Any]) -> None:
|
|
607
|
+
# Bypass normal message parser
|
|
608
|
+
self._state = 'cgi'
|
|
609
|
+
|
|
610
|
+
# Extract headers
|
|
611
|
+
headers = self.headers
|
|
612
|
+
url = self.url
|
|
613
|
+
base = url.base
|
|
614
|
+
for name, value in env.items():
|
|
615
|
+
if not (m := re.match(r'HTTP_(.*)', name, re.I | re.S)):
|
|
616
|
+
continue
|
|
617
|
+
name, value = m[1].replace('_', '-'), str(value)
|
|
618
|
+
headers.header(name, value)
|
|
619
|
+
|
|
620
|
+
# Host/Port
|
|
621
|
+
if name == 'HOST':
|
|
622
|
+
if m := re.search(r':(\d+)$', value):
|
|
623
|
+
base.host, base.port = value[: m.start()], int(m[1])
|
|
624
|
+
else:
|
|
625
|
+
base.host = value
|
|
626
|
+
|
|
627
|
+
# Content-Type is a special case on some servers
|
|
628
|
+
if env.get('CONTENT_TYPE'):
|
|
629
|
+
headers.content_type = str(env['CONTENT_TYPE'])
|
|
630
|
+
|
|
631
|
+
# Content-Length is a special case on some servers
|
|
632
|
+
if env.get('CONTENT_LENGTH'):
|
|
633
|
+
headers.content_length = str(env['CONTENT_LENGTH'])
|
|
634
|
+
|
|
635
|
+
# Query
|
|
636
|
+
if env.get('QUERY_STRING'):
|
|
637
|
+
url.query.parse(str(env['QUERY_STRING']))
|
|
638
|
+
|
|
639
|
+
# Method
|
|
640
|
+
if env.get('REQUEST_METHOD'):
|
|
641
|
+
self.method = str(env['REQUEST_METHOD'])
|
|
642
|
+
|
|
643
|
+
# Scheme/Version
|
|
644
|
+
if m := re.fullmatch(r'([^/]+)/([^/]+)', str(env.get('SERVER_PROTOCOL', ''))):
|
|
645
|
+
base.scheme, self.version = m[1], m[2]
|
|
646
|
+
|
|
647
|
+
# HTTPS
|
|
648
|
+
if str(env.get('HTTPS', '')).upper() == 'ON':
|
|
649
|
+
base.scheme = 'https'
|
|
650
|
+
|
|
651
|
+
# Path
|
|
652
|
+
path = url.path.parse(str(env['PATH_INFO']) if env.get('PATH_INFO') else '')
|
|
653
|
+
|
|
654
|
+
# Base path
|
|
655
|
+
if value := env.get('SCRIPT_NAME'):
|
|
656
|
+
value = str(value)
|
|
657
|
+
|
|
658
|
+
# Make sure there is a trailing slash (important for merging)
|
|
659
|
+
base.path.parse(value if value.endswith('/') else f'{value}/')
|
|
660
|
+
|
|
661
|
+
# Remove SCRIPT_NAME prefix if necessary (unless the server already did it)
|
|
662
|
+
buffer = path.to_str()
|
|
663
|
+
value = re.sub(r'^/|/$', '', value)
|
|
664
|
+
uri = url_unescape(re.sub(r'\?.*$', '', str(env.get('REQUEST_URI') or ''), count=1))
|
|
665
|
+
if not uri.startswith(f'/{value}' + str(env.get('PATH_INFO') or '')):
|
|
666
|
+
buffer = re.sub(rf'^/?{re.escape(value)}(?:/|$)', '', buffer, count=1)
|
|
667
|
+
buffer = re.sub(r'^/', '', buffer, count=1)
|
|
668
|
+
path.parse(buffer)
|
|
669
|
+
|
|
670
|
+
def _start_line(self) -> bytes:
|
|
671
|
+
if self._start_buffer is not None:
|
|
672
|
+
return self._start_buffer
|
|
673
|
+
|
|
674
|
+
# Path
|
|
675
|
+
url = self.url
|
|
676
|
+
path = url.path_query
|
|
677
|
+
if not path.startswith('/'):
|
|
678
|
+
path = f'/{path}'
|
|
679
|
+
|
|
680
|
+
# CONNECT
|
|
681
|
+
method = self.method.upper()
|
|
682
|
+
if method == 'CONNECT':
|
|
683
|
+
port = url.port if url.port is not None else 443 if url.protocol == 'https' else 80
|
|
684
|
+
path = f'{url.ihost}:{port}'
|
|
685
|
+
|
|
686
|
+
# Proxy
|
|
687
|
+
elif self.proxy and self.via_proxy and url.protocol != 'https':
|
|
688
|
+
if not self.is_handshake:
|
|
689
|
+
path = url.clone().set(userinfo=None).to_str()
|
|
690
|
+
|
|
691
|
+
self._start_buffer = f'{method} {path} HTTP/{self.version}\x0d\x0a'.encode('latin-1')
|
|
692
|
+
return self._start_buffer
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def _basic(header: str | None) -> str | None:
|
|
696
|
+
if not header or not (m := re.search(r'Basic (.+)$', header)):
|
|
697
|
+
return None
|
|
698
|
+
# Unlike Mojolicious: the userinfo is decoded from UTF-8 where it can be, as the URL decodes it; Mojo keeps the bytes
|
|
699
|
+
info = b64_decode(m[1])
|
|
700
|
+
return decoded if (decoded := decode('UTF-8', info)) is not None else info.decode('latin-1')
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def _basic_encode(info: str) -> str:
|
|
704
|
+
# Unlike Mojolicious: the userinfo is encoded as UTF-8, as the URL decodes it from UTF-8; Mojo encodes it as
|
|
705
|
+
# Latin-1 and dies of "Wide character" on any other character
|
|
706
|
+
return b64_encode(info.encode('UTF-8'), '')
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
# The seed and the counter of request IDs, made on first use, as Perl's state variables
|
|
710
|
+
_seed: str | None = None
|
|
711
|
+
_counter = 0
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def _request_id() -> str:
|
|
715
|
+
global _seed, _counter
|
|
716
|
+
if _seed is None:
|
|
717
|
+
_seed, _counter = f'{os.getpid()}{time.time()}{random.random()}', random.randrange(0xFFFFFF)
|
|
718
|
+
_counter = (_counter + 1) % 0xFFFFFF
|
|
719
|
+
b64 = base64.b64encode(hashlib.sha1(f'{_seed}{_counter}'.encode()).digest()).decode()[:12]
|
|
720
|
+
return b64.translate(str.maketrans('+/', '-_'))
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
new = Pyjo_Message_Request.new
|
|
724
|
+
object = Pyjo_Message_Request
|
|
725
|
+
Request = Pyjo_Message_Request
|