forked from jamesroberts/fastwsgi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fastwsgi.py
160 lines (140 loc) · 5.74 KB
/
fastwsgi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import os
import sys
import signal
import importlib
import click
import _fastwsgi
from pkg_resources import get_distribution
__version__ = get_distribution("fastwsgi").version
LL_DISABLED = 0
LL_FATAL_ERROR = 1
LL_CRIT_ERROR = 2
LL_ERROR = 3
LL_WARNING = 4
LL_NOTICE = 5
LL_INFO = 6
LL_DEBUG = 7
LL_TRACE = 8
class _Server():
def __init__(self):
self.app = None
self.host = "0.0.0.0"
self.port = 5000
self.backlog = 2048
self.loglevel = LL_ERROR
self.hook_sigint = 2 # 0 = ignore Ctrl-C; 1 = stop server on Ctrl-C; 2 = halt process on Ctrl-C
self.allow_keepalive = True
self.add_header_date = True
self.add_header_server = "FastWSGI/{}".format(__version__)
self.max_content_length = None # def value: 999999999
self.max_chunk_size = None # def value: 256 KiB
self.read_buffer_size = None # def value: 64 KiB
self.tcp_nodelay = 0 # 0 = Nagle's algo enabled; 1 = Nagle's algo disabled;
self.tcp_keepalive = 0 # -1 = disabled; 0 = system default; 1...N = timeout in seconds
self.tcp_send_buf_size = 0 # 0 = system default; 1...N = size in bytes
self.tcp_recv_buf_size = 0 # 0 = system default; 1...N = size in bytes
self.nowait = 0
self.num_workers = 1
self.worker_list = [ ]
def init(self, app, host = None, port = None, loglevel = None, workers = None):
self.app = app
self.host = host if host else self.host
self.port = port if port else self.port
self.loglevel = loglevel if loglevel is not None else self.loglevel
self.num_workers = workers if workers is not None else self.num_workers
if self.num_workers > 1:
return 0
return _fastwsgi.init_server(self)
def set_allow_keepalive(self, value):
self.allow_keepalive = value
_fastwsgi.change_setting(self, "allow_keepalive")
def run(self):
if self.nowait:
if self.num_workers > 1:
raise Exception('Incorrect server options')
return _fastwsgi.run_nowait(self)
if self.num_workers > 1:
return self.multi_run()
ret = _fastwsgi.run_server(self)
self.close()
return ret
def close(self):
return _fastwsgi.close_server(self)
def multi_run(self, num_workers = None):
if num_workers is not None:
self.num_workers = num_workers
for _ in range(self.num_workers):
pid = os.fork()
if pid > 0:
self.worker_list.append(pid)
print(f"Worker process added with PID: {pid}")
continue
try:
_fastwsgi.init_server(self)
_fastwsgi.run_server(self)
except KeyboardInterrupt:
pass
sys.exit(0)
try:
for _ in range(self.num_workers):
os.wait()
except KeyboardInterrupt:
print("\n" + "Stopping all workers")
for worker in self.worker_list:
os.kill(worker, signal.SIGINT)
return 0
server = _Server()
# -------------------------------------------------------------------------------------
def import_from_string(import_str):
module_str, _, attrs_str = import_str.partition(":")
if not module_str or not attrs_str:
raise ImportError("Import string should be in the format <module>:<attribute>")
try:
relpath = f"{module_str}.py"
if os.path.isfile(relpath):
spec = importlib.util.spec_from_file_location(module_str, relpath)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
else:
module = importlib.import_module(module_str)
for attr_str in attrs_str.split("."):
module = getattr(module, attr_str)
except AttributeError:
raise ImportError(f'Attribute "{attrs_str}" not found in module "{module_str}"')
return module
# -------------------------------------------------------------------------------------
@click.command()
@click.version_option(version=get_distribution("fastwsgi").version, message="%(version)s")
@click.option("--host", help="Host the socket is bound to.", type=str, default=server.host, show_default=True)
@click.option("-p", "--port", help="Port the socket is bound to.", type=int, default=server.port, show_default=True)
@click.option("-l", "--loglevel", help="Logging level.", type=int, default=server.loglevel, show_default=True)
@click.argument(
"wsgi_app_import_string",
type=str,
required=True,
)
def run_from_cli(host, port, wsgi_app_import_string, loglevel):
"""
Run FastWSGI server from CLI
"""
try:
wsgi_app = import_from_string(wsgi_app_import_string)
except ImportError as e:
print(f"Error importing WSGI app: {e}")
sys.exit(1)
server.init(wsgi_app, host, port, loglevel)
print(f"FastWSGI server listening at http://{server.host}:{server.port}")
server.run()
# -------------------------------------------------------------------------------------
def run(app = None, host = None, port = None, loglevel = None, workers = None, wsgi_app = None):
if app and wsgi_app:
raise Exception("It is not allowed to specify several applications at once.")
if app is None:
app = wsgi_app
if app is None:
raise Exception("app not specify.")
print("FastWSGI server running on PID:", os.getpid())
server.init(app, host, port, loglevel, workers)
addon = " multiple workers" if server.num_workers > 1 else ""
print(f"FastWSGI server{addon} listening at http://{server.host}:{server.port}")
server.run()