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.
Files changed (200) hide show
  1. Pyjo/App/ASGI.py +735 -0
  2. Pyjo/App/CGI.py +773 -0
  3. Pyjo/App/FastCGI.py +631 -0
  4. Pyjo/App/Lambda.py +987 -0
  5. Pyjo/App/WSGI.py +427 -0
  6. Pyjo/App/__init__.py +409 -0
  7. Pyjo/Asset/File.py +377 -0
  8. Pyjo/Asset/Memory.py +235 -0
  9. Pyjo/Asset/__init__.py +200 -0
  10. Pyjo/Base.py +215 -0
  11. Pyjo/Cache.py +119 -0
  12. Pyjo/Collection.py +570 -0
  13. Pyjo/Content/MultiPart.py +325 -0
  14. Pyjo/Content/Single.py +193 -0
  15. Pyjo/Content/__init__.py +806 -0
  16. Pyjo/Cookie/Request.py +90 -0
  17. Pyjo/Cookie/Response.py +294 -0
  18. Pyjo/Cookie/__init__.py +143 -0
  19. Pyjo/DOM/CSS.py +1019 -0
  20. Pyjo/DOM/HTML.py +550 -0
  21. Pyjo/DOM/__init__.py +1329 -0
  22. Pyjo/Date.py +294 -0
  23. Pyjo/EventEmitter.py +522 -0
  24. Pyjo/Exception.py +494 -0
  25. Pyjo/File.py +959 -0
  26. Pyjo/GetOpt.py +605 -0
  27. Pyjo/Headers.py +760 -0
  28. Pyjo/HelloWorld.py +77 -0
  29. Pyjo/Home.py +109 -0
  30. Pyjo/IOLoop/ChildProcess.py +591 -0
  31. Pyjo/IOLoop/Client.py +736 -0
  32. Pyjo/IOLoop/Datagram.py +982 -0
  33. Pyjo/IOLoop/Messages.py +664 -0
  34. Pyjo/IOLoop/Server.py +458 -0
  35. Pyjo/IOLoop/Stdio.py +446 -0
  36. Pyjo/IOLoop/Stream.py +644 -0
  37. Pyjo/IOLoop/Subprocess.py +419 -0
  38. Pyjo/IOLoop/TLS.py +360 -0
  39. Pyjo/IOLoop/__init__.py +1036 -0
  40. Pyjo/IOLoop/certs/README.md +41 -0
  41. Pyjo/IOLoop/certs/ca.crt +20 -0
  42. Pyjo/IOLoop/certs/server.crt +21 -0
  43. Pyjo/IOLoop/certs/server.key +28 -0
  44. Pyjo/JSON/Pointer.py +135 -0
  45. Pyjo/JSON/RPC.py +772 -0
  46. Pyjo/JSON/Schema.py +2506 -0
  47. Pyjo/JSON/__init__.py +573 -0
  48. Pyjo/Loader.py +342 -0
  49. Pyjo/Log.py +723 -0
  50. Pyjo/Message/Request.py +725 -0
  51. Pyjo/Message/Response.py +476 -0
  52. Pyjo/Message/__init__.py +830 -0
  53. Pyjo/Parameters.py +472 -0
  54. Pyjo/Path.py +419 -0
  55. Pyjo/Promise.py +894 -0
  56. Pyjo/Prove.py +1305 -0
  57. Pyjo/Reactor/Asyncio.py +854 -0
  58. Pyjo/Reactor/Poll.py +439 -0
  59. Pyjo/Reactor/__init__.py +525 -0
  60. Pyjo/SSE.py +119 -0
  61. Pyjo/Server/ASGI.py +486 -0
  62. Pyjo/Server/CGI.py +259 -0
  63. Pyjo/Server/Daemon.py +1007 -0
  64. Pyjo/Server/FastCGI.py +517 -0
  65. Pyjo/Server/Hot/__init__.py +687 -0
  66. Pyjo/Server/Hot/__main__.py +5 -0
  67. Pyjo/Server/Lambda.py +641 -0
  68. Pyjo/Server/Prefork.py +917 -0
  69. Pyjo/Server/Prethread.py +394 -0
  70. Pyjo/Server/Reload/Backend/Poll.py +114 -0
  71. Pyjo/Server/Reload/Backend/__init__.py +126 -0
  72. Pyjo/Server/Reload/__init__.py +482 -0
  73. Pyjo/Server/Reload/__main__.py +5 -0
  74. Pyjo/Server/Stdio.py +228 -0
  75. Pyjo/Server/WSGI.py +349 -0
  76. Pyjo/Server/__init__.py +396 -0
  77. Pyjo/String/Bytes.py +532 -0
  78. Pyjo/String/HTML.py +210 -0
  79. Pyjo/String/Mixin.py +333 -0
  80. Pyjo/String/Template.py +470 -0
  81. Pyjo/String/Unicode.py +553 -0
  82. Pyjo/String/__init__.py +0 -0
  83. Pyjo/TOML.py +232 -0
  84. Pyjo/Template.py +1290 -0
  85. Pyjo/Test/Pyjo.py +1454 -0
  86. Pyjo/Test/__init__.py +1233 -0
  87. Pyjo/Test/pytest_plugin.py +305 -0
  88. Pyjo/Transaction/HTTP.py +374 -0
  89. Pyjo/Transaction/WebSocket.py +709 -0
  90. Pyjo/Transaction/__init__.py +429 -0
  91. Pyjo/URL/Template.py +459 -0
  92. Pyjo/URL/__init__.py +651 -0
  93. Pyjo/Upload.py +139 -0
  94. Pyjo/UserAgent/CookieJar.py +391 -0
  95. Pyjo/UserAgent/Proxy.py +138 -0
  96. Pyjo/UserAgent/Server.py +239 -0
  97. Pyjo/UserAgent/Transactor.py +766 -0
  98. Pyjo/UserAgent/__init__.py +1243 -0
  99. Pyjo/Util.py +3567 -0
  100. Pyjo/WebSocket.py +332 -0
  101. Pyjo/YAML.py +1167 -0
  102. Pyjo/__init__.py +441 -0
  103. Pyjo/__main__.py +35 -0
  104. Pyjo/py.typed +0 -0
  105. Pyjoyment/Command/Author/__init__.py +20 -0
  106. Pyjoyment/Command/Author/generate/__init__.py +106 -0
  107. Pyjoyment/Command/Author/generate/app.py +299 -0
  108. Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
  109. Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
  110. Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
  111. Pyjoyment/Command/Author/generate/lite_app.py +113 -0
  112. Pyjoyment/Command/Author/generate/plugin.py +267 -0
  113. Pyjoyment/Command/Author/generate/pyproject.py +123 -0
  114. Pyjoyment/Command/Author/inflate.py +101 -0
  115. Pyjoyment/Command/__init__.py +332 -0
  116. Pyjoyment/Command/asgi.py +92 -0
  117. Pyjoyment/Command/cgi.py +90 -0
  118. Pyjoyment/Command/daemon.py +160 -0
  119. Pyjoyment/Command/eval.py +128 -0
  120. Pyjoyment/Command/fastcgi.py +99 -0
  121. Pyjoyment/Command/get.py +305 -0
  122. Pyjoyment/Command/lambda.py +117 -0
  123. Pyjoyment/Command/prefork.py +181 -0
  124. Pyjoyment/Command/prethread.py +126 -0
  125. Pyjoyment/Command/routes.py +133 -0
  126. Pyjoyment/Command/stdio.py +92 -0
  127. Pyjoyment/Command/version.py +141 -0
  128. Pyjoyment/Command/wsgi.py +93 -0
  129. Pyjoyment/Commands.py +546 -0
  130. Pyjoyment/Controller.py +1307 -0
  131. Pyjoyment/HelloWorld.py +56 -0
  132. Pyjoyment/Lite.py +494 -0
  133. Pyjoyment/Plugin/AllowedHosts.py +205 -0
  134. Pyjoyment/Plugin/Config.py +248 -0
  135. Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
  136. Pyjoyment/Plugin/EPLRenderer.py +134 -0
  137. Pyjoyment/Plugin/EPRenderer.py +212 -0
  138. Pyjoyment/Plugin/HeaderCondition.py +98 -0
  139. Pyjoyment/Plugin/Health.py +325 -0
  140. Pyjoyment/Plugin/JSONConfig.py +172 -0
  141. Pyjoyment/Plugin/Mount.py +223 -0
  142. Pyjoyment/Plugin/PyRenderer.py +209 -0
  143. Pyjoyment/Plugin/TOMLConfig.py +141 -0
  144. Pyjoyment/Plugin/TagHelpers.py +959 -0
  145. Pyjoyment/Plugin/YAMLConfig.py +178 -0
  146. Pyjoyment/Plugin/__init__.py +80 -0
  147. Pyjoyment/Plugins.py +215 -0
  148. Pyjoyment/Renderer.py +673 -0
  149. Pyjoyment/Routes/Match.py +251 -0
  150. Pyjoyment/Routes/Pattern.py +443 -0
  151. Pyjoyment/Routes/Route.py +827 -0
  152. Pyjoyment/Routes/__init__.py +419 -0
  153. Pyjoyment/Sessions.py +255 -0
  154. Pyjoyment/Static.py +421 -0
  155. Pyjoyment/Types.py +238 -0
  156. Pyjoyment/Validator/Validation.py +395 -0
  157. Pyjoyment/Validator/__init__.py +304 -0
  158. Pyjoyment/__init__.py +1083 -0
  159. Pyjoyment/__main__.py +5 -0
  160. Pyjoyment/py.typed +0 -0
  161. Pyjoyment/resources/public/favicon.ico +0 -0
  162. Pyjoyment/resources/public/favicon.svg +345 -0
  163. Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
  164. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
  165. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
  166. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
  167. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
  168. Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
  169. Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
  170. Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
  171. Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
  172. Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
  173. Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
  174. Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
  175. Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
  176. Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
  177. Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
  178. Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
  179. Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
  180. Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
  181. Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
  182. Pyjoyment/resources/public/pyjo/logo.png +0 -0
  183. Pyjoyment/resources/public/pyjo/logo.svg +346 -0
  184. Pyjoyment/resources/public/pyjo/notfound.png +0 -0
  185. Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
  186. Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
  187. Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
  188. Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
  189. Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
  190. Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
  191. Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
  192. Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
  193. Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
  194. Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
  195. Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
  196. pyjoyment-0.1.0.dist-info/METADATA +376 -0
  197. pyjoyment-0.1.0.dist-info/RECORD +200 -0
  198. pyjoyment-0.1.0.dist-info/WHEEL +4 -0
  199. pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
  200. 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), '')