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

root/tags/cherrypy-2.1.0-rc1/cherrypy/_cpwsgiserver.py

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

Preparing for 2.1.0-rc1 release

Line 
1 """
2 Copyright (c) 2005, 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 """
30 A high-speed, production ready, thread pooled, generic WSGI server.
31 """
32
33 import socket
34 import threading
35 import Queue
36 import mimetools # todo: use email
37 import sys
38 import time
39 import traceback
40
41 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
42 monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
43                    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
44
45 class MaxSizeExceeded(Exception):
46     pass
47
48 class SizeCheckWrapper(object):
49     """ Wrapper around the rfile object. For each data reading method,
50         it reads the data but it checks that the size of the data doesn't
51         exceed a certain limit
52     """
53     def __init__(self, rfile, maxlen):
54         self.rfile = rfile
55         self.maxlen = maxlen
56         self.bytes_read = 0
57     def _check_length(self):
58         if self.maxlen and self.bytes_read > self.maxlen:
59             raise MaxSizeExceeded()
60     def read(self, size = None):
61         data = self.rfile.read(size)
62         self.bytes_read += len(data)
63         self._check_length()
64         return data
65     def readline(self, size = None):
66         if size is not None:
67             data = self.rfile.readline(size)
68             self.bytes_read += len(data)
69             self._check_length()
70             return data
71
72         # User didn't specify a size ...
73         # We read the line in chunks to make sure it's not a 100MB line !
74         res = []
75         while True:
76             data = self.rfile.readline(256)
77             self.bytes_read += len(data)
78             self._check_length()
79             res.append(data)
80             if len(data) < 256:
81                 return ''.join(res)
82     def close(self):
83         self.rfile.close()
84
85     def __iter__(self):
86         return self.rfile
87
88     def next(self):
89         data = self.rfile.next()
90         self.bytes_read += len(data)
91         self._check_length()
92 ##      Normally the next method must raise StopIteration when it
93 ##      fails but CP expects MaxSizeExceeded
94 ##        try:
95 ##            self._check_length()
96 ##        except:
97 ##            raise StopIteration()
98         return data
99
100 class HTTPRequest(object):
101     def __init__(self, socket, addr, server):
102         self.socket = socket
103         self.addr = addr
104         self.server = server
105         self.environ = None
106         self.ready = False
107         self.started_response = False
108         self.status = None
109         self.outheaders = None
110         self.outheaderkeys = None
111         self.rfile = self.socket.makefile("r", self.server.bufsize)
112         if self.server.config:
113             mhs = self.server.config.get(
114                 'server.maxRequestHeaderSize',
115                 500 * 1024) # 500KB by default
116             self.rfile = SizeCheckWrapper(self.rfile, mhs)
117         self.wfile = self.socket.makefile("w", self.server.bufsize)
118         self.sent_headers = False
119     def parse_request(self):
120         self.sent_headers = False
121         self.environ = {}
122         self.environ["wsgi.version"] = (1,0)
123         self.environ["wsgi.url_scheme"] = "http"
124         self.environ["wsgi.input"] = self.rfile
125         self.environ["wsgi.errors"] = self.server.stderr
126         self.environ["wsgi.multithread"] = True
127         self.environ["wsgi.multiprocess"] = False
128         self.environ["wsgi.run_once"] = False
129         request_line = self.rfile.readline()
130         if not request_line:
131             self.ready = False
132             return
133         method,path,version = request_line.strip().split(" ", 2)
134         if "?" in path:
135             path, qs = path.split("?", 1)
136         else:
137             qs = ""
138         self.environ["REQUEST_METHOD"] = method
139         if path == "*":
140             self.environ["SCRIPT_NAME"] = path
141         else:
142             self.environ["SCRIPT_NAME"] = path[1:]
143         self.environ["PATH_INFO"] = ""
144         self.environ["QUERY_STRING"] = qs
145         self.environ["SERVER_PROTOCOL"] = version
146         self.environ["SERVER_NAME"] = self.server.server_name
147         self.environ["SERVER_PORT"] = str(self.server.bind_addr[1])
148         # optional values
149         self.environ["REMOTE_HOST"] = self.addr[0]
150         self.environ["REMOTE_ADDR"] = self.addr[0]
151         self.environ["REMOTE_PORT"] = str(self.addr[1])
152         # then all the http headers
153         headers = mimetools.Message(self.rfile)
154         self.environ["CONTENT_TYPE"] = headers.getheader("Content-type", "")
155         self.environ["CONTENT_LENGTH"] = headers.getheader("Content-length", "")
156         for (k, v) in headers.items():
157             envname = "HTTP_" + k.upper().replace("-","_")
158             self.environ[envname] = v
159         self.ready = True
160
161         # Request header is parsed
162         # We prepare the SizeCheckWrapper for the request body
163         if self.server.config:
164             mbs = self.server.config.get(
165                 'server.maxRequestBodySize',
166                 100 * 1024 * 1024, # 100MB by default
167                 path = path)
168             self.rfile.bytes_read = 0
169             self.rfile.maxlen = mbs
170    
171     def start_response(self, status, headers, exc_info = None):
172         if self.started_response:
173             if not exc_info:
174                 assert False, "Already started response"
175             else:
176                 try:
177                     raise exc_info[0], exc_info[1], exc_info[2]
178                 finally:
179                     exc_info = None
180         self.started_response = True
181         self.status = status
182         self.outheaders = headers
183         self.outheaderkeys = [key.lower() for (key,value) in self.outheaders]
184         return self.write
185    
186     def write(self, d):
187         if not self.sent_headers:
188             self.sent_headers = True
189             self.send_headers()
190         self.wfile.write(d)
191         self.wfile.flush()
192    
193     def send_headers(self):
194         if "content-length" not in self.outheaderkeys:
195             self.close_at_end = True
196         if "date" not in self.outheaderkeys:
197             # HTTP 1.1 mandates date output in RFC 1123 format.
198             year, month, day, hh, mm, ss, wd, y, z = time.gmtime()
199             dt = ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %
200                   (weekdayname[wd], day, monthname[month], year, hh, mm, ss))
201             self.outheaders.append(("Date", dt))
202         if "server" not in self.outheaderkeys:
203             self.outheaders.append(("Server", self.server.version))
204         if (self.environ["SERVER_PROTOCOL"] == "HTTP/1.1"
205             and "connection" not in self.outheaderkeys):
206             self.outheaders.append(("Connection", "close"))
207         self.wfile.write(self.environ["SERVER_PROTOCOL"] + " " + self.status + "\r\n")
208         for (k,v) in self.outheaders:
209             self.wfile.write(k + ": " + v + "\r\n")
210         self.wfile.write("\r\n")
211         self.wfile.flush()
212    
213     def terminate(self):
214         if self.ready and not self.sent_headers:
215             self.sent_headers = True
216             self.send_headers()
217         self.rfile.close()
218         self.wfile.close()
219         self.socket.close()
220
221
222 _SHUTDOWNREQUEST = None
223
224 class WorkerThread(threading.Thread):
225    
226     def __init__(self, server):
227         self.server = server
228         threading.Thread.__init__(self)
229    
230     def run(self):
231         while self.server._running:
232             request = self.server.requests.get()
233             if request is _SHUTDOWNREQUEST:
234                 return
235            
236             try:
237                 try:
238                     request.parse_request()
239                     if request.ready:
240                         response = self.server.wsgi_app(request.environ,
241                                                         request.start_response)
242                         for line in response:
243                             request.write(line)
244                 except socket.error, e:
245                     errno = e.args[0]
246                     if errno in (32, 104, 10053, 10054):
247                         # Client probably closed the connection before the
248                         # response was sent.
249                         pass
250                     else:
251                         raise
252                 except MaxSizeExceeded:
253                     str = "Request Entity Too Large"
254                     proto = request.environ.get("SERVER_PROTOCOL", "HTTP/1.0")
255                     request.wfile.write("%s 413 %s\r\n" % (proto, str))
256                     request.wfile.write("Content-Length: %s\r\n\r\n" % len(str))
257                     request.wfile.write(str)
258                     request.wfile.flush()
259                 except:
260                     traceback.print_exc()
261             finally:
262                 request.terminate()
263
264
265 class CherryPyWSGIServer(object):
266     version = "CherryPy/2.1.0-rc1"
267     def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
268                  stderr=sys.stderr, bufsize=-1, max=-1,
269                  config = None):
270         '''
271         be careful w/ max
272         '''
273         self.requests = Queue.Queue(max)
274         self.wsgi_app = wsgi_app
275         self.bind_addr = bind_addr
276         self.numthreads = numthreads or 1
277         self.config = config
278         if server_name:
279             self.server_name = server_name
280         else:
281             self.server_name = socket.gethostname()
282         self.stderr = stderr
283         self.bufsize = bufsize
284         self._workerThreads = []
285    
286     def start(self):
287         '''
288         run the server forever
289         '''
290         # We don't have to trap KeyboardInterrupt or SystemExit here,
291         # because cherrpy.server already does so, calling self.stop() for us.
292         # If you're using this server with another framework, you should
293         # trap those exceptions in whatever code block calls start().
294         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
295         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
296         self.socket.bind(self.bind_addr)
297         self.socket.settimeout(1)
298         self.socket.listen(5)
299        
300         self._running = True
301        
302         # Create worker threads
303         for i in xrange(self.numthreads):
304             self._workerThreads.append(WorkerThread(self))
305         for worker in self._workerThreads:
306             worker.start()
307        
308         while self._running:
309             self.tick()
310    
311     def stop(self):
312         """Gracefully shutdown a server that is serving forever."""
313         self._running = False
314        
315         # Must shut down threads here so the code that calls
316         # this method can know when all threads are stopped.
317         for worker in self._workerThreads:
318             self.requests.put(_SHUTDOWNREQUEST)
319        
320         current = threading.currentThread()
321         for worker in self._workerThreads:
322             if worker is not current:
323                 worker.join()
324         self._workerThreads = []
325    
326     def tick(self):
327         try:
328             s, addr = self.socket.accept()
329             if hasattr(s, 'setblocking'):
330                 s.setblocking(1)
331             request = HTTPRequest(s, addr, self)
332             self.requests.put(request)
333             # optimized version follows
334             #self.requests.put(HTTPRequest(*self.socket.accept()))
335         except socket.timeout:
336             # The only reason for the timeout in start() is so we can
337             # notice keyboard interrupts on Win32, which don't interrupt
338             # accept() by default
339             return
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets