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

Source Code for Module cherrypy.wsgiserver.wsgiserver3

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