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

root/tags/cherrypy-2.0.0/cherrypy/_cphttpserver.py

Revision 132 (checked in by rdelon, 3 years ago)

pass threadIndex to onStartThreadList functions - ticket #91

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 cpg, sys, threading, SocketServer, _cphttptools
30 import BaseHTTPServer, socket, Queue, _cputil
31
32 def stop():
33     cpg._server.shutdown()
34
35 def start():
36     """ Prepare the HTTP server and then run it """
37
38     # If sessions are stored in files and we
39     # use threading, we need a lock on the file
40     if (cpg.configOption.threadPool > 1) and \
41             cpg.configOption.sessionStorageType == 'file':
42         cpg._sessionFileLock = threading.RLock()
43
44
45     if cpg.configOption.socketFile:
46         # AF_UNIX socket
47         # TODO: Handle threading here
48         class MyCherryHTTPServer(CherryHTTPServer): address_family = socket.AF_UNIX
49     else:
50         # AF_INET socket
51         if cpg.configOption.threadPool > 1:
52             MyCherryHTTPServer = PooledThreadServer
53         else:
54             MyCherryHTTPServer = CherryHTTPServer
55
56     MyCherryHTTPServer.request_queue_size = cpg.configOption.socketQueueSize
57
58     # Set protocol_version
59     CherryHTTPRequestHandler.protocol_version = cpg.configOption.protocolVersion
60
61     run_server(CherryHTTPRequestHandler, MyCherryHTTPServer, \
62         (cpg.configOption.socketHost, cpg.configOption.socketPort), \
63         cpg.configOption.socketFile)
64
65 def run_server(HandlerClass, ServerClass, server_address, socketFile):
66     """Run the HTTP request handler class."""
67
68     if cpg.configOption.socketFile:
69         try: os.unlink(cpg.configOption.socketFile) # So we can reuse the socket
70         except: pass
71         server_address = cpg.configOption.socketFile
72     if cpg.configOption.threadPool > 1:
73         myCherryHTTPServer = ServerClass(server_address, cpg.configOption.threadPool, HandlerClass)
74     else:
75         myCherryHTTPServer = ServerClass(server_address, HandlerClass)
76     cpg._server = myCherryHTTPServer
77     if cpg.configOption.socketFile:
78         try: os.chmod(socketFile, 0777) # So everyone can access the socket
79         except: pass
80     global _cpLogMessage
81     _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
82
83     servingWhat = "HTTP"
84     if cpg.configOption.socketPort: onWhat = "socket: ('%s', %s)" % (cpg.configOption.socketHost, cpg.configOption.socketPort)
85     else: onWhat = "socket file: %s" % cpg.configOption.socketFile
86     _cpLogMessage("Serving %s on %s" % (servingWhat, onWhat), 'HTTP')
87
88     try:
89         # Call the functions from cpg.server.onStartServerList
90         for func in cpg.server.onStartServerList:
91             func()
92         myCherryHTTPServer.serve_forever()
93     except KeyboardInterrupt:
94         _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
95         myCherryHTTPServer.server_close()
96     # Call the functions from cpg.server.onStartServerList
97     for func in cpg.server.onStopServerList:
98         func()
99
100 class CherryHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
101
102     """CherryPy HTTP request handler with the following commands:
103
104         o  GET
105         o  HEAD
106         o  POST
107         o  HOTRELOAD
108
109     """
110
111     def address_string(self):
112         """ Try to do a reverse DNS based on [server]reverseDNS in the config file """
113         if cpg.configOption.reverseDNS:
114             return BaseHTTPServer.BaseHTTPRequestHandler.address_string(self)
115         else:
116             return self.client_address[0]
117
118     def do_GET(self):
119         """Serve a GET request."""
120         cpg.request.method = 'GET'
121         _cphttptools.doRequest(
122             self.client_address[0],
123             self.address_string(),
124             self.raw_requestline,
125             self.headers,
126             self.rfile,
127             self.wfile
128         )
129
130     def do_HEAD(self): # Head is not implemented
131         """Serve a HEAD request."""
132         cpg.request.method = 'HEAD'
133         _cphttptools.doRequest(
134             self.client_address[0],
135             self.address_string(),
136             self.raw_requestline,
137             self.headers,
138             self.rfile,
139             self.wfile
140         )
141
142     def do_POST(self):
143         """Serve a POST request."""
144         cpg.request.method = 'POST'
145         _cphttptools.doRequest(
146             self.client_address[0],
147             self.address_string(),
148             self.raw_requestline,
149             self.headers,
150             self.rfile,
151             self.wfile
152         )
153
154         self.connection = self.request
155
156     def log_message(self, format, *args):
157         """ We have to override this to use our own logging mechanism """
158         _cputil.getSpecialFunction('_cpLogMessage')(format % args, "HTTP")
159
160
161 class CherryHTTPServer(BaseHTTPServer.HTTPServer):
162     def server_activate(self):
163         """Override server_activate to set timeout on our listener socket"""
164         self.socket.settimeout(1)
165         BaseHTTPServer.HTTPServer.server_activate(self)
166
167     def server_bind(self):
168         # Removed getfqdn call because it was timing out on localhost when calling gethostbyaddr
169         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
170         self.socket.bind(self.server_address)
171
172     def get_request(self):
173         # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode
174         #  results in request sockets that are also set in nonblocking mode. Since that doesn't play
175         #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set
176         #  the request socket to blocking
177
178         request, client_address = self.socket.accept()
179         request.setblocking(1)
180         return request, client_address
181
182     def handle_request(self):
183         """Override handle_request to trap timeout exception."""
184         try:
185             BaseHTTPServer.HTTPServer.handle_request(self)
186         except socket.timeout:
187             # The only reason for the timeout is so we can notice keyboard
188             # interrupts on Win32, which don't interrupt accept() by default
189             return 1
190         except KeyboardInterrupt:
191             _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
192             self.shutdown()
193
194     def serve_forever(self):
195         """Override serve_forever to handle shutdown."""
196         self.__running = 1
197         while self.__running:
198             self.handle_request()
199
200     def shutdown(self):
201         self.__running = 0
202
203 _SHUTDOWNREQUEST = (0,0)
204
205 class ServerThread(threading.Thread):
206     def __init__(self, RequestHandlerClass, requestQueue, threadIndex):
207         threading.Thread.__init__(self)
208         self._RequestHandlerClass = RequestHandlerClass
209         self._requestQueue = requestQueue
210         self._threadIndex = threadIndex
211        
212     def run(self):
213         # Call the functions from cpg.server.onStartThreadList
214         for func in cpg.server.onStartThreadList:
215             func(self._threadIndex)
216         while 1:
217             request, client_address = self._requestQueue.get()
218             if (request, client_address) == _SHUTDOWNREQUEST:
219                 # Call the functions from cpg.server.onStopThreadList
220                 for func in cpg.server.onStopThreadList:
221                     func()
222                 return
223             if self.verify_request(request, client_address):           
224                 try:
225                     self.process_request(request, client_address)
226                 except:
227                     self.handle_error(request, client_address)
228                     self.close_request(request)
229             else:
230                 self.close_request(request)
231
232     def verify_request(self, request, client_address):
233         """ Verify the request.  May be overridden.
234             Return 1 if we should proceed with this request. """
235         return 1
236
237     def process_request(self, request, client_address):
238         self._RequestHandlerClass(request, client_address, self)       
239         self.close_request(request)
240
241     def close_request(self, request):
242         """ Called to clean up an individual request. """
243         request.close()
244
245     def handle_error(self, request, client_address):
246         """ Handle an error gracefully.  May be overridden.
247             The default is to print a traceback and continue.
248         """
249         import traceback, StringIO
250         bodyFile=StringIO.StringIO()
251         traceback.print_exc(file=bodyFile)
252         errorBody=bodyFile.getvalue()
253         bodyFile.close()
254         _cputil.getSpecialFunction('_cpLogMessage')(errorBody)
255        
256
257 class PooledThreadServer(SocketServer.TCPServer):
258
259     allow_reuse_address = 1
260
261     """A TCP Server using a pool of worker threads. This is superior to the
262        alternatives provided by the Python standard library, which only offer
263        (1) handling a single request at a time, (2) handling each request in
264        a separate thread (via ThreadingMixIn), or (3) handling each request in
265        a separate process (via ForkingMixIn). It's also superior in some ways
266        to the pure async approach used by Twisted because it allows a more
267        straightforward and simple programming model in the face of blocking
268        requests (i.e. you don't have to bother with Deferreds)."""
269     def __init__(self, serverAddress, numThreads, RequestHandlerClass, ThreadClass=ServerThread):
270         assert(numThreads > 0)
271         # I know it says "do not override", but I have to in order to implement SSL support !
272         SocketServer.BaseServer.__init__(self, serverAddress, RequestHandlerClass)
273         self.socket=socket.socket(self.address_family, self.socket_type)
274         self.server_bind()
275         self.server_activate()
276
277         self._numThreads = numThreads       
278         self._RequestHandlerClass = RequestHandlerClass
279         self._ThreadClass = ThreadClass
280         self._requestQueue = Queue.Queue()
281         self._workerThreads = []
282
283     def createThread(self, threadIndex):
284         return self._ThreadClass(self._RequestHandlerClass, self._requestQueue, threadIndex)
285            
286     def start(self):
287         if self._workerThreads != []:
288             return
289         for i in xrange(self._numThreads):
290             self._workerThreads.append(self.createThread(i))       
291         for worker in self._workerThreads:
292             worker.start()
293            
294     def server_close(self):
295         """Override server_close to shutdown thread pool"""
296         SocketServer.TCPServer.server_close(self)
297         for worker in self._workerThreads:
298             self._requestQueue.put(_SHUTDOWNREQUEST)
299         for worker in self._workerThreads:
300             worker.join()
301         self._workerThreads = []
302
303     def server_activate(self):
304         """Override server_activate to set timeout on our listener socket"""
305         self.socket.settimeout(1)
306         SocketServer.TCPServer.server_activate(self)
307
308     def server_bind(self):
309         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
310         self.socket.bind(self.server_address)
311
312     def shutdown(self):
313         """Gracefully shutdown a server that is serve_forever()ing."""
314         self.__running = 0
315
316     def serve_forever(self):
317         """Handle one request at a time until doomsday (or shutdown is called)."""
318         if self._workerThreads == []:
319             self.start()
320         self.__running = 1
321         while self.__running:
322             if not self.handle_request():
323                 break
324         self.server_close()           
325        
326     def handle_request(self):
327         """Override handle_request to enqueue requests rather than handle
328            them synchronously. Return 1 by default, 0 to shutdown the
329            server."""
330         try:
331             request, client_address = self.get_request()
332         except KeyboardInterrupt:
333             _cpLogMessage("<Ctrl-C> hit: shutting down", "HTTP")
334             return 0
335         except socket.error, e:
336             return 1
337         self._requestQueue.put((request, client_address))
338         return 1
339
340     def get_request(self):
341         # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode
342         #  results in request sockets that are also set in nonblocking mode. Since that doesn't play
343         #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set
344         #  the request socket to blocking
345
346         request, client_address = self.socket.accept()
347         if hasattr(request,'setblocking'):
348             request.setblocking(1)
349         return request, client_address
350
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets