Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/branches/cherrypy-2.1/cherrypy/_cphttpserver.py

Revision 698 (checked in by fumanchu, 3 years ago)

Ugly fix for #321. cherrypy.server could really use some encapsulation now.

1. server.start now MUST be called from the main thread, or restart and interrupts won't work. You can stop and restart CherryPy safely now with the server.stop and server.restart methods. However, stop() only suspends the process; if you want to shut down the CP process, raise SystemExit? or KeyboardInterrupt?. If you need to do so in your own threads, set cherrypy._interrupt to an instance of one of those exceptions.

2. New cherrypy._httpserverclass attribute, so that threads can wait for the HTTP server to truly start.

3. server.start() now defaults serverClass to _missing, so if you were using None to get the WSGIServer, switch to _missing.

4. server has some new methods: start_app_server, start_http_server, stop_http_server, stop_app_server.

5. There's also a new start_with_callback function, so you don't have to code the threading yourself if you want to start the server but run another task in a new thread.

6. test/helper.py doesn't have startServer/stopServer methods anymore. Just call server.start/stop instead.

Line 
1 """
2 Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
7
8     * Redistributions of source code must retain the above copyright notice,
9       this list of conditions and the following disclaimer.
10     * Redistributions in binary form must reproduce the above copyright notice,
11       this list of conditions and the following disclaimer in the documentation
12       and/or other materials provided with the distribution.
13     * Neither the name of the CherryPy Team nor the names of its contributors
14       may be used to endorse or promote products derived from this software
15       without specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 """
28
29 import threading, os, socket
30 import SocketServer, BaseHTTPServer, Queue
31 import cherrypy
32 from cherrypy import _cputil, _cphttptools
33
34
35 class CherryHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
36    
37     """CherryPy HTTP request handler with the following commands:
38         
39         o  GET
40         o  HEAD
41         o  POST
42         o  HOTRELOAD
43         
44     """
45    
46     def address_string(self):
47         """ Try to do a reverse DNS based on [server]reverseDNS in the config file """
48         if cherrypy.config.get('server.reverseDNS'):
49             return BaseHTTPServer.BaseHTTPRequestHandler.address_string(self)
50         else:
51             return self.client_address[0]
52    
53     def _headerlist(self):
54         list = []
55         hit = 0
56         for line in self.headers.headers:
57             if line:
58                 if line[0] in ' \t':
59                     # Continuation line. Add to previous entry.
60                     if list:
61                         list[-1][1] += " " + line.lstrip()
62                 else:
63                     # New header. Add a new entry. We don't catch
64                     # ValueError here because we trust rfc822.py.
65                     name, value = line.split(":", 1)
66                     list.append((name.strip(), value.strip()))
67         return list
68    
69     def handle_one_request(self):
70         """Handle a single HTTP request."""
71        
72         self.raw_requestline = self.rfile.readline()
73         if not self.raw_requestline:
74             self.close_connection = 1
75             return
76         if not self.parse_request(): # An error code has been sent, just exit
77             return
78        
79         cherrypy.request.purge__()
80         cherrypy.response.purge__()
81        
82         tp = cherrypy.config.get("server.threadPool")
83         cherrypy.request.multithread = (tp > 1)
84         cherrypy.request.multiprocess = False
85         cherrypy.server.request(self.client_address,
86                                 self.address_string(),
87                                 self.raw_requestline,
88                                 self._headerlist(),
89                                 self.rfile, "http")
90         wfile = self.wfile
91         wfile.write("%s %s\r\n" %
92                     (self.protocol_version, cherrypy.response.status))
93        
94         has_close_conn = False
95         for name, value in cherrypy.response.headers:
96             wfile.write("%s: %s\r\n" % (name, value))
97             if name.lower == 'connection' and value.lower == 'close':
98                 has_close_conn = True
99         if not has_close_conn:
100             # This server doesn't support persistent connections yet, so we
101             # must add a "Connection: close" header to tell the client that
102             # we will close the connection when we're done sending output.
103             #
104             # From RFC 2616 sec 14.10:
105             # HTTP/1.1 defines the "close" connection option for the sender
106             # to signal that the connection will be closed after completion
107             # of the response. For example,
108             #
109             #    Connection: close
110             #
111             # in either the request or the response header fields indicates
112             # that the connection SHOULD NOT be considered `persistent'
113             # (section 8.1) after the current request/response is complete.
114             #
115             # HTTP/1.1 applications that do not support persistent connections
116             # MUST include the "close" connection option in every message.
117             wfile.write("Connection: close\r\n")
118        
119         wfile.write("\r\n")
120         try:
121             for chunk in cherrypy.response.body:
122                 wfile.write(chunk)
123         except:
124             s, h, b = _cphttptools.bareError()
125             for chunk in b:
126                 wfile.write(chunk)
127        
128         if self.command == "POST":
129             self.connection = self.request
130        
131         # Close the conn, since we do not yet support persistent connections.
132         self.close_connection = 1
133    
134     def log_message(self, format, *args):
135         """ We have to override this to use our own logging mechanism """
136         cherrypy.log(format % args, "HTTP")
137
138
139 class CherryHTTPServer(SocketServer.TCPServer):
140     # Subclass TCPServer (instead of BaseHTTPServer.HTTPServer), because
141     # getfqdn call was timing out on localhost when calling gethostbyaddr.
142    
143     ready = False
144     interrupt = None
145    
146     allow_reuse_address = True
147    
148     def __init__(self):
149         # Set protocol_version
150         proto = cherrypy.config.get('server.protocolVersion') or "HTTP/1.0"
151         CherryHTTPRequestHandler.protocol_version = proto
152        
153         # Select the appropriate server based on config options
154         sockFile = cherrypy.config.get('server.socketFile')
155         if sockFile:
156             # AF_UNIX socket
157             self.address_family = socket.AF_UNIX
158            
159             # So we can reuse the socket
160             try: os.unlink(sockFile)
161             except: pass
162            
163             # So everyone can access the socket
164             try: os.chmod(sockFile, 0777)
165             except: pass
166            
167             server_address = sockFile
168         else:
169             # AF_INET socket
170             server_address = (cherrypy.config.get('server.socketHost'),
171                               cherrypy.config.get('server.socketPort'))
172        
173         self.request_queue_size = cherrypy.config.get('server.socketQueueSize')
174        
175         SocketServer.TCPServer.__init__(self, server_address, CherryHTTPRequestHandler)
176    
177     def server_activate(self):
178         """Override server_activate to set timeout on our listener socket"""
179         self.socket.settimeout(1)
180         SocketServer.TCPServer.server_activate(self)
181    
182     def get_request(self):
183         # With Python 2.3 it seems that an accept socket in timeout
184         # (nonblocking) mode results in request sockets that are also set
185         # in nonblocking mode. Since that doesn't play well with makefile()
186         # (where wfile and rfile are set in SocketServer.py) we explicitly
187         # set the request socket to blocking
188        
189         request, client_address = self.socket.accept()
190         request.setblocking(1)
191         return request, client_address
192    
193     def handle_request(self):
194         """Handle one request, possibly blocking."""
195         # Overridden to trap socket.timeout and KeyboardInterrupt/SystemExit.
196         try:
197             request, client_address = self.get_request()
198         except socket.error:
199             return
200         except socket.timeout:
201             # The only reason for the timeout is so we can notice keyboard
202             # interrupts on Win32, which don't interrupt accept() by default
203             return 1
204        
205         if self.verify_request(request, client_address):
206             try:
207                 self.process_request(request, client_address)
208             except (KeyboardInterrupt, SystemExit):
209                 self.close_request(request)
210                 raise
211             except:
212                 self.handle_error(request, client_address)
213                 self.close_request(request)
214    
215     def handle_error(self, request, client_address):
216         errorBody = _cputil.formatExc()
217         cherrypy.log(errorBody)
218    
219     def serve_forever(self):
220         """Override serve_forever to handle shutdown."""
221         self.ready = True
222         while self.ready:
223             self.handle_request()
224             if self.interrupt:
225                 raise self.interrupt
226     start = serve_forever
227    
228     def shutdown(self):
229         self.ready = False
230         # Close the socket
231         self.server_close()
232     stop = shutdown
233
234
235 _SHUTDOWNREQUEST = (0,0)
236
237 class ServerThread(threading.Thread):
238    
239     def __init__(self, RequestHandlerClass, requestQueue, server):
240         self.server = server
241         self.ready = False
242         threading.Thread.__init__(self)
243         self._RequestHandlerClass = RequestHandlerClass
244         self._requestQueue = requestQueue
245    
246     def run(self):
247         try:
248             self.ready = True
249             while 1:
250                 request, client_address = self._requestQueue.get()
251                 if (request, client_address) == _SHUTDOWNREQUEST:
252                     return
253                 if self.verify_request(request, client_address):
254                     try:
255                         self.process_request(request, client_address)
256                     except (KeyboardInterrupt, SystemExit):
257                         self.close_request(request)
258                         raise
259                     except:
260                         self.handle_error(request, client_address)
261                         self.close_request(request)
262                 else:
263                     self.close_request(request)
264         except (KeyboardInterrupt, SystemExit), exc:
265             self.server.interrupt = exc
266    
267     def verify_request(self, request, client_address):
268         """ Verify the request.  May be overridden.
269             Return 1 if we should proceed with this request. """
270         return 1
271    
272     def process_request(self, request, client_address):
273         self._RequestHandlerClass(request, client_address, self)
274         self.close_request(request)
275    
276     def close_request(self, request):
277         """ Called to clean up an individual request. """
278         request.close()
279    
280     def handle_error(self, request, client_address):
281         """Handle an error gracefully.  May be overridden."""
282         errorBody = _cputil.formatExc()
283         cherrypy.log(errorBody)
284
285
286 class PooledThreadServer(SocketServer.TCPServer):
287     """A TCP Server using a pool of worker threads. This is superior to the
288        alternatives provided by the Python standard library, which only offer
289        (1) handling a single request at a time, (2) handling each request in
290        a separate thread (via ThreadingMixIn), or (3) handling each request in
291        a separate process (via ForkingMixIn). It's also superior in some ways
292        to the pure async approach used by Twisted because it allows a more
293        straightforward and simple programming model in the face of blocking
294        requests (i.e. you don't have to bother with Deferreds)."""
295    
296     allow_reuse_address = 1
297     ready = False
298     interrupt = None
299    
300     def __init__(self):
301         # Set protocol_version
302         proto = cherrypy.config.get('server.protocolVersion') or "HTTP/1.0"
303         CherryHTTPRequestHandler.protocol_version = proto
304        
305         # Select the appropriate server based on config options
306         threadPool = cherrypy.config.get('server.threadPool')
307         server_address = (cherrypy.config.get('server.socketHost'),
308                           cherrypy.config.get('server.socketPort'))
309         self.request_queue_size = cherrypy.config.get('server.socketQueueSize')
310        
311         # I know it says "do not override",
312         # but I have to in order to implement SSL support !
313         SocketServer.BaseServer.__init__(self, server_address, CherryHTTPRequestHandler)
314         self.socket = socket.socket(self.address_family, self.socket_type)
315         self.server_bind()
316         self.server_activate()
317        
318         self._numThreads = threadPool
319         self._RequestHandlerClass = CherryHTTPRequestHandler
320         self._ThreadClass = ServerThread
321         self._requestQueue = Queue.Queue()
322         self._workerThreads = []
323    
324     def createThread(self):
325         return self._ThreadClass(self._RequestHandlerClass, self._requestQueue, self)
326    
327     def server_activate(self):
328         """Override server_activate to set timeout on our listener socket"""
329         self.socket.settimeout(1)
330         SocketServer.TCPServer.server_activate(self)
331    
332     def shutdown(self):
333         """Gracefully shutdown a server that is serve_forever()ing."""
334         self.ready = False
335         # Close the socket so restarts work.
336         self.server_close()
337        
338         # Must shut down threads here so the code that calls
339         # this method can know when all threads are stopped.
340         for worker in self._workerThreads:
341             self._requestQueue.put(_SHUTDOWNREQUEST)
342         current = threading.currentThread()
343         for worker in self._workerThreads:
344             if worker is not current and worker.isAlive:
345                 worker.join()
346         self._workerThreads = []
347     stop = shutdown
348    
349     def serve_forever(self):
350         """Handle one request at a time until doomsday (or shutdown is called)."""
351         if self._workerThreads == []:
352             for i in xrange(self._numThreads):
353                 self._workerThreads.append(self.createThread())
354             for worker in self._workerThreads:
355                 worker.start()
356        
357         for worker in self._workerThreads:
358             while not worker.ready:
359                 time.sleep(.1)
360        
361         self.ready = True
362         while self.ready:
363             if self.interrupt:
364                 raise self.interrupt
365             if not self.handle_request():
366                 break
367         self.server_close()
368     start = serve_forever
369    
370     def handle_request(self):
371         """Override handle_request to enqueue requests rather than handle
372            them synchronously. Return 1 by default, 0 to shutdown the
373            server."""
374         try:
375             request, client_address = self.get_request()
376         except socket.error, e:
377             return 1
378         self._requestQueue.put((request, client_address))
379         return 1
380    
381     def get_request(self):
382         # With Python 2.3 it seems that an accept socket in timeout
383         # (nonblocking) mode results in request sockets that are also set
384         # in nonblocking mode. Since that doesn't play well with makefile()
385         # (where wfile and rfile are set in SocketServer.py) we explicitly
386         # set the request socket to blocking
387        
388         request, client_address = self.socket.accept()
389         if hasattr(request,'setblocking'):
390             request.setblocking(1)
391         return request, client_address
392
393
394 def embedded_server(handler=None):
395     """Selects and instantiates the appropriate server."""
396    
397     # Select the appropriate server based on config options
398     sockFile = cherrypy.config.get('server.socketFile')
399     threadPool = cherrypy.config.get('server.threadPool')
400     if threadPool > 1 and not sockFile:
401         ServerClass = PooledThreadServer
402     else:
403         ServerClass = CherryHTTPServer
404     return ServerClass()
405
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets