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