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 self.server = server 1550 self.min = min 1551 self.max = max 1552 self._threads = [] 1553 self._queue = queue.Queue() 1554 self.get = self._queue.get
1555
1556 - def start(self):
1557 """Start the pool of threads.""" 1558 for i in range(self.min): 1559 self._threads.append(WorkerThread(self.server)) 1560 for worker in self._threads: 1561 worker.setName("CP Server " + worker.getName()) 1562 worker.start() 1563 for worker in self._threads: 1564 while not worker.ready: 1565 time.sleep(.1)
1566
1567 - def _get_idle(self):
1568 """Number of worker threads which are idle. Read-only.""" 1569 return len([t for t in self._threads if t.conn is None])
1570 idle = property(_get_idle, doc=_get_idle.__doc__) 1571
1572 - def put(self, obj):
1573 self._queue.put(obj) 1574 if obj is _SHUTDOWNREQUEST: 1575 return
1576
1577 - def grow(self, amount):
1578 """Spawn new worker threads (not above self.max).""" 1579 if self.max > 0: 1580 budget = max(self.max - len(self._threads), 0) 1581 else: 1582 # self.max <= 0 indicates no maximum 1583 budget = float('inf') 1584 1585 n_new = min(amount, budget) 1586 1587 workers = [self._spawn_worker() for i in range(n_new)] 1588 while not self._all(operator.attrgetter('ready'), workers): 1589 time.sleep(.1) 1590 self._threads.extend(workers)
1591
1592 - def _spawn_worker(self):
1593 worker = WorkerThread(self.server) 1594 worker.setName("CP Server " + worker.getName()) 1595 worker.start() 1596 return worker
1597
1598 - def _all(func, items):
1599 results = [func(item) for item in items] 1600 return reduce(operator.and_, results, True)
1601 _all = staticmethod(_all) 1602
1603 - def shrink(self, amount):
1604 """Kill off worker threads (not below self.min).""" 1605 # Grow/shrink the pool if necessary. 1606 # Remove any dead threads from our list 1607 for t in self._threads: 1608 if not t.isAlive(): 1609 self._threads.remove(t) 1610 amount -= 1 1611 1612 # calculate the number of threads above the minimum 1613 n_extra = max(len(self._threads) - self.min, 0) 1614 1615 # don't remove more than amount 1616 n_to_remove = min(amount, n_extra) 1617 1618 # put shutdown requests on the queue equal to the number of threads 1619 # to remove. As each request is processed by a worker, that worker 1620 # will terminate and be culled from the list. 1621 for n in range(n_to_remove): 1622 self._queue.put(_SHUTDOWNREQUEST)
1623
1624 - def stop(self, timeout=5):
1625 # Must shut down threads here so the code that calls 1626 # this method can know when all threads are stopped. 1627 for worker in self._threads: 1628 self._queue.put(_SHUTDOWNREQUEST) 1629 1630 # Don't join currentThread (when stop is called inside a request). 1631 current = threading.currentThread() 1632 if timeout and timeout >= 0: 1633 endtime = time.time() + timeout 1634 while self._threads: 1635 worker = self._threads.pop() 1636 if worker is not current and worker.isAlive(): 1637 try: 1638 if timeout is None or timeout < 0: 1639 worker.join() 1640 else: 1641 remaining_time = endtime - time.time() 1642 if remaining_time > 0: 1643 worker.join(remaining_time) 1644 if worker.isAlive(): 1645 # We exhausted the timeout. 1646 # Forcibly shut down the socket. 1647 c = worker.conn 1648 if c and not c.rfile.closed: 1649 try: 1650 c.socket.shutdown(socket.SHUT_RD) 1651 except TypeError: 1652 # pyOpenSSL sockets don't take an arg 1653 c.socket.shutdown() 1654 worker.join() 1655 except (AssertionError, 1656 # Ignore repeated Ctrl-C. 1657 # See 1658 # https://bitbucket.org/cherrypy/cherrypy/issue/691. 1659 KeyboardInterrupt): 1660 pass
1661
1662 - def _get_qsize(self):
1663 return self._queue.qsize()
1664 qsize = property(_get_qsize)
1665 1666 1667 try: 1668 import fcntl 1669 except ImportError: 1670 try: 1671 from ctypes import windll, WinError 1672 import ctypes.wintypes 1673 _SetHandleInformation = windll.kernel32.SetHandleInformation 1674 _SetHandleInformation.argtypes = [ 1675 ctypes.wintypes.HANDLE, 1676 ctypes.wintypes.DWORD, 1677 ctypes.wintypes.DWORD, 1678 ] 1679 _SetHandleInformation.restype = ctypes.wintypes.BOOL 1680 except ImportError:
1681 - def prevent_socket_inheritance(sock):
1682 """Dummy function, since neither fcntl nor ctypes are available.""" 1683 pass
1684 else:
1685 - def prevent_socket_inheritance(sock):
1686 """Mark the given socket fd as non-inheritable (Windows).""" 1687 if not _SetHandleInformation(sock.fileno(), 1, 0): 1688 raise WinError()
1689 else:
1690 - def prevent_socket_inheritance(sock):
1691 """Mark the given socket fd as non-inheritable (POSIX).""" 1692 fd = sock.fileno() 1693 old_flags = fcntl.fcntl(fd, fcntl.F_GETFD) 1694 fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1695 1696
1697 -class SSLAdapter(object):
1698 1699 """Base class for SSL driver library adapters. 1700 1701 Required methods: 1702 1703 * ``wrap(sock) -> (wrapped socket, ssl environ dict)`` 1704 * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> 1705 socket file object`` 1706 """ 1707
1708 - def __init__(self, certificate, private_key, certificate_chain=None):
1712
1713 - def wrap(self, sock):
1714 raise NotImplemented
1715
1716 - def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
1717 raise NotImplemented
1718 1719
1720 -class HTTPServer(object):
1721 1722 """An HTTP server.""" 1723 1724 _bind_addr = "127.0.0.1" 1725 _interrupt = None 1726 1727 gateway = None 1728 """A Gateway instance.""" 1729 1730 minthreads = None 1731 """The minimum number of worker threads to create (default 10).""" 1732 1733 maxthreads = None 1734 """The maximum number of worker threads to create (default -1 = no limit). 1735 """ 1736 1737 server_name = None 1738 """The name of the server; defaults to socket.gethostname().""" 1739 1740 protocol = "HTTP/1.1" 1741 """The version string to write in the Status-Line of all HTTP responses. 1742 1743 For example, "HTTP/1.1" is the default. This also limits the supported 1744 features used in the response.""" 1745 1746 request_queue_size = 5 1747 """The 'backlog' arg to socket.listen(); max queued connections 1748 (default 5). 1749 """ 1750 1751 shutdown_timeout = 5 1752 """The total time, in seconds, to wait for worker threads to cleanly exit. 1753 """ 1754 1755 timeout = 10 1756 """The timeout in seconds for accepted connections (default 10).""" 1757 1758 version = "CherryPy/3.3.0" 1759 """A version string for the HTTPServer.""" 1760 1761 software = None 1762 """The value to set for the SERVER_SOFTWARE entry in the WSGI environ. 1763 1764 If None, this defaults to ``'%s Server' % self.version``.""" 1765 1766 ready = False 1767 """An internal flag which marks whether the socket is accepting connections 1768 """ 1769 1770 max_request_header_size = 0 1771 """The maximum size, in bytes, for request headers, or 0 for no limit.""" 1772 1773 max_request_body_size = 0 1774 """The maximum size, in bytes, for request bodies, or 0 for no limit.""" 1775 1776 nodelay = True 1777 """If True (the default since 3.1), sets the TCP_NODELAY socket option.""" 1778 1779 ConnectionClass = HTTPConnection 1780 """The class to use for handling HTTP connections.""" 1781 1782 ssl_adapter = None 1783 """An instance of SSLAdapter (or a subclass). 1784 1785 You must have the corresponding SSL driver library installed.""" 1786
1787 - def __init__(self, bind_addr, gateway, minthreads=10, maxthreads=-1, 1788 server_name=None):
1789 self.bind_addr = bind_addr 1790 self.gateway = gateway 1791 1792 self.requests = ThreadPool(self, min=minthreads or 1, max=maxthreads) 1793 1794 if not server_name: 1795 server_name = socket.gethostname() 1796 self.server_name = server_name 1797 self.clear_stats()
1798
1799 - def clear_stats(self):
1800 self._start_time = None 1801 self._run_time = 0 1802 self.stats = { 1803 'Enabled': False, 1804 'Bind Address': lambda s: repr(self.bind_addr), 1805 'Run time': lambda s: (not s['Enabled']) and -1 or self.runtime(), 1806 'Accepts': 0, 1807 'Accepts/sec': lambda s: s['Accepts'] / self.runtime(), 1808 'Queue': lambda s: getattr(self.requests, "qsize", None), 1809 'Threads': lambda s: len(getattr(self.requests, "_threads", [])), 1810 'Threads Idle': lambda s: getattr(self.requests, "idle", None), 1811 'Socket Errors': 0, 1812 'Requests': lambda s: (not s['Enabled']) and -1 or sum( 1813 [w['Requests'](w) for w in s['Worker Threads'].values()], 0), 1814 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum( 1815 [w['Bytes Read'](w) for w in s['Worker Threads'].values()], 0), 1816 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum( 1817 [w['Bytes Written'](w) for w in s['Worker Threads'].values()], 1818 0), 1819 'Work Time': lambda s: (not s['Enabled']) and -1 or sum( 1820 [w['Work Time'](w) for w in s['Worker Threads'].values()], 0), 1821 'Read Throughput': lambda s: (not s['Enabled']) and -1 or sum( 1822 [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6) 1823 for w in s['Worker Threads'].values()], 0), 1824 'Write Throughput': lambda s: (not s['Enabled']) and -1 or sum( 1825 [w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6) 1826 for w in s['Worker Threads'].values()], 0), 1827 'Worker Threads': {}, 1828 } 1829 logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
1830
1831 - def runtime(self):
1832 if self._start_time is None: 1833 return self._run_time 1834 else: 1835 return self._run_time + (time.time() - self._start_time)
1836
1837 - def __str__(self):
1838 return "%s.%s(%r)" % (self.__module__, self.__class__.__name__, 1839 self.bind_addr)
1840
1841 - def _get_bind_addr(self):
1842 return self._bind_addr
1843
1844 - def _set_bind_addr(self, value):
1845 if isinstance(value, tuple) and value[0] in ('', None): 1846 # Despite the socket module docs, using '' does not 1847 # allow AI_PASSIVE to work. Passing None instead 1848 # returns '0.0.0.0' like we want. In other words: 1849 # host AI_PASSIVE result 1850 # '' Y 192.168.x.y 1851 # '' N 192.168.x.y 1852 # None Y 0.0.0.0 1853 # None N 127.0.0.1 1854 # But since you can get the same effect with an explicit 1855 # '0.0.0.0', we deny both the empty string and None as values. 1856 raise ValueError("Host values of '' or None are not allowed. " 1857 "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead " 1858 "to listen on all active interfaces.") 1859 self._bind_addr = value
1860 bind_addr = property( 1861 _get_bind_addr, 1862 _set_bind_addr, 1863 doc="""The interface on which to listen for connections. 1864 1865 For TCP sockets, a (host, port) tuple. Host values may be any IPv4 1866 or IPv6 address, or any valid hostname. The string 'localhost' is a 1867 synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6). 1868 The string '0.0.0.0' is a special IPv4 entry meaning "any active 1869 interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for 1870 IPv6. The empty string or None are not allowed. 1871 1872 For UNIX sockets, supply the filename as a string.""") 1873
1874 - def start(self):
1875 """Run the server forever.""" 1876 # We don't have to trap KeyboardInterrupt or SystemExit here, 1877 # because cherrpy.server already does so, calling self.stop() for us. 1878 # If you're using this server with another framework, you should 1879 # trap those exceptions in whatever code block calls start(). 1880 self._interrupt = None 1881 1882 if self.software is None: 1883 self.software = "%s Server" % self.version 1884 1885 # SSL backward compatibility 1886 if (self.ssl_adapter is None and 1887 getattr(self, 'ssl_certificate', None) and 1888 getattr(self, 'ssl_private_key', None)): 1889 warnings.warn( 1890 "SSL attributes are deprecated in CherryPy 3.2, and will " 1891 "be removed in CherryPy 3.3. Use an ssl_adapter attribute " 1892 "instead.", 1893 DeprecationWarning 1894 ) 1895 try: 1896 from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter 1897 except ImportError: 1898 pass 1899 else: 1900 self.ssl_adapter = pyOpenSSLAdapter( 1901 self.ssl_certificate, self.ssl_private_key, 1902 getattr(self, 'ssl_certificate_chain', None)) 1903 1904 # Select the appropriate socket 1905 if isinstance(self.bind_addr, basestring): 1906 # AF_UNIX socket 1907 1908 # So we can reuse the socket... 1909 try: 1910 os.unlink(self.bind_addr) 1911 except: 1912 pass 1913 1914 # So everyone can access the socket... 1915 try: 1916 os.chmod(self.bind_addr, 511) # 0777 1917 except: 1918 pass 1919 1920 info = [ 1921 (socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)] 1922 else: 1923 # AF_INET or AF_INET6 socket 1924 # Get the correct address family for our host (allows IPv6 1925 # addresses) 1926 host, port = self.bind_addr 1927 try: 1928 info = socket.getaddrinfo( 1929 host, port, socket.AF_UNSPEC, 1930 socket.SOCK_STREAM, 0, socket.AI_PASSIVE) 1931 except socket.gaierror: 1932 if ':' in self.bind_addr[0]: 1933 info = [(socket.AF_INET6, socket.SOCK_STREAM, 1934 0, "", self.bind_addr + (0, 0))] 1935 else: 1936 info = [(socket.AF_INET, socket.SOCK_STREAM, 1937 0, "", self.bind_addr)] 1938 1939 self.socket = None 1940 msg = "No socket could be created" 1941 for res in info: 1942 af, socktype, proto, canonname, sa = res 1943 try: 1944 self.bind(af, socktype, proto) 1945 except socket.error, serr: 1946 msg = "%s -- (%s: %s)" % (msg, sa, serr) 1947 if self.socket: 1948 self.socket.close() 1949 self.socket = None 1950 continue 1951 break 1952 if not self.socket: 1953 raise socket.error(msg) 1954 1955 # Timeout so KeyboardInterrupt can be caught on Win32 1956 self.socket.settimeout(1) 1957 self.socket.listen(self.request_queue_size) 1958 1959 # Create worker threads 1960 self.requests.start() 1961 1962 self.ready = True 1963 self._start_time = time.time() 1964 while self.ready: 1965 try: 1966 self.tick() 1967 except (KeyboardInterrupt, SystemExit): 1968 raise 1969 except: 1970 self.error_log("Error in HTTPServer.tick", level=logging.ERROR, 1971 traceback=True) 1972 1973 if self.interrupt: 1974 while self.interrupt is True: 1975 # Wait for self.stop() to complete. See _set_interrupt. 1976 time.sleep(0.1) 1977 if self.interrupt: 1978 raise self.interrupt
1979
1980 - def error_log(self, msg="", level=20, traceback=False):
1981 # Override this in subclasses as desired 1982 sys.stderr.write(msg + '\n') 1983 sys.stderr.flush() 1984 if traceback: 1985 tblines = format_exc() 1986 sys.stderr.write(tblines) 1987 sys.stderr.flush()
1988
1989 - def bind(self, family, type, proto=0):
1990 """Create (or recreate) the actual socket object.""" 1991 self.socket = socket.socket(family, type, proto) 1992 prevent_socket_inheritance(self.socket) 1993 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 1994 if self.nodelay and not isinstance(self.bind_addr, str): 1995 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 1996 1997 if self.ssl_adapter is not None: 1998 self.socket = self.ssl_adapter.bind(self.socket) 1999 2000 # If listening on the IPV6 any address ('::' = IN6ADDR_ANY), 2001 # activate dual-stack. See 2002 # https://bitbucket.org/cherrypy/cherrypy/issue/871. 2003 if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6 2004 and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')): 2005 try: 2006 self.socket.setsockopt( 2007 socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) 2008 except (AttributeError, socket.error): 2009 # Apparently, the socket option is not available in 2010 # this machine's TCP stack 2011 pass 2012 2013 self.socket.bind(self.bind_addr)
2014
2015 - def tick(self):
2016 """Accept a new connection and put it on the Queue.""" 2017 try: 2018 s, addr = self.socket.accept() 2019 if self.stats['Enabled']: 2020 self.stats['Accepts'] += 1 2021 if not self.ready: 2022 return 2023 2024 prevent_socket_inheritance(s) 2025 if hasattr(s, 'settimeout'): 2026 s.settimeout(self.timeout) 2027 2028 makefile = CP_fileobject 2029 ssl_env = {} 2030 # if ssl cert and key are set, we try to be a secure HTTP server 2031 if self.ssl_adapter is not None: 2032 try: 2033 s, ssl_env = self.ssl_adapter.wrap(s) 2034 except NoSSLError: 2035 msg = ("The client sent a plain HTTP request, but " 2036 "this server only speaks HTTPS on this port.") 2037 buf = ["%s 400 Bad Request\r\n" % self.protocol, 2038 "Content-Length: %s\r\n" % len(msg), 2039 "Content-Type: text/plain\r\n\r\n", 2040 msg] 2041 2042 wfile = makefile(s._sock, "wb", DEFAULT_BUFFER_SIZE) 2043 try: 2044 wfile.sendall("".join(buf)) 2045 except socket.error: 2046 x = sys.exc_info()[1] 2047 if x.args[0] not in socket_errors_to_ignore: 2048 raise 2049 return 2050 if not s: 2051 return 2052 makefile = self.ssl_adapter.makefile 2053 # Re-apply our timeout since we may have a new socket object 2054 if hasattr(s, 'settimeout'): 2055 s.settimeout(self.timeout) 2056 2057 conn = self.ConnectionClass(self, s, makefile) 2058 2059 if not isinstance(self.bind_addr, basestring): 2060 # optional values 2061 # Until we do DNS lookups, omit REMOTE_HOST 2062 if addr is None: # sometimes this can happen 2063 # figure out if AF_INET or AF_INET6. 2064 if len(s.getsockname()) == 2: 2065 # AF_INET 2066 addr = ('0.0.0.0', 0) 2067 else: 2068 # AF_INET6 2069 addr = ('::', 0) 2070 conn.remote_addr = addr[0] 2071 conn.remote_port = addr[1] 2072 2073 conn.ssl_env = ssl_env 2074 2075 self.requests.put(conn) 2076 except socket.timeout: 2077 # The only reason for the timeout in start() is so we can 2078 # notice keyboard interrupts on Win32, which don't interrupt 2079 # accept() by default 2080 return 2081 except socket.error: 2082 x = sys.exc_info()[1] 2083 if self.stats['Enabled']: 2084 self.stats['Socket Errors'] += 1 2085 if x.args[0] in socket_error_eintr: 2086 # I *think* this is right. EINTR should occur when a signal 2087 # is received during the accept() call; all docs say retry 2088 # the call, and I *think* I'm reading it right that Python 2089 # will then go ahead and poll for and handle the signal 2090 # elsewhere. See 2091 # https://bitbucket.org/cherrypy/cherrypy/issue/707. 2092 return 2093 if x.args[0] in socket_errors_nonblocking: 2094 # Just try again. See 2095 # https://bitbucket.org/cherrypy/cherrypy/issue/479. 2096 return 2097 if x.args[0] in socket_errors_to_ignore: 2098 # Our socket was closed. 2099 # See https://bitbucket.org/cherrypy/cherrypy/issue/686. 2100 return 2101 raise
2102
2103 - def _get_interrupt(self):
2104 return self._interrupt
2105
2106 - def _set_interrupt(self, interrupt):
2107 self._interrupt = True 2108 self.stop() 2109 self._interrupt = interrupt
2110 interrupt = property(_get_interrupt, _set_interrupt, 2111 doc="Set this to an Exception instance to " 2112 "interrupt the server.") 2113
2114 - def stop(self):
2115 """Gracefully shutdown a server that is serving forever.""" 2116 self.ready = False 2117 if self._start_time is not None: 2118 self._run_time += (time.time() - self._start_time) 2119 self._start_time = None 2120 2121 sock = getattr(self, "socket", None) 2122 if sock: 2123 if not isinstance(self.bind_addr, basestring): 2124 # Touch our own socket to make accept() return immediately. 2125 try: 2126 host, port = sock.getsockname()[:2] 2127 except socket.error: 2128 x = sys.exc_info()[1] 2129 if x.args[0] not in socket_errors_to_ignore: 2130 # Changed to use error code and not message 2131 # See 2132 # https://bitbucket.org/cherrypy/cherrypy/issue/860. 2133 raise 2134 else: 2135 # Note that we're explicitly NOT using AI_PASSIVE, 2136 # here, because we want an actual IP to touch. 2137 # localhost won't work if we've bound to a public IP, 2138 # but it will if we bound to '0.0.0.0' (INADDR_ANY). 2139 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, 2140 socket.SOCK_STREAM): 2141 af, socktype, proto, canonname, sa = res 2142 s = None 2143 try: 2144 s = socket.socket(af, socktype, proto) 2145 # See 2146 # http://groups.google.com/group/cherrypy-users/ 2147 # browse_frm/thread/bbfe5eb39c904fe0 2148 s.settimeout(1.0) 2149 s.connect((host, port)) 2150 s.close() 2151 except socket.error: 2152 if s: 2153 s.close() 2154 if hasattr(sock, "close"): 2155 sock.close() 2156 self.socket = None 2157 2158 self.requests.stop(self.shutdown_timeout)
2159 2160
2161 -class Gateway(object):
2162 2163 """A base class to interface HTTPServer with other systems, such as WSGI. 2164 """ 2165
2166 - def __init__(self, req):
2167 self.req = req
2168
2169 - def respond(self):
2170 """Process the current request. Must be overridden in a subclass.""" 2171 raise NotImplemented
2172 2173 2174 # These may either be wsgiserver.SSLAdapter subclasses or the string names 2175 # of such classes (in which case they will be lazily loaded). 2176 ssl_adapters = { 2177 'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter', 2178 'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter', 2179 } 2180 2181
2182 -def get_ssl_adapter_class(name='pyopenssl'):
2183 """Return an SSL adapter class for the given name.""" 2184 adapter = ssl_adapters[name.lower()] 2185 if isinstance(adapter, basestring): 2186 last_dot = adapter.rfind(".") 2187 attr_name = adapter[last_dot + 1:] 2188 mod_path = adapter[:last_dot] 2189 2190 try: 2191 mod = sys.modules[mod_path] 2192 if mod is None: 2193 raise KeyError() 2194 except KeyError: 2195 # The last [''] is important. 2196 mod = __import__(mod_path, globals(), locals(), ['']) 2197 2198 # Let an AttributeError propagate outward. 2199 try: 2200 adapter = getattr(mod, attr_name) 2201 except AttributeError: 2202 raise AttributeError("'%s' object has no attribute '%s'" 2203 % (mod_path, attr_name)) 2204 2205 return adapter
2206 2207 # ------------------------------- WSGI Stuff -------------------------------- # 2208 2209
2210 -class CherryPyWSGIServer(HTTPServer):
2211 2212 """A subclass of HTTPServer which calls a WSGI application.""" 2213 2214 wsgi_version = (1, 0) 2215 """The version of WSGI to produce.""" 2216
2217 - def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, 2218 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
2219 self.requests = ThreadPool(self, min=numthreads or 1, max=max) 2220 self.wsgi_app = wsgi_app 2221 self.gateway = wsgi_gateways[self.wsgi_version] 2222 2223 self.bind_addr = bind_addr 2224 if not server_name: 2225 server_name = socket.gethostname() 2226 self.server_name = server_name 2227 self.request_queue_size = request_queue_size 2228 2229 self.timeout = timeout 2230 self.shutdown_timeout = shutdown_timeout 2231 self.clear_stats()
2232
2233 - def _get_numthreads(self):
2234 return self.requests.min
2235
2236 - def _set_numthreads(self, value):
2237 self.requests.min = value
2238 numthreads = property(_get_numthreads, _set_numthreads)
2239 2240
2241 -class WSGIGateway(Gateway):
2242 2243 """A base class to interface HTTPServer with WSGI.""" 2244
2245 - def __init__(self, req):
2246 self.req = req 2247 self.started_response = False 2248 self.env = self.get_environ() 2249 self.remaining_bytes_out = None
2250
2251 - def get_environ(self):
2252 """Return a new environ dict targeting the given wsgi.version""" 2253 raise NotImplemented
2254
2255 - def respond(self):
2256 """Process the current request.""" 2257 response = self.req.server.wsgi_app(self.env, self.start_response) 2258 try: 2259 for chunk in response: 2260 # "The start_response callable must not actually transmit 2261 # the response headers. Instead, it must store them for the 2262 # server or gateway to transmit only after the first 2263 # iteration of the application return value that yields 2264 # a NON-EMPTY string, or upon the application's first 2265 # invocation of the write() callable." (PEP 333) 2266 if chunk: 2267 if isinstance(chunk, unicodestr): 2268 chunk = chunk.encode('ISO-8859-1') 2269 self.write(chunk) 2270 finally: 2271 if hasattr(response, "close"): 2272 response.close()
2273
2274 - def start_response(self, status, headers, exc_info=None):
2275 """WSGI callable to begin the HTTP response.""" 2276 # "The application may call start_response more than once, 2277 # if and only if the exc_info argument is provided." 2278 if self.started_response and not exc_info: 2279 raise AssertionError("WSGI start_response called a second " 2280 "time with no exc_info.") 2281 self.started_response = True 2282 2283 # "if exc_info is provided, and the HTTP headers have already been 2284 # sent, start_response must raise an error, and should raise the 2285 # exc_info tuple." 2286 if self.req.sent_headers: 2287 try: 2288 raise exc_info[0], exc_info[1], exc_info[2] 2289 finally: 2290 exc_info = None 2291 2292 self.req.status = status 2293 for k, v in headers: 2294 if not isinstance(k, str): 2295 raise TypeError( 2296 "WSGI response header key %r is not of type str." % k) 2297 if not isinstance(v, str): 2298 raise TypeError( 2299 "WSGI response header value %r is not of type str." % v) 2300 if k.lower() == 'content-length': 2301 self.remaining_bytes_out = int(v) 2302 self.req.outheaders.extend(headers) 2303 2304 return self.write
2305
2306 - def write(self, chunk):
2307 """WSGI callable to write unbuffered data to the client. 2308 2309 This method is also used internally by start_response (to write 2310 data from the iterable returned by the WSGI application). 2311 """ 2312 if not self.started_response: 2313 raise AssertionError("WSGI write called before start_response.") 2314 2315 chunklen = len(chunk) 2316 rbo = self.remaining_bytes_out 2317 if rbo is not None and chunklen > rbo: 2318 if not self.req.sent_headers: 2319 # Whew. We can send a 500 to the client. 2320 self.req.simple_response( 2321 "500 Internal Server Error", 2322 "The requested resource returned more bytes than the " 2323 "declared Content-Length.") 2324 else: 2325 # Dang. We have probably already sent data. Truncate the chunk 2326 # to fit (so the client doesn't hang) and raise an error later. 2327 chunk = chunk[:rbo] 2328 2329 if not self.req.sent_headers: 2330 self.req.sent_headers = True 2331 self.req.send_headers() 2332 2333 self.req.write(chunk) 2334 2335 if rbo is not None: 2336 rbo -= chunklen 2337 if rbo < 0: 2338 raise ValueError( 2339 "Response body exceeds the declared Content-Length.")
2340 2341
2342 -class WSGIGateway_10(WSGIGateway):
2343 2344 """A Gateway class to interface HTTPServer with WSGI 1.0.x.""" 2345
2346 - def get_environ(self):
2347 """Return a new environ dict targeting the given wsgi.version""" 2348 req = self.req 2349 env = { 2350 # set a non-standard environ entry so the WSGI app can know what 2351 # the *real* server protocol is (and what features to support). 2352 # See http://www.faqs.org/rfcs/rfc2145.html. 2353 'ACTUAL_SERVER_PROTOCOL': req.server.protocol, 2354 'PATH_INFO': req.path, 2355 'QUERY_STRING': req.qs, 2356 'REMOTE_ADDR': req.conn.remote_addr or '', 2357 'REMOTE_PORT': str(req.conn.remote_port or ''), 2358 'REQUEST_METHOD': req.method, 2359 'REQUEST_URI': req.uri, 2360 'SCRIPT_NAME': '', 2361 'SERVER_NAME': req.server.server_name, 2362 # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. 2363 'SERVER_PROTOCOL': req.request_protocol, 2364 'SERVER_SOFTWARE': req.server.software, 2365 'wsgi.errors': sys.stderr, 2366 'wsgi.input': req.rfile, 2367 'wsgi.multiprocess': False, 2368 'wsgi.multithread': True, 2369 'wsgi.run_once': False, 2370 'wsgi.url_scheme': req.scheme, 2371 'wsgi.version': (1, 0), 2372 } 2373 2374 if isinstance(req.server.bind_addr, basestring): 2375 # AF_UNIX. This isn't really allowed by WSGI, which doesn't 2376 # address unix domain sockets. But it's better than nothing. 2377 env["SERVER_PORT"] = "" 2378 else: 2379 env["SERVER_PORT"] = str(req.server.bind_addr[1]) 2380 2381 # Request headers 2382 for k, v in req.inheaders.iteritems(): 2383 env["HTTP_" + k.upper().replace("-", "_")] = v 2384 2385 # CONTENT_TYPE/CONTENT_LENGTH 2386 ct = env.pop("HTTP_CONTENT_TYPE", None) 2387 if ct is not None: 2388 env["CONTENT_TYPE"] = ct 2389 cl = env.pop("HTTP_CONTENT_LENGTH", None) 2390 if cl is not None: 2391 env["CONTENT_LENGTH"] = cl 2392 2393 if req.conn.ssl_env: 2394 env.update(req.conn.ssl_env) 2395 2396 return env
2397 2398
2399 -class WSGIGateway_u0(WSGIGateway_10):
2400 2401 """A Gateway class to interface HTTPServer with WSGI u.0. 2402 2403 WSGI u.0 is an experimental protocol, which uses unicode for keys and 2404 values in both Python 2 and Python 3. 2405 """ 2406
2407 - def get_environ(self):
2408 """Return a new environ dict targeting the given wsgi.version""" 2409 req = self.req 2410 env_10 = WSGIGateway_10.get_environ(self) 2411 env = dict([(k.decode('ISO-8859-1'), v) 2412 for k, v in env_10.iteritems()]) 2413 env[u'wsgi.version'] = ('u', 0) 2414 2415 # Request-URI 2416 env.setdefault(u'wsgi.url_encoding', u'utf-8') 2417 try: 2418 for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]: 2419 env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding']) 2420 except UnicodeDecodeError: 2421 # Fall back to latin 1 so apps can transcode if needed. 2422 env[u'wsgi.url_encoding'] = u'ISO-8859-1' 2423 for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]: 2424 env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding']) 2425 2426 for k, v in sorted(env.items()): 2427 if isinstance(v, str) and k not in ('REQUEST_URI', 'wsgi.input'): 2428 env[k] = v.decode('ISO-8859-1') 2429 2430 return env
2431 2432 wsgi_gateways = { 2433 (1, 0): WSGIGateway_10, 2434 ('u', 0): WSGIGateway_u0, 2435 } 2436 2437
2438 -class WSGIPathInfoDispatcher(object):
2439 2440 """A WSGI dispatcher for dispatch based on the PATH_INFO. 2441 2442 apps: a dict or list of (path_prefix, app) pairs. 2443 """ 2444
2445 - def __init__(self, apps):
2446 try: 2447 apps = list(apps.items()) 2448 except AttributeError: 2449 pass 2450 2451 # Sort the apps by len(path), descending 2452 apps.sort(cmp=lambda x, y: cmp(len(x[0]), len(y[0]))) 2453 apps.reverse() 2454 2455 # The path_prefix strings must start, but not end, with a slash. 2456 # Use "" instead of "/". 2457 self.apps = [(p.rstrip("/"), a) for p, a in apps]
2458
2459 - def __call__(self, environ, start_response):
2460 path = environ["PATH_INFO"] or "/" 2461 for p, app in self.apps: 2462 # The apps list should be sorted by length, descending. 2463 if path.startswith(p + "/") or path == p: 2464 environ = environ.copy() 2465 environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p 2466 environ["PATH_INFO"] = path[len(p):] 2467 return app(environ, start_response) 2468 2469 start_response('404 Not Found', [('Content-Type', 'text/plain'), 2470 ('Content-Length', '0')]) 2471 return ['']
2472