| Home | Trees | Indices | Help |
|
|---|
|
|
1 """A high-speed, production ready, thread pooled, generic WSGI 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!\n']
13
14 # Here we set our application to the script_name '/'
15 wsgi_apps = [('/', my_crazy_app)]
16
17 server = wsgiserver.CherryPyWSGIServer(('localhost', 8070), wsgi_apps,
18 server_name='localhost')
19
20 # Want SSL support? Just set these attributes
21 # server.ssl_certificate = <filename>
22 # server.ssl_private_key = <filename>
23
24 if __name__ == '__main__':
25 try:
26 server.start()
27 except KeyboardInterrupt:
28 server.stop()
29
30 This won't call the CherryPy engine (application side) at all, only the
31 WSGI server, which is independant from the rest of CherryPy. Don't
32 let the name "CherryPyWSGIServer" throw you; the name merely reflects
33 its origin, not it's coupling.
34
35 The CherryPy WSGI server can serve as many WSGI applications
36 as you want in one instance:
37
38 wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)]
39
40 """
41
42
43 import base64
44 import Queue
45 import os
46 import re
47 quoted_slash = re.compile("(?i)%2F")
48 import rfc822
49 import socket
50 try:
51 import cStringIO as StringIO
52 except ImportError:
53 import StringIO
54 import sys
55 import threading
56 import time
57 import traceback
58 from urllib import unquote
59 from urlparse import urlparse
60
61 try:
62 from OpenSSL import SSL
63 from OpenSSL import crypto
64 except ImportError:
65 SSL = None
66
67 import errno
68 socket_errors_to_ignore = []
69 # Not all of these names will be defined for every platform.
70 for _ in ("EPIPE", "ETIMEDOUT", "ECONNREFUSED", "ECONNRESET",
71 "EHOSTDOWN", "EHOSTUNREACH",
72 "WSAECONNABORTED", "WSAECONNREFUSED", "WSAECONNRESET",
73 "WSAENETRESET", "WSAETIMEDOUT"):
74 if _ in dir(errno):
75 socket_errors_to_ignore.append(getattr(errno, _))
76 # de-dupe the list
77 socket_errors_to_ignore = dict.fromkeys(socket_errors_to_ignore).keys()
78 socket_errors_to_ignore.append("timed out")
79
80 comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING',
81 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL',
82 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT',
83 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE',
84 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING',
85 'WWW-AUTHENTICATE']
86
88 """An HTTP Request (and response).
89
90 A single HTTP connection may consist of multiple request/response pairs.
91
92 connection: the HTTP Connection object which spawned this request.
93 rfile: the 'read' fileobject from the connection's socket
94 ready: when True, the request has been parsed and is ready to begin
95 generating the response. When False, signals the calling Connection
96 that the response should not be generated and the connection should
97 close.
98 close_connection: signals the calling Connection that the request
99 should close. This does not imply an error! The client and/or
100 server may each request that the connection be closed.
101 chunked_write: if True, output will be encoded with the "chunked"
102 transfer-coding. This value is set automatically inside
103 send_headers.
104 """
105
107 self.connection = connection
108 self.rfile = self.connection.rfile
109 self.sendall = self.connection.sendall
110 self.environ = connection.environ.copy()
111
112 self.ready = False
113 self.started_response = False
114 self.status = ""
115 self.outheaders = []
116 self.sent_headers = False
117 self.close_connection = False
118 self.chunked_write = False
119
121 """Parse the next HTTP request start-line and message-headers."""
122 # HTTP/1.1 connections are persistent by default. If a client
123 # requests a page, then idles (leaves the connection open),
124 # then rfile.readline() will raise socket.error("timed out").
125 # Note that it does this based on the value given to settimeout(),
126 # and doesn't need the client to request or acknowledge the close
127 # (although your TCP stack might suffer for it: cf Apache's history
128 # with FIN_WAIT_2).
129 request_line = self.rfile.readline()
130 if not request_line:
131 # Force self.ready = False so the connection will close.
132 self.ready = False
133 return
134
135 if request_line == "\r\n":
136 # RFC 2616 sec 4.1: "...if the server is reading the protocol
137 # stream at the beginning of a message and receives a CRLF
138 # first, it should ignore the CRLF."
139 # But only ignore one leading line! else we enable a DoS.
140 request_line = self.rfile.readline()
141 if not request_line:
142 self.ready = False
143 return
144
145 server = self.connection.server
146 environ = self.environ
147 environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version
148
149 method, path, req_protocol = request_line.strip().split(" ", 2)
150 environ["REQUEST_METHOD"] = method
151
152 # path may be an abs_path (including "http://host.domain.tld");
153 scheme, location, path, params, qs, frag = urlparse(path)
154
155 if frag:
156 self.simple_response("400 Bad Request",
157 "Illegal #fragment in Request-URI.")
158 return
159
160 if scheme:
161 environ["wsgi.url_scheme"] = scheme
162 if params:
163 path = path + ";" + params
164
165 # Unquote the path+params (e.g. "/this%20path" -> "this path").
166 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
167 #
168 # But note that "...a URI must be separated into its components
169 # before the escaped characters within those components can be
170 # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
171 atoms = [unquote(x) for x in quoted_slash.split(path)]
172 path = "%2F".join(atoms)
173
174 if path == "*":
175 # This means, of course, that the last wsgi_app (shortest path)
176 # will always handle a URI of "*".
177 environ["SCRIPT_NAME"] = ""
178 environ["PATH_INFO"] = "*"
179 self.wsgi_app = server.mount_points[-1][1]
180 else:
181 for mount_point, wsgi_app in server.mount_points:
182 # The mount_points list should be sorted by length, descending.
183 if path.startswith(mount_point + "/") or path == mount_point:
184 environ["SCRIPT_NAME"] = mount_point
185 environ["PATH_INFO"] = path[len(mount_point):]
186 self.wsgi_app = wsgi_app
187 break
188 else:
189 self.simple_response("404 Not Found")
190 return
191
192 # Note that, like wsgiref and most other WSGI servers,
193 # we unquote the path but not the query string.
194 environ["QUERY_STRING"] = qs
195
196 # Compare request and server HTTP protocol versions, in case our
197 # server does not support the requested protocol. Limit our output
198 # to min(req, server). We want the following output:
199 # request server actual written supported response
200 # protocol protocol response protocol feature set
201 # a 1.0 1.0 1.0 1.0
202 # b 1.0 1.1 1.1 1.0
203 # c 1.1 1.0 1.0 1.0
204 # d 1.1 1.1 1.1 1.1
205 # Notice that, in (b), the response will be "HTTP/1.1" even though
206 # the client only understands 1.0. RFC 2616 10.5.6 says we should
207 # only return 505 if the _major_ version is different.
208 rp = int(req_protocol[5]), int(req_protocol[7])
209 sp = int(server.protocol[5]), int(server.protocol[7])
210 if sp[0] != rp[0]:
211 self.simple_response("505 HTTP Version Not Supported")
212 return
213 # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
214 environ["SERVER_PROTOCOL"] = req_protocol
215 # set a non-standard environ entry so the WSGI app can know what
216 # the *real* server protocol is (and what features to support).
217 # See http://www.faqs.org/rfcs/rfc2145.html.
218 environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol
219 self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
220
221 # If the Request-URI was an absoluteURI, use its location atom.
222 if location:
223 environ["SERVER_NAME"] = location
224
225 # then all the http headers
226 try:
227 self.read_headers()
228 except ValueError, ex:
229 self.simple_response("400 Bad Request", repr(ex.args))
230 return
231
232 creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1)
233 environ["AUTH_TYPE"] = creds[0]
234 if creds[0].lower() == 'basic':
235 user, pw = base64.decodestring(creds[1]).split(":", 1)
236 environ["REMOTE_USER"] = user
237
238 # Persistent connection support
239 if self.response_protocol == "HTTP/1.1":
240 if environ.get("HTTP_CONNECTION", "") == "close":
241 self.close_connection = True
242 else:
243 # HTTP/1.0
244 if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
245 self.close_connection = True
246
247 # Transfer-Encoding support
248 te = None
249 if self.response_protocol == "HTTP/1.1":
250 te = environ.get("HTTP_TRANSFER_ENCODING")
251 if te:
252 te = [x.strip().lower() for x in te.split(",") if x.strip()]
253
254 read_chunked = False
255
256 if te:
257 for enc in te:
258 if enc == "chunked":
259 read_chunked = True
260 else:
261 # Note that, even if we see "chunked", we must reject
262 # if there is an extension we don't recognize.
263 self.simple_response("501 Unimplemented")
264 self.close_connection = True
265 return
266
267 if read_chunked:
268 if not self.decode_chunked():
269 return
270
271 # From PEP 333:
272 # "Servers and gateways that implement HTTP 1.1 must provide
273 # transparent support for HTTP 1.1's "expect/continue" mechanism.
274 # This may be done in any of several ways:
275 # 1. Respond to requests containing an Expect: 100-continue request
276 # with an immediate "100 Continue" response, and proceed normally.
277 # 2. Proceed with the request normally, but provide the application
278 # with a wsgi.input stream that will send the "100 Continue"
279 # response if/when the application first attempts to read from
280 # the input stream. The read request must then remain blocked
281 # until the client responds.
282 # 3. Wait until the client decides that the server does not support
283 # expect/continue, and sends the request body on its own.
284 # (This is suboptimal, and is not recommended.)
285 #
286 # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
287 # but it seems like it would be a big slowdown for such a rare case.
288 if environ.get("HTTP_EXPECT", "") == "100-continue":
289 self.simple_response(100)
290
291 self.ready = True
292
294 """Read header lines from the incoming stream."""
295 environ = self.environ
296
297 while True:
298 line = self.rfile.readline()
299 if not line:
300 # No more data--illegal end of headers
301 raise ValueError("Illegal end of headers.")
302
303 if line == '\r\n':
304 # Normal end of headers
305 break
306
307 if line[0] in ' \t':
308 # It's a continuation line.
309 v = line.strip()
310 else:
311 k, v = line.split(":", 1)
312 k, v = k.strip().upper(), v.strip()
313 envname = "HTTP_" + k.replace("-", "_")
314
315 if k in comma_separated_headers:
316 existing = environ.get(envname)
317 if existing:
318 v = ", ".join((existing, v))
319 environ[envname] = v
320
321 ct = environ.pop("HTTP_CONTENT_TYPE", None)
322 if ct:
323 environ["CONTENT_TYPE"] = ct
324 cl = environ.pop("HTTP_CONTENT_LENGTH", None)
325 if cl:
326 environ["CONTENT_LENGTH"] = cl
327
329 """Decode the 'chunked' transfer coding."""
330 cl = 0
331 data = StringIO.StringIO()
332 while True:
333 line = self.rfile.readline().strip().split(";", 1)
334 chunk_size = int(line.pop(0), 16)
335 if chunk_size <= 0:
336 break
337 ## if line: chunk_extension = line[0]
338 cl += chunk_size
339 data.write(self.rfile.read(chunk_size))
340 crlf = self.rfile.read(2)
341 if crlf != "\r\n":
342 self.simple_response("400 Bad Request",
343 "Bad chunked transfer coding "
344 "(expected '\\r\\n', got %r)" % crlf)
345 return
346
347 # Grab any trailer headers
348 self.read_headers()
349
350 data.seek(0)
351 self.environ["wsgi.input"] = data
352 self.environ["CONTENT_LENGTH"] = str(cl) or ""
353 return True
354
356 """Call the appropriate WSGI app and write its iterable output."""
357 response = self.wsgi_app(self.environ, self.start_response)
358 try:
359 for chunk in response:
360 # "The start_response callable must not actually transmit
361 # the response headers. Instead, it must store them for the
362 # server or gateway to transmit only after the first
363 # iteration of the application return value that yields
364 # a NON-EMPTY string, or upon the application's first
365 # invocation of the write() callable." (PEP 333)
366 if chunk:
367 self.write(chunk)
368 finally:
369 if hasattr(response, "close"):
370 response.close()
371 if (self.ready and not self.sent_headers
372 and not self.connection.server.interrupt):
373 self.sent_headers = True
374 self.send_headers()
375 if self.chunked_write:
376 self.sendall("0\r\n\r\n")
377
379 """Write a simple response back to the client."""
380 status = str(status)
381 buf = ["%s %s\r\n" % (self.connection.server.protocol, status),
382 "Content-Length: %s\r\n" % len(msg)]
383
384 if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
385 # Request Entity Too Large
386 self.close_connection = True
387 buf.append("Connection: close\r\n")
388
389 buf.append("\r\n")
390 if msg:
391 buf.append(msg)
392 self.sendall("".join(buf))
393
395 """WSGI callable to begin the HTTP response."""
396 if self.started_response:
397 if not exc_info:
398 raise AssertionError("WSGI start_response called a second "
399 "time with no exc_info.")
400 else:
401 try:
402 raise exc_info[0], exc_info[1], exc_info[2]
403 finally:
404 exc_info = None
405 self.started_response = True
406 self.status = status
407 self.outheaders.extend(headers)
408 return self.write
409
411 """WSGI callable to write unbuffered data to the client.
412
413 This method is also used internally by start_response (to write
414 data from the iterable returned by the WSGI application).
415 """
416 if not self.started_response:
417 raise AssertionError("WSGI write called before start_response.")
418
419 if not self.sent_headers:
420 self.sent_headers = True
421 self.send_headers()
422
423 if self.chunked_write and chunk:
424 buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"]
425 self.sendall("".join(buf))
426 else:
427 self.sendall(chunk)
428
430 """Assert, process, and send the HTTP response message-headers."""
431 hkeys = [key.lower() for key, value in self.outheaders]
432 status = int(self.status[:3])
433
434 if status == 413:
435 # Request Entity Too Large. Close conn to avoid garbage.
436 self.close_connection = True
437 elif "content-length" not in hkeys:
438 # "All 1xx (informational), 204 (no content),
439 # and 304 (not modified) responses MUST NOT
440 # include a message-body." So no point chunking.
441 if status < 200 or status in (204, 205, 304):
442 pass
443 else:
444 if self.response_protocol == 'HTTP/1.1':
445 # Use the chunked transfer-coding
446 self.chunked_write = True
447 self.outheaders.append(("Transfer-Encoding", "chunked"))
448 else:
449 # Closing the conn is the only way to determine len.
450 self.close_connection = True
451
452 if "connection" not in hkeys:
453 if self.response_protocol == 'HTTP/1.1':
454 if self.close_connection:
455 self.outheaders.append(("Connection", "close"))
456 else:
457 if not self.close_connection:
458 self.outheaders.append(("Connection", "Keep-Alive"))
459
460 if "date" not in hkeys:
461 self.outheaders.append(("Date", rfc822.formatdate()))
462
463 server = self.connection.server
464
465 if "server" not in hkeys:
466 self.outheaders.append(("Server", server.version))
467
468 buf = [server.protocol, " ", self.status, "\r\n"]
469 try:
470 buf += [k + ": " + v + "\r\n" for k, v in self.outheaders]
471 except TypeError:
472 if not isinstance(k, str):
473 raise TypeError("WSGI response header key %r is not a string.")
474 if not isinstance(v, str):
475 raise TypeError("WSGI response header value %r is not a string.")
476 else:
477 raise
478 buf.append("\r\n")
479 self.sendall("".join(buf))
480
481
485
486
488 """Wrap the given method with SSL error-trapping.
489
490 is_reader: if False (the default), EOF errors will be raised.
491 If True, EOF errors will return "" (to emulate normal sockets).
492 """
493 def ssl_method_wrapper(self, *args, **kwargs):
494 ## print (id(self), method, args, kwargs)
495 start = time.time()
496 while True:
497 try:
498 return method(self, *args, **kwargs)
499 except (SSL.WantReadError, SSL.WantWriteError):
500 # Sleep and try again. This is dangerous, because it means
501 # the rest of the stack has no way of differentiating
502 # between a "new handshake" error and "client dropped".
503 # Note this isn't an endless loop: there's a timeout below.
504 time.sleep(self.ssl_retry)
505 except SSL.SysCallError, e:
506 if is_reader and e.args == (-1, 'Unexpected EOF'):
507 return ""
508
509 errno = e.args[0]
510 if is_reader and errno in socket_errors_to_ignore:
511 return ""
512 raise socket.error(errno)
513 except SSL.Error, e:
514 if is_reader and e.args == (-1, 'Unexpected EOF'):
515 return ""
516
517 thirdarg = None
518 try:
519 thirdarg = e.args[0][0][2]
520 except IndexError:
521 pass
522
523 if is_reader and thirdarg == 'ssl handshake failure':
524 return ""
525 if thirdarg == 'http request':
526 # The client is talking HTTP to an HTTPS server.
527 raise NoSSLError()
528 raise
529 if time.time() - start > self.ssl_timeout:
530 raise socket.timeout("timed out")
531 return ssl_method_wrapper
532
534 """Faux file object attached to a socket object."""
535
536 ssl_timeout = 3
537 ssl_retry = .01
538
539 close = _ssl_wrap_method(socket._fileobject.close)
540 flush = _ssl_wrap_method(socket._fileobject.flush)
541 write = _ssl_wrap_method(socket._fileobject.write)
542 writelines = _ssl_wrap_method(socket._fileobject.writelines)
543 read = _ssl_wrap_method(socket._fileobject.read, is_reader=True)
544 readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True)
545 readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True)
546
547
549 """An HTTP connection (active socket).
550
551 socket: the raw socket object (usually TCP) for this connection.
552 addr: the "bind address" for the remote end of the socket.
553 For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT).
554 For UNIX domain sockets, this will be a string.
555 server: the HTTP Server for this Connection. Usually, the server
556 object possesses a passive (server) socket which spawns multiple,
557 active (client) sockets, one for each connection.
558
559 environ: a WSGI environ template. This will be copied for each request.
560 rfile: a fileobject for reading from the socket.
561 sendall: a function for writing (+ flush) to the socket.
562 """
563
564 rbufsize = -1
565 RequestHandlerClass = HTTPRequest
566 environ = {"wsgi.version": (1, 0),
567 "wsgi.url_scheme": "http",
568 "wsgi.multithread": True,
569 "wsgi.multiprocess": False,
570 "wsgi.run_once": False,
571 "wsgi.errors": sys.stderr,
572 }
573
575 self.socket = sock
576 self.addr = addr
577 self.server = server
578
579 # Copy the class environ into self.
580 self.environ = self.environ.copy()
581
582 if SSL and isinstance(sock, SSL.ConnectionType):
583 timeout = sock.gettimeout()
584 self.rfile = SSL_fileobject(sock, "r", self.rbufsize)
585 self.rfile.ssl_timeout = timeout
586 self.sendall = _ssl_wrap_method(sock.sendall)
587 self.environ["wsgi.url_scheme"] = "https"
588 self.environ["HTTPS"] = "on"
589 sslenv = getattr(server, "ssl_environ", None)
590 if sslenv:
591 self.environ.update(sslenv)
592 else:
593 self.rfile = sock.makefile("rb", self.rbufsize)
594 self.sendall = sock.sendall
595
596 self.environ.update({"wsgi.input": self.rfile,
597 "SERVER_NAME": self.server.server_name,
598 })
599
600 if isinstance(self.server.bind_addr, basestring):
601 # AF_UNIX. This isn't really allowed by WSGI, which doesn't
602 # address unix domain sockets. But it's better than nothing.
603 self.environ["SERVER_PORT"] = ""
604 else:
605 self.environ["SERVER_PORT"] = str(self.server.bind_addr[1])
606 # optional values
607 # Until we do DNS lookups, omit REMOTE_HOST
608 self.environ["REMOTE_ADDR"] = self.addr[0]
609 self.environ["REMOTE_PORT"] = str(self.addr[1])
610
612 """Read each request and respond appropriately."""
613 try:
614 while True:
615 # (re)set req to None so that if something goes wrong in
616 # the RequestHandlerClass constructor, the error doesn't
617 # get written to the previous request.
618 req = None
619 req = self.RequestHandlerClass(self)
620 # This order of operations should guarantee correct pipelining.
621 req.parse_request()
622 if not req.ready:
623 return
624 req.respond()
625 if req.close_connection:
626 return
627 except socket.error, e:
628 errno = e.args[0]
629 if errno not in socket_errors_to_ignore:
630 if req:
631 req.simple_response("500 Internal Server Error",
632 format_exc())
633 return
634 except (KeyboardInterrupt, SystemExit):
635 raise
636 except NoSSLError:
637 # Unwrap our sendall
638 req.sendall = self.socket._sock.sendall
639 req.simple_response("400 Bad Request",
640 "The client sent a plain HTTP request, but "
641 "this server only speaks HTTPS on this port.")
642 except:
643 if req:
644 req.simple_response("500 Internal Server Error", format_exc())
645
650
651
653 """Like print_exc() but return a string. Backport for Python 2.3."""
654 try:
655 etype, value, tb = sys.exc_info()
656 return ''.join(traceback.format_exception(etype, value, tb, limit))
657 finally:
658 etype = value = tb = None
659
660
661 _SHUTDOWNREQUEST = None
662
664 """Thread which continuously polls a Queue for Connection objects.
665
666 server: the HTTP Server which spawned this thread, and which owns the
667 Queue and is placing active connections into it.
668 ready: a simple flag for the calling server to know when this thread
669 has begun polling the Queue.
670
671 Due to the timing issues of polling a Queue, a WorkerThread does not
672 check its own 'ready' flag after it has started. To stop the thread,
673 it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
674 (one for each running WorkerThread).
675 """
676
681
683 try:
684 self.ready = True
685 while True:
686 conn = self.server.requests.get()
687 if conn is _SHUTDOWNREQUEST:
688 return
689
690 try:
691 conn.communicate()
692 finally:
693 conn.close()
694 except (KeyboardInterrupt, SystemExit), exc:
695 self.server.interrupt = exc
696
697
699 """A thread-safe wrapper for an SSL.Connection.
700
701 *args: the arguments to create the wrapped SSL.Connection(*args).
702 """
703
707
708 for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
709 'renegotiate', 'bind', 'listen', 'connect', 'accept',
710 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list',
711 'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
712 'makefile', 'get_app_data', 'set_app_data', 'state_string',
713 'sock_shutdown', 'get_peer_certificate', 'want_read',
714 'want_write', 'set_connect_state', 'set_accept_state',
715 'connect_ex', 'sendall', 'settimeout'):
716 exec """def %s(self, *args):
717 self._lock.acquire()
718 try:
719 return self._ssl_conn.%s(*args)
720 finally:
721 self._lock.release()
722 """ % (f, f)
723
724
726 """An HTTP server for WSGI.
727
728 bind_addr: a (host, port) tuple if TCP sockets are desired;
729 for UNIX sockets, supply the filename as a string.
730 wsgi_app: the WSGI 'application callable'; multiple WSGI applications
731 may be passed as (script_name, callable) pairs.
732 numthreads: the number of worker threads to create (default 10).
733 server_name: the string to set for WSGI's SERVER_NAME environ entry.
734 Defaults to socket.gethostname().
735 max: the maximum number of queued requests (defaults to -1 = no limit).
736 request_queue_size: the 'backlog' argument to socket.listen();
737 specifies the maximum number of queued connections (default 5).
738 timeout: the timeout in seconds for accepted connections (default 10).
739
740 protocol: the version string to write in the Status-Line of all
741 HTTP responses. For example, "HTTP/1.1" (the default). This
742 also limits the supported features used in the response.
743
744
745 SSL/HTTPS
746 ---------
747 The OpenSSL module must be importable for SSL functionality.
748 You can obtain it from http://pyopenssl.sourceforge.net/
749
750 ssl_certificate: the filename of the server SSL certificate.
751 ssl_privatekey: the filename of the server's private key file.
752
753 If either of these is None (both are None by default), this server
754 will not use SSL. If both are given and are valid, they will be read
755 on server start and used in the SSL context for the listening socket.
756 """
757
758 protocol = "HTTP/1.1"
759 version = "CherryPy/3.0.2"
760 ready = False
761 _interrupt = None
762 ConnectionClass = HTTPConnection
763
764 # Paths to certificate and private key files
765 ssl_certificate = None
766 ssl_private_key = None
767
768 - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
769 max=-1, request_queue_size=5, timeout=10):
770 self.requests = Queue.Queue(max)
771
772 if callable(wsgi_app):
773 # We've been handed a single wsgi_app, in CP-2.1 style.
774 # Assume it's mounted at "".
775 self.mount_points = [("", wsgi_app)]
776 else:
777 # We've been handed a list of (mount_point, wsgi_app) tuples,
778 # so that the server can call different wsgi_apps, and also
779 # correctly set SCRIPT_NAME.
780 self.mount_points = wsgi_app
781 self.mount_points.sort()
782 self.mount_points.reverse()
783
784 self.bind_addr = bind_addr
785 self.numthreads = numthreads or 1
786 if not server_name:
787 server_name = socket.gethostname()
788 self.server_name = server_name
789 self.request_queue_size = request_queue_size
790 self._workerThreads = []
791
792 self.timeout = timeout
793
795 """Run the server forever."""
796 # We don't have to trap KeyboardInterrupt or SystemExit here,
797 # because cherrpy.server already does so, calling self.stop() for us.
798 # If you're using this server with another framework, you should
799 # trap those exceptions in whatever code block calls start().
800 self._interrupt = None
801
802 # Select the appropriate socket
803 if isinstance(self.bind_addr, basestring):
804 # AF_UNIX socket
805
806 # So we can reuse the socket...
807 try: os.unlink(self.bind_addr)
808 except: pass
809
810 # So everyone can access the socket...
811 try: os.chmod(self.bind_addr, 0777)
812 except: pass
813
814 info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
815 else:
816 # AF_INET or AF_INET6 socket
817 # Get the correct address family for our host (allows IPv6 addresses)
818 host, port = self.bind_addr
819 flags = 0
820 if host == '':
821 # Despite the socket module docs, using '' does not
822 # allow AI_PASSIVE to work. Passing None instead
823 # returns '0.0.0.0' like we want. In other words:
824 # host AI_PASSIVE result
825 # '' Y 192.168.x.y
826 # '' N 192.168.x.y
827 # None Y 0.0.0.0
828 # None N 127.0.0.1
829 host = None
830 flags = socket.AI_PASSIVE
831 try:
832 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
833 socket.SOCK_STREAM, 0, flags)
834 except socket.gaierror:
835 # Probably a DNS issue. Assume IPv4.
836 info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)]
837
838 self.socket = None
839 msg = "No socket could be created"
840 for res in info:
841 af, socktype, proto, canonname, sa = res
842 try:
843 self.bind(af, socktype, proto)
844 except socket.error, msg:
845 if self.socket:
846 self.socket.close()
847 self.socket = None
848 continue
849 break
850 if not self.socket:
851 raise socket.error, msg
852
853 # Timeout so KeyboardInterrupt can be caught on Win32
854 self.socket.settimeout(1)
855 self.socket.listen(self.request_queue_size)
856
857 # Create worker threads
858 for i in xrange(self.numthreads):
859 self._workerThreads.append(WorkerThread(self))
860 for worker in self._workerThreads:
861 worker.setName("CP WSGIServer " + worker.getName())
862 worker.start()
863 for worker in self._workerThreads:
864 while not worker.ready:
865 time.sleep(.1)
866
867 self.ready = True
868 while self.ready:
869 self.tick()
870 if self.interrupt:
871 while self.interrupt is True:
872 # Wait for self.stop() to complete. See _set_interrupt.
873 time.sleep(0.1)
874 raise self.interrupt
875
877 """Create (or recreate) the actual socket object."""
878 self.socket = socket.socket(family, type, proto)
879 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
880 ## self.socket.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1)
881 if self.ssl_certificate and self.ssl_private_key:
882 if SSL is None:
883 raise ImportError("You must install pyOpenSSL to use HTTPS.")
884
885 # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
886 ctx = SSL.Context(SSL.SSLv23_METHOD)
887 ctx.use_privatekey_file(self.ssl_private_key)
888 ctx.use_certificate_file(self.ssl_certificate)
889 self.socket = SSLConnection(ctx, self.socket)
890 self.populate_ssl_environ()
891 self.socket.bind(self.bind_addr)
892
894 """Accept a new connection and put it on the Queue."""
895 try:
896 s, addr = self.socket.accept()
897 if not self.ready:
898 return
899 if hasattr(s, 'settimeout'):
900 s.settimeout(self.timeout)
901 conn = self.ConnectionClass(s, addr, self)
902 self.requests.put(conn)
903 except socket.timeout:
904 # The only reason for the timeout in start() is so we can
905 # notice keyboard interrupts on Win32, which don't interrupt
906 # accept() by default
907 return
908 except socket.error, x:
909 msg = x.args[1]
910 if msg in ("Bad file descriptor", "Socket operation on non-socket"):
911 # Our socket was closed.
912 return
913 if msg == "Resource temporarily unavailable":
914 # Just try again. See http://www.cherrypy.org/ticket/479.
915 return
916 raise
917
919 return self._interrupt
924 interrupt = property(_get_interrupt, _set_interrupt,
925 doc="Set this to an Exception instance to "
926 "interrupt the server.")
927
929 """Gracefully shutdown a server that is serving forever."""
930 self.ready = False
931
932 sock = getattr(self, "socket", None)
933 if sock:
934 if not isinstance(self.bind_addr, basestring):
935 # Touch our own socket to make accept() return immediately.
936 try:
937 host, port = sock.getsockname()[:2]
938 except socket.error, x:
939 if x.args[1] != "Bad file descriptor":
940 raise
941 else:
942 # Note that we're explicitly NOT using AI_PASSIVE,
943 # here, because we want an actual IP to touch.
944 # localhost won't work if we've bound to a public IP,
945 # but it would if we bound to INADDR_ANY via host = ''.
946 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
947 socket.SOCK_STREAM):
948 af, socktype, proto, canonname, sa = res
949 s = None
950 try:
951 s = socket.socket(af, socktype, proto)
952 # See http://groups.google.com/group/cherrypy-users/
953 # browse_frm/thread/bbfe5eb39c904fe0
954 s.settimeout(1.0)
955 s.connect((host, port))
956 s.close()
957 except socket.error:
958 if s:
959 s.close()
960 if hasattr(sock, "close"):
961 sock.close()
962 self.socket = None
963
964 # Must shut down threads here so the code that calls
965 # this method can know when all threads are stopped.
966 for worker in self._workerThreads:
967 self.requests.put(_SHUTDOWNREQUEST)
968
969 # Don't join currentThread (when stop is called inside a request).
970 current = threading.currentThread()
971 while self._workerThreads:
972 worker = self._workerThreads.pop()
973 if worker is not current and worker.isAlive:
974 try:
975 worker.join()
976 except AssertionError:
977 pass
978
980 """Create WSGI environ entries to be merged into each request."""
981 cert = open(self.ssl_certificate).read()
982 cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
983 self.ssl_environ = {
984 # pyOpenSSL doesn't provide access to any of these AFAICT
985 ## 'SSL_PROTOCOL': 'SSLv2',
986 ## SSL_CIPHER string The cipher specification name
987 ## SSL_VERSION_INTERFACE string The mod_ssl program version
988 ## SSL_VERSION_LIBRARY string The OpenSSL program version
989 }
990
991 # Server certificate attributes
992 self.ssl_environ.update({
993 'SSL_SERVER_M_VERSION': cert.get_version(),
994 'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
995 ## 'SSL_SERVER_V_START': Validity of server's certificate (start time),
996 ## 'SSL_SERVER_V_END': Validity of server's certificate (end time),
997 })
998
999 for prefix, dn in [("I", cert.get_issuer()),
1000 ("S", cert.get_subject())]:
1001 # X509Name objects don't seem to have a way to get the
1002 # complete DN string. Use str() and slice it instead,
1003 # because str(dn) == "<X509Name object '/C=US/ST=...'>"
1004 dnstr = str(dn)[18:-2]
1005
1006 wsgikey = 'SSL_SERVER_%s_DN' % prefix
1007 self.ssl_environ[wsgikey] = dnstr
1008
1009 # The DN should be of the form: /k1=v1/k2=v2, but we must allow
1010 # for any value to contain slashes itself (in a URL).
1011 while dnstr:
1012 pos = dnstr.rfind("=")
1013 dnstr, value = dnstr[:pos], dnstr[pos + 1:]
1014 pos = dnstr.rfind("/")
1015 dnstr, key = dnstr[:pos], dnstr[pos + 1:]
1016 if key and value:
1017 wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
1018 self.ssl_environ[wsgikey] = value
1019
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0beta1 on Thu Jan 24 16:33:12 2008 | http://epydoc.sourceforge.net |