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

root/trunk/cherrypy/wsgiserver/__init__.py

Revision 2688 (checked in by fumanchu, 4 weeks ago)

First draft of wsgiserver docs.

  • Property svn:eol-style set to native
Line 
1 """A high-speed, production ready, thread pooled, generic HTTP server.
2
3 Simplest example on how to use this module directly
4 (without using CherryPy's application machinery)::
5
6     from cherrypy import wsgiserver
7     
8     def my_crazy_app(environ, start_response):
9         status = '200 OK'
10         response_headers = [('Content-type','text/plain')]
11         start_response(status, response_headers)
12         return ['Hello world!']
13     
14     server = wsgiserver.CherryPyWSGIServer(
15                 ('0.0.0.0', 8070), my_crazy_app,
16                 server_name='www.cherrypy.example')
17
18 The CherryPy WSGI server can serve as many WSGI applications
19 as you want in one instance by using a WSGIPathInfoDispatcher::
20     
21     d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
22     server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
23
24 Want SSL support? Just set server.ssl_adapter to an SSLAdapter instance.
25
26 This won't call the CherryPy engine (application side) at all, only the
27 HTTP server, which is independent from the rest of CherryPy. Don't
28 let the name "CherryPyWSGIServer" throw you; the name merely reflects
29 its origin, not its coupling.
30
31 For those of you wanting to understand internals of this module, here's the
32 basic call flow. The server's listening thread runs a very tight loop,
33 sticking incoming connections onto a Queue::
34
35     server = CherryPyWSGIServer(...)
36     server.start()
37     while True:
38         tick()
39         # This blocks until a request comes in:
40         child = socket.accept()
41         conn = HTTPConnection(child, ...)
42         server.requests.put(conn)
43
44 Worker threads are kept in a pool and poll the Queue, popping off and then
45 handling each connection in turn. Each connection can consist of an arbitrary
46 number of requests and their responses, so we run a nested loop::
47
48     while True:
49         conn = server.requests.get()
50         conn.communicate()
51         ->  while True:
52                 req = HTTPRequest(...)
53                 req.parse_request()
54                 ->  # Read the Request-Line, e.g. "GET /page HTTP/1.1"
55                     req.rfile.readline()
56                     read_headers(req.rfile, req.inheaders)
57                 req.respond()
58                 ->  response = app(...)
59                     try:
60                         for chunk in response:
61                             if chunk:
62                                 req.write(chunk)
63                     finally:
64                         if hasattr(response, "close"):
65                             response.close()
66                 if req.close_connection:
67                     return
68 """
69
70 CRLF = '\r\n'
71 import os
72 import Queue
73 import re
74 quoted_slash = re.compile("(?i)%2F")
75 import rfc822
76 import socket
77 import sys
78 if 'win' in sys.platform and not hasattr(socket, 'IPPROTO_IPV6'):
79     socket.IPPROTO_IPV6 = 41
80 try:
81     import cStringIO as StringIO
82 except ImportError:
83     import StringIO
84
85 _fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
86
87 import threading
88 import time
89 import traceback
90 from urllib import unquote
91 from urlparse import urlparse
92 import warnings
93
94 import errno
95
96 def plat_specific_errors(*errnames):
97     """Return error numbers for all errors in errnames on this platform.
98     
99     The 'errno' module contains different global constants depending on
100     the specific platform (OS). This function will return the list of
101     numeric values for a given list of potential names.
102     """
103     errno_names = dir(errno)
104     nums = [getattr(errno, k) for k in errnames if k in errno_names]
105     # de-dupe the list
106     return dict.fromkeys(nums).keys()
107
108 socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
109
110 socket_errors_to_ignore = plat_specific_errors(
111     "EPIPE",
112     "EBADF", "WSAEBADF",
113     "ENOTSOCK", "WSAENOTSOCK",
114     "ETIMEDOUT", "WSAETIMEDOUT",
115     "ECONNREFUSED", "WSAECONNREFUSED",
116     "ECONNRESET", "WSAECONNRESET",
117     "ECONNABORTED", "WSAECONNABORTED",
118     "ENETRESET", "WSAENETRESET",
119     "EHOSTDOWN", "EHOSTUNREACH",
120     )
121 socket_errors_to_ignore.append("timed out")
122 socket_errors_to_ignore.append("The read operation timed out")
123
124 socket_errors_nonblocking = plat_specific_errors(
125     'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
126
127 comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding',
128     'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control',
129     'Connection', 'Content-Encoding', 'Content-Language', 'Expect',
130     'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE',
131     'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning',
132     'WWW-Authenticate']
133
134
135 def read_headers(rfile, hdict=None):
136     """Read headers from the given stream into the given header dict.
137     
138     If hdict is None, a new header dict is created. Returns the populated
139     header dict.
140     
141     Headers which are repeated are folded together using a comma if their
142     specification so dictates.
143     
144     This function raises ValueError when the read bytes violate the HTTP spec.
145     You should probably return "400 Bad Request" if this happens.
146     """
147     if hdict is None:
148         hdict = {}
149    
150     while True:
151         line = rfile.readline()
152         if not line:
153             # No more data--illegal end of headers
154             raise ValueError("Illegal end of headers.")
155        
156         if line == CRLF:
157             # Normal end of headers
158             break
159         if not line.endswith(CRLF):
160             raise ValueError("HTTP requires CRLF terminators")
161        
162         if line[0] in ' \t':
163             # It's a continuation line.
164             v = line.strip()
165         else:
166             try:
167                 k, v = line.split(":", 1)
168             except ValueError:
169                 raise ValueError("Illegal header line.")
170             # TODO: what about TE and WWW-Authenticate?
171             k = k.strip().title()
172             v = v.strip()
173             hname = k
174        
175         if k in comma_separated_headers:
176             existing = hdict.get(hname)
177             if existing:
178                 v = ", ".join((existing, v))
179         hdict[hname] = v
180    
181     return hdict
182
183
184 class MaxSizeExceeded(Exception):
185     pass
186
187 class SizeCheckWrapper(object):
188     """Wraps a file-like object, raising MaxSizeExceeded if too large."""
189    
190     def __init__(self, rfile, maxlen):
191         self.rfile = rfile
192         self.maxlen = maxlen
193         self.bytes_read = 0
194    
195     def _check_length(self):
196         if self.maxlen and self.bytes_read > self.maxlen:
197             raise MaxSizeExceeded()
198    
199     def read(self, size=None):
200         data = self.rfile.read(size)
201         self.bytes_read += len(data)
202         self._check_length()
203         return data
204    
205     def readline(self, size=None):
206         if size is not None:
207             data = self.rfile.readline(size)
208             self.bytes_read += len(data)
209             self._check_length()
210             return data
211        
212         # User didn't specify a size ...
213         # We read the line in chunks to make sure it's not a 100MB line !
214         res = []
215         while True:
216             data = self.rfile.readline(256)
217             self.bytes_read += len(data)
218             self._check_length()
219             res.append(data)
220             # See http://www.cherrypy.org/ticket/421
221             if len(data) < 256 or data[-1:] == "\n":
222                 return ''.join(res)
223    
224     def readlines(self, sizehint=0):
225         # Shamelessly stolen from StringIO
226         total = 0
227         lines = []
228         line = self.readline()
229         while line:
230             lines.append(line)
231             total += len(line)
232             if 0 < sizehint <= total:
233                 break
234             line = self.readline()
235         return lines
236    
237     def close(self):
238         self.rfile.close()
239    
240     def __iter__(self):
241         return self
242    
243     def next(self):
244         data = self.rfile.next()
245         self.bytes_read += len(data)
246         self._check_length()
247         return data
248
249
250 class KnownLengthRFile(object):
251     """Wraps a file-like object, returning an empty string when exhausted."""
252    
253     def __init__(self, rfile, content_length):
254         self.rfile = rfile
255         self.remaining = content_length
256    
257     def read(self, size=None):
258         if self.remaining == 0:
259             return ''
260         if size is None:
261             size = self.remaining
262         else:
263             size = min(size, self.remaining)
264        
265         data = self.rfile.read(size)
266         self.remaining -= len(data)
267         return data
268    
269     def readline(self, size=None):
270         if self.remaining == 0:
271             return ''
272         if size is None:
273             size = self.remaining
274         else:
275             size = min(size, self.remaining)
276        
277         data = self.rfile.readline(size)
278         self.remaining -= len(data)
279         return data
280    
281     def readlines(self, sizehint=0):
282         # Shamelessly stolen from StringIO
283         total = 0
284         lines = []
285         line = self.readline(sizehint)
286         while line:
287             lines.append(line)
288             total += len(line)
289             if 0 < sizehint <= total:
290                 break
291             line = self.readline(sizehint)
292         return lines
293    
294     def close(self):
295         self.rfile.close()
296    
297     def __iter__(self):
298         return self
299    
300     def __next__(self):
301         data = next(self.rfile)
302         self.remaining -= len(data)
303         return data
304
305
306 class ChunkedRFile(object):
307     """Wraps a file-like object, returning an empty string when exhausted.
308     
309     This class is intended to provide a conforming wsgi.input value for
310     request entities that have been encoded with the 'chunked' transfer
311     encoding.
312     """
313    
314     def __init__(self, rfile, maxlen, bufsize=8192):
315         self.rfile = rfile
316         self.maxlen = maxlen
317         self.bytes_read = 0
318         self.buffer = ''
319         self.bufsize = bufsize
320         self.closed = False
321    
322     def _fetch(self):
323         if self.closed:
324             return
325        
326         line = self.rfile.readline()
327         self.bytes_read += len(line)
328        
329         if self.maxlen and self.bytes_read > self.maxlen:
330             raise MaxSizeExceeded("Request Entity Too Large", self.maxlen)
331        
332         line = line.strip().split(";", 1)
333        
334         try:
335             chunk_size = line.pop(0)
336             chunk_size = int(chunk_size, 16)
337         except ValueError:
338             raise ValueError("Bad chunked transfer size: " + repr(chunk_size))
339        
340         if chunk_size <= 0:
341             self.closed = True
342             return
343        
344 ##            if line: chunk_extension = line[0]
345        
346         if self.maxlen and self.bytes_read + chunk_size > self.maxlen:
347             raise IOError("Request Entity Too Large")
348        
349         chunk = self.rfile.read(chunk_size)
350         self.bytes_read += len(chunk)
351         self.buffer += chunk
352        
353         crlf = self.rfile.read(2)
354         if crlf != CRLF:
355             raise ValueError(
356                  "Bad chunked transfer coding (expected '\\r\\n', "
357                  "got " + repr(crlf) + ")")
358    
359     def read(self, size=None):
360         data = ''
361         while True:
362             if size and len(data) >= size:
363                 return data
364            
365             if not self.buffer:
366                 self._fetch()
367                 if not self.buffer:
368                     # EOF
369                     return data
370            
371             if size:
372                 remaining = size - len(data)
373                 data += self.buffer[:remaining]
374                 self.buffer = self.buffer[remaining:]
375             else:
376                 data += self.buffer
377    
378     def readline(self, size=None):
379         data = ''
380         while True:
381             if size and len(data) >= size:
382                 return data
383            
384             if not self.buffer:
385                 self._fetch()
386                 if not self.buffer:
387                     # EOF
388                     return data
389            
390             newline_pos = self.buffer.find('\n')
391             if size:
392                 if newline_pos == -1:
393                     remaining = size - len(data)
394                     data += self.buffer[:remaining]
395                     self.buffer = self.buffer[remaining:]
396                 else:
397                     remaining = min(size - len(data), newline_pos)
398                     data += self.buffer[:remaining]
399                     self.buffer = self.buffer[remaining:]
400             else:
401                 if newline_pos == -1:
402                     data += self.buffer
403                 else:
404                     data += self.buffer[:newline_pos]
405                     self.buffer = self.buffer[newline_pos:]
406    
407     def readlines(self, sizehint=0):
408         # Shamelessly stolen from StringIO
409         total = 0
410         lines = []
411         line = self.readline(sizehint)
412         while line:
413             lines.append(line)
414             total += len(line)
415             if 0 < sizehint <= total:
416                 break
417             line = self.readline(sizehint)
418         return lines
419    
420     def read_trailer_lines(self):
421         if not self.closed:
422             raise ValueError(
423                 "Cannot read trailers until the request body has been read.")
424        
425         while True:
426             line = self.rfile.readline()
427             if not line:
428                 # No more data--illegal end of headers
429                 raise ValueError("Illegal end of headers.")
430            
431             self.bytes_read += len(line)
432             if self.maxlen and self.bytes_read > self.maxlen:
433                 raise IOError("Request Entity Too Large")
434            
435             if line == CRLF:
436                 # Normal end of headers
437                 break
438             if not line.endswith(CRLF):
439                 raise ValueError("HTTP requires CRLF terminators")
440            
441             yield line
442    
443     def close(self):
444         self.rfile.close()
445    
446     def __iter__(self):
447         # Shamelessly stolen from StringIO
448         total = 0
449         line = self.readline(sizehint)
450         while line:
451             yield line
452             total += len(line)
453             if 0 < sizehint <= total:
454                 break
455             line = self.readline(sizehint)
456
457
458 class HTTPRequest(object):
459     """An HTTP Request (and response).
460     
461     A single HTTP connection may consist of multiple request/response pairs.
462     """
463    
464     server = None
465     """The HTTPServer object which is receiving this request."""
466    
467     conn = None
468     """The HTTPConnection object on which this request connected."""
469    
470     inheaders = {}
471     """A dict of request headers."""
472    
473     outheaders = []
474     """A list of header tuples to write in the response."""
475    
476     ready = False
477     """When True, the request has been parsed and is ready to begin generating
478     the response. When False, signals the calling Connection that the response
479     should not be generated and the connection should close."""
480    
481     close_connection = False
482     """Signals the calling Connection that the request should close. This does
483     not imply an error! The client and/or server may each request that the
484     connection be closed."""
485    
486     chunked_write = False
487     """If True, output will be encoded with the "chunked" transfer-coding.
488     
489     This value is set automatically inside send_headers."""
490    
491     def __init__(self, server, conn):
492         self.server= server
493         self.conn = conn
494        
495         self.ready = False
496         self.started_request = False
497         self.scheme = "http"
498         if self.server.ssl_adapter is not None:
499             self.scheme = "https"
500         # Use the lowest-common protocol in case read_request_line errors.
501         self.response_protocol = 'HTTP/1.0'
502         self.inheaders = {}
503        
504         self.status = ""
505         self.outheaders = []
506         self.sent_headers = False
507         self.close_connection = False
508         self.chunked_read = False
509         self.chunked_write = False
510    
511     def parse_request(self):
512         """Parse the next HTTP request start-line and message-headers."""
513         self.rfile = SizeCheckWrapper(self.conn.rfile,
514                                       self.server.max_request_header_size)
515         try:
516             self.read_request_line()
517         except MaxSizeExceeded:
518             self.simple_response("414 Request-URI Too Long",
519                 "The Request-URI sent with the request exceeds the maximum "
520                 "allowed bytes.")
521             return
522        
523         try:
524             success = self.read_request_headers()
525         except MaxSizeExceeded:
526             self.simple_response("413 Request Entity Too Large",
527                 "The headers sent with the request exceed the maximum "
528                 "allowed bytes.")
529             return
530         else:
531             if not success:
532                 return
533        
534         self.ready = True
535    
536     def read_request_line(self):
537         # HTTP/1.1 connections are persistent by default. If a client
538         # requests a page, then idles (leaves the connection open),
539         # then rfile.readline() will raise socket.error("timed out").
540         # Note that it does this based on the value given to settimeout(),
541         # and doesn't need the client to request or acknowledge the close
542         # (although your TCP stack might suffer for it: cf Apache's history
543         # with FIN_WAIT_2).
544         request_line = self.rfile.readline()
545        
546         # Set started_request to True so communicate() knows to send 408
547         # from here on out.
548         self.started_request = True
549         if not request_line:
550             # Force self.ready = False so the connection will close.
551             self.ready = False
552             return
553        
554         if request_line == CRLF:
555             # RFC 2616 sec 4.1: "...if the server is reading the protocol
556             # stream at the beginning of a message and receives a CRLF
557             # first, it should ignore the CRLF."
558             # But only ignore one leading line! else we enable a DoS.
559             request_line = self.rfile.readline()
560             if not request_line:
561                 self.ready = False
562                 return
563        
564         if not request_line.endswith(CRLF):
565             self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
566             return
567        
568         try:
569             method, uri, req_protocol = request_line.strip().split(" ", 2)
570         except ValueError:
571             self.simple_response("400 Bad Request", "Malformed Request-Line")
572             return
573        
574         self.uri = uri
575         self.method = method
576        
577         # uri may be an abs_path (including "http://host.domain.tld");
578         scheme, authority, path = self.parse_request_uri(uri)
579         if '#' in path:
580             self.simple_response("400 Bad Request",
581                                  "Illegal #fragment in Request-URI.")
582             return
583        
584         if scheme:
585             self.scheme = scheme
586        
587         qs = ''
588         if '?' in path:
589             path, qs = path.split('?', 1)
590        
591         # Unquote the path+params (e.g. "/this%20path" -> "/this path").
592         # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
593         #
594         # But note that "...a URI must be separated into its components
595         # before the escaped characters within those components can be
596         # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
597         # Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
598         try:
599             atoms = [unquote(x) for x in quoted_slash.split(path)]
600         except ValueError, ex:
601             self.simple_response("400 Bad Request", ex.args[0])
602             return
603         path = "%2F".join(atoms)
604         self.path = path
605        
606         # Note that, like wsgiref and most other HTTP servers,
607         # we "% HEX HEX"-unquote the path but not the query string.
608         self.qs = qs
609        
610         # Compare request and server HTTP protocol versions, in case our
611         # server does not support the requested protocol. Limit our output
612         # to min(req, server). We want the following output:
613         #     request    server     actual written   supported response
614         #     protocol   protocol  response protocol    feature set
615         # a     1.0        1.0           1.0                1.0
616         # b     1.0        1.1           1.1                1.0
617         # c     1.1        1.0           1.0                1.0
618         # d     1.1        1.1           1.1                1.1
619         # Notice that, in (b), the response will be "HTTP/1.1" even though
620         # the client only understands 1.0. RFC 2616 10.5.6 says we should
621         # only return 505 if the _major_ version is different.
622         rp = int(req_protocol[5]), int(req_protocol[7])
623         sp = int(self.server.protocol[5]), int(self.server.protocol[7])
624        
625         if sp[0] != rp[0]:
626             self.simple_response("505 HTTP Version Not Supported")
627             return
628         self.request_protocol = req_protocol
629         self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
630    
631     def read_request_headers(self):
632         """Read self.rfile into self.inheaders. Return success."""
633        
634         # then all the http headers
635         try:
636             read_headers(self.rfile, self.inheaders)
637         except ValueError, ex:
638             self.simple_response("400 Bad Request", ex.args[0])
639             return False
640        
641         mrbs = self.server.max_request_body_size
642         if mrbs and int(self.inheaders.get("Content-Length", 0)) > mrbs:
643             self.simple_response("413 Request Entity Too Large",
644                 "The entity sent with the request exceeds the maximum "
645                 "allowed bytes.")
646             return False
647        
648         # Persistent connection support
649         if self.response_protocol == "HTTP/1.1":
650             # Both server and client are HTTP/1.1
651             if self.inheaders.get("Connection", "") == "close":
652                 self.close_connection = True
653         else:
654             # Either the server or client (or both) are HTTP/1.0
655             if self.inheaders.get("Connection", "") != "Keep-Alive":
656                 self.close_connection = True
657        
658         # Transfer-Encoding support
659         te = None
660         if self.response_protocol == "HTTP/1.1":
661             te = self.inheaders.get("Transfer-Encoding")
662             if te:
663                 te = [x.strip().lower() for x in te.split(",") if x.strip()]
664        
665         if te:
666             for enc in te:
667                 if enc == "chunked":
668                     self.chunked_read = True
669                 else:
670                     # Note that, even if we see "chunked", we must reject
671                     # if there is an extension we don't recognize.
672                     self.simple_response("501 Unimplemented")
673                     self.close_connection = True
674                     return False
675        
676         # From PEP 333:
677         # "Servers and gateways that implement HTTP 1.1 must provide
678         # transparent support for HTTP 1.1's "expect/continue" mechanism.
679         # This may be done in any of several ways:
680         #   1. Respond to requests containing an Expect: 100-continue request
681         #      with an immediate "100 Continue" response, and proceed normally.
682         #   2. Proceed with the request normally, but provide the application
683         #      with a wsgi.input stream that will send the "100 Continue"
684         #      response if/when the application first attempts to read from
685         #      the input stream. The read request must then remain blocked
686         #      until the client responds.
687         #   3. Wait until the client decides that the server does not support
688         #      expect/continue, and sends the request body on its own.
689         #      (This is suboptimal, and is not recommended.)
690         #
691         # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
692         # but it seems like it would be a big slowdown for such a rare case.
693         if self.inheaders.get("Expect", "") == "100-continue":
694             # Don't use simple_response here, because it emits headers
695             # we don't want. See http://www.cherrypy.org/ticket/951
696             msg = self.server.protocol + " 100 Continue\r\n\r\n"
697             try:
698                 self.conn.wfile.sendall(msg)
699             except socket.error, x:
700                 if x.args[0] not in socket_errors_to_ignore:
701                     raise
702         return True
703    
704     def parse_request_uri(self, uri):
705         """Parse a Request-URI into (scheme, authority, path).
706         
707         Note that Request-URI's must be one of::
708             
709             Request-URI    = "*" | absoluteURI | abs_path | authority
710         
711         Therefore, a Request-URI which starts with a double forward-slash
712         cannot be a "net_path"::
713         
714             net_path      = "//" authority [ abs_path ]
715         
716         Instead, it must be interpreted as an "abs_path" with an empty first
717         path segment::
718         
719             abs_path      = "/"  path_segments
720             path_segments = segment *( "/" segment )
721             segment       = *pchar *( ";" param )
722             param         = *pchar
723         """
724         if uri == "*":
725             return None, None, uri
726        
727         i = uri.find('://')
728         if i > 0 and '?' not in uri[:i]:
729             # An absoluteURI.
730             # If there's a scheme (and it must be http or https), then:
731             # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
732             scheme, remainder = uri[:i].lower(), uri[i + 3:]
733             authority, path = remainder.split("/", 1)
734             return scheme, authority, path
735        
736         if uri.startswith('/'):
737             # An abs_path.
738             return None, None, uri
739         else:
740             # An authority.
741             return None, uri, None
742    
743     def respond(self):
744         """Call the gateway and write its iterable output."""
745         mrbs = self.server.max_request_body_size
746         if self.chunked_read:
747             self.rfile = ChunkedRFile(self.conn.rfile, mrbs)
748         else:
749             cl = int(self.inheaders.get("Content-Length", 0))
750             if mrbs and mrbs < cl:
751                 if not self.sent_headers:
752                     self.simple_response("413 Request Entity Too Large",
753                         "The entity sent with the request exceeds the maximum "
754                         "allowed bytes.")
755                 return
756             self.rfile = KnownLengthRFile(self.conn.rfile, cl)
757        
758         self.server.gateway(self).respond()
759        
760         if (self.ready and not self.sent_headers):
761             self.sent_headers = True
762             self.send_headers()
763         if self.chunked_write:
764             self.conn.wfile.sendall("0\r\n\r\n")
765    
766     def simple_response(self, status, msg=""):
767         """Write a simple response back to the client."""
768         status = str(status)
769         buf = ["Content-Length: %s\r\n" % len(msg),
770                "Content-Type: text/plain\r\n"]
771        
772         if status[:3] in ("413", "414"):
773             # Request Entity Too Large / Request-URI Too Long
774             self.close_connection = True
775             if self.response_protocol == 'HTTP/1.1':
776                 # This will not be true for 414, since read_request_line
777                 # usually raises 414 before reading the whole line, and we
778                 # therefore cannot know the proper response_protocol.
779                 buf.append("Connection: close\r\n")
780             else:
781                 # HTTP/1.0 had no 413/414 status nor Connection header.
782                 # Emit 400 instead and trust the message body is enough.
783                 status = "400 Bad Request"
784        
785         buf.append(CRLF)
786         if msg:
787             if isinstance(msg, unicode):
788                 msg = msg.encode("ISO-8859-1")
789             buf.append(msg)
790        
791         status_line = self.server.protocol + " " + status + CRLF
792         try:
793             self.conn.wfile.sendall(status_line + "".join(buf))
794         except socket.error, x:
795             if x.args[0] not in socket_errors_to_ignore:
796                 raise
797    
798     def write(self, chunk):
799         """Write unbuffered data to the client."""
800         if self.chunked_write and chunk:
801             buf = [hex(len(chunk))[2:], CRLF, chunk, CRLF]
802             self.conn.wfile.sendall("".join(buf))
803         else:
804             self.conn.wfile.sendall(chunk)
805    
806     def send_headers(self):
807         """Assert, process, and send the HTTP response message-headers.
808         
809         You must set self.status, and self.outheaders before calling this.
810         """
811         hkeys = [key.lower() for key, value in self.outheaders]
812         status = int(self.status[:3])
813        
814         if status == 413:
815             # Request Entity Too Large. Close conn to avoid garbage.
816             self.close_connection = True
817         elif "content-length" not in hkeys:
818             # "All 1xx (informational), 204 (no content),
819             # and 304 (not modified) responses MUST NOT
820             # include a message-body." So no point chunking.
821             if status < 200 or status in (204, 205, 304):
822                 pass
823             else:
824                 if (self.response_protocol == 'HTTP/1.1'
825                     and self.method != 'HEAD'):
826                     # Use the chunked transfer-coding
827                     self.chunked_write = True
828                     self.outheaders.append(("Transfer-Encoding", "chunked"))
829                 else:
830                     # Closing the conn is the only way to determine len.
831                     self.close_connection = True
832        
833         if "connection" not in hkeys:
834             if self.response_protocol == 'HTTP/1.1':
835                 # Both server and client are HTTP/1.1 or better
836                 if self.close_connection:
837                     self.outheaders.append(("Connection", "close"))
838             else:
839                 # Server and/or client are HTTP/1.0
840                 if not self.close_connection:
841                     self.outheaders.append(("Connection", "Keep-Alive"))
842        
843         if (not self.close_connection) and (not self.chunked_read):
844             # Read any remaining request body data on the socket.
845             # "If an origin server receives a request that does not include an
846             # Expect request-header field with the "100-continue" expectation,
847             # the request includes a request body, and the server responds
848             # with a final status code before reading the entire request body
849             # from the transport connection, then the server SHOULD NOT close
850             # the transport connection until it has read the entire request,
851             # or until the client closes the connection. Otherwise, the client
852             # might not reliably receive the response message. However, this
853             # requirement is not be construed as preventing a server from
854             # defending itself against denial-of-service attacks, or from
855             # badly broken client implementations."
856             remaining = getattr(self.rfile, 'remaining', 0)
857             if remaining > 0:
858                 self.rfile.read(remaining)
859        
860         if "date" not in hkeys:
861             self.outheaders.append(("Date", rfc822.formatdate()))
862        
863         if "server" not in hkeys:
864             self.outheaders.append(("Server", self.server.server_name))
865        
866         buf = [self.server.protocol + " " + self.status + CRLF]
867         for k, v in self.outheaders:
868             buf.append(k + ": " + v + CRLF)
869         buf.append(CRLF)
870         self.conn.wfile.sendall("".join(buf))
871
872
873 class NoSSLError(Exception):
874     """Exception raised when a client speaks HTTP to an HTTPS socket."""
875     pass
876
877
878 class FatalSSLAlert(Exception):
879     """Exception raised when the SSL implementation signals a fatal alert."""
880     pass
881
882
883 if not _fileobject_uses_str_type:
884     class CP_fileobject(socket._fileobject):
885         """Faux file object attached to a socket object."""
886
887         def sendall(self, data):
888             """Sendall for non-blocking sockets."""
889             while data:
890                 try:
891                     bytes_sent = self.send(data)
892                     data = data[bytes_sent:]
893                 except socket.error, e:
894                     if e.args[0] not in socket_errors_nonblocking:
895                         raise
896
897         def send(self, data):
898             return self._sock.send(data)
899
900         def flush(self):
901             if self._wbuf:
902                 buffer = "".join(self._wbuf)
903                 self._wbuf = []
904                 self.sendall(buffer)
905
906         def recv(self, size):
907             while True:
908                 try:
909                     return self._sock.recv(size)
910                 except socket.error, e:
911                     if (e.args[0] not in socket_errors_nonblocking
912                         and e.args[0] not in socket_error_eintr):
913                         raise
914
915         def read(self, size=-1):
916             # Use max, disallow tiny reads in a loop as they are very inefficient.
917             # We never leave read() with any leftover data from a new recv() call
918             # in our internal buffer.
919             rbufsize = max(self._rbufsize, self.default_bufsize)
920             # Our use of StringIO rather than lists of string objects returned by
921             # recv() minimizes memory usage and fragmentation that occurs when
922             # rbufsize is large compared to the typical return value of recv().
923             buf = self._rbuf
924             buf.seek(0, 2)  # seek end
925             if size < 0:
926                 # Read until EOF
927                 self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
928                 while True:
929                     data = self.recv(rbufsize)
930                     if not data:
931                         break
932                     buf.write(data)
933                 return buf.getvalue()
934             else:
935                 # Read until size bytes or EOF seen, whichever comes first
936                 buf_len = buf.tell()
937                 if buf_len >= size:
938                     # Already have size bytes in our buffer?  Extract and return.
939                     buf.seek(0)
940                     rv = buf.read(size)
941                     self._rbuf = StringIO.StringIO()
942                     self._rbuf.write(buf.read())
943                     return rv
944
945                 self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
946                 while True:
947                     left = size - buf_len
948                     # recv() will malloc the amount of memory given as its
949                     # parameter even though it often returns much less data
950                     # than that.  The returned data string is short lived
951                     # as we copy it into a StringIO and free it.  This avoids
952                     # fragmentation issues on many platforms.
953                     data = self.recv(left)
954                     if not data:
955                         break
956                     n = len(data)
957                     if n == size and not buf_len:
958                         # Shortcut.  Avoid buffer data copies when:
959                         # - We have no data in our buffer.
960                         # AND
961                         # - Our call to recv returned exactly the
962                         #   number of bytes we were asked to read.
963                         return data
964                     if n == left:
965                         buf.write(data)
966                         del data  # explicit free
967                         break
968                     assert n <= left, "recv(%d) returned %d bytes" % (left, n)
969                     buf.write(data)
970                     buf_len += n
971                     del data  # explicit free
972                     #assert buf_len == buf.tell()
973                 return buf.getvalue()
974
975         def readline(self, size=-1):
976             buf = self._rbuf
977             buf.seek(0, 2)  # seek end
978             if buf.tell() > 0:
979                 # check if we already have it in our buffer
980                 buf.seek(0)
981                 bline = buf.readline(size)
982                 if bline.endswith('\n') or len(bline) == size:
983                     self._rbuf = StringIO.StringIO()
984                     self._rbuf.write(buf.read())
985                     return bline
986                 del bline
987             if size < 0:
988                 # Read until \n or EOF, whichever comes first
989                 if self._rbufsize <= 1:
990                     # Speed up unbuffered case
991                     buf.seek(0)
992                     buffers = [buf.read()]
993                     self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
994                     data = None
995                     recv = self.recv
996                     while data != "\n":
997                         data = recv(1)
998                         if not data:
999                             break
1000                         buffers.append(data)
1001                     return "".join(buffers)
1002
1003                 buf.seek(0, 2)  # seek end
1004                 self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
1005                 while True:
1006                     data = self.recv(self._rbufsize)
1007                     if not data:
1008                         break
1009                     nl = data.find('\n')
1010                     if nl >= 0:
1011                         nl += 1
1012                         buf.write(data[:nl])
1013                         self._rbuf.write(data[nl:])
1014                         del data
1015                         break
1016                     buf.write(data)
1017                 return buf.getvalue()
1018             else:
1019                 # Read until size bytes or \n or EOF seen, whichever comes first
1020                 buf.seek(0, 2)  # seek end
1021                 buf_len = buf.tell()
1022                 if buf_len >= size:
1023                     buf.seek(0)
1024                     rv = buf.read(size)
1025                     self._rbuf = StringIO.StringIO()
1026                     self._rbuf.write(buf.read())
1027                     return rv
1028                 self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
1029                 while True:
1030                     data = self.recv(self._rbufsize)
1031                     if not data:
1032                         break
1033                     left = size - buf_len
1034                     # did we just receive a newline?
1035                     nl = data.find('\n', 0, left)
1036                     if nl >= 0:
1037                         nl += 1
1038                         # save the excess data to _rbuf
1039                         self._rbuf.write(data[nl:])
1040                         if buf_len:
1041                             buf.write(data[:nl])
1042                             break
1043                         else:
1044                             # Shortcut.  Avoid data copy through buf when returning
1045                             # a substring of our first recv().
1046                             return data[:nl]
1047                     n = len(data)
1048                     if n == size and not buf_len:
1049                         # Shortcut.  Avoid data copy through buf when
1050                         # returning exactly all of our first recv().
1051                         return data
1052                     if n >= left:
1053                         buf.write(data[:left])
1054                         self._rbuf.write(data[left:])
1055                         break
1056                     buf.write(data)
1057                     buf_len += n
1058                     #assert buf_len == buf.tell()
1059                 return buf.getvalue()
1060
1061 else:
1062     class CP_fileobject(socket._fileobject):
1063         """Faux file object attached to a socket object."""
1064
1065         def sendall(self, data):
1066             """Sendall for non-blocking sockets."""
1067             while data:
1068                 try:
1069                     bytes_sent = self.send(data)
1070                     data = data[bytes_sent:]
1071                 except socket.error, e:
1072                     if e.args[0] not in socket_errors_nonblocking:
1073                         raise
1074
1075         def send(self, data):
1076             return self._sock.send(data)
1077
1078         def flush(self):
1079             if self._wbuf:
1080                 buffer = "".join(self._wbuf)
1081                 self._wbuf = []
1082                 self.sendall(buffer)
1083
1084         def recv(self, size):
1085             while True:
1086                 try:
1087                     return self._sock.recv(size)
1088                 except socket.error, e:
1089                     if (e.args[0] not in socket_errors_nonblocking
1090                         and e.args[0] not in socket_error_eintr):
1091                         raise
1092
1093         def read(self, size=-1):
1094             if size < 0:
1095                 # Read until EOF
1096                 buffers = [self._rbuf]
1097                 self._rbuf = ""
1098                 if self._rbufsize <= 1:
1099                     recv_size = self.default_bufsize
1100                 else:
1101                     recv_size = self._rbufsize
1102
1103                 while True:
1104                     data = self.recv(recv_size)
1105                     if not data:
1106                         break
1107                     buffers.append(data)
1108                 return "".join(buffers)
1109             else:
1110                 # Read until size bytes or EOF seen, whichever comes first
1111                 data = self._rbuf
1112                 buf_len = len(data)
1113                 if buf_len >= size:
1114                     self._rbuf = data[size:]
1115                     return data[:size]
1116                 buffers = []
1117                 if data:
1118                     buffers.append(data)
1119                 self._rbuf = ""
1120                 while True:
1121                     left = size - buf_len
1122                     recv_size = max(self._rbufsize, left)
1123                     data = self.recv(recv_size)
1124                     if not data:
1125                         break
1126                     buffers.append(data)
1127                     n = len(data)
1128                     if n >= left:
1129                         self._rbuf = data[left:]
1130                         buffers[-1] = data[:left]
1131                         break
1132                     buf_len += n
1133                 return "".join(buffers)
1134
1135         def readline(self, size=-1):
1136             data = self._rbuf
1137             if size < 0:
1138                 # Read until \n or EOF, whichever comes first
1139                 if self._rbufsize <= 1:
1140                     # Speed up unbuffered case
1141                     assert data == ""
1142                     buffers = []
1143                     while data != "\n":
1144                         data = self.recv(1)
1145                         if not data:
1146                             break
1147                         buffers.append(data)
1148                     return "".join(buffers)
1149                 nl = data.find('\n')
1150                 if nl >= 0:
1151                     nl += 1
1152                     self._rbuf = data[nl:]
1153                     return data[:nl]
1154                 buffers = []
1155                 if data:
1156                     buffers.append(data)
1157                 self._rbuf = ""
1158                 while True:
1159                     data = self.recv(self._rbufsize)
1160                     if not data:
1161                         break
1162                     buffers.append(data)
1163                     nl = data.find('\n')
1164                     if nl >= 0:
1165                         nl += 1
1166                         self._rbuf = data[nl:]
1167                         buffers[-1] = data[:nl]
1168                         break
1169                 return "".join(buffers)
1170             else:
1171                 # Read until size bytes or \n or EOF seen, whichever comes first
1172                 nl = data.find('\n', 0, size)
1173                 if nl >= 0:
1174                     nl += 1
1175                     self._rbuf = data[nl:]
1176                     return data[:nl]
1177                 buf_len = len(data)
1178                 if buf_len >= size:
1179                     self._rbuf = data[size:]
1180                     return data[:size]
1181                 buffers = []
1182                 if data:
1183                     buffers.append(data)
1184                 self._rbuf = ""
1185                 while True:
1186                     data = self.recv(self._rbufsize)
1187                     if not data:
1188                         break
1189                     buffers.append(data)
1190                     left = size - buf_len
1191                     nl = data.find('\n', 0, left)
1192                     if nl >= 0:
1193                         nl += 1
1194                         self._rbuf = data[nl:]
1195                         buffers[-1] = data[:nl]
1196                         break
1197                     n = len(data)
1198                     if n >= left:
1199                         self._rbuf = data[left:]
1200                         buffers[-1] = data[:left]
1201                         break
1202                     buf_len += n
1203                 return "".join(buffers)
1204
1205
1206 class HTTPConnection(object):
1207     """An HTTP connection (active socket).
1208     
1209     server: the Server object which received this connection.
1210     socket: the raw socket object (usually TCP) for this connection.
1211     makefile: a fileobject class for reading from the socket.
1212     """
1213    
1214     remote_addr = None
1215     remote_port = None
1216     ssl_env = None
1217     rbufsize = -1
1218     RequestHandlerClass = HTTPRequest
1219    
1220     def __init__(self, server, sock, makefile=CP_fileobject):
1221         self.server = server
1222         self.socket = sock
1223         self.rfile = makefile(sock, "rb", self.rbufsize)
1224         self.wfile = makefile(sock, "wb", -1)
1225    
1226     def communicate(self):
1227         """Read each request and respond appropriately."""
1228         request_seen = False
1229         try:
1230             while True:
1231                 # (re)set req to None so that if something goes wrong in
1232                 # the RequestHandlerClass constructor, the error doesn't
1233                 # get written to the previous request.
1234                 req = None
1235                 req = self.RequestHandlerClass(self.server, self)
1236                
1237                 # This order of operations should guarantee correct pipelining.
1238                 req.parse_request()
1239                 if not req.ready:
1240                     # Something went wrong in the parsing (and the server has
1241                     # probably already made a simple_response). Return and
1242                     # let the conn close.
1243                     return
1244                
1245                 request_seen = True
1246                 req.respond()
1247                 if req.close_connection:
1248                     return
1249         except socket.error, e:
1250             errnum = e.args[0]
1251             if errnum == 'timed out':
1252                 # Don't error if we're between requests; only error
1253                 # if 1) no request has been started at all, or 2) we're
1254                 # in the middle of a request.
1255                 # See http://www.cherrypy.org/ticket/853
1256                 if (not request_seen) or (req and req.started_request):
1257                     # Don't bother writing the 408 if the response
1258                     # has already started being written.
1259                     if req and not req.sent_headers:
1260                         try:
1261                             req.simple_response("408 Request Timeout")
1262                         except FatalSSLAlert:
1263                             # Close the connection.
1264                             return
1265             elif errnum not in socket_errors_to_ignore:
1266                 if req and not req.sent_headers:
1267                     try:
1268                         req.simple_response("500 Internal Server Error",
1269                                             format_exc())
1270                     except FatalSSLAlert:
1271                         # Close the connection.
1272                         return
1273             return
1274         except (KeyboardInterrupt, SystemExit):
1275             raise
1276         except FatalSSLAlert:
1277             # Close the connection.
1278             return
1279         except NoSSLError:
1280             if req and not req.sent_headers:
1281                 # Unwrap our wfile
1282                 self.wfile = CP_fileobject(self.socket._sock, "wb", -1)
1283                 req.simple_response("400 Bad Request",
1284                     "The client sent a plain HTTP request, but "
1285                     "this server only speaks HTTPS on this port.")
1286                 self.linger = True
1287         except Exception:
1288             if req and not req.sent_headers:
1289                 try:
1290                     req.simple_response("500 Internal Server Error", format_exc())
1291                 except FatalSSLAlert:
1292                     # Close the connection.
1293                     return
1294    
1295     linger = False
1296    
1297     def close(self):
1298         """Close the socket underlying this connection."""
1299         self.rfile.close()
1300        
1301         if not self.linger:
1302             # Python's socket module does NOT call close on the kernel socket
1303             # when you call socket.close(). We do so manually here because we
1304             # want this server to send a FIN TCP segment immediately. Note this
1305             # must be called *before* calling socket.close(), because the latter
1306             # drops its reference to the kernel socket.
1307             if hasattr(self.socket, '_sock'):
1308                 self.socket._sock.close()
1309             self.socket.close()
1310         else:
1311             # On the other hand, sometimes we want to hang around for a bit
1312             # to make sure the client has a chance to read our entire
1313             # response. Skipping the close() calls here delays the FIN
1314             # packet until the socket object is garbage-collected later.
1315             # Someday, perhaps, we'll do the full lingering_close that
1316             # Apache does, but not today.
1317             pass
1318
1319
1320 def format_exc(limit=None):
1321     """Like print_exc() but return a string. Backport for Python 2.3."""
1322     try:
1323         etype, value, tb = sys.exc_info()
1324         return ''.join(traceback.format_exception(etype, value, tb, limit))
1325     finally:
1326         etype = value = tb = None
1327
1328
1329 _SHUTDOWNREQUEST = None
1330
1331 class WorkerThread(threading.Thread):
1332     """Thread which continuously polls a Queue for Connection objects.
1333     
1334     Due to the timing issues of polling a Queue, a WorkerThread does not
1335     check its own 'ready' flag after it has started. To stop the thread,
1336     it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
1337     (one for each running WorkerThread).
1338     """
1339    
1340     conn = None
1341     """The current connection pulled off the Queue, or None."""
1342    
1343     server = None
1344     """The HTTP Server which spawned this thread, and which owns the
1345     Queue and is placing active connections into it."""
1346    
1347     ready = False
1348     """A simple flag for the calling server to know when this thread
1349     has begun polling the Queue."""
1350    
1351    
1352     def __init__(self, server):
1353         self.ready = False
1354         self.server = server
1355         threading.Thread.__init__(self)
1356    
1357     def run(self):
1358         try:
1359             self.ready = True
1360             while True:
1361                 conn = self.server.requests.get()
1362                 if conn is _SHUTDOWNREQUEST:
1363                     return
1364                
1365                 self.conn = conn
1366                 try:
1367                     conn.communicate()
1368                 finally:
1369                     conn.close()
1370                     self.conn = None
1371         except (KeyboardInterrupt, SystemExit), exc:
1372             self.server.interrupt = exc
1373
1374
1375 class ThreadPool(object):
1376     """A Request Queue for the CherryPyWSGIServer which pools threads.
1377     
1378     ThreadPool objects must provide min, get(), put(obj), start()
1379     and stop(timeout) attributes.
1380     """
1381    
1382     def __init__(self, server, min=10, max=-1):
1383         self.server = server
1384         self.min = min
1385         self.max = max
1386         self._threads = []
1387         self._queue = Queue.Queue()
1388         self.get = self._queue.get
1389    
1390     def start(self):
1391         """Start the pool of threads."""
1392         for i in range(self.min):
1393             self._threads.append(WorkerThread(self.server))
1394         for worker in self._threads:
1395             worker.setName("CP Server " + worker.getName())
1396             worker.start()
1397         for worker in self._threads:
1398             while not worker.ready:
1399                 time.sleep(.1)
1400    
1401     def _get_idle(self):
1402         """Number of worker threads which are idle. Read-only."""
1403         return len([t for t in self._threads if t.conn is None])
1404     idle = property(_get_idle, doc=_get_idle.__doc__)
1405    
1406     def put(self, obj):
1407         self._queue.put(obj)
1408         if obj is _SHUTDOWNREQUEST:
1409             return
1410    
1411     def grow(self, amount):
1412         """Spawn new worker threads (not above self.max)."""
1413         for i in range(amount):
1414             if self.max > 0 and len(self._threads) >= self.max:
1415                 break
1416             worker = WorkerThread(self.server)
1417             worker.setName("CP Server " + worker.getName())
1418             self._threads.append(worker)
1419             worker.start()
1420    
1421     def shrink(self, amount):
1422         """Kill off worker threads (not below self.min)."""
1423         # Grow/shrink the pool if necessary.
1424         # Remove any dead threads from our list
1425         for t in self._threads:
1426             if not t.isAlive():
1427                 self._threads.remove(t)
1428                 amount -= 1
1429        
1430         if amount > 0:
1431             for i in range(min(amount, len(self._threads) - self.min)):
1432                 # Put a number of shutdown requests on the queue equal
1433                 # to 'amount'. Once each of those is processed by a worker,
1434                 # that worker will terminate and be culled from our list
1435                 # in self.put.
1436                 self._queue.put(_SHUTDOWNREQUEST)
1437    
1438     def stop(self, timeout=5):
1439         # Must shut down threads here so the code that calls
1440         # this method can know when all threads are stopped.
1441         for worker in self._threads:
1442             self._queue.put(_SHUTDOWNREQUEST)
1443        
1444         # Don't join currentThread (when stop is called inside a request).
1445         current = threading.currentThread()
1446         if timeout and timeout >= 0:
1447             endtime = time.time() + timeout
1448         while self._threads:
1449             worker = self._threads.pop()
1450             if worker is not current and worker.isAlive():
1451                 try:
1452                     if timeout is None or timeout < 0:
1453                         worker.join()
1454                     else:
1455                         remaining_time = endtime - time.time()
1456                         if remaining_time > 0:
1457                             worker.join(remaining_time)
1458                         if worker.isAlive():
1459                             # We exhausted the timeout.
1460                             # Forcibly shut down the socket.
1461                             c = worker.conn
1462                             if c and not c.rfile.closed:
1463                                 try:
1464                                     c.socket.shutdown(socket.SHUT_RD)
1465                                 except TypeError:
1466                                     # pyOpenSSL sockets don't take an arg
1467                                     c.socket.shutdown()
1468                             worker.join()
1469                 except (AssertionError,
1470                         # Ignore repeated Ctrl-C.
1471                         # See http://www.cherrypy.org/ticket/691.
1472                         KeyboardInterrupt), exc1:
1473                     pass
1474
1475
1476
1477 try:
1478     import fcntl
1479 except ImportError:
1480     try:
1481         from ctypes import windll, WinError
1482     except ImportError:
1483         def prevent_socket_inheritance(sock):
1484             """Dummy function, since neither fcntl nor ctypes are available."""
1485             pass
1486     else:
1487         def prevent_socket_inheritance(sock):
1488             """Mark the given socket fd as non-inheritable (Windows)."""
1489             if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
1490                 raise WinError()
1491 else:
1492     def prevent_socket_inheritance(sock):
1493         """Mark the given socket fd as non-inheritable (POSIX)."""
1494         fd = sock.fileno()
1495         old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
1496         fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1497
1498
1499 class SSLAdapter(object):
1500     """Base class for SSL driver library adapters.
1501     
1502     Required methods:
1503     
1504         * ``wrap(sock) -> (wrapped socket, ssl environ dict)``
1505         * ``makefile(sock, mode='r', bufsize=-1) -> socket file object``
1506     """
1507    
1508     def __init__(self, certificate, private_key, certificate_chain=None):
1509         self.certificate = certificate
1510         self.private_key = private_key
1511         self.certificate_chain = certificate_chain
1512    
1513     def wrap(self, sock):
1514         raise NotImplemented
1515    
1516     def makefile(self, sock, mode='r', bufsize=-1):
1517         raise NotImplemented
1518
1519
1520 class HTTPServer(object):
1521     """An HTTP server."""
1522    
1523     _bind_addr = "127.0.0.1"
1524     _interrupt = None
1525    
1526     gateway = None
1527     """A Gateway instance."""
1528    
1529     minthreads = None
1530     """The minimum number of worker threads to create (default 10)."""
1531    
1532     maxthreads = None
1533     """The maximum number of worker threads to create (default -1 = no limit)."""
1534    
1535     server_name = None
1536     """The name of the server; defaults to socket.gethostname()."""
1537    
1538     protocol = "HTTP/1.1"
1539     """The version string to write in the Status-Line of all HTTP responses.
1540     
1541     For example, "HTTP/1.1" is the default. This also limits the supported
1542     features used in the response."""
1543    
1544     request_queue_size = 5
1545     """The 'backlog' arg to socket.listen(); max queued connections (default 5)."""
1546    
1547     shutdown_timeout = 5
1548     """The total time, in seconds, to wait for worker threads to cleanly exit."""
1549    
1550     timeout = 10
1551     """The timeout in seconds for accepted connections (default 10)."""
1552    
1553     version = "CherryPy/3.2.0rc1"
1554     """A version string for the HTTPServer."""
1555    
1556     software = None
1557     """The value to set for the SERVER_SOFTWARE entry in the WSGI environ.
1558     
1559     If None, this defaults to ``'%s Server' % self.version``."""
1560    
1561     ready = False
1562     """An internal flag which marks whether the socket is accepting connections."""
1563    
1564     max_request_header_size = 0
1565     """The maximum size, in bytes, for request headers, or 0 for no limit."""
1566    
1567     max_request_body_size = 0
1568     """The maximum size, in bytes, for request bodies, or 0 for no limit."""
1569    
1570     nodelay = True
1571     """If True (the default since 3.1), sets the TCP_NODELAY socket option."""
1572    
1573     ConnectionClass = HTTPConnection
1574     """The class to use for handling HTTP connections."""
1575    
1576     ssl_adapter = None
1577     """An instance of SSLAdapter (or a subclass).
1578     
1579     You must have the corresponding SSL driver library installed."""
1580    
1581     def __init__(self, bind_addr, gateway, minthreads=10, maxthreads=-1,
1582                  server_name=None):
1583         self.bind_addr = bind_addr
1584         self.gateway = gateway
1585        
1586         self.requests = ThreadPool(self, min=minthreads or 1, max=maxthreads)
1587        
1588         if not server_name:
1589             server_name = socket.gethostname()
1590         self.server_name = server_name
1591    
1592     def __str__(self):
1593         return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
1594                               self.bind_addr)
1595    
1596     def _get_bind_addr(self):
1597         return self._bind_addr
1598     def _set_bind_addr(self, value):
1599         if isinstance(value, tuple) and value[0] in ('', None):
1600             # Despite the socket module docs, using '' does not
1601             # allow AI_PASSIVE to work. Passing None instead
1602             # returns '0.0.0.0' like we want. In other words:
1603             #     host    AI_PASSIVE     result
1604             #      ''         Y         192.168.x.y
1605             #      ''         N         192.168.x.y
1606             #     None        Y         0.0.0.0
1607             #     None        N         127.0.0.1
1608             # But since you can get the same effect with an explicit
1609             # '0.0.0.0', we deny both the empty string and None as values.
1610             raise ValueError("Host values of '' or None are not allowed. "
1611                              "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
1612                              "to listen on all active interfaces.")
1613         self._bind_addr = value
1614     bind_addr = property(_get_bind_addr, _set_bind_addr,
1615         doc="""The interface on which to listen for connections.
1616         
1617         For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1618         or IPv6 address, or any valid hostname. The string 'localhost' is a
1619         synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1620         The string '0.0.0.0' is a special IPv4 entry meaning "any active
1621         interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1622         IPv6. The empty string or None are not allowed.
1623         
1624         For UNIX sockets, supply the filename as a string.""")
1625    
1626     def start(self):
1627         """Run the server forever."""
1628         # We don't have to trap KeyboardInterrupt or SystemExit here,
1629         # because cherrpy.server already does so, calling self.stop() for us.
1630         # If you're using this server with another framework, you should
1631         # trap those exceptions in whatever code block calls start().
1632         self._interrupt = None
1633        
1634         if self.software is None:
1635             self.software = "%s Server" % self.version
1636        
1637         # SSL backward compatibility
1638         if (self.ssl_adapter is None and
1639             getattr(self, 'ssl_certificate', None) and
1640             getattr(self, 'ssl_private_key', None)):
1641             warnings.warn(
1642                     "SSL attributes are deprecated in CherryPy 3.2, and will "
1643                     "be removed in CherryPy 3.3. Use an ssl_adapter attribute "
1644                     "instead.",
1645                     DeprecationWarning
1646                 )
1647             try:
1648                 from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
1649             except ImportError:
1650                 pass
1651             else:
1652                 self.ssl_adapter = pyOpenSSLAdapter(
1653                     self.ssl_certificate, self.ssl_private_key,
1654                     getattr(self, 'ssl_certificate_chain', None))
1655        
1656         # Select the appropriate socket
1657         if isinstance(self.bind_addr, basestring):
1658             # AF_UNIX socket
1659            
1660             # So we can reuse the socket...
1661             try: os.unlink(self.bind_addr)
1662             except: pass
1663            
1664             # So everyone can access the socket...
1665             try: os.chmod(self.bind_addr, 0777)
1666             except: pass
1667            
1668             info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1669         else:
1670             # AF_INET or AF_INET6 socket
1671             # Get the correct address family for our host (allows IPv6 addresses)
1672             host, port = self.bind_addr
1673             try:
1674                 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1675                                           socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
1676             except socket.gaierror:
1677                 if ':' in self.bind_addr[0]:
1678                     info = [(socket.AF_INET6, socket.SOCK_STREAM,
1679                              0, "", self.bind_addr + (0, 0))]
1680                 else:
1681                     info = [(socket.AF_INET, socket.SOCK_STREAM,
1682                              0, "", self.bind_addr)]
1683        
1684         self.socket = None
1685         msg = "No socket could be created"
1686         for res in info:
1687             af, socktype, proto, canonname, sa = res
1688             try:
1689                 self.bind(af, socktype, proto)
1690             except socket.error, msg:
1691                 if self.socket:
1692                     self.socket.close()
1693                 self.socket = None
1694                 continue
1695             break
1696         if not self.socket:
1697             raise socket.error(msg)
1698        
1699         # Timeout so KeyboardInterrupt can be caught on Win32
1700         self.socket.settimeout(1)
1701         self.socket.listen(self.request_queue_size)
1702        
1703         # Create worker threads
1704         self.requests.start()
1705        
1706         self.ready = True
1707         while self.ready:
1708             self.tick()
1709             if self.interrupt:
1710                 while self.interrupt is True:
1711                     # Wait for self.stop() to complete. See _set_interrupt.
1712                     time.sleep(0.1)
1713                 if self.interrupt:
1714                     raise self.interrupt
1715    
1716     def bind(self, family, type, proto=0):
1717         """Create (or recreate) the actual socket object."""
1718         self.socket = socket.socket(family, type, proto)
1719         prevent_socket_inheritance(self.socket)
1720         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1721         if self.nodelay and not isinstance(self.bind_addr, str):
1722             self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1723        
1724         if self.ssl_adapter is not None:
1725             self.socket = self.ssl_adapter.bind(self.socket)
1726        
1727         # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
1728         # activate dual-stack. See http://www.cherrypy.org/ticket/871.
1729         if (family == socket.AF_INET6
1730             and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
1731             try:
1732                 self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
1733             except (AttributeError, socket.error):
1734                 # Apparently, the socket option is not available in
1735                 # this machine's TCP stack
1736                 pass
1737        
1738         self.socket.bind(self.bind_addr)
1739    
1740     def tick(self):
1741         """Accept a new connection and put it on the Queue."""
1742         try:
1743             s, addr = self.socket.accept()
1744             if not self.ready:
1745                 return
1746            
1747             prevent_socket_inheritance(s)
1748             if hasattr(s, 'settimeout'):
1749                 s.settimeout(self.timeout)
1750            
1751             makefile = CP_fileobject
1752             ssl_env = {}
1753             # if ssl cert and key are set, we try to be a secure HTTP server
1754             if self.ssl_adapter is not None:
1755                 try:
1756                     s, ssl_env = self.ssl_adapter.wrap(s)
1757                 except NoSSLError:
1758                     msg = ("The client sent a plain HTTP request, but "
1759                            "this server only speaks HTTPS on this port.")
1760                     buf = ["%s 400 Bad Request\r\n" % self.protocol,
1761                            "Content-Length: %s\r\n" % len(msg),
1762                            "Content-Type: text/plain\r\n\r\n",
1763                            msg]
1764                    
1765                     wfile = CP_fileobject(s, "wb", -1)
1766                     try:
1767                         wfile.sendall("".join(buf))
1768                     except socket.error, x:
1769                         if x.args[0] not in socket_errors_to_ignore:
1770                             raise
1771                     return
1772                 if not s:
1773                     return
1774                 makefile = self.ssl_adapter.makefile
1775            
1776             conn = self.ConnectionClass(self, s, makefile)
1777            
1778             if not isinstance(self.bind_addr, basestring):
1779                 # optional values
1780                 # Until we do DNS lookups, omit REMOTE_HOST
1781                 if addr is None: # sometimes this can happen
1782                     # figure out if AF_INET or AF_INET6.
1783                     if len(s.getsockname()) == 2:
1784                         # AF_INET
1785                         addr = ('0.0.0.0', 0)
1786                     else:
1787                         # AF_INET6
1788                         addr = ('::', 0)
1789                 conn.remote_addr = addr[0]
1790                 conn.remote_port = addr[1]
1791            
1792             conn.ssl_env = ssl_env
1793            
1794             self.requests.put(conn)
1795         except socket.timeout:
1796             # The only reason for the timeout in start() is so we can
1797             # notice keyboard interrupts on Win32, which don't interrupt
1798             # accept() by default
1799             return
1800         except socket.error, x:
1801             if x.args[0] in socket_error_eintr:
1802                 # I *think* this is right. EINTR should occur when a signal
1803                 # is received during the accept() call; all docs say retry
1804                 # the call, and I *think* I'm reading it right that Python
1805                 # will then go ahead and poll for and handle the signal
1806                 # elsewhere. See http://www.cherrypy.org/ticket/707.
1807                 return
1808             if x.args[0] in socket_errors_nonblocking:
1809                 # Just try again. See http://www.cherrypy.org/ticket/479.
1810                 return
1811             if x.args[0] in socket_errors_to_ignore:
1812                 # Our socket was closed.
1813                 # See http://www.cherrypy.org/ticket/686.
1814                 return
1815             raise
1816    
1817     def _get_interrupt(self):
1818         return self._interrupt
1819     def _set_interrupt(self, interrupt):
1820         self._interrupt = True
1821         self.stop()
1822         self._interrupt = interrupt
1823     interrupt = property(_get_interrupt, _set_interrupt,
1824                          doc="Set this to an Exception instance to "
1825                              "interrupt the server.")
1826    
1827     def stop(self):
1828         """Gracefully shutdown a server that is serving forever."""
1829         self.ready = False
1830        
1831         sock = getattr(self, "socket", None)
1832         if sock:
1833             if not isinstance(self.bind_addr, basestring):
1834                 # Touch our own socket to make accept() return immediately.
1835                 try:
1836                     host, port = sock.getsockname()[:2]
1837                 except socket.error, x:
1838                     if x.args[0] not in socket_errors_to_ignore:
1839                         # Changed to use error code and not message
1840                         # See http://www.cherrypy.org/ticket/860.
1841                         raise
1842                 else:
1843                     # Note that we're explicitly NOT using AI_PASSIVE,
1844                     # here, because we want an actual IP to touch.
1845                     # localhost won't work if we've bound to a public IP,
1846                     # but it will if we bound to '0.0.0.0' (INADDR_ANY).
1847                     for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1848                                                   socket.SOCK_STREAM):
1849                         af, socktype, proto, canonname, sa = res
1850                         s = None
1851                         try:
1852                             s = socket.socket(af, socktype, proto)
1853                             # See http://groups.google.com/group/cherrypy-users/
1854                             #        browse_frm/thread/bbfe5eb39c904fe0
1855                             s.settimeout(1.0)
1856                             s.connect((host, port))
1857                             s.close()
1858                         except socket.error:
1859                             if s:
1860                                 s.close()
1861             if hasattr(sock, "close"):
1862                 sock.close()
1863             self.socket = None
1864        
1865         self.requests.stop(self.shutdown_timeout)
1866
1867
1868 class Gateway(object):
1869    
1870     def __init__(self, req):
1871         self.req = req
1872    
1873     def respond(self):
1874         raise NotImplemented
1875
1876
1877 # These may either be wsgiserver.SSLAdapter subclasses or the string names
1878 # of such classes (in which case they will be lazily loaded).
1879 ssl_adapters = {
1880     'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
1881     'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
1882     }
1883
1884 def get_ssl_adapter_class(name='pyopenssl'):
1885     adapter = ssl_adapters[name.lower()]
1886     if isinstance(adapter, basestring):
1887         last_dot = adapter.rfind(".")
1888         attr_name = adapter[last_dot + 1:]
1889         mod_path = adapter[:last_dot]
1890        
1891         try:
1892             mod = sys.modules[mod_path]
1893             if mod is None:
1894                 raise KeyError()
1895         except KeyError:
1896             # The last [''] is important.
1897             mod = __import__(mod_path, globals(), locals(), [''])
1898        
1899         # Let an AttributeError propagate outward.
1900         try:
1901             adapter = getattr(mod, attr_name)
1902         except AttributeError:
1903             raise AttributeError("'%s' object has no attribute '%s'"
1904                                  % (mod_path, attr_name))
1905    
1906     return adapter
1907
1908 # -------------------------------- WSGI Stuff -------------------------------- #
1909
1910
1911 class CherryPyWSGIServer(HTTPServer):
1912    
1913     wsgi_version = (1, 1)
1914    
1915     def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
1916                  max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
1917         self.requests = ThreadPool(self, min=numthreads or 1, max=max)
1918         self.wsgi_app = wsgi_app
1919         self.gateway = wsgi_gateways[self.wsgi_version]
1920        
1921         self.bind_addr = bind_addr
1922         if not server_name:
1923             server_name = socket.gethostname()
1924         self.server_name = server_name
1925         self.request_queue_size = request_queue_size
1926        
1927         self.timeout = timeout
1928         self.shutdown_timeout = shutdown_timeout
1929    
1930     def _get_numthreads(self):
1931         return self.requests.min
1932     def _set_numthreads(self, value):
1933         self.requests.min = value
1934     numthreads = property(_get_numthreads, _set_numthreads)
1935
1936
1937 class WSGIGateway(Gateway):
1938    
1939     def __init__(self, req):
1940         self.req = req
1941         self.started_response = False
1942         self.env = self.get_environ()
1943    
1944     def get_environ(self):
1945         """Return a new environ dict targeting the given wsgi.version"""
1946         raise NotImplemented
1947    
1948     def respond(self):
1949         response = self.req.server.wsgi_app(self.env, self.start_response)
1950         try:
1951             for chunk in response:
1952                 # "The start_response callable must not actually transmit
1953                 # the response headers. Instead, it must store them for the
1954                 # server or gateway to transmit only after the first
1955                 # iteration of the application return value that yields
1956                 # a NON-EMPTY string, or upon the application's first
1957                 # invocation of the write() callable." (PEP 333)
1958                 if chunk:
1959                     if isinstance(chunk, unicode):
1960                         chunk = chunk.encode('ISO-8859-1')
1961                     self.write(chunk)
1962         finally:
1963             if hasattr(response, "close"):
1964                 response.close()
1965    
1966     def start_response(self, status, headers, exc_info = None):
1967         """WSGI callable to begin the HTTP response."""
1968         # "The application may call start_response more than once,
1969         # if and only if the exc_info argument is provided."
1970         if self.started_response and not exc_info:
1971             raise AssertionError("WSGI start_response called a second "
1972                                  "time with no exc_info.")
1973         self.started_response = True
1974        
1975         # "if exc_info is provided, and the HTTP headers have already been
1976         # sent, start_response must raise an error, and should raise the
1977         # exc_info tuple."
1978         if self.req.sent_headers:
1979             try:
1980                 raise exc_info[0], exc_info[1], exc_info[2]
1981             finally:
1982                 exc_info = None
1983        
1984         self.req.status = status
1985         for k, v in headers:
1986             if not isinstance(k, str):
1987                 raise TypeError("WSGI response header key %r is not a byte string." % k)
1988             if not isinstance(v, str):
1989                 raise TypeError("WSGI response header value %r is not a byte string." % v)
1990         self.req.outheaders.extend(headers)
1991        
1992         return self.write
1993    
1994     def write(self, chunk):
1995         """WSGI callable to write unbuffered data to the client.
1996         
1997         This method is also used internally by start_response (to write
1998         data from the iterable returned by the WSGI application).
1999         """
2000         if not self.started_response:
2001             raise AssertionError("WSGI write called before start_response.")
2002        
2003         if not self.req.sent_headers:
2004             self.req.sent_headers = True
2005             self.req.send_headers()
2006        
2007         self.req.write(chunk)
2008
2009
2010 class WSGIGateway_10(WSGIGateway):
2011    
2012     def get_environ(self):
2013         """Return a new environ dict targeting the given wsgi.version"""
2014         req = self.req
2015         env = {
2016             # set a non-standard environ entry so the WSGI app can know what
2017             # the *real* server protocol is (and what features to support).
2018             # See http://www.faqs.org/rfcs/rfc2145.html.
2019             'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
2020             'PATH_INFO': req.path,
2021             'QUERY_STRING': req.qs,
2022             'REMOTE_ADDR': req.conn.remote_addr or '',
2023             'REMOTE_PORT': str(req.conn.remote_port or ''),
2024             'REQUEST_METHOD': req.method,
2025             'REQUEST_URI': req.uri,
2026             'SCRIPT_NAME': '',
2027             'SERVER_NAME': req.server.server_name,
2028             # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
2029             'SERVER_PROTOCOL': req.request_protocol,
2030             'SERVER_SOFTWARE': req.server.software,
2031             'wsgi.errors': sys.stderr,
2032             'wsgi.input': req.rfile,
2033             'wsgi.multiprocess': False,
2034             'wsgi.multithread': True,
2035             'wsgi.run_once': False,
2036             'wsgi.url_scheme': req.scheme,
2037             'wsgi.version': (1, 0),
2038             }
2039        
2040         if isinstance(req.server.bind_addr, basestring):
2041             # AF_UNIX. This isn't really allowed by WSGI, which doesn't
2042             # address unix domain sockets. But it's better than nothing.
2043             env["SERVER_PORT"] = ""
2044         else:
2045             env["SERVER_PORT"] = str(req.server.bind_addr[1])
2046        
2047         # CONTENT_TYPE/CONTENT_LENGTH
2048         for k, v in req.inheaders.iteritems():
2049             env["HTTP_" + k.upper().replace("-", "_")] = v
2050         ct = env.pop("HTTP_CONTENT_TYPE", None)
2051         if ct is not None:
2052             env["CONTENT_TYPE"] = ct
2053         cl = env.pop("HTTP_CONTENT_LENGTH", None)
2054         if cl is not None:
2055             env["CONTENT_LENGTH"] = cl
2056        
2057         if req.conn.ssl_env:
2058             env.update(req.conn.ssl_env)
2059        
2060         return env
2061
2062
2063 class WSGIGateway_11(WSGIGateway_10):
2064    
2065     def get_environ(self):
2066         env = WSGIGateway_10.get_environ(self)
2067         env['wsgi.version'] = (1, 1)
2068         return env
2069
2070
2071 class WSGIGateway_u0(WSGIGateway_10):
2072    
2073     def get_environ(self):
2074         """Return a new environ dict targeting the given wsgi.version"""
2075         req = self.req
2076         env_10 = WSGIGateway_10.get_environ(self)
2077         env = dict([(k.decode('ISO-8859-1'), v) for k, v in env_10.iteritems()])
2078         env[u'wsgi.version'] = ('u', 0)
2079        
2080         # Request-URI
2081         env.setdefault(u'wsgi.url_encoding', u'utf-8')
2082         try:
2083             for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
2084                 env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
2085         except UnicodeDecodeError:
2086             # Fall back to latin 1 so apps can transcode if needed.
2087             env[u'wsgi.url_encoding'] = u'ISO-8859-1'
2088             for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
2089                 env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
2090        
2091         for k, v in sorted(env.items()):
2092             if isinstance(v, str) and k not in ('REQUEST_URI', 'wsgi.input'):
2093                 env[k] = v.decode('ISO-8859-1')
2094        
2095         return env
2096
2097 wsgi_gateways = {
2098     (1, 0): WSGIGateway_10,
2099     (1, 1): WSGIGateway_11,
2100     ('u', 0): WSGIGateway_u0,
2101 }
2102
2103 class WSGIPathInfoDispatcher(object):
2104     """A WSGI dispatcher for dispatch based on the PATH_INFO.
2105     
2106     apps: a dict or list of (path_prefix, app) pairs.
2107     """
2108    
2109     def __init__(self, apps):
2110         try:
2111             apps = apps.items()
2112         except AttributeError:
2113             pass
2114        
2115         # Sort the apps by len(path), descending
2116         apps.sort(cmp=lambda x,y: cmp(len(x[0]), len(y[0])))
2117         apps.reverse()
2118        
2119         # The path_prefix strings must start, but not end, with a slash.
2120         # Use "" instead of "/".
2121         self.apps = [(p.rstrip("/"), a) for p, a in apps]
2122    
2123     def __call__(self, environ, start_response):
2124         path = environ["PATH_INFO"] or "/"
2125         for p, app in self.apps:
2126             # The apps list should be sorted by length, descending.
2127             if path.startswith(p + "/") or path == p:
2128                 environ = environ.copy()
2129                 environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
2130                 environ["PATH_INFO"] = path[len(p):]
2131                 return app(environ, start_response)
2132        
2133         start_response('404 Not Found', [('Content-Type', 'text/plain'),
2134                                          ('Content-Length', '0')])
2135         return ['']
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets