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/WebSocket.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Pyjo.WebSocket - The WebSocket protocol
|
|
3
|
+
=======================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.WebSocket import WS_TEXT, build_frame, parse_frame
|
|
7
|
+
|
|
8
|
+
data = build_frame(0, 1, 0, 0, 0, WS_TEXT, b'Hello World!')
|
|
9
|
+
frame = parse_frame(bytearray(data), 262144)
|
|
10
|
+
|
|
11
|
+
:mod:`Pyjo.WebSocket` implements the WebSocket protocol as described in
|
|
12
|
+
`RFC 6455 <https://tools.ietf.org/html/rfc6455>`_.
|
|
13
|
+
|
|
14
|
+
A frame is a tuple of the FIN bit, the RSV1, RSV2 and RSV3 bits, the opcode
|
|
15
|
+
and the payload, ``(fin, rsv1, rsv2, rsv3, op, payload)``.
|
|
16
|
+
|
|
17
|
+
Functions
|
|
18
|
+
---------
|
|
19
|
+
|
|
20
|
+
:mod:`Pyjo.WebSocket` implements the following functions, which can be
|
|
21
|
+
imported individually.
|
|
22
|
+
|
|
23
|
+
Constants
|
|
24
|
+
---------
|
|
25
|
+
|
|
26
|
+
:mod:`Pyjo.WebSocket` implements the following constants, which can be
|
|
27
|
+
imported individually.
|
|
28
|
+
|
|
29
|
+
WS_BINARY
|
|
30
|
+
~~~~~~~~~
|
|
31
|
+
|
|
32
|
+
Opcode for ``Binary`` frames.
|
|
33
|
+
|
|
34
|
+
WS_CLOSE
|
|
35
|
+
~~~~~~~~
|
|
36
|
+
|
|
37
|
+
Opcode for ``Close`` frames.
|
|
38
|
+
|
|
39
|
+
WS_CONTINUATION
|
|
40
|
+
~~~~~~~~~~~~~~~
|
|
41
|
+
|
|
42
|
+
Opcode for ``Continuation`` frames.
|
|
43
|
+
|
|
44
|
+
WS_PING
|
|
45
|
+
~~~~~~~
|
|
46
|
+
|
|
47
|
+
Opcode for ``Ping`` frames.
|
|
48
|
+
|
|
49
|
+
WS_PONG
|
|
50
|
+
~~~~~~~
|
|
51
|
+
|
|
52
|
+
Opcode for ``Pong`` frames.
|
|
53
|
+
|
|
54
|
+
WS_TEXT
|
|
55
|
+
~~~~~~~
|
|
56
|
+
|
|
57
|
+
Opcode for ``Text`` frames.
|
|
58
|
+
|
|
59
|
+
Debugging
|
|
60
|
+
---------
|
|
61
|
+
|
|
62
|
+
You can set the ``PYJO_WEBSOCKET_DEBUG`` environment variable to get some
|
|
63
|
+
advanced diagnostics information printed to ``STDERR``. ::
|
|
64
|
+
|
|
65
|
+
PYJO_WEBSOCKET_DEBUG=1
|
|
66
|
+
|
|
67
|
+
See also
|
|
68
|
+
--------
|
|
69
|
+
|
|
70
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
71
|
+
|
|
72
|
+
Ported from `Mojo::WebSocket <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/WebSocket.pm>`_
|
|
73
|
+
of Mojolicious 9.49.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
from Pyjo.Util import b64_encode, sha1_bytes, warn, xor_encode
|
|
77
|
+
|
|
78
|
+
import os
|
|
79
|
+
import random
|
|
80
|
+
import re
|
|
81
|
+
import struct
|
|
82
|
+
|
|
83
|
+
from typing import TYPE_CHECKING
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if TYPE_CHECKING:
|
|
87
|
+
import Pyjo.Transaction
|
|
88
|
+
import Pyjo.Transaction.WebSocket
|
|
89
|
+
|
|
90
|
+
__all__ = [
|
|
91
|
+
'WS_BINARY',
|
|
92
|
+
'WS_CLOSE',
|
|
93
|
+
'WS_CONTINUATION',
|
|
94
|
+
'WS_PING',
|
|
95
|
+
'WS_PONG',
|
|
96
|
+
'WS_TEXT',
|
|
97
|
+
'Frame',
|
|
98
|
+
'build_frame',
|
|
99
|
+
'challenge',
|
|
100
|
+
'client_handshake',
|
|
101
|
+
'parse_frame',
|
|
102
|
+
'server_handshake',
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
DEBUG = bool(os.environ.get('PYJO_WEBSOCKET_DEBUG'))
|
|
106
|
+
|
|
107
|
+
# Unique value from RFC 6455
|
|
108
|
+
GUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
|
109
|
+
|
|
110
|
+
# Left out from Mojolicious: MODERN, as Python's integers make 64-bit frames work without Perl's support for quads
|
|
111
|
+
|
|
112
|
+
# Opcodes
|
|
113
|
+
WS_CONTINUATION = 0x0
|
|
114
|
+
WS_TEXT = 0x1
|
|
115
|
+
WS_BINARY = 0x2
|
|
116
|
+
WS_CLOSE = 0x8
|
|
117
|
+
WS_PING = 0x9
|
|
118
|
+
WS_PONG = 0xA
|
|
119
|
+
|
|
120
|
+
# The FIN bit, the RSV1, RSV2 and RSV3 bits, the opcode and the payload
|
|
121
|
+
type Frame = tuple[int, int, int, int, int, bytes]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def build_frame(
|
|
125
|
+
masked: object, fin: object, rsv1: object, rsv2: object, rsv3: object, op: int, payload: bytes
|
|
126
|
+
) -> bytes:
|
|
127
|
+
r"""::
|
|
128
|
+
|
|
129
|
+
data = build_frame(masked, fin, rsv1, rsv2, rsv3, op, payload)
|
|
130
|
+
|
|
131
|
+
Build WebSocket frame. ::
|
|
132
|
+
|
|
133
|
+
# Masked binary frame with FIN bit and payload
|
|
134
|
+
print(build_frame(1, 1, 0, 0, 0, WS_BINARY, b'Hello World!'))
|
|
135
|
+
|
|
136
|
+
# Text frame with payload but without FIN bit
|
|
137
|
+
print(build_frame(0, 0, 0, 0, 0, WS_TEXT, b'Hello '))
|
|
138
|
+
|
|
139
|
+
# Continuation frame with FIN bit and payload
|
|
140
|
+
print(build_frame(0, 1, 0, 0, 0, WS_CONTINUATION, b'World!'))
|
|
141
|
+
|
|
142
|
+
# Close frame with FIN bit and without payload
|
|
143
|
+
print(build_frame(0, 1, 0, 0, 0, WS_CLOSE, b''))
|
|
144
|
+
|
|
145
|
+
# Ping frame with FIN bit and payload
|
|
146
|
+
print(build_frame(0, 1, 0, 0, 0, WS_PING, b'Test 123'))
|
|
147
|
+
|
|
148
|
+
# Pong frame with FIN bit and payload
|
|
149
|
+
print(build_frame(0, 1, 0, 0, 0, WS_PONG, b'Test 123'))
|
|
150
|
+
"""
|
|
151
|
+
if DEBUG:
|
|
152
|
+
warn(f'-- Building frame ({_bit(fin)}, {_bit(rsv1)}, {_bit(rsv2)}, {_bit(rsv3)}, {op})')
|
|
153
|
+
|
|
154
|
+
# Head
|
|
155
|
+
head = op + (128 if fin else 0)
|
|
156
|
+
if rsv1:
|
|
157
|
+
head |= 0b01000000
|
|
158
|
+
if rsv2:
|
|
159
|
+
head |= 0b00100000
|
|
160
|
+
if rsv3:
|
|
161
|
+
head |= 0b00010000
|
|
162
|
+
frame = struct.pack('B', head)
|
|
163
|
+
|
|
164
|
+
# Small payload
|
|
165
|
+
length = len(payload)
|
|
166
|
+
if length < 126:
|
|
167
|
+
if DEBUG:
|
|
168
|
+
warn(f'-- Small payload ({length})\n{payload!r}')
|
|
169
|
+
frame += struct.pack('B', (length | 128) if masked else length)
|
|
170
|
+
|
|
171
|
+
# Extended payload (16-bit)
|
|
172
|
+
elif length < 65536:
|
|
173
|
+
if DEBUG:
|
|
174
|
+
warn(f'-- Extended 16-bit payload ({length})\n{payload!r}')
|
|
175
|
+
frame += struct.pack('>BH', (126 | 128) if masked else 126, length)
|
|
176
|
+
|
|
177
|
+
# Extended payload (64-bit)
|
|
178
|
+
else:
|
|
179
|
+
if DEBUG:
|
|
180
|
+
warn(f'-- Extended 64-bit payload ({length})\n{payload!r}')
|
|
181
|
+
frame += struct.pack('>BQ', (127 | 128) if masked else 127, length)
|
|
182
|
+
|
|
183
|
+
# Mask payload
|
|
184
|
+
if masked:
|
|
185
|
+
mask = struct.pack('>I', random.randrange(9999999))
|
|
186
|
+
payload = mask + xor_encode(payload, mask * 128)
|
|
187
|
+
|
|
188
|
+
return frame + payload
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def challenge(tx: 'Pyjo.Transaction.WebSocket.object') -> bool:
|
|
192
|
+
"""::
|
|
193
|
+
|
|
194
|
+
boolean = challenge(Pyjo.Transaction.WebSocket.new())
|
|
195
|
+
|
|
196
|
+
Check WebSocket handshake challenge.
|
|
197
|
+
"""
|
|
198
|
+
# "permessage-deflate" extension
|
|
199
|
+
headers = tx.res.headers
|
|
200
|
+
if re.search(r'permessage-deflate', headers.sec_websocket_extensions or ''):
|
|
201
|
+
tx.compressed = True
|
|
202
|
+
|
|
203
|
+
return _challenge(tx.req.headers.sec_websocket_key) == headers.sec_websocket_accept
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def client_handshake[T: 'Pyjo.Transaction.object'](tx: T) -> T:
|
|
207
|
+
"""::
|
|
208
|
+
|
|
209
|
+
tx = client_handshake(Pyjo.Transaction.HTTP.new())
|
|
210
|
+
|
|
211
|
+
Perform WebSocket handshake client-side.
|
|
212
|
+
"""
|
|
213
|
+
headers = tx.req.headers
|
|
214
|
+
if not headers.upgrade:
|
|
215
|
+
headers.upgrade = 'websocket'
|
|
216
|
+
if not headers.connection:
|
|
217
|
+
headers.connection = 'Upgrade'
|
|
218
|
+
if not headers.sec_websocket_version:
|
|
219
|
+
headers.sec_websocket_version = 13
|
|
220
|
+
|
|
221
|
+
# Generate 16 byte WebSocket challenge
|
|
222
|
+
challenge = b64_encode(f'{random.randrange(9999999999999999):16d}'.encode(), '')
|
|
223
|
+
if not headers.sec_websocket_key:
|
|
224
|
+
headers.sec_websocket_key = challenge
|
|
225
|
+
|
|
226
|
+
return tx
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# Unlike Mojolicious: the buffer is a bytearray, which parse_frame changes, where Mojo takes a reference to a string
|
|
230
|
+
def parse_frame(buffer: bytearray, max: int) -> Frame | bool | None:
|
|
231
|
+
r"""::
|
|
232
|
+
|
|
233
|
+
frame = parse_frame(buffer, limit)
|
|
234
|
+
|
|
235
|
+
Parse WebSocket frame from a :class:`bytearray`, which loses the parsed
|
|
236
|
+
frame. Returns ``None`` if no complete frame was found, and ``True`` if
|
|
237
|
+
the frame is bigger than the limit. ::
|
|
238
|
+
|
|
239
|
+
# Parse single frame and remove it from buffer
|
|
240
|
+
frame = parse_frame(buffer, 262144)
|
|
241
|
+
print(f'FIN: {frame[0]}')
|
|
242
|
+
print(f'RSV1: {frame[1]}')
|
|
243
|
+
print(f'RSV2: {frame[2]}')
|
|
244
|
+
print(f'RSV3: {frame[3]}')
|
|
245
|
+
print(f'Opcode: {frame[4]}')
|
|
246
|
+
print(f'Payload: {frame[5]}')
|
|
247
|
+
"""
|
|
248
|
+
# Head
|
|
249
|
+
if len(buffer) < 2:
|
|
250
|
+
return None
|
|
251
|
+
first, second = buffer[0], buffer[1]
|
|
252
|
+
|
|
253
|
+
# FIN
|
|
254
|
+
fin = 1 if (first & 0b10000000) == 0b10000000 else 0
|
|
255
|
+
|
|
256
|
+
# RSV1-3
|
|
257
|
+
rsv1 = 1 if (first & 0b01000000) == 0b01000000 else 0
|
|
258
|
+
rsv2 = 1 if (first & 0b00100000) == 0b00100000 else 0
|
|
259
|
+
rsv3 = 1 if (first & 0b00010000) == 0b00010000 else 0
|
|
260
|
+
|
|
261
|
+
# Opcode
|
|
262
|
+
op = first & 0b00001111
|
|
263
|
+
if DEBUG:
|
|
264
|
+
warn(f'-- Parsing frame ({fin}, {rsv1}, {rsv2}, {rsv3}, {op})')
|
|
265
|
+
|
|
266
|
+
# Small payload
|
|
267
|
+
hlen, length = 2, second & 0b01111111
|
|
268
|
+
if length < 126:
|
|
269
|
+
if DEBUG:
|
|
270
|
+
warn(f'-- Small payload ({length})')
|
|
271
|
+
|
|
272
|
+
# Extended payload (16-bit)
|
|
273
|
+
elif length == 126:
|
|
274
|
+
if not len(buffer) > 4:
|
|
275
|
+
return None
|
|
276
|
+
hlen = 4
|
|
277
|
+
length = struct.unpack_from('>H', buffer, 2)[0]
|
|
278
|
+
if DEBUG:
|
|
279
|
+
warn(f'-- Extended 16-bit payload ({length})')
|
|
280
|
+
|
|
281
|
+
# Extended payload (64-bit)
|
|
282
|
+
else:
|
|
283
|
+
if not len(buffer) > 10:
|
|
284
|
+
return None
|
|
285
|
+
hlen = 10
|
|
286
|
+
length = struct.unpack_from('>Q', buffer, 2)[0]
|
|
287
|
+
if DEBUG:
|
|
288
|
+
warn(f'-- Extended 64-bit payload ({length})')
|
|
289
|
+
|
|
290
|
+
# Check message size
|
|
291
|
+
if length > max:
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
# Check if whole packet has arrived
|
|
295
|
+
if masked := second & 0b10000000:
|
|
296
|
+
length += 4
|
|
297
|
+
if len(buffer) < hlen + length:
|
|
298
|
+
return None
|
|
299
|
+
del buffer[:hlen]
|
|
300
|
+
|
|
301
|
+
# Payload
|
|
302
|
+
payload = bytes(buffer[:length])
|
|
303
|
+
del buffer[:length]
|
|
304
|
+
if masked:
|
|
305
|
+
payload = xor_encode(payload[4:], payload[:4] * 128)
|
|
306
|
+
if DEBUG:
|
|
307
|
+
warn(repr(payload))
|
|
308
|
+
|
|
309
|
+
return (fin, rsv1, rsv2, rsv3, op, payload)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def server_handshake[T: 'Pyjo.Transaction.object'](tx: T) -> T:
|
|
313
|
+
"""::
|
|
314
|
+
|
|
315
|
+
tx = server_handshake(Pyjo.Transaction.HTTP.new())
|
|
316
|
+
|
|
317
|
+
Perform WebSocket handshake server-side.
|
|
318
|
+
"""
|
|
319
|
+
headers = tx.res.headers
|
|
320
|
+
headers.upgrade = 'websocket'
|
|
321
|
+
headers.connection = 'Upgrade'
|
|
322
|
+
headers.sec_websocket_accept = _challenge(tx.req.headers.sec_websocket_key)
|
|
323
|
+
|
|
324
|
+
return tx
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _bit(value: object) -> int:
|
|
328
|
+
return 1 if value else 0
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _challenge(key: str | None) -> str:
|
|
332
|
+
return b64_encode(sha1_bytes((key or '').encode('latin-1') + GUID), '')
|