Selaa lähdekoodia

Enhancement of multisite WSGIServer

- found a way to make it implements the socketserver.ForkinTCPServer features reducing the global code size (and removed a lot of dirty copy & paste from cpython sources)
- deleted useless imports
Yann Weber 7 vuotta sitten
vanhempi
commit
9668aed506
1 muutettua tiedostoa jossa 32 lisäystä ja 95 poistoa
  1. 32
    95
      lodel/plugins/multisite/main.py

+ 32
- 95
lodel/plugins/multisite/main.py Näytä tiedosto

@@ -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")

Loading…
Peruuta
Tallenna