Package wsgiwapi :: Package cpwsgiserver
[frames] | no frames]

Source Code for Package wsgiwapi.cpwsgiserver

   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      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 these attributes: 
  25       
  26      server.ssl_certificate = <filename> 
  27      server.ssl_private_key = <filename> 
  28       
  29      if __name__ == '__main__': 
  30          try: 
  31              server.start() 
  32          except KeyboardInterrupt: 
  33              server.stop() 
  34   
  35  This won't call the CherryPy engine (application side) at all, only the 
  36  WSGI server, which is independant from the rest of CherryPy. Don't 
  37  let the name "CherryPyWSGIServer" throw you; the name merely reflects 
  38  its origin, not its coupling. 
  39   
  40  For those of you wanting to understand internals of this module, here's the 
  41  basic call flow. The server's listening thread runs a very tight loop, 
  42  sticking incoming connections onto a Queue: 
  43   
  44      server = CherryPyWSGIServer(...) 
  45      server.start() 
  46      while True: 
  47          tick() 
  48          # This blocks until a request comes in: 
  49          child = socket.accept() 
  50          conn = HTTPConnection(child, ...) 
  51          server.requests.put(conn) 
  52   
  53  Worker threads are kept in a pool and poll the Queue, popping off and then 
  54  handling each connection in turn. Each connection can consist of an arbitrary 
  55  number of requests and their responses, so we run a nested loop: 
  56   
  57      while True: 
  58          conn = server.requests.get() 
  59          conn.communicate() 
  60          ->  while True: 
  61                  req = HTTPRequest(...) 
  62                  req.parse_request() 
  63                  ->  # Read the Request-Line, e.g. "GET /page HTTP/1.1" 
  64                      req.rfile.readline() 
  65                      req.read_headers() 
  66                  req.respond() 
  67                  ->  response = wsgi_app(...) 
  68                      try: 
  69                          for chunk in response: 
  70                              if chunk: 
  71                                  req.write(chunk) 
  72                      finally: 
  73                          if hasattr(response, "close"): 
  74                              response.close() 
  75                  if req.close_connection: 
  76                      return 
  77  """ 
  78   
  79   
  80  import base64 
  81  import os 
  82  import Queue 
  83  import re 
  84  quoted_slash = re.compile("(?i)%2F") 
  85  import rfc822 
  86  import socket 
  87  try: 
  88      import cStringIO as StringIO 
  89  except ImportError: 
  90      import StringIO 
  91   
  92  _fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring) 
  93   
  94  import sys 
  95  import threading 
  96  import time 
  97  import traceback 
  98  from urllib import unquote 
  99  from urlparse import urlparse 
 100  import warnings 
 101   
 102  try: 
 103      from OpenSSL import SSL 
 104      from OpenSSL import crypto 
 105  except ImportError: 
 106      SSL = None 
 107   
 108  import errno 
 109   
110 -def plat_specific_errors(*errnames):
111 """Return error numbers for all errors in errnames on this platform. 112 113 The 'errno' module contains different global constants depending on 114 the specific platform (OS). This function will return the list of 115 numeric values for a given list of potential names. 116 """ 117 errno_names = dir(errno) 118 nums = [getattr(errno, k) for k in errnames if k in errno_names] 119 # de-dupe the list 120 return dict.fromkeys(nums).keys()
121 122 socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR") 123 124 socket_errors_to_ignore = plat_specific_errors( 125 "EPIPE", 126 "EBADF", "WSAEBADF", 127 "ENOTSOCK", "WSAENOTSOCK", 128 "ETIMEDOUT", "WSAETIMEDOUT", 129 "ECONNREFUSED", "WSAECONNREFUSED", 130 "ECONNRESET", "WSAECONNRESET", 131 "ECONNABORTED", "WSAECONNABORTED", 132 "ENETRESET", "WSAENETRESET", 133 "EHOSTDOWN", "EHOSTUNREACH", 134 ) 135 socket_errors_to_ignore.append("timed out") 136 137 socket_errors_nonblocking = plat_specific_errors( 138 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK') 139 140 comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING', 141 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL', 142 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT', 143 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE', 144 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING', 145 'WWW-AUTHENTICATE'] 146 147
148 -class WSGIPathInfoDispatcher(object):
149 """A WSGI dispatcher for dispatch based on the PATH_INFO. 150 151 apps: a dict or list of (path_prefix, app) pairs. 152 """ 153
154 - def __init__(self, apps):
155 try: 156 apps = apps.items() 157 except AttributeError: 158 pass 159 160 # Sort the apps by len(path), descending 161 apps.sort() 162 apps.reverse() 163 164 # The path_prefix strings must start, but not end, with a slash. 165 # Use "" instead of "/". 166 self.apps = [(p.rstrip("/"), a) for p, a in apps]
167
168 - def __call__(self, environ, start_response):
169 path = environ["PATH_INFO"] or "/" 170 for p, app in self.apps: 171 # The apps list should be sorted by length, descending. 172 if path.startswith(p + "/") or path == p: 173 environ = environ.copy() 174 environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p 175 environ["PATH_INFO"] = path[len(p):] 176 return app(environ, start_response) 177 178 start_response('404 Not Found', [('Content-Type', 'text/plain'), 179 ('Content-Length', '0')]) 180 return ['']
181 182
183 -class MaxSizeExceeded(Exception):
184 pass
185
186 -class SizeCheckWrapper(object):
187 """Wraps a file-like object, raising MaxSizeExceeded if too large.""" 188
189 - def __init__(self, rfile, maxlen):
190 self.rfile = rfile 191 self.maxlen = maxlen 192 self.bytes_read = 0
193
194 - def _check_length(self):
195 if self.maxlen and self.bytes_read > self.maxlen: 196 raise MaxSizeExceeded()
197
198 - def read(self, size=None):
199 data = self.rfile.read(size) 200 self.bytes_read += len(data) 201 self._check_length() 202 return data
203
204 - def readline(self, size=None):
205 if size is not None: 206 data = self.rfile.readline(size) 207 self.bytes_read += len(data) 208 self._check_length() 209 return data 210 211 # User didn't specify a size ... 212 # We read the line in chunks to make sure it's not a 100MB line ! 213 res = [] 214 while True: 215 data = self.rfile.readline(256) 216 self.bytes_read += len(data) 217 self._check_length() 218 res.append(data) 219 # See http://www.cherrypy.org/ticket/421 220 if len(data) < 256 or data[-1:] == "\n": 221 return ''.join(res)
222
223 - def readlines(self, sizehint=0):
224 # Shamelessly stolen from StringIO 225 total = 0 226 lines = [] 227 line = self.readline() 228 while line: 229 lines.append(line) 230 total += len(line) 231 if 0 < sizehint <= total: 232 break 233 line = self.readline() 234 return lines
235
236 - def close(self):
237 self.rfile.close()
238
239 - def __iter__(self):
240 return self
241
242 - def next(self):
243 data = self.rfile.next() 244 self.bytes_read += len(data) 245 self._check_length() 246 return data
247 248
249 -class HTTPRequest(object):
250 """An HTTP Request (and response). 251 252 A single HTTP connection may consist of multiple request/response pairs. 253 254 send: the 'send' method from the connection's socket object. 255 wsgi_app: the WSGI application to call. 256 environ: a partial WSGI environ (server and connection entries). 257 The caller MUST set the following entries: 258 * All wsgi.* entries, including .input 259 * SERVER_NAME and SERVER_PORT 260 * Any SSL_* entries 261 * Any custom entries like REMOTE_ADDR and REMOTE_PORT 262 * SERVER_SOFTWARE: the value to write in the "Server" response header. 263 * ACTUAL_SERVER_PROTOCOL: the value to write in the Status-Line of 264 the response. From RFC 2145: "An HTTP server SHOULD send a 265 response version equal to the highest version for which the 266 server is at least conditionally compliant, and whose major 267 version is less than or equal to the one received in the 268 request. An HTTP server MUST NOT send a version for which 269 it is not at least conditionally compliant." 270 271 outheaders: a list of header tuples to write in the response. 272 ready: when True, the request has been parsed and is ready to begin 273 generating the response. When False, signals the calling Connection 274 that the response should not be generated and the connection should 275 close. 276 close_connection: signals the calling Connection that the request 277 should close. This does not imply an error! The client and/or 278 server may each request that the connection be closed. 279 chunked_write: if True, output will be encoded with the "chunked" 280 transfer-coding. This value is set automatically inside 281 send_headers. 282 """ 283 284 max_request_header_size = 0 285 max_request_body_size = 0 286
287 - def __init__(self, wfile, environ, wsgi_app):
288 self.rfile = environ['wsgi.input'] 289 self.wfile = wfile 290 self.environ = environ.copy() 291 self.wsgi_app = wsgi_app 292 293 self.ready = False 294 self.started_response = False 295 self.status = "" 296 self.outheaders = [] 297 self.sent_headers = False 298 self.close_connection = False 299 self.chunked_write = False
300
301 - def parse_request(self):
302 """Parse the next HTTP request start-line and message-headers.""" 303 self.rfile.maxlen = self.max_request_header_size 304 self.rfile.bytes_read = 0 305 306 try: 307 self._parse_request() 308 except MaxSizeExceeded: 309 self.simple_response("413 Request Entity Too Large") 310 return
311
312 - def _parse_request(self):
313 # HTTP/1.1 connections are persistent by default. If a client 314 # requests a page, then idles (leaves the connection open), 315 # then rfile.readline() will raise socket.error("timed out"). 316 # Note that it does this based on the value given to settimeout(), 317 # and doesn't need the client to request or acknowledge the close 318 # (although your TCP stack might suffer for it: cf Apache's history 319 # with FIN_WAIT_2). 320 request_line = self.rfile.readline() 321 if not request_line: 322 # Force self.ready = False so the connection will close. 323 self.ready = False 324 return 325 326 if request_line == "\r\n": 327 # RFC 2616 sec 4.1: "...if the server is reading the protocol 328 # stream at the beginning of a message and receives a CRLF 329 # first, it should ignore the CRLF." 330 # But only ignore one leading line! else we enable a DoS. 331 request_line = self.rfile.readline() 332 if not request_line: 333 self.ready = False 334 return 335 336 environ = self.environ 337 338 try: 339 method, path, req_protocol = request_line.strip().split(" ", 2) 340 except ValueError: 341 self.simple_response(400, "Malformed Request-Line") 342 return 343 344 environ["REQUEST_METHOD"] = method 345 346 # path may be an abs_path (including "http://host.domain.tld"); 347 scheme, location, path, params, qs, frag = urlparse(path) 348 349 if frag: 350 self.simple_response("400 Bad Request", 351 "Illegal #fragment in Request-URI.") 352 return 353 354 if scheme: 355 environ["wsgi.url_scheme"] = scheme 356 if params: 357 path = path + ";" + params 358 359 environ["SCRIPT_NAME"] = "" 360 361 # Unquote the path+params (e.g. "/this%20path" -> "this path"). 362 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 363 # 364 # But note that "...a URI must be separated into its components 365 # before the escaped characters within those components can be 366 # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 367 atoms = [unquote(x) for x in quoted_slash.split(path)] 368 path = "%2F".join(atoms) 369 environ["PATH_INFO"] = path 370 371 # Note that, like wsgiref and most other WSGI servers, 372 # we unquote the path but not the query string. 373 environ["QUERY_STRING"] = qs 374 375 # Compare request and server HTTP protocol versions, in case our 376 # server does not support the requested protocol. Limit our output 377 # to min(req, server). We want the following output: 378 # request server actual written supported response 379 # protocol protocol response protocol feature set 380 # a 1.0 1.0 1.0 1.0 381 # b 1.0 1.1 1.1 1.0 382 # c 1.1 1.0 1.0 1.0 383 # d 1.1 1.1 1.1 1.1 384 # Notice that, in (b), the response will be "HTTP/1.1" even though 385 # the client only understands 1.0. RFC 2616 10.5.6 says we should 386 # only return 505 if the _major_ version is different. 387 rp = int(req_protocol[5]), int(req_protocol[7]) 388 server_protocol = environ["ACTUAL_SERVER_PROTOCOL"] 389 sp = int(server_protocol[5]), int(server_protocol[7]) 390 if sp[0] != rp[0]: 391 self.simple_response("505 HTTP Version Not Supported") 392 return 393 # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. 394 environ["SERVER_PROTOCOL"] = req_protocol 395 self.response_protocol = "HTTP/%s.%s" % min(rp, sp) 396 397 # If the Request-URI was an absoluteURI, use its location atom. 398 if location: 399 environ["SERVER_NAME"] = location 400 401 # then all the http headers 402 try: 403 self.read_headers() 404 except ValueError, ex: 405 self.simple_response("400 Bad Request", repr(ex.args)) 406 return 407 408 mrbs = self.max_request_body_size 409 if mrbs and int(environ.get("CONTENT_LENGTH", 0)) > mrbs: 410 self.simple_response("413 Request Entity Too Large") 411 return 412 413 # Persistent connection support 414 if self.response_protocol == "HTTP/1.1": 415 # Both server and client are HTTP/1.1 416 if environ.get("HTTP_CONNECTION", "") == "close": 417 self.close_connection = True 418 else: 419 # Either the server or client (or both) are HTTP/1.0 420 if environ.get("HTTP_CONNECTION", "") != "Keep-Alive": 421 self.close_connection = True 422 423 # Transfer-Encoding support 424 te = None 425 if self.response_protocol == "HTTP/1.1": 426 te = environ.get("HTTP_TRANSFER_ENCODING") 427 if te: 428 te = [x.strip().lower() for x in te.split(",") if x.strip()] 429 430 self.chunked_read = False 431 432 if te: 433 for enc in te: 434 if enc == "chunked": 435 self.chunked_read = True 436 else: 437 # Note that, even if we see "chunked", we must reject 438 # if there is an extension we don't recognize. 439 self.simple_response("501 Unimplemented") 440 self.close_connection = True 441 return 442 443 # From PEP 333: 444 # "Servers and gateways that implement HTTP 1.1 must provide 445 # transparent support for HTTP 1.1's "expect/continue" mechanism. 446 # This may be done in any of several ways: 447 # 1. Respond to requests containing an Expect: 100-continue request 448 # with an immediate "100 Continue" response, and proceed normally. 449 # 2. Proceed with the request normally, but provide the application 450 # with a wsgi.input stream that will send the "100 Continue" 451 # response if/when the application first attempts to read from 452 # the input stream. The read request must then remain blocked 453 # until the client responds. 454 # 3. Wait until the client decides that the server does not support 455 # expect/continue, and sends the request body on its own. 456 # (This is suboptimal, and is not recommended.) 457 # 458 # We used to do 3, but are now doing 1. Maybe we'll do 2 someday, 459 # but it seems like it would be a big slowdown for such a rare case. 460 if environ.get("HTTP_EXPECT", "") == "100-continue": 461 self.simple_response(100) 462 463 self.ready = True
464
465 - def read_headers(self):
466 """Read header lines from the incoming stream.""" 467 environ = self.environ 468 469 while True: 470 line = self.rfile.readline() 471 if not line: 472 # No more data--illegal end of headers 473 raise ValueError("Illegal end of headers.") 474 475 if line == '\r\n': 476 # Normal end of headers 477 break 478 479 if line[0] in ' \t': 480 # It's a continuation line. 481 v = line.strip() 482 else: 483 k, v = line.split(":", 1) 484 k, v = k.strip().upper(), v.strip() 485 envname = "HTTP_" + k.replace("-", "_") 486 487 if k in comma_separated_headers: 488 existing = environ.get(envname) 489 if existing: 490 v = ", ".join((existing, v)) 491 environ[envname] = v 492 493 ct = environ.pop("HTTP_CONTENT_TYPE", None) 494 if ct is not None: 495 environ["CONTENT_TYPE"] = ct 496 cl = environ.pop("HTTP_CONTENT_LENGTH", None) 497 if cl is not None: 498 environ["CONTENT_LENGTH"] = cl
499
500 - def decode_chunked(self):
501 """Decode the 'chunked' transfer coding.""" 502 cl = 0 503 data = StringIO.StringIO() 504 while True: 505 line = self.rfile.readline().strip().split(";", 1) 506 chunk_size = int(line.pop(0), 16) 507 if chunk_size <= 0: 508 break 509 ## if line: chunk_extension = line[0] 510 cl += chunk_size 511 data.write(self.rfile.read(chunk_size)) 512 crlf = self.rfile.read(2) 513 if crlf != "\r\n": 514 self.simple_response("400 Bad Request", 515 "Bad chunked transfer coding " 516 "(expected '\\r\\n', got %r)" % crlf) 517 return 518 519 # Grab any trailer headers 520 self.read_headers() 521 522 data.seek(0) 523 self.environ["wsgi.input"] = data 524 self.environ["CONTENT_LENGTH"] = str(cl) or "" 525 return True
526
527 - def respond(self):
528 """Call the appropriate WSGI app and write its iterable output.""" 529 # Set rfile.maxlen to ensure we don't read past Content-Length. 530 # This will also be used to read the entire request body if errors 531 # are raised before the app can read the body. 532 if self.chunked_read: 533 # If chunked, Content-Length will be 0. 534 self.rfile.maxlen = self.max_request_body_size 535 else: 536 cl = int(self.environ.get("CONTENT_LENGTH", 0)) 537 if self.max_request_body_size: 538 self.rfile.maxlen = min(cl, self.max_request_body_size) 539 else: 540 self.rfile.maxlen = cl 541 self.rfile.bytes_read = 0 542 543 try: 544 self._respond() 545 except MaxSizeExceeded: 546 if not self.sent_headers: 547 self.simple_response("413 Request Entity Too Large") 548 return
549
550 - def _respond(self):
551 if self.chunked_read: 552 if not self.decode_chunked(): 553 self.close_connection = True 554 return 555 556 response = self.wsgi_app(self.environ, self.start_response) 557 try: 558 for chunk in response: 559 # "The start_response callable must not actually transmit 560 # the response headers. Instead, it must store them for the 561 # server or gateway to transmit only after the first 562 # iteration of the application return value that yields 563 # a NON-EMPTY string, or upon the application's first 564 # invocation of the write() callable." (PEP 333) 565 if chunk: 566 self.write(chunk) 567 finally: 568 if hasattr(response, "close"): 569 response.close() 570 571 if (self.ready and not self.sent_headers): 572 self.sent_headers = True 573 self.send_headers() 574 if self.chunked_write: 575 self.wfile.sendall("0\r\n\r\n")
576
577 - def simple_response(self, status, msg=""):
578 """Write a simple response back to the client.""" 579 status = str(status) 580 buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status), 581 "Content-Length: %s\r\n" % len(msg), 582 "Content-Type: text/plain\r\n"] 583 584 if status[:3] == "413" and self.response_protocol == 'HTTP/1.1': 585 # Request Entity Too Large 586 self.close_connection = True 587 buf.append("Connection: close\r\n") 588 589 buf.append("\r\n") 590 if msg: 591 buf.append(msg) 592 593 try: 594 self.wfile.sendall("".join(buf)) 595 except socket.error, x: 596 if x.args[0] not in socket_errors_to_ignore: 597 raise
598
599 - def start_response(self, status, headers, exc_info = None):
600 """WSGI callable to begin the HTTP response.""" 601 # "The application may call start_response more than once, 602 # if and only if the exc_info argument is provided." 603 if self.started_response and not exc_info: 604 raise AssertionError("WSGI start_response called a second " 605 "time with no exc_info.") 606 607 # "if exc_info is provided, and the HTTP headers have already been 608 # sent, start_response must raise an error, and should raise the 609 # exc_info tuple." 610 if self.sent_headers: 611 try: 612 raise exc_info[0], exc_info[1], exc_info[2] 613 finally: 614 exc_info = None 615 616 self.started_response = True 617 self.status = status 618 self.outheaders.extend(headers) 619 return self.write
620
621 - def write(self, chunk):
622 """WSGI callable to write unbuffered data to the client. 623 624 This method is also used internally by start_response (to write 625 data from the iterable returned by the WSGI application). 626 """ 627 if not self.started_response: 628 raise AssertionError("WSGI write called before start_response.") 629 630 if not self.sent_headers: 631 self.sent_headers = True 632 self.send_headers() 633 634 if self.chunked_write and chunk: 635 buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"] 636 self.wfile.sendall("".join(buf)) 637 else: 638 self.wfile.sendall(chunk)
639
640 - def send_headers(self):
641 """Assert, process, and send the HTTP response message-headers.""" 642 hkeys = [key.lower() for key, value in self.outheaders] 643 status = int(self.status[:3]) 644 645 if status == 413: 646 # Request Entity Too Large. Close conn to avoid garbage. 647 self.close_connection = True 648 elif "content-length" not in hkeys: 649 # "All 1xx (informational), 204 (no content), 650 # and 304 (not modified) responses MUST NOT 651 # include a message-body." So no point chunking. 652 if status < 200 or status in (204, 205, 304): 653 pass 654 else: 655 if (self.response_protocol == 'HTTP/1.1' 656 and self.environ["REQUEST_METHOD"] != 'HEAD'): 657 # Use the chunked transfer-coding 658 self.chunked_write = True 659 self.outheaders.append(("Transfer-Encoding", "chunked")) 660 else: 661 # Closing the conn is the only way to determine len. 662 self.close_connection = True 663 664 if "connection" not in hkeys: 665 if self.response_protocol == 'HTTP/1.1': 666 # Both server and client are HTTP/1.1 or better 667 if self.close_connection: 668 self.outheaders.append(("Connection", "close")) 669 else: 670 # Server and/or client are HTTP/1.0 671 if not self.close_connection: 672 self.outheaders.append(("Connection", "Keep-Alive")) 673 674 if (not self.close_connection) and (not self.chunked_read): 675 # Read any remaining request body data on the socket. 676 # "If an origin server receives a request that does not include an 677 # Expect request-header field with the "100-continue" expectation, 678 # the request includes a request body, and the server responds 679 # with a final status code before reading the entire request body 680 # from the transport connection, then the server SHOULD NOT close 681 # the transport connection until it has read the entire request, 682 # or until the client closes the connection. Otherwise, the client 683 # might not reliably receive the response message. However, this 684 # requirement is not be construed as preventing a server from 685 # defending itself against denial-of-service attacks, or from 686 # badly broken client implementations." 687 size = self.rfile.maxlen - self.rfile.bytes_read 688 if size > 0: 689 self.rfile.read(size) 690 691 if "date" not in hkeys: 692 self.outheaders.append(("Date", rfc822.formatdate())) 693 694 if "server" not in hkeys: 695 self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE'])) 696 697 buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"] 698 try: 699 buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] 700 except TypeError: 701 if not isinstance(k, str): 702 raise TypeError("WSGI response header key %r is not a string.") 703 if not isinstance(v, str): 704 raise TypeError("WSGI response header value %r is not a string.") 705 else: 706 raise 707 buf.append("\r\n") 708 self.wfile.sendall("".join(buf))
709 710
711 -class NoSSLError(Exception):
712 """Exception raised when a client speaks HTTP to an HTTPS socket.""" 713 pass
714 715
716 -class FatalSSLAlert(Exception):
717 """Exception raised when the SSL implementation signals a fatal alert.""" 718 pass
719 720 721 if not _fileobject_uses_str_type:
722 - class CP_fileobject(socket._fileobject):
723 """Faux file object attached to a socket object.""" 724
725 - def sendall(self, data):
726 """Sendall for non-blocking sockets.""" 727 while data: 728 try: 729 bytes_sent = self.send(data) 730 data = data[bytes_sent:] 731 except socket.error, e: 732 if e.args[0] not in socket_errors_nonblocking: 733 raise
734
735 - def send(self, data):
736 return self._sock.send(data)
737
738 - def flush(self):
739 if self._wbuf: 740 buffer = "".join(self._wbuf) 741 self._wbuf = [] 742 self.sendall(buffer)
743
744 - def recv(self, size):
745 while True: 746 try: 747 return self._sock.recv(size) 748 except socket.error, e: 749 if (e.args[0] not in socket_errors_nonblocking 750 and e.args[0] not in socket_error_eintr): 751 raise
752
753 - def read(self, size=-1):
754 # Use max, disallow tiny reads in a loop as they are very inefficient. 755 # We never leave read() with any leftover data from a new recv() call 756 # in our internal buffer. 757 rbufsize = max(self._rbufsize, self.default_bufsize) 758 # Our use of StringIO rather than lists of string objects returned by 759 # recv() minimizes memory usage and fragmentation that occurs when 760 # rbufsize is large compared to the typical return value of recv(). 761 buf = self._rbuf 762 buf.seek(0, 2) # seek end 763 if size < 0: 764 # Read until EOF 765 self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. 766 while True: 767 data = self.recv(rbufsize) 768 if not data: 769 break 770 buf.write(data) 771 return buf.getvalue() 772 else: 773 # Read until size bytes or EOF seen, whichever comes first 774 buf_len = buf.tell() 775 if buf_len >= size: 776 # Already have size bytes in our buffer? Extract and return. 777 buf.seek(0) 778 rv = buf.read(size) 779 self._rbuf = StringIO.StringIO() 780 self._rbuf.write(buf.read()) 781 return rv 782 783 self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. 784 while True: 785 left = size - buf_len 786 # recv() will malloc the amount of memory given as its 787 # parameter even though it often returns much less data 788 # than that. The returned data string is short lived 789 # as we copy it into a StringIO and free it. This avoids 790 # fragmentation issues on many platforms. 791 data = self.recv(left) 792 if not data: 793 break 794 n = len(data) 795 if n == size and not buf_len: 796 # Shortcut. Avoid buffer data copies when: 797 # - We have no data in our buffer. 798 # AND 799 # - Our call to recv returned exactly the 800 # number of bytes we were asked to read. 801 return data 802 if n == left: 803 buf.write(data) 804 del data # explicit free 805 break 806 assert n <= left, "recv(%d) returned %d bytes" % (left, n) 807 buf.write(data) 808 buf_len += n 809 del data # explicit free 810 #assert buf_len == buf.tell() 811 return buf.getvalue()
812
813 - def readline(self, size=-1):
814 buf = self._rbuf 815 buf.seek(0, 2) # seek end 816 if buf.tell() > 0: 817 # check if we already have it in our buffer 818 buf.seek(0) 819 bline = buf.readline(size) 820 if bline.endswith('\n') or len(bline) == size: 821 self._rbuf = StringIO.StringIO() 822 self._rbuf.write(buf.read()) 823 return bline 824 del bline 825 if size < 0: 826 # Read until \n or EOF, whichever comes first 827 if self._rbufsize <= 1: 828 # Speed up unbuffered case 829 buf.seek(0) 830 buffers = [buf.read()] 831 self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. 832 data = None 833 recv = self.recv 834 while data != "\n": 835 data = recv(1) 836 if not data: 837 break 838 buffers.append(data) 839 return "".join(buffers) 840 841 buf.seek(0, 2) # seek end 842 self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. 843 while True: 844 data = self.recv(self._rbufsize) 845 if not data: 846 break 847 nl = data.find('\n') 848 if nl >= 0: 849 nl += 1 850 buf.write(data[:nl]) 851 self._rbuf.write(data[nl:]) 852 del data 853 break 854 buf.write(data) 855 return buf.getvalue() 856 else: 857 # Read until size bytes or \n or EOF seen, whichever comes first 858 buf.seek(0, 2) # seek end 859 buf_len = buf.tell() 860 if buf_len >= size: 861 buf.seek(0) 862 rv = buf.read(size) 863 self._rbuf = StringIO.StringIO() 864 self._rbuf.write(buf.read()) 865 return rv 866 self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. 867 while True: 868 data = self.recv(self._rbufsize) 869 if not data: 870 break 871 left = size - buf_len 872 # did we just receive a newline? 873 nl = data.find('\n', 0, left) 874 if nl >= 0: 875 nl += 1 876 # save the excess data to _rbuf 877 self._rbuf.write(data[nl:]) 878 if buf_len: 879 buf.write(data[:nl]) 880 break 881 else: 882 # Shortcut. Avoid data copy through buf when returning 883 # a substring of our first recv(). 884 return data[:nl] 885 n = len(data) 886 if n == size and not buf_len: 887 # Shortcut. Avoid data copy through buf when 888 # returning exactly all of our first recv(). 889 return data 890 if n >= left: 891 buf.write(data[:left]) 892 self._rbuf.write(data[left:]) 893 break 894 buf.write(data) 895 buf_len += n 896 #assert buf_len == buf.tell() 897 return buf.getvalue()
898 899 else:
900 - class CP_fileobject(socket._fileobject):
901 """Faux file object attached to a socket object.""" 902
903 - def sendall(self, data):
904 """Sendall for non-blocking sockets.""" 905 while data: 906 try: 907 bytes_sent = self.send(data) 908 data = data[bytes_sent:] 909 except socket.error, e: 910 if e.args[0] not in socket_errors_nonblocking: 911 raise
912
913 - def send(self, data):
914 return self._sock.send(data)
915
916 - def flush(self):
917 if self._wbuf: 918 buffer = "".join(self._wbuf) 919 self._wbuf = [] 920 self.sendall(buffer)
921
922 - def recv(self, size):
923 while True: 924 try: 925 return self._sock.recv(size) 926 except socket.error, e: 927 if (e.args[0] not in socket_errors_nonblocking 928 and e.args[0] not in socket_error_eintr): 929 raise
930
931 - def read(self, size=-1):
932 if size < 0: 933 # Read until EOF 934 buffers = [self._rbuf] 935 self._rbuf = "" 936 if self._rbufsize <= 1: 937 recv_size = self.default_bufsize 938 else: 939 recv_size = self._rbufsize 940 941 while True: 942 data = self.recv(recv_size) 943 if not data: 944 break 945 buffers.append(data) 946 return "".join(buffers) 947 else: 948 # Read until size bytes or EOF seen, whichever comes first 949 data = self._rbuf 950 buf_len = len(data) 951 if buf_len >= size: 952 self._rbuf = data[size:] 953 return data[:size] 954 buffers = [] 955 if data: 956 buffers.append(data) 957 self._rbuf = "" 958 while True: 959 left = size - buf_len 960 recv_size = max(self._rbufsize, left) 961 data = self.recv(recv_size) 962 if not data: 963 break 964 buffers.append(data) 965 n = len(data) 966 if n >= left: 967 self._rbuf = data[left:] 968 buffers[-1] = data[:left] 969 break 970 buf_len += n 971 return "".join(buffers)
972
973 - def readline(self, size=-1):
974 data = self._rbuf 975 if size < 0: 976 # Read until \n or EOF, whichever comes first 977 if self._rbufsize <= 1: 978 # Speed up unbuffered case 979 assert data == "" 980 buffers = [] 981 while data != "\n": 982 data = self.recv(1) 983 if not data: 984 break 985 buffers.append(data) 986 return "".join(buffers) 987 nl = data.find('\n') 988 if nl >= 0: 989 nl += 1 990 self._rbuf = data[nl:] 991 return data[:nl] 992 buffers = [] 993 if data: 994 buffers.append(data) 995 self._rbuf = "" 996 while True: 997 data = self.recv(self._rbufsize) 998 if not data: 999 break 1000 buffers.append(data) 1001 nl = data.find('\n') 1002 if nl >= 0: 1003 nl += 1 1004 self._rbuf = data[nl:] 1005 buffers[-1] = data[:nl] 1006 break 1007 return "".join(buffers) 1008 else: 1009 # Read until size bytes or \n or EOF seen, whichever comes first 1010 nl = data.find('\n', 0, size) 1011 if nl >= 0: 1012 nl += 1 1013 self._rbuf = data[nl:] 1014 return data[:nl] 1015 buf_len = len(data) 1016 if buf_len >= size: 1017 self._rbuf = data[size:] 1018 return data[:size] 1019 buffers = [] 1020 if data: 1021 buffers.append(data) 1022 self._rbuf = "" 1023 while True: 1024 data = self.recv(self._rbufsize) 1025 if not data: 1026 break 1027 buffers.append(data) 1028 left = size - buf_len 1029 nl = data.find('\n', 0, left) 1030 if nl >= 0: 1031 nl += 1 1032 self._rbuf = data[nl:] 1033 buffers[-1] = data[:nl] 1034 break 1035 n = len(data) 1036 if n >= left: 1037 self._rbuf = data[left:] 1038 buffers[-1] = data[:left] 1039 break 1040 buf_len += n 1041 return "".join(buffers)
1042 1043
1044 -class SSL_fileobject(CP_fileobject):
1045 """SSL file object attached to a socket object.""" 1046 1047 ssl_timeout = 3 1048 ssl_retry = .01 1049
1050 - def _safe_call(self, is_reader, call, *args, **kwargs):
1051 """Wrap the given call with SSL error-trapping. 1052 1053 is_reader: if False EOF errors will be raised. If True, EOF errors 1054 will return "" (to emulate normal sockets). 1055 """ 1056 start = time.time() 1057 while True: 1058 try: 1059 return call(*args, **kwargs) 1060 except SSL.WantReadError: 1061 # Sleep and try again. This is dangerous, because it means 1062 # the rest of the stack has no way of differentiating 1063 # between a "new handshake" error and "client dropped". 1064 # Note this isn't an endless loop: there's a timeout below. 1065 time.sleep(self.ssl_retry) 1066 except SSL.WantWriteError: 1067 time.sleep(self.ssl_retry) 1068 except SSL.SysCallError, e: 1069 if is_reader and e.args == (-1, 'Unexpected EOF'): 1070 return "" 1071 1072 errnum = e.args[0] 1073 if is_reader and errnum in socket_errors_to_ignore: 1074 return "" 1075 raise socket.error(errnum) 1076 except SSL.Error, e: 1077 if is_reader and e.args == (-1, 'Unexpected EOF'): 1078 return "" 1079 1080 thirdarg = None 1081 try: 1082 thirdarg = e.args[0][0][2] 1083 except IndexError: 1084 pass 1085 1086 if thirdarg == 'http request': 1087 # The client is talking HTTP to an HTTPS server. 1088 raise NoSSLError() 1089 raise FatalSSLAlert(*e.args) 1090 except: 1091 raise 1092 1093 if time.time() - start > self.ssl_timeout: 1094 raise socket.timeout("timed out")
1095
1096 - def recv(self, *args, **kwargs):
1097 buf = [] 1098 r = super(SSL_fileobject, self).recv 1099 while True: 1100 data = self._safe_call(True, r, *args, **kwargs) 1101 buf.append(data) 1102 p = self._sock.pending() 1103 if not p: 1104 return "".join(buf)
1105
1106 - def sendall(self, *args, **kwargs):
1107 return self._safe_call(False, super(SSL_fileobject, self).sendall, *args, **kwargs)
1108
1109 - def send(self, *args, **kwargs):
1110 return self._safe_call(False, super(SSL_fileobject, self).send, *args, **kwargs)
1111 1112
1113 -class HTTPConnection(object):
1114 """An HTTP connection (active socket). 1115 1116 socket: the raw socket object (usually TCP) for this connection. 1117 wsgi_app: the WSGI application for this server/connection. 1118 environ: a WSGI environ template. This will be copied for each request. 1119 1120 rfile: a fileobject for reading from the socket. 1121 send: a function for writing (+ flush) to the socket. 1122 """ 1123 1124 rbufsize = -1 1125 RequestHandlerClass = HTTPRequest 1126 environ = {"wsgi.version": (1, 0), 1127 "wsgi.url_scheme": "http", 1128 "wsgi.multithread": True, 1129 "wsgi.multiprocess": False, 1130 "wsgi.run_once": False, 1131 "wsgi.errors": sys.stderr, 1132 } 1133
1134 - def __init__(self, sock, wsgi_app, environ):
1135 self.socket = sock 1136 self.wsgi_app = wsgi_app 1137 1138 # Copy the class environ into self. 1139 self.environ = self.environ.copy() 1140 self.environ.update(environ) 1141 1142 if SSL and isinstance(sock, SSL.ConnectionType): 1143 timeout = sock.gettimeout() 1144 self.rfile = SSL_fileobject(sock, "rb", self.rbufsize) 1145 self.rfile.ssl_timeout = timeout 1146 self.wfile = SSL_fileobject(sock, "wb", -1) 1147 self.wfile.ssl_timeout = timeout 1148 else: 1149 self.rfile = CP_fileobject(sock, "rb", self.rbufsize) 1150 self.wfile = CP_fileobject(sock, "wb", -1) 1151 1152 # Wrap wsgi.input but not HTTPConnection.rfile itself. 1153 # We're also not setting maxlen yet; we'll do that separately 1154 # for headers and body for each iteration of self.communicate 1155 # (if maxlen is 0 the wrapper doesn't check length). 1156 self.environ["wsgi.input"] = SizeCheckWrapper(self.rfile, 0)
1157
1158 - def communicate(self):
1159 """Read each request and respond appropriately.""" 1160 try: 1161 while True: 1162 # (re)set req to None so that if something goes wrong in 1163 # the RequestHandlerClass constructor, the error doesn't 1164 # get written to the previous request. 1165 req = None 1166 req = self.RequestHandlerClass(self.wfile, self.environ, 1167 self.wsgi_app) 1168 1169 # This order of operations should guarantee correct pipelining. 1170 req.parse_request() 1171 if not req.ready: 1172 return 1173 1174 req.respond() 1175 if req.close_connection: 1176 return 1177 1178 except socket.error, e: 1179 errnum = e.args[0] 1180 if errnum == 'timed out': 1181 if req and not req.sent_headers: 1182 req.simple_response("408 Request Timeout") 1183 elif errnum not in socket_errors_to_ignore: 1184 if req and not req.sent_headers: 1185 req.simple_response("500 Internal Server Error", 1186 format_exc()) 1187 return 1188 except (KeyboardInterrupt, SystemExit): 1189 raise 1190 except FatalSSLAlert, e: 1191 # Close the connection. 1192 return 1193 except NoSSLError: 1194 # Unwrap our wfile 1195 req.wfile = CP_fileobject(self.socket, "wb", -1) 1196 if req and not req.sent_headers: 1197 req.simple_response("400 Bad Request", 1198 "The client sent a plain HTTP request, but " 1199 "this server only speaks HTTPS on this port.") 1200 except Exception, e: 1201 if req and not req.sent_headers: 1202 req.simple_response("500 Internal Server Error", format_exc())
1203
1204 - def close(self):
1205 """Close the socket underlying this connection.""" 1206 self.rfile.close() 1207 1208 # Python's socket module does NOT call close on the kernel socket 1209 # when you call socket.close(). We do so manually here because we 1210 # want this server to send a FIN TCP segment immediately. Note this 1211 # must be called *before* calling socket.close(), because the latter 1212 # drops its reference to the kernel socket. 1213 self.socket._sock.close() 1214 1215 self.socket.close()
1216 1217
1218 -def format_exc(limit=None):
1219 """Like print_exc() but return a string. Backport for Python 2.3.""" 1220 try: 1221 etype, value, tb = sys.exc_info() 1222 return ''.join(traceback.format_exception(etype, value, tb, limit)) 1223 finally: 1224 etype = value = tb = None
1225 1226 1227 _SHUTDOWNREQUEST = None 1228
1229 -class WorkerThread(threading.Thread):
1230 """Thread which continuously polls a Queue for Connection objects. 1231 1232 server: the HTTP Server which spawned this thread, and which owns the 1233 Queue and is placing active connections into it. 1234 ready: a simple flag for the calling server to know when this thread 1235 has begun polling the Queue. 1236 1237 Due to the timing issues of polling a Queue, a WorkerThread does not 1238 check its own 'ready' flag after it has started. To stop the thread, 1239 it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue 1240 (one for each running WorkerThread). 1241 """ 1242 1243 conn = None 1244
1245 - def __init__(self, server):
1246 self.ready = False 1247 self.server = server 1248 threading.Thread.__init__(self)
1249
1250 - def run(self):
1251 try: 1252 self.ready = True 1253 while True: 1254 conn = self.server.requests.get() 1255 if conn is _SHUTDOWNREQUEST: 1256 return 1257 1258 self.conn = conn 1259 try: 1260 conn.communicate() 1261 finally: 1262 conn.close() 1263 self.conn = None 1264 except (KeyboardInterrupt, SystemExit), exc: 1265 self.server.interrupt = exc
1266 1267
1268 -class ThreadPool(object):
1269 """A Request Queue for the CherryPyWSGIServer which pools threads. 1270 1271 ThreadPool objects must provide min, get(), put(obj), start() 1272 and stop(timeout) attributes. 1273 """ 1274
1275 - def __init__(self, server, min=10, max=-1):
1276 self.server = server 1277 self.min = min 1278 self.max = max 1279 self._threads = [] 1280 self._queue = Queue.Queue() 1281 self.get = self._queue.get
1282
1283 - def start(self):
1284 """Start the pool of threads.""" 1285 for i in xrange(self.min): 1286 self._threads.append(WorkerThread(self.server)) 1287 for worker in self._threads: 1288 worker.setName("CP WSGIServer " + worker.getName()) 1289 worker.start() 1290 for worker in self._threads: 1291 while not worker.ready: 1292 time.sleep(.1)
1293
1294 - def _get_idle(self):
1295 """Number of worker threads which are idle. Read-only.""" 1296 return len([t for t in self._threads if t.conn is None])
1297 idle = property(_get_idle, doc=_get_idle.__doc__) 1298
1299 - def put(self, obj):
1300 self._queue.put(obj) 1301 if obj is _SHUTDOWNREQUEST: 1302 return
1303
1304 - def grow(self, amount):
1305 """Spawn new worker threads (not above self.max).""" 1306 for i in xrange(amount): 1307 if self.max > 0 and len(self._threads) >= self.max: 1308 break 1309 worker = WorkerThread(self.server) 1310 worker.setName("CP WSGIServer " + worker.getName()) 1311 self._threads.append(worker) 1312 worker.start()
1313
1314 - def shrink(self, amount):
1315 """Kill off worker threads (not below self.min).""" 1316 # Grow/shrink the pool if necessary. 1317 # Remove any dead threads from our list 1318 for t in self._threads: 1319 if not t.isAlive(): 1320 self._threads.remove(t) 1321 amount -= 1 1322 1323 if amount > 0: 1324 for i in xrange(min(amount, len(self._threads) - self.min)): 1325 # Put a number of shutdown requests on the queue equal 1326 # to 'amount'. Once each of those is processed by a worker, 1327 # that worker will terminate and be culled from our list 1328 # in self.put. 1329 self._queue.put(_SHUTDOWNREQUEST)
1330
1331 - def stop(self, timeout=5):
1332 # Must shut down threads here so the code that calls 1333 # this method can know when all threads are stopped. 1334 for worker in self._threads: 1335 self._queue.put(_SHUTDOWNREQUEST) 1336 1337 # Don't join currentThread (when stop is called inside a request). 1338 current = threading.currentThread() 1339 while self._threads: 1340 worker = self._threads.pop() 1341 if worker is not current and worker.isAlive(): 1342 try: 1343 if timeout is None or timeout < 0: 1344 worker.join() 1345 else: 1346 worker.join(timeout) 1347 if worker.isAlive(): 1348 # We exhausted the timeout. 1349 # Forcibly shut down the socket. 1350 c = worker.conn 1351 if c and not c.rfile.closed: 1352 if SSL and isinstance(c.socket, SSL.ConnectionType): 1353 # pyOpenSSL.socket.shutdown takes no args 1354 c.socket.shutdown() 1355 else: 1356 c.socket.shutdown(socket.SHUT_RD) 1357 worker.join() 1358 except (AssertionError, 1359 # Ignore repeated Ctrl-C. 1360 # See http://www.cherrypy.org/ticket/691. 1361 KeyboardInterrupt), exc1: 1362 pass
1363 1364 1365
1366 -class SSLConnection:
1367 """A thread-safe wrapper for an SSL.Connection. 1368 1369 *args: the arguments to create the wrapped SSL.Connection(*args). 1370 """ 1371
1372 - def __init__(self, *args):
1373 self._ssl_conn = SSL.Connection(*args) 1374 self._lock = threading.RLock()
1375 1376 for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', 1377 'renegotiate', 'bind', 'listen', 'connect', 'accept', 1378 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', 1379 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', 1380 'makefile', 'get_app_data', 'set_app_data', 'state_string', 1381 'sock_shutdown', 'get_peer_certificate', 'want_read', 1382 'want_write', 'set_connect_state', 'set_accept_state', 1383 'connect_ex', 'sendall', 'settimeout'): 1384 exec """def %s(self, *args): 1385 self._lock.acquire() 1386 try: 1387 return self._ssl_conn.%s(*args) 1388 finally: 1389 self._lock.release() 1390 """ % (f, f)
1391 1392 1393 try: 1394 import fcntl 1395 except ImportError: 1396 try: 1397 from ctypes import windll, WinError 1398 except ImportError:
1399 - def prevent_socket_inheritance(sock):
1400 """Dummy function, since neither fcntl nor ctypes are available.""" 1401 pass
1402 else:
1403 - def prevent_socket_inheritance(sock):
1404 """Mark the given socket fd as non-inheritable (Windows).""" 1405 if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0): 1406 raise WinError()
1407 else:
1408 - def prevent_socket_inheritance(sock):
1409 """Mark the given socket fd as non-inheritable (POSIX).""" 1410 fd = sock.fileno() 1411 old_flags = fcntl.fcntl(fd, fcntl.F_GETFD) 1412 fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1413 1414
1415 -class CherryPyWSGIServer(object):
1416 """An HTTP server for WSGI. 1417 1418 bind_addr: The interface on which to listen for connections. 1419 For TCP sockets, a (host, port) tuple. Host values may be any IPv4 1420 or IPv6 address, or any valid hostname. The string 'localhost' is a 1421 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6). 1422 The string '0.0.0.0' is a special IPv4 entry meaning "any active 1423 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for 1424 IPv6. The empty string or None are not allowed. 1425 1426 For UNIX sockets, supply the filename as a string. 1427 wsgi_app: the WSGI 'application callable'; multiple WSGI applications 1428 may be passed as (path_prefix, app) pairs. 1429 numthreads: the number of worker threads to create (default 10). 1430 server_name: the string to set for WSGI's SERVER_NAME environ entry. 1431 Defaults to socket.gethostname(). 1432 max: the maximum number of queued requests (defaults to -1 = no limit). 1433 request_queue_size: the 'backlog' argument to socket.listen(); 1434 specifies the maximum number of queued connections (default 5). 1435 timeout: the timeout in seconds for accepted connections (default 10). 1436 1437 nodelay: if True (the default since 3.1), sets the TCP_NODELAY socket 1438 option. 1439 1440 protocol: the version string to write in the Status-Line of all 1441 HTTP responses. For example, "HTTP/1.1" (the default). This 1442 also limits the supported features used in the response. 1443 1444 1445 SSL/HTTPS 1446 --------- 1447 The OpenSSL module must be importable for SSL functionality. 1448 You can obtain it from http://pyopenssl.sourceforge.net/ 1449 1450 ssl_certificate: the filename of the server SSL certificate. 1451 ssl_privatekey: the filename of the server's private key file. 1452 1453 If either of these is None (both are None by default), this server 1454 will not use SSL. If both are given and are valid, they will be read 1455 on server start and used in the SSL context for the listening socket. 1456 """ 1457 1458 protocol = "HTTP/1.1" 1459 _bind_addr = "127.0.0.1" 1460 version = "CherryPy/3.1.0" 1461 ready = False 1462 _interrupt = None 1463 1464 nodelay = True 1465 1466 ConnectionClass = HTTPConnection 1467 environ = {} 1468 1469 # Paths to certificate and private key files 1470 ssl_certificate = None 1471 ssl_private_key = None 1472
1473 - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, 1474 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
1475 self.requests = ThreadPool(self, min=numthreads or 1, max=max) 1476 1477 if callable(wsgi_app): 1478 # We've been handed a single wsgi_app, in CP-2.1 style. 1479 # Assume it's mounted at "". 1480 self.wsgi_app = wsgi_app 1481 else: 1482 # We've been handed a list of (path_prefix, wsgi_app) tuples, 1483 # so that the server can call different wsgi_apps, and also 1484 # correctly set SCRIPT_NAME. 1485 warnings.warn("The ability to pass multiple apps is deprecated " 1486 "and will be removed in 3.2. You should explicitly " 1487 "include a WSGIPathInfoDispatcher instead.", 1488 DeprecationWarning) 1489 self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app) 1490 1491 self.bind_addr = bind_addr 1492 if not server_name: 1493 server_name = socket.gethostname() 1494 self.server_name = server_name 1495 self.request_queue_size = request_queue_size 1496 1497 self.timeout = timeout 1498 self.shutdown_timeout = shutdown_timeout
1499
1500 - def _get_numthreads(self):
1501 return self.requests.min
1502 - def _set_numthreads(self, value):
1503 self.requests.min = value
1504 numthreads = property(_get_numthreads, _set_numthreads) 1505
1506 - def __str__(self):
1507 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__, 1508 self.bind_addr)
1509
1510 - def _get_bind_addr(self):
1511 return self._bind_addr
1512 - def _set_bind_addr(self, value):
1513 if isinstance(value, tuple) and value[0] in ('', None): 1514 # Despite the socket module docs, using '' does not 1515 # allow AI_PASSIVE to work. Passing None instead 1516 # returns '0.0.0.0' like we want. In other words: 1517 # host AI_PASSIVE result 1518 # '' Y 192.168.x.y 1519 # '' N 192.168.x.y 1520 # None Y 0.0.0.0 1521 # None N 127.0.0.1 1522 # But since you can get the same effect with an explicit 1523 # '0.0.0.0', we deny both the empty string and None as values. 1524 raise ValueError("Host values of '' or None are not allowed. " 1525 "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead " 1526 "to listen on all active interfaces.") 1527 self._bind_addr = value
1528 bind_addr = property(_get_bind_addr, _set_bind_addr, 1529 doc="""The interface on which to listen for connections. 1530 1531 For TCP sockets, a (host, port) tuple. Host values may be any IPv4 1532 or IPv6 address, or any valid hostname. The string 'localhost' is a 1533 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6). 1534 The string '0.0.0.0' is a special IPv4 entry meaning "any active 1535 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for 1536 IPv6. The empty string or None are not allowed. 1537 1538 For UNIX sockets, supply the filename as a string.""") 1539
1540 - def start(self):
1541 """Run the server forever.""" 1542 # We don't have to trap KeyboardInterrupt or SystemExit here, 1543 # because cherrpy.server already does so, calling self.stop() for us. 1544 # If you're using this server with another framework, you should 1545 # trap those exceptions in whatever code block calls start(). 1546 self._interrupt = None 1547 1548 # Select the appropriate socket 1549 if isinstance(self.bind_addr, basestring): 1550 # AF_UNIX socket 1551 1552 # So we can reuse the socket... 1553 try: os.unlink(self.bind_addr) 1554 except: pass 1555 1556 # So everyone can access the socket... 1557 try: os.chmod(self.bind_addr, 0777) 1558 except: pass 1559 1560 info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)] 1561 else: 1562 # AF_INET or AF_INET6 socket 1563 # Get the correct address family for our host (allows IPv6 addresses) 1564 host, port = self.bind_addr 1565 try: 1566 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, 1567 socket.SOCK_STREAM, 0, socket.AI_PASSIVE) 1568 except socket.gaierror: 1569 # Probably a DNS issue. Assume IPv4. 1570 info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)] 1571 1572 self.socket = None 1573 msg = "No socket could be created" 1574 for res in info: 1575 af, socktype, proto, canonname, sa = res 1576 try: 1577 self.bind(af, socktype, proto) 1578 except socket.error, msg: 1579 if self.socket: 1580 self.socket.close() 1581 self.socket = None 1582 continue 1583 break 1584 if not self.socket: 1585 raise socket.error, msg 1586 1587 # Timeout so KeyboardInterrupt can be caught on Win32 1588 self.socket.settimeout(1) 1589 self.socket.listen(self.request_queue_size) 1590 1591 # Create worker threads 1592 self.requests.start() 1593 1594 self.ready = True 1595 while self.ready: 1596 self.tick() 1597 if self.interrupt: 1598 while self.interrupt is True: 1599 # Wait for self.stop() to complete. See _set_interrupt. 1600 time.sleep(0.1) 1601 if self.interrupt: 1602 raise self.interrupt
1603
1604 - def bind(self, family, type, proto=0):
1605 """Create (or recreate) the actual socket object.""" 1606 self.socket = socket.socket(family, type, proto) 1607 prevent_socket_inheritance(self.socket) 1608 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 1609 if self.nodelay: 1610 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 1611 if self.ssl_certificate and self.ssl_private_key: 1612 if SSL is None: 1613 raise ImportError("You must install pyOpenSSL to use HTTPS.") 1614 1615 # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 1616 ctx = SSL.Context(SSL.SSLv23_METHOD) 1617 ctx.use_privatekey_file(self.ssl_private_key) 1618 ctx.use_certificate_file(self.ssl_certificate) 1619 self.socket = SSLConnection(ctx, self.socket) 1620 self.populate_ssl_environ() 1621 1622 # If listening on the IPV6 any address ('::' = IN6ADDR_ANY), 1623 # activate dual-stack. See http://www.cherrypy.org/ticket/871. 1624 if (not isinstance(self.bind_addr, basestring) 1625 and self.bind_addr[0] == '::' and family == socket.AF_INET6): 1626 try: 1627 self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) 1628 except (AttributeError, socket.error): 1629 # Apparently, the socket option is not available in 1630 # this machine's TCP stack 1631 pass 1632 1633 self.socket.bind(self.bind_addr)
1634
1635 - def tick(self):
1636 """Accept a new connection and put it on the Queue.""" 1637 try: 1638 s, addr = self.socket.accept() 1639 prevent_socket_inheritance(s) 1640 if not self.ready: 1641 return 1642 if hasattr(s, 'settimeout'): 1643 s.settimeout(self.timeout) 1644 1645 environ = self.environ.copy() 1646 # SERVER_SOFTWARE is common for IIS. It's also helpful for 1647 # us to pass a default value for the "Server" response header. 1648 if environ.get("SERVER_SOFTWARE") is None: 1649 environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version 1650 # set a non-standard environ entry so the WSGI app can know what 1651 # the *real* server protocol is (and what features to support). 1652 # See http://www.faqs.org/rfcs/rfc2145.html. 1653 environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol 1654 environ["SERVER_NAME"] = self.server_name 1655 1656 if isinstance(self.bind_addr, basestring): 1657 # AF_UNIX. This isn't really allowed by WSGI, which doesn't 1658 # address unix domain sockets. But it's better than nothing. 1659 environ["SERVER_PORT"] = "" 1660 else: 1661 environ["SERVER_PORT"] = str(self.bind_addr[1]) 1662 # optional values 1663 # Until we do DNS lookups, omit REMOTE_HOST 1664 environ["REMOTE_ADDR"] = addr[0] 1665 environ["REMOTE_PORT"] = str(addr[1]) 1666 1667 conn = self.ConnectionClass(s, self.wsgi_app, environ) 1668 self.requests.put(conn) 1669 except socket.timeout: 1670 # The only reason for the timeout in start() is so we can 1671 # notice keyboard interrupts on Win32, which don't interrupt 1672 # accept() by default 1673 return 1674 except socket.error, x: 1675 if x.args[0] in socket_error_eintr: 1676 # I *think* this is right. EINTR should occur when a signal 1677 # is received during the accept() call; all docs say retry 1678 # the call, and I *think* I'm reading it right that Python 1679 # will then go ahead and poll for and handle the signal 1680 # elsewhere. See http://www.cherrypy.org/ticket/707. 1681 return 1682 if x.args[0] in socket_errors_nonblocking: 1683 # Just try again. See http://www.cherrypy.org/ticket/479. 1684 return 1685 if x.args[0] in socket_errors_to_ignore: 1686 # Our socket was closed. 1687 # See http://www.cherrypy.org/ticket/686. 1688 return 1689 raise
1690
1691 - def _get_interrupt(self):
1692 return self._interrupt
1693 - def _set_interrupt(self, interrupt):
1694 self._interrupt = True 1695 self.stop() 1696 self._interrupt = interrupt
1697 interrupt = property(_get_interrupt, _set_interrupt, 1698 doc="Set this to an Exception instance to " 1699 "interrupt the server.") 1700
1701 - def stop(self):
1702 """Gracefully shutdown a server that is serving forever.""" 1703 self.ready = False 1704 1705 sock = getattr(self, "socket", None) 1706 if sock: 1707 if not isinstance(self.bind_addr, basestring): 1708 # Touch our own socket to make accept() return immediately. 1709 try: 1710 host, port = sock.getsockname()[:2] 1711 except socket.error, x: 1712 if x.args[1] != "Bad file descriptor": 1713 raise 1714 else: 1715 # Note that we're explicitly NOT using AI_PASSIVE, 1716 # here, because we want an actual IP to touch. 1717 # localhost won't work if we've bound to a public IP, 1718 # but it will if we bound to '0.0.0.0' (INADDR_ANY). 1719 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, 1720 socket.SOCK_STREAM): 1721 af, socktype, proto, canonname, sa = res 1722 s = None 1723 try: 1724 s = socket.socket(af, socktype, proto) 1725 # See http://groups.google.com/group/cherrypy-users/ 1726 # browse_frm/thread/bbfe5eb39c904fe0 1727 s.settimeout(1.0) 1728 s.connect((host, port)) 1729 s.close() 1730 except socket.error: 1731 if s: 1732 s.close() 1733 if hasattr(sock, "close"): 1734 sock.close() 1735 self.socket = None 1736 1737 self.requests.stop(self.shutdown_timeout)
1738
1739 - def populate_ssl_environ(self):
1740 """Create WSGI environ entries to be merged into each request.""" 1741 cert = open(self.ssl_certificate, 'rb').read() 1742 cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert) 1743 ssl_environ = { 1744 "wsgi.url_scheme": "https", 1745 "HTTPS": "on", 1746 # pyOpenSSL doesn't provide access to any of these AFAICT 1747 ## 'SSL_PROTOCOL': 'SSLv2', 1748 ## SSL_CIPHER string The cipher specification name 1749 ## SSL_VERSION_INTERFACE string The mod_ssl program version 1750 ## SSL_VERSION_LIBRARY string The OpenSSL program version 1751 } 1752 1753 # Server certificate attributes 1754 ssl_environ.update({ 1755 'SSL_SERVER_M_VERSION': cert.get_version(), 1756 'SSL_SERVER_M_SERIAL': cert.get_serial_number(), 1757 ## 'SSL_SERVER_V_START': Validity of server's certificate (start time), 1758 ## 'SSL_SERVER_V_END': Validity of server's certificate (end time), 1759 }) 1760 1761 for prefix, dn in [("I", cert.get_issuer()), 1762 ("S", cert.get_subject())]: 1763 # X509Name objects don't seem to have a way to get the 1764 # complete DN string. Use str() and slice it instead, 1765 # because str(dn) == "<X509Name object '/C=US/ST=...'>" 1766 dnstr = str(dn)[18:-2] 1767 1768 wsgikey = 'SSL_SERVER_%s_DN' % prefix 1769 ssl_environ[wsgikey] = dnstr 1770 1771 # The DN should be of the form: /k1=v1/k2=v2, but we must allow 1772 # for any value to contain slashes itself (in a URL). 1773 while dnstr: 1774 pos = dnstr.rfind("=") 1775 dnstr, value = dnstr[:pos], dnstr[pos + 1:] 1776 pos = dnstr.rfind("/") 1777 dnstr, key = dnstr[:pos], dnstr[pos + 1:] 1778 if key and value: 1779 wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key) 1780 ssl_environ[wsgikey] = value 1781 1782 self.environ.update(ssl_environ)
1783