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/IOLoop/Server.py
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjo.IOLoop.Server - Non-blocking TCP and UNIX domain socket server
|
|
3
|
+
===================================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.IOLoop.Server import Server
|
|
7
|
+
|
|
8
|
+
# Create listen socket
|
|
9
|
+
server = Server()
|
|
10
|
+
server.on('accept', lambda server, handle: ...)
|
|
11
|
+
server.listen(port=3000)
|
|
12
|
+
|
|
13
|
+
# Or import the module, as in Google Python Style Guide
|
|
14
|
+
|
|
15
|
+
import Pyjo.IOLoop.Server
|
|
16
|
+
|
|
17
|
+
# Create listen socket
|
|
18
|
+
server = Pyjo.IOLoop.Server.new()
|
|
19
|
+
server.on('accept', lambda server, handle: ...)
|
|
20
|
+
server.listen(port=3000)
|
|
21
|
+
|
|
22
|
+
# Start and stop accepting connections
|
|
23
|
+
server.start()
|
|
24
|
+
server.stop()
|
|
25
|
+
|
|
26
|
+
# Start reactor if necessary
|
|
27
|
+
if not server.reactor.is_running:
|
|
28
|
+
server.reactor.start()
|
|
29
|
+
|
|
30
|
+
:mod:`Pyjo.IOLoop.Server` accepts TCP/IP and UNIX domain socket connections
|
|
31
|
+
for :mod:`Pyjo.IOLoop`.
|
|
32
|
+
|
|
33
|
+
The end of a ``with`` block closes the listen socket.
|
|
34
|
+
|
|
35
|
+
Events
|
|
36
|
+
------
|
|
37
|
+
|
|
38
|
+
:mod:`Pyjo.IOLoop.Server` inherits all events from :mod:`Pyjo.EventEmitter`
|
|
39
|
+
and can emit the following new ones.
|
|
40
|
+
|
|
41
|
+
accept
|
|
42
|
+
~~~~~~
|
|
43
|
+
::
|
|
44
|
+
|
|
45
|
+
server.on('accept', lambda server, handle: ...)
|
|
46
|
+
|
|
47
|
+
Emitted for each accepted connection, with its :class:`socket.socket`, or the
|
|
48
|
+
:class:`ssl.SSLSocket` once TLS has been negotiated.
|
|
49
|
+
|
|
50
|
+
See also
|
|
51
|
+
--------
|
|
52
|
+
|
|
53
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
54
|
+
|
|
55
|
+
Ported from `Mojo::IOLoop::Server <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/IOLoop/Server.pm>`_
|
|
56
|
+
of Mojolicious 9.49.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
import Pyjo.EventEmitter
|
|
60
|
+
import Pyjo.IOLoop
|
|
61
|
+
import Pyjo.IOLoop.TLS
|
|
62
|
+
import Pyjo.Reactor
|
|
63
|
+
|
|
64
|
+
import errno
|
|
65
|
+
import os
|
|
66
|
+
import re
|
|
67
|
+
import socket
|
|
68
|
+
import stat
|
|
69
|
+
import sys
|
|
70
|
+
import weakref
|
|
71
|
+
|
|
72
|
+
from types import TracebackType
|
|
73
|
+
from typing import Any, Self
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Pyjo_IOLoop_Server(Pyjo.EventEmitter.object):
|
|
77
|
+
"""
|
|
78
|
+
:mod:`Pyjo.IOLoop.Server` inherits all methods from
|
|
79
|
+
:mod:`Pyjo.EventEmitter` and implements the following new ones.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self) -> None:
|
|
83
|
+
"""::
|
|
84
|
+
|
|
85
|
+
server = Pyjo.IOLoop.Server.new()
|
|
86
|
+
|
|
87
|
+
Construct a new :mod:`Pyjo.IOLoop.Server` object.
|
|
88
|
+
"""
|
|
89
|
+
super().__init__()
|
|
90
|
+
self._active = False
|
|
91
|
+
self._args: dict[str, Any] = {}
|
|
92
|
+
self._handle: socket.socket | None = None
|
|
93
|
+
self._reactor: weakref.ref[Pyjo.Reactor.object] | None = None
|
|
94
|
+
self._reuse: str | None = None
|
|
95
|
+
self._watched: weakref.ref[Pyjo.Reactor.object] | None = None
|
|
96
|
+
|
|
97
|
+
def __del__(self) -> None:
|
|
98
|
+
# Module globals may be gone at the end of the interpreter
|
|
99
|
+
try:
|
|
100
|
+
if not sys.is_finalizing() and getattr(self, '_handle', None) is not None:
|
|
101
|
+
reactor = self._reactor() if self._reactor is not None else None
|
|
102
|
+
Pyjo.Reactor._finalize(self.close, reactor) # pyright: ignore[reportPrivateUsage]
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
# Not in Mojolicious: a context manager, which closes the listen socket where the block ends, as PyPy frees the
|
|
107
|
+
# object only when its garbage collector runs
|
|
108
|
+
def __enter__(self) -> Self:
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
def __exit__(
|
|
112
|
+
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
113
|
+
) -> None:
|
|
114
|
+
self.close()
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def reactor(self) -> Pyjo.Reactor.object:
|
|
118
|
+
"""::
|
|
119
|
+
|
|
120
|
+
reactor = server.reactor
|
|
121
|
+
server.reactor = Pyjo.Reactor.Poll.new()
|
|
122
|
+
|
|
123
|
+
Low-level event reactor, defaults to the ``reactor`` attribute value of
|
|
124
|
+
the global :mod:`Pyjo.IOLoop` singleton. Note that this attribute is
|
|
125
|
+
weakened.
|
|
126
|
+
"""
|
|
127
|
+
if self._reactor is None:
|
|
128
|
+
self._reactor = weakref.ref(Pyjo.IOLoop.singleton.reactor)
|
|
129
|
+
if (reactor := self._reactor()) is None:
|
|
130
|
+
raise ReferenceError('weakly-referenced object no longer exists')
|
|
131
|
+
return reactor
|
|
132
|
+
|
|
133
|
+
@reactor.setter
|
|
134
|
+
def reactor(self, value: Pyjo.Reactor.object) -> None:
|
|
135
|
+
self._reactor = weakref.ref(value)
|
|
136
|
+
|
|
137
|
+
# Not in Mojolicious: what DESTROY does, which Python does when it frees the server, PyPy only later
|
|
138
|
+
def close(self) -> None:
|
|
139
|
+
"""::
|
|
140
|
+
|
|
141
|
+
server.close()
|
|
142
|
+
|
|
143
|
+
Stop accepting connections, close the listen socket and remove it from
|
|
144
|
+
the ``PYJO_REUSE`` environment variable, as it happens when the server
|
|
145
|
+
is freed.
|
|
146
|
+
"""
|
|
147
|
+
if (reuse := self._reuse) is not None:
|
|
148
|
+
self._reuse = None
|
|
149
|
+
os.environ['PYJO_REUSE'] = re.sub(
|
|
150
|
+
rf'(?:^|,){re.escape(reuse)}', '', os.environ.get('PYJO_REUSE', ''), count=1
|
|
151
|
+
)
|
|
152
|
+
if (handle := self._handle) is not None:
|
|
153
|
+
if self._reactor is None or self._reactor() is not None:
|
|
154
|
+
self.stop()
|
|
155
|
+
self._handle = None
|
|
156
|
+
handle.close()
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def generate_port(cls) -> int:
|
|
160
|
+
"""::
|
|
161
|
+
|
|
162
|
+
port = Pyjo.IOLoop.Server.object.generate_port()
|
|
163
|
+
|
|
164
|
+
Find a free TCP port, primarily used for tests.
|
|
165
|
+
"""
|
|
166
|
+
with socket.create_server(('127.0.0.1', 0), backlog=5) as handle:
|
|
167
|
+
return handle.getsockname()[1]
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def handle(self) -> socket.socket | None:
|
|
171
|
+
"""::
|
|
172
|
+
|
|
173
|
+
handle = server.handle
|
|
174
|
+
|
|
175
|
+
Get handle for server, usually a :class:`socket.socket` object.
|
|
176
|
+
"""
|
|
177
|
+
return self._handle
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def is_accepting(self) -> bool:
|
|
181
|
+
"""::
|
|
182
|
+
|
|
183
|
+
boolean = server.is_accepting
|
|
184
|
+
|
|
185
|
+
Check if connections are currently being accepted.
|
|
186
|
+
"""
|
|
187
|
+
return self._active
|
|
188
|
+
|
|
189
|
+
def listen(self, **options: Any) -> None:
|
|
190
|
+
"""::
|
|
191
|
+
|
|
192
|
+
server.listen(port=3000)
|
|
193
|
+
|
|
194
|
+
Create a new listen socket.
|
|
195
|
+
|
|
196
|
+
These options are currently available:
|
|
197
|
+
|
|
198
|
+
address
|
|
199
|
+
::
|
|
200
|
+
|
|
201
|
+
address='127.0.0.1'
|
|
202
|
+
|
|
203
|
+
Local address to listen on, defaults to ``0.0.0.0``.
|
|
204
|
+
|
|
205
|
+
backlog
|
|
206
|
+
::
|
|
207
|
+
|
|
208
|
+
backlog=128
|
|
209
|
+
|
|
210
|
+
Maximum backlog size, defaults to :data:`socket.SOMAXCONN`.
|
|
211
|
+
|
|
212
|
+
fd
|
|
213
|
+
::
|
|
214
|
+
|
|
215
|
+
fd=3
|
|
216
|
+
|
|
217
|
+
File descriptor with an already prepared listen socket.
|
|
218
|
+
|
|
219
|
+
path
|
|
220
|
+
::
|
|
221
|
+
|
|
222
|
+
path='/tmp/myapp.sock'
|
|
223
|
+
|
|
224
|
+
Path for UNIX domain socket to listen on.
|
|
225
|
+
|
|
226
|
+
port
|
|
227
|
+
::
|
|
228
|
+
|
|
229
|
+
port=80
|
|
230
|
+
|
|
231
|
+
Port to listen on, defaults to a random port.
|
|
232
|
+
|
|
233
|
+
reuse
|
|
234
|
+
::
|
|
235
|
+
|
|
236
|
+
reuse=True
|
|
237
|
+
|
|
238
|
+
Allow multiple servers to use the same port with the
|
|
239
|
+
``SO_REUSEPORT`` socket option, which Windows does not have.
|
|
240
|
+
|
|
241
|
+
single_accept
|
|
242
|
+
::
|
|
243
|
+
|
|
244
|
+
single_accept=True
|
|
245
|
+
|
|
246
|
+
Only accept one connection at a time.
|
|
247
|
+
|
|
248
|
+
tls
|
|
249
|
+
::
|
|
250
|
+
|
|
251
|
+
tls=True
|
|
252
|
+
|
|
253
|
+
Enable TLS.
|
|
254
|
+
|
|
255
|
+
tls_ca
|
|
256
|
+
::
|
|
257
|
+
|
|
258
|
+
tls_ca='/etc/tls/ca.crt'
|
|
259
|
+
|
|
260
|
+
Path to TLS certificate authority file.
|
|
261
|
+
|
|
262
|
+
tls_cert
|
|
263
|
+
::
|
|
264
|
+
|
|
265
|
+
tls_cert='/etc/tls/server.crt'
|
|
266
|
+
tls_cert={'mojolicious.org': '/etc/tls/mojo.crt'}
|
|
267
|
+
|
|
268
|
+
Path to the TLS cert file, defaults to a built-in test certificate.
|
|
269
|
+
|
|
270
|
+
tls_context
|
|
271
|
+
::
|
|
272
|
+
|
|
273
|
+
tls_context=ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
|
274
|
+
|
|
275
|
+
The :class:`ssl.SSLContext` to use instead of the one that the
|
|
276
|
+
other options make.
|
|
277
|
+
|
|
278
|
+
tls_key
|
|
279
|
+
::
|
|
280
|
+
|
|
281
|
+
tls_key='/etc/tls/server.key'
|
|
282
|
+
tls_key={'mojolicious.org': '/etc/tls/mojo.key'}
|
|
283
|
+
|
|
284
|
+
Path to the TLS key file, defaults to a built-in test key.
|
|
285
|
+
"""
|
|
286
|
+
# Look for reusable file descriptor
|
|
287
|
+
path = options.get('path')
|
|
288
|
+
address = options.get('address') or '0.0.0.0'
|
|
289
|
+
port = options.get('port')
|
|
290
|
+
reuse_env = os.environ.setdefault('PYJO_REUSE', '')
|
|
291
|
+
fd: int | None = None
|
|
292
|
+
if path and (m := re.search(rf'(?:^|,)unix:{re.escape(path)}:(\d+)', reuse_env)):
|
|
293
|
+
fd = int(m[1])
|
|
294
|
+
elif port and (m := re.search(rf'(?:^|,){re.escape(f"{address}:{port}")}:(\d+)', reuse_env)):
|
|
295
|
+
fd = int(m[1])
|
|
296
|
+
|
|
297
|
+
# Reuse file descriptor
|
|
298
|
+
if fd is None:
|
|
299
|
+
fd = options.get('fd')
|
|
300
|
+
if fd is not None:
|
|
301
|
+
try:
|
|
302
|
+
handle = socket.socket(fileno=fd)
|
|
303
|
+
except OSError as e:
|
|
304
|
+
raise _error(e, f"Can't open file descriptor {fd}") from e
|
|
305
|
+
|
|
306
|
+
else:
|
|
307
|
+
backlog = options.get('backlog')
|
|
308
|
+
backlog = socket.SOMAXCONN if backlog is None else int(backlog)
|
|
309
|
+
created: socket.socket | None = None
|
|
310
|
+
try:
|
|
311
|
+
# UNIX domain socket
|
|
312
|
+
if path:
|
|
313
|
+
# Not in Mojolicious: Python has no UNIX domain sockets on Windows
|
|
314
|
+
if sys.platform == 'win32':
|
|
315
|
+
raise OSError(errno.EAFNOSUPPORT, 'UNIX domain sockets are not supported')
|
|
316
|
+
if os.path.exists(path) and stat.S_ISSOCK(os.stat(path).st_mode):
|
|
317
|
+
os.remove(path)
|
|
318
|
+
handle = created = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
319
|
+
handle.bind(path)
|
|
320
|
+
handle.listen(backlog)
|
|
321
|
+
reuse = self._reuse = f'unix:{path}:{handle.fileno()}'
|
|
322
|
+
|
|
323
|
+
# IP socket
|
|
324
|
+
else:
|
|
325
|
+
host = address.replace('[', '').replace(']', '')
|
|
326
|
+
family, _, _, _, sockaddr = socket.getaddrinfo(
|
|
327
|
+
host, port or 0, type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE
|
|
328
|
+
)[0]
|
|
329
|
+
handle = created = socket.socket(family, socket.SOCK_STREAM)
|
|
330
|
+
# Unlike Mojolicious: no SO_REUSEADDR on Windows, where it lets a second socket listen on a port
|
|
331
|
+
# that is taken, as socket.create_server leaves it out there, and SO_REUSEPORT is not there at all,
|
|
332
|
+
# where Perl dies of the missing constant
|
|
333
|
+
if sys.platform != 'win32':
|
|
334
|
+
handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
335
|
+
if options.get('reuse'):
|
|
336
|
+
if sys.platform == 'win32':
|
|
337
|
+
raise OSError(errno.ENOPROTOOPT, 'SO_REUSEPORT is not supported')
|
|
338
|
+
handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
|
339
|
+
handle.bind(sockaddr)
|
|
340
|
+
handle.listen(backlog)
|
|
341
|
+
reuse = self._reuse = f'{address}:{handle.getsockname()[1]}:{handle.fileno()}'
|
|
342
|
+
except OSError as e:
|
|
343
|
+
# The socket that cannot listen is closed, as Perl frees it
|
|
344
|
+
if created is not None:
|
|
345
|
+
created.close()
|
|
346
|
+
raise _error(e, "Can't create listen socket") from e
|
|
347
|
+
|
|
348
|
+
os.environ['PYJO_REUSE'] = f'{reuse_env},{reuse}' if len(reuse_env) else reuse
|
|
349
|
+
|
|
350
|
+
# Allow file descriptor inheritance, as local $^F = 1023
|
|
351
|
+
handle.set_inheritable(True)
|
|
352
|
+
handle.setblocking(False)
|
|
353
|
+
self._args, self._handle = options, handle
|
|
354
|
+
|
|
355
|
+
if not Pyjo.IOLoop.TLS.object.can_tls() and options.get('tls'):
|
|
356
|
+
raise RuntimeError('TLS support is disabled')
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def port(self) -> int | None:
|
|
360
|
+
"""::
|
|
361
|
+
|
|
362
|
+
port = server.port
|
|
363
|
+
|
|
364
|
+
Get port this server is listening on.
|
|
365
|
+
"""
|
|
366
|
+
if self._handle is None or self._handle.family == getattr(socket, 'AF_UNIX', None):
|
|
367
|
+
return None
|
|
368
|
+
return self._handle.getsockname()[1]
|
|
369
|
+
|
|
370
|
+
def start(self) -> None:
|
|
371
|
+
"""::
|
|
372
|
+
|
|
373
|
+
server.start()
|
|
374
|
+
|
|
375
|
+
Start or resume accepting connections.
|
|
376
|
+
"""
|
|
377
|
+
if (handle := self._handle) is None:
|
|
378
|
+
return
|
|
379
|
+
# Not in Mojolicious: an acceptor that is active already in this reactor is not watched again, where Mojo
|
|
380
|
+
# replaces its watcher
|
|
381
|
+
reactor = self.reactor
|
|
382
|
+
if self._active and self._watched is not None and self._watched() is reactor:
|
|
383
|
+
return
|
|
384
|
+
ref = weakref.ref(self)
|
|
385
|
+
|
|
386
|
+
def cb(reactor: Any, writable: bool) -> None:
|
|
387
|
+
if (server := ref()) is not None:
|
|
388
|
+
server._accept()
|
|
389
|
+
|
|
390
|
+
self._active, self._watched = True, weakref.ref(reactor)
|
|
391
|
+
reactor.io(handle, cb).watch(handle, True, False)
|
|
392
|
+
|
|
393
|
+
def stop(self) -> None:
|
|
394
|
+
"""::
|
|
395
|
+
|
|
396
|
+
server.stop()
|
|
397
|
+
|
|
398
|
+
Stop accepting connections.
|
|
399
|
+
"""
|
|
400
|
+
if self._active:
|
|
401
|
+
self._active, self._watched = False, None
|
|
402
|
+
if self._handle is not None:
|
|
403
|
+
self.reactor.remove(self._handle)
|
|
404
|
+
|
|
405
|
+
def _accept(self) -> None:
|
|
406
|
+
# Greedy accept
|
|
407
|
+
args = self._args
|
|
408
|
+
accepted = 0
|
|
409
|
+
ref = weakref.ref(self)
|
|
410
|
+
|
|
411
|
+
def upgrade(tls: Any, handle: Any) -> None:
|
|
412
|
+
if (server := ref()) is not None:
|
|
413
|
+
server.emit('accept', handle)
|
|
414
|
+
|
|
415
|
+
while self._active and self._handle is not None:
|
|
416
|
+
if args.get('single_accept'):
|
|
417
|
+
if accepted:
|
|
418
|
+
break
|
|
419
|
+
accepted += 1
|
|
420
|
+
try:
|
|
421
|
+
handle = self._handle.accept()[0]
|
|
422
|
+
except OSError:
|
|
423
|
+
return
|
|
424
|
+
handle.setblocking(False)
|
|
425
|
+
|
|
426
|
+
# Disable Nagle's algorithm
|
|
427
|
+
try:
|
|
428
|
+
handle.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
429
|
+
except OSError:
|
|
430
|
+
pass
|
|
431
|
+
|
|
432
|
+
if not args.get('tls'):
|
|
433
|
+
self.emit('accept', handle)
|
|
434
|
+
continue
|
|
435
|
+
|
|
436
|
+
# Start TLS handshake
|
|
437
|
+
tls = Pyjo.IOLoop.TLS.new(handle)
|
|
438
|
+
tls.reactor = self.reactor
|
|
439
|
+
tls.on('upgrade', upgrade)
|
|
440
|
+
tls.on('error', _ignore)
|
|
441
|
+
tls.negotiate(**{**args, 'server': True})
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# The errors of a TLS handshake that fails are ignored
|
|
445
|
+
def _ignore(tls: Any, err: Any) -> None:
|
|
446
|
+
pass
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
# Mojo's croak with $!: the same kind of error, with the message in front
|
|
450
|
+
def _error(e: OSError, message: str) -> OSError:
|
|
451
|
+
error = type(e)(f'{message}: {e.strerror}' if e.strerror else message)
|
|
452
|
+
error.errno = e.errno
|
|
453
|
+
return error
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
new = Pyjo_IOLoop_Server.new
|
|
457
|
+
object = Pyjo_IOLoop_Server
|
|
458
|
+
Server = Pyjo_IOLoop_Server
|