Package cherrypy :: Package wsgiserver :: Module wsgiserver2
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.wsgiserver.wsgiserver2

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