Mercurial > traipse_dev
comparison plugins/cherrypy/_cphttpserver.py @ 0:4385a7d0efd1 grumpy-goblin
Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author | sirebral |
---|---|
date | Tue, 14 Jul 2009 16:41:58 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4385a7d0efd1 |
---|---|
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 |