OLD | NEW |
(Empty) | |
| 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 terminat
ors") |
| 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 o
rd'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 te
ll. |
| 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) a
nd 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): |
| 1332 self.certificate = certificate |
| 1333 self.private_key = private_key |
| 1334 self.certificate_chain = certificate_chain |
| 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 R
ead'](w) for w |
| 1432 in s['Worker Threads'].values()], 0), |
| 1433 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum([w['Byte
s 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 Tim
e'](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_PASSI
VE) |
| 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 st
r." % 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-88
59-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 |
OLD | NEW |