|
@@ -1,20 +1,17 @@
|
1
|
1
|
#Known bugs :
|
2
|
|
-#Sockets are not closed properly leading in a listening socket leak
|
|
2
|
+#Sockets are not closed properly leading in a listening socket leak, a patch
|
|
3
|
+#will be submitted for cpython 3.6
|
3
|
4
|
#
|
4
|
5
|
|
|
6
|
+import os
|
|
7
|
+import sys
|
|
8
|
+import signal
|
|
9
|
+import socket
|
|
10
|
+import socketserver
|
5
|
11
|
import wsgiref
|
6
|
12
|
import wsgiref.simple_server
|
7
|
13
|
from wsgiref.simple_server import make_server
|
8
|
|
-import http.server
|
9
|
|
-import multiprocessing as mp
|
10
|
|
-import socketserver
|
11
|
|
-import socket
|
12
|
|
-import os
|
13
|
14
|
from io import BufferedWriter
|
14
|
|
-import urllib
|
15
|
|
-import threading
|
16
|
|
-
|
17
|
|
-import sys, signal
|
18
|
15
|
|
19
|
16
|
from lodel.context import LodelContext
|
20
|
17
|
from lodel.context import ContextError
|
|
@@ -25,7 +22,10 @@ LISTEN_ADDR = ''
|
25
|
22
|
LISTEN_PORT = 1337
|
26
|
23
|
|
27
|
24
|
##@brief Set the poll interval to detect shutdown requests (do not work)
|
28
|
|
-SHUTDOWN_POLL_INTERVAL = 0.1
|
|
25
|
+SHUTDOWN_POLL_INTERVAL = 0.1 # <-- No impact because of ForkingTCPServer bug
|
|
26
|
+
|
|
27
|
+##@brief Stores the signal we uses to kill childs
|
|
28
|
+KILLING_CHILDS_SIGNAL = signal.SIGTERM
|
29
|
29
|
|
30
|
30
|
##@brief Reimplementation of WSGIRequestHandler
|
31
|
31
|
#
|
|
@@ -33,7 +33,7 @@ SHUTDOWN_POLL_INTERVAL = 0.1
|
33
|
33
|
#a request.
|
34
|
34
|
#We inherit from wsgiref.simple_server.WSGIRequestHandler to avoid writing
|
35
|
35
|
#all the construction of the wsgi variables
|
36
|
|
-class HtppHandler(wsgiref.simple_server.WSGIRequestHandler):
|
|
36
|
+class LodelWSGIHandler(wsgiref.simple_server.WSGIRequestHandler):
|
37
|
37
|
|
38
|
38
|
##@brief Method called by the socketserver to handle a request
|
39
|
39
|
def handle(self):
|
|
@@ -44,7 +44,7 @@ class HtppHandler(wsgiref.simple_server.WSGIRequestHandler):
|
44
|
44
|
req_ref.close()
|
45
|
45
|
print("Client %d stopping by signal" % os.getpid())
|
46
|
46
|
os._exit(0)
|
47
|
|
- signal.signal(signal.SIGTERM, sigstop_handler_client)
|
|
47
|
+ signal.signal(KILLING_CHILDS_SIGNAL, sigstop_handler_client)
|
48
|
48
|
#Dirty copy & past from Lib/http/server.py in Cpython sources
|
49
|
49
|
try:
|
50
|
50
|
self.raw_requestline = self.rfile.readline(65537)
|
|
@@ -80,85 +80,27 @@ class HtppHandler(wsgiref.simple_server.WSGIRequestHandler):
|
80
|
80
|
self.request.close()
|
81
|
81
|
super().close()
|
82
|
82
|
|
83
|
|
- ##@brief Copy of wsgiref.simple_server.WSGIRequestHandler.get_environ method
|
84
|
|
- def get_environ(self):
|
85
|
|
- env = self.server.base_environ.copy()
|
86
|
|
- env['SERVER_PROTOCOL'] = self.request_version
|
87
|
|
- env['SERVER_SOFTWARE'] = self.server_version
|
88
|
|
- env['REQUEST_METHOD'] = self.command
|
89
|
|
- if '?' in self.path:
|
90
|
|
- path,query = self.path.split('?',1)
|
91
|
|
- else:
|
92
|
|
- path,query = self.path,''
|
93
|
|
-
|
94
|
|
- env['PATH_INFO'] = urllib.parse.unquote(path, 'iso-8859-1')
|
95
|
|
- env['QUERY_STRING'] = query
|
96
|
|
-
|
97
|
|
- host = self.address_string()
|
98
|
|
- if host != self.client_address[0]:
|
99
|
|
- env['REMOTE_HOST'] = host
|
100
|
|
- env['REMOTE_ADDR'] = self.client_address[0]
|
101
|
|
-
|
102
|
|
- if self.headers.get('content-type') is None:
|
103
|
|
- env['CONTENT_TYPE'] = self.headers.get_content_type()
|
104
|
|
- else:
|
105
|
|
- env['CONTENT_TYPE'] = self.headers['content-type']
|
106
|
|
-
|
107
|
|
- length = self.headers.get('content-length')
|
108
|
|
- if length:
|
109
|
|
- env['CONTENT_LENGTH'] = length
|
110
|
|
-
|
111
|
|
- for k, v in self.headers.items():
|
112
|
|
- k=k.replace('-','_').upper(); v=v.strip()
|
113
|
|
- if k in env:
|
114
|
|
- continue # skip content length, type,etc.
|
115
|
|
- if 'HTTP_'+k in env:
|
116
|
|
- env['HTTP_'+k] += ','+v # comma-separate multiple headers
|
117
|
|
- else:
|
118
|
|
- env['HTTP_'+k] = v
|
119
|
|
- return env
|
120
|
|
-
|
121
|
|
-##@brief Speciallized ForkingTCPServer to fit specs of WSGIHandler
|
122
|
|
-class HttpServer(socketserver.ForkingTCPServer):
|
123
|
|
-
|
124
|
|
- ##@brief Max childs count
|
125
|
|
- max_children = 80
|
126
|
|
-
|
127
|
|
- ##@brief Onverwritting of ForkingTCPServer.server_bind method
|
128
|
|
- #to fit the wsgiref specs
|
129
|
|
- def server_bind(self):
|
130
|
|
- super().server_bind()
|
131
|
|
- #Copy & paste from Lib/http/server.py
|
132
|
|
- host, port = self.socket.getsockname()[:2]
|
133
|
|
- self.server_name = socket.getfqdn(host)
|
134
|
|
- self.server_port = port
|
135
|
|
- # Copy&paste from Lib/wsgiref/simple_server.py
|
136
|
|
- # Set up base environment
|
137
|
|
- env = self.base_environ = {}
|
138
|
|
- env['SERVER_NAME'] = self.server_name
|
139
|
|
- env['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
140
|
|
- env['SERVER_PORT'] = str(self.server_port)
|
141
|
|
- env['REMOTE_HOST']=''
|
142
|
|
- env['CONTENT_LENGTH']=''
|
143
|
|
- env['SCRIPT_NAME'] = ''
|
144
|
|
-
|
145
|
|
- ##@brief Hardcoded callback function
|
146
|
|
- def get_app(self):
|
147
|
|
- return wsgi_router
|
148
|
|
-
|
149
|
|
- ##@brief An attempt to solve the socket leak problem
|
150
|
|
- def server_close(self):
|
151
|
|
- self.collect_children()
|
152
|
|
- print("Closing listening socket")
|
153
|
|
- self.socket.close()
|
154
|
|
-
|
|
83
|
+##@brief WSGIServer implementing ForkingTCPServer.
|
|
84
|
+#
|
|
85
|
+#Same features than wsgiref.simple_server.WSGIServer but process each requests
|
|
86
|
+#in a child process
|
|
87
|
+class ForkingWSGIServer(
|
|
88
|
+ wsgiref.simple_server.WSGIServer, socketserver.ForkingTCPServer):
|
155
|
89
|
##@brief Custom reimplementation of shutdown method in order to ensure
|
156
|
90
|
#that we close all listening sockets
|
|
91
|
+ #
|
|
92
|
+ #This method is here because of a bug (or a missing feature) :
|
|
93
|
+ #The socketserver implementation force to call the shutdown method
|
|
94
|
+ #from another thread/process else it leads in a deadlock.
|
|
95
|
+ #The problem is that the implementation of shutdown set a private attribute
|
|
96
|
+ #__shutdown_request to true. So we cannot reimplement a method that will
|
|
97
|
+ #just set the flag to True, we have to manually collect each actives
|
|
98
|
+ #childs. A patch is prepared and will be proposed for cpython upstream.
|
157
|
99
|
def shutdown(self):
|
158
|
100
|
if self.active_children is not None:
|
159
|
101
|
for pid in self.active_children.copy():
|
160
|
102
|
print("Killing : %d"%pid)
|
161
|
|
- os.kill(pid, signal.SIGTERM)
|
|
103
|
+ os.kill(pid, KILLING_CHILDS_SIGNAL)
|
162
|
104
|
try:
|
163
|
105
|
pid, _ = os.waitpid(pid, 0)
|
164
|
106
|
self.active_children.discard(pid)
|
|
@@ -216,20 +158,15 @@ def wsgi_router(env, start_response):
|
216
|
158
|
#mp.Process(target=foo, args=(env,start_response))
|
217
|
159
|
return child_proc(env, start_response)
|
218
|
160
|
|
|
161
|
+##@brief Starts the server until a SIGINT is received
|
219
|
162
|
def main_loop():
|
220
|
|
-
|
221
|
|
- #Set the start method for multiprocessing
|
222
|
|
- mp.set_start_method('forkserver')
|
223
|
163
|
print("PID = %d" % os.getpid())
|
224
|
164
|
|
225
|
165
|
listen_addr = LISTEN_ADDR
|
226
|
166
|
listen_port = LISTEN_PORT
|
227
|
|
-
|
228
|
|
- #server = socketserver.ForkingTCPServer((listen_addr, listen_port),
|
229
|
|
- # HtppHandler)
|
230
|
|
- server = HttpServer((listen_addr, listen_port),
|
231
|
|
- HtppHandler)
|
232
|
|
-
|
|
167
|
+ server = wsgiref.simple_server.make_server(
|
|
168
|
+ listen_addr, listen_port, wsgi_router,
|
|
169
|
+ server_class=ForkingWSGIServer, handler_class = LodelWSGIHandler)
|
233
|
170
|
#Signal handler to close server properly on sigint
|
234
|
171
|
def sigint_handler(signal, frame):
|
235
|
172
|
print("Ctrl-c pressed, exiting")
|