1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/OpenSecurity/install/web.py-0.37/web/wsgiserver/__init__.py Mon Dec 02 14:02:05 2013 +0100
1.3 @@ -0,0 +1,2219 @@
1.4 +"""A high-speed, production ready, thread pooled, generic HTTP server.
1.5 +
1.6 +Simplest example on how to use this module directly
1.7 +(without using CherryPy's application machinery)::
1.8 +
1.9 + from cherrypy import wsgiserver
1.10 +
1.11 + def my_crazy_app(environ, start_response):
1.12 + status = '200 OK'
1.13 + response_headers = [('Content-type','text/plain')]
1.14 + start_response(status, response_headers)
1.15 + return ['Hello world!']
1.16 +
1.17 + server = wsgiserver.CherryPyWSGIServer(
1.18 + ('0.0.0.0', 8070), my_crazy_app,
1.19 + server_name='www.cherrypy.example')
1.20 + server.start()
1.21 +
1.22 +The CherryPy WSGI server can serve as many WSGI applications
1.23 +as you want in one instance by using a WSGIPathInfoDispatcher::
1.24 +
1.25 + d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
1.26 + server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
1.27 +
1.28 +Want SSL support? Just set server.ssl_adapter to an SSLAdapter instance.
1.29 +
1.30 +This won't call the CherryPy engine (application side) at all, only the
1.31 +HTTP server, which is independent from the rest of CherryPy. Don't
1.32 +let the name "CherryPyWSGIServer" throw you; the name merely reflects
1.33 +its origin, not its coupling.
1.34 +
1.35 +For those of you wanting to understand internals of this module, here's the
1.36 +basic call flow. The server's listening thread runs a very tight loop,
1.37 +sticking incoming connections onto a Queue::
1.38 +
1.39 + server = CherryPyWSGIServer(...)
1.40 + server.start()
1.41 + while True:
1.42 + tick()
1.43 + # This blocks until a request comes in:
1.44 + child = socket.accept()
1.45 + conn = HTTPConnection(child, ...)
1.46 + server.requests.put(conn)
1.47 +
1.48 +Worker threads are kept in a pool and poll the Queue, popping off and then
1.49 +handling each connection in turn. Each connection can consist of an arbitrary
1.50 +number of requests and their responses, so we run a nested loop::
1.51 +
1.52 + while True:
1.53 + conn = server.requests.get()
1.54 + conn.communicate()
1.55 + -> while True:
1.56 + req = HTTPRequest(...)
1.57 + req.parse_request()
1.58 + -> # Read the Request-Line, e.g. "GET /page HTTP/1.1"
1.59 + req.rfile.readline()
1.60 + read_headers(req.rfile, req.inheaders)
1.61 + req.respond()
1.62 + -> response = app(...)
1.63 + try:
1.64 + for chunk in response:
1.65 + if chunk:
1.66 + req.write(chunk)
1.67 + finally:
1.68 + if hasattr(response, "close"):
1.69 + response.close()
1.70 + if req.close_connection:
1.71 + return
1.72 +"""
1.73 +
1.74 +CRLF = '\r\n'
1.75 +import os
1.76 +import Queue
1.77 +import re
1.78 +quoted_slash = re.compile("(?i)%2F")
1.79 +import rfc822
1.80 +import socket
1.81 +import sys
1.82 +if 'win' in sys.platform and not hasattr(socket, 'IPPROTO_IPV6'):
1.83 + socket.IPPROTO_IPV6 = 41
1.84 +try:
1.85 + import cStringIO as StringIO
1.86 +except ImportError:
1.87 + import StringIO
1.88 +DEFAULT_BUFFER_SIZE = -1
1.89 +
1.90 +_fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
1.91 +
1.92 +import threading
1.93 +import time
1.94 +import traceback
1.95 +def format_exc(limit=None):
1.96 + """Like print_exc() but return a string. Backport for Python 2.3."""
1.97 + try:
1.98 + etype, value, tb = sys.exc_info()
1.99 + return ''.join(traceback.format_exception(etype, value, tb, limit))
1.100 + finally:
1.101 + etype = value = tb = None
1.102 +
1.103 +
1.104 +from urllib import unquote
1.105 +from urlparse import urlparse
1.106 +import warnings
1.107 +
1.108 +import errno
1.109 +
1.110 +def plat_specific_errors(*errnames):
1.111 + """Return error numbers for all errors in errnames on this platform.
1.112 +
1.113 + The 'errno' module contains different global constants depending on
1.114 + the specific platform (OS). This function will return the list of
1.115 + numeric values for a given list of potential names.
1.116 + """
1.117 + errno_names = dir(errno)
1.118 + nums = [getattr(errno, k) for k in errnames if k in errno_names]
1.119 + # de-dupe the list
1.120 + return dict.fromkeys(nums).keys()
1.121 +
1.122 +socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
1.123 +
1.124 +socket_errors_to_ignore = plat_specific_errors(
1.125 + "EPIPE",
1.126 + "EBADF", "WSAEBADF",
1.127 + "ENOTSOCK", "WSAENOTSOCK",
1.128 + "ETIMEDOUT", "WSAETIMEDOUT",
1.129 + "ECONNREFUSED", "WSAECONNREFUSED",
1.130 + "ECONNRESET", "WSAECONNRESET",
1.131 + "ECONNABORTED", "WSAECONNABORTED",
1.132 + "ENETRESET", "WSAENETRESET",
1.133 + "EHOSTDOWN", "EHOSTUNREACH",
1.134 + )
1.135 +socket_errors_to_ignore.append("timed out")
1.136 +socket_errors_to_ignore.append("The read operation timed out")
1.137 +
1.138 +socket_errors_nonblocking = plat_specific_errors(
1.139 + 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
1.140 +
1.141 +comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding',
1.142 + 'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control',
1.143 + 'Connection', 'Content-Encoding', 'Content-Language', 'Expect',
1.144 + 'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE',
1.145 + 'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning',
1.146 + 'WWW-Authenticate']
1.147 +
1.148 +
1.149 +import logging
1.150 +if not hasattr(logging, 'statistics'): logging.statistics = {}
1.151 +
1.152 +
1.153 +def read_headers(rfile, hdict=None):
1.154 + """Read headers from the given stream into the given header dict.
1.155 +
1.156 + If hdict is None, a new header dict is created. Returns the populated
1.157 + header dict.
1.158 +
1.159 + Headers which are repeated are folded together using a comma if their
1.160 + specification so dictates.
1.161 +
1.162 + This function raises ValueError when the read bytes violate the HTTP spec.
1.163 + You should probably return "400 Bad Request" if this happens.
1.164 + """
1.165 + if hdict is None:
1.166 + hdict = {}
1.167 +
1.168 + while True:
1.169 + line = rfile.readline()
1.170 + if not line:
1.171 + # No more data--illegal end of headers
1.172 + raise ValueError("Illegal end of headers.")
1.173 +
1.174 + if line == CRLF:
1.175 + # Normal end of headers
1.176 + break
1.177 + if not line.endswith(CRLF):
1.178 + raise ValueError("HTTP requires CRLF terminators")
1.179 +
1.180 + if line[0] in ' \t':
1.181 + # It's a continuation line.
1.182 + v = line.strip()
1.183 + else:
1.184 + try:
1.185 + k, v = line.split(":", 1)
1.186 + except ValueError:
1.187 + raise ValueError("Illegal header line.")
1.188 + # TODO: what about TE and WWW-Authenticate?
1.189 + k = k.strip().title()
1.190 + v = v.strip()
1.191 + hname = k
1.192 +
1.193 + if k in comma_separated_headers:
1.194 + existing = hdict.get(hname)
1.195 + if existing:
1.196 + v = ", ".join((existing, v))
1.197 + hdict[hname] = v
1.198 +
1.199 + return hdict
1.200 +
1.201 +
1.202 +class MaxSizeExceeded(Exception):
1.203 + pass
1.204 +
1.205 +class SizeCheckWrapper(object):
1.206 + """Wraps a file-like object, raising MaxSizeExceeded if too large."""
1.207 +
1.208 + def __init__(self, rfile, maxlen):
1.209 + self.rfile = rfile
1.210 + self.maxlen = maxlen
1.211 + self.bytes_read = 0
1.212 +
1.213 + def _check_length(self):
1.214 + if self.maxlen and self.bytes_read > self.maxlen:
1.215 + raise MaxSizeExceeded()
1.216 +
1.217 + def read(self, size=None):
1.218 + data = self.rfile.read(size)
1.219 + self.bytes_read += len(data)
1.220 + self._check_length()
1.221 + return data
1.222 +
1.223 + def readline(self, size=None):
1.224 + if size is not None:
1.225 + data = self.rfile.readline(size)
1.226 + self.bytes_read += len(data)
1.227 + self._check_length()
1.228 + return data
1.229 +
1.230 + # User didn't specify a size ...
1.231 + # We read the line in chunks to make sure it's not a 100MB line !
1.232 + res = []
1.233 + while True:
1.234 + data = self.rfile.readline(256)
1.235 + self.bytes_read += len(data)
1.236 + self._check_length()
1.237 + res.append(data)
1.238 + # See http://www.cherrypy.org/ticket/421
1.239 + if len(data) < 256 or data[-1:] == "\n":
1.240 + return ''.join(res)
1.241 +
1.242 + def readlines(self, sizehint=0):
1.243 + # Shamelessly stolen from StringIO
1.244 + total = 0
1.245 + lines = []
1.246 + line = self.readline()
1.247 + while line:
1.248 + lines.append(line)
1.249 + total += len(line)
1.250 + if 0 < sizehint <= total:
1.251 + break
1.252 + line = self.readline()
1.253 + return lines
1.254 +
1.255 + def close(self):
1.256 + self.rfile.close()
1.257 +
1.258 + def __iter__(self):
1.259 + return self
1.260 +
1.261 + def next(self):
1.262 + data = self.rfile.next()
1.263 + self.bytes_read += len(data)
1.264 + self._check_length()
1.265 + return data
1.266 +
1.267 +
1.268 +class KnownLengthRFile(object):
1.269 + """Wraps a file-like object, returning an empty string when exhausted."""
1.270 +
1.271 + def __init__(self, rfile, content_length):
1.272 + self.rfile = rfile
1.273 + self.remaining = content_length
1.274 +
1.275 + def read(self, size=None):
1.276 + if self.remaining == 0:
1.277 + return ''
1.278 + if size is None:
1.279 + size = self.remaining
1.280 + else:
1.281 + size = min(size, self.remaining)
1.282 +
1.283 + data = self.rfile.read(size)
1.284 + self.remaining -= len(data)
1.285 + return data
1.286 +
1.287 + def readline(self, size=None):
1.288 + if self.remaining == 0:
1.289 + return ''
1.290 + if size is None:
1.291 + size = self.remaining
1.292 + else:
1.293 + size = min(size, self.remaining)
1.294 +
1.295 + data = self.rfile.readline(size)
1.296 + self.remaining -= len(data)
1.297 + return data
1.298 +
1.299 + def readlines(self, sizehint=0):
1.300 + # Shamelessly stolen from StringIO
1.301 + total = 0
1.302 + lines = []
1.303 + line = self.readline(sizehint)
1.304 + while line:
1.305 + lines.append(line)
1.306 + total += len(line)
1.307 + if 0 < sizehint <= total:
1.308 + break
1.309 + line = self.readline(sizehint)
1.310 + return lines
1.311 +
1.312 + def close(self):
1.313 + self.rfile.close()
1.314 +
1.315 + def __iter__(self):
1.316 + return self
1.317 +
1.318 + def __next__(self):
1.319 + data = next(self.rfile)
1.320 + self.remaining -= len(data)
1.321 + return data
1.322 +
1.323 +
1.324 +class ChunkedRFile(object):
1.325 + """Wraps a file-like object, returning an empty string when exhausted.
1.326 +
1.327 + This class is intended to provide a conforming wsgi.input value for
1.328 + request entities that have been encoded with the 'chunked' transfer
1.329 + encoding.
1.330 + """
1.331 +
1.332 + def __init__(self, rfile, maxlen, bufsize=8192):
1.333 + self.rfile = rfile
1.334 + self.maxlen = maxlen
1.335 + self.bytes_read = 0
1.336 + self.buffer = ''
1.337 + self.bufsize = bufsize
1.338 + self.closed = False
1.339 +
1.340 + def _fetch(self):
1.341 + if self.closed:
1.342 + return
1.343 +
1.344 + line = self.rfile.readline()
1.345 + self.bytes_read += len(line)
1.346 +
1.347 + if self.maxlen and self.bytes_read > self.maxlen:
1.348 + raise MaxSizeExceeded("Request Entity Too Large", self.maxlen)
1.349 +
1.350 + line = line.strip().split(";", 1)
1.351 +
1.352 + try:
1.353 + chunk_size = line.pop(0)
1.354 + chunk_size = int(chunk_size, 16)
1.355 + except ValueError:
1.356 + raise ValueError("Bad chunked transfer size: " + repr(chunk_size))
1.357 +
1.358 + if chunk_size <= 0:
1.359 + self.closed = True
1.360 + return
1.361 +
1.362 +## if line: chunk_extension = line[0]
1.363 +
1.364 + if self.maxlen and self.bytes_read + chunk_size > self.maxlen:
1.365 + raise IOError("Request Entity Too Large")
1.366 +
1.367 + chunk = self.rfile.read(chunk_size)
1.368 + self.bytes_read += len(chunk)
1.369 + self.buffer += chunk
1.370 +
1.371 + crlf = self.rfile.read(2)
1.372 + if crlf != CRLF:
1.373 + raise ValueError(
1.374 + "Bad chunked transfer coding (expected '\\r\\n', "
1.375 + "got " + repr(crlf) + ")")
1.376 +
1.377 + def read(self, size=None):
1.378 + data = ''
1.379 + while True:
1.380 + if size and len(data) >= size:
1.381 + return data
1.382 +
1.383 + if not self.buffer:
1.384 + self._fetch()
1.385 + if not self.buffer:
1.386 + # EOF
1.387 + return data
1.388 +
1.389 + if size:
1.390 + remaining = size - len(data)
1.391 + data += self.buffer[:remaining]
1.392 + self.buffer = self.buffer[remaining:]
1.393 + else:
1.394 + data += self.buffer
1.395 +
1.396 + def readline(self, size=None):
1.397 + data = ''
1.398 + while True:
1.399 + if size and len(data) >= size:
1.400 + return data
1.401 +
1.402 + if not self.buffer:
1.403 + self._fetch()
1.404 + if not self.buffer:
1.405 + # EOF
1.406 + return data
1.407 +
1.408 + newline_pos = self.buffer.find('\n')
1.409 + if size:
1.410 + if newline_pos == -1:
1.411 + remaining = size - len(data)
1.412 + data += self.buffer[:remaining]
1.413 + self.buffer = self.buffer[remaining:]
1.414 + else:
1.415 + remaining = min(size - len(data), newline_pos)
1.416 + data += self.buffer[:remaining]
1.417 + self.buffer = self.buffer[remaining:]
1.418 + else:
1.419 + if newline_pos == -1:
1.420 + data += self.buffer
1.421 + else:
1.422 + data += self.buffer[:newline_pos]
1.423 + self.buffer = self.buffer[newline_pos:]
1.424 +
1.425 + def readlines(self, sizehint=0):
1.426 + # Shamelessly stolen from StringIO
1.427 + total = 0
1.428 + lines = []
1.429 + line = self.readline(sizehint)
1.430 + while line:
1.431 + lines.append(line)
1.432 + total += len(line)
1.433 + if 0 < sizehint <= total:
1.434 + break
1.435 + line = self.readline(sizehint)
1.436 + return lines
1.437 +
1.438 + def read_trailer_lines(self):
1.439 + if not self.closed:
1.440 + raise ValueError(
1.441 + "Cannot read trailers until the request body has been read.")
1.442 +
1.443 + while True:
1.444 + line = self.rfile.readline()
1.445 + if not line:
1.446 + # No more data--illegal end of headers
1.447 + raise ValueError("Illegal end of headers.")
1.448 +
1.449 + self.bytes_read += len(line)
1.450 + if self.maxlen and self.bytes_read > self.maxlen:
1.451 + raise IOError("Request Entity Too Large")
1.452 +
1.453 + if line == CRLF:
1.454 + # Normal end of headers
1.455 + break
1.456 + if not line.endswith(CRLF):
1.457 + raise ValueError("HTTP requires CRLF terminators")
1.458 +
1.459 + yield line
1.460 +
1.461 + def close(self):
1.462 + self.rfile.close()
1.463 +
1.464 + def __iter__(self):
1.465 + # Shamelessly stolen from StringIO
1.466 + total = 0
1.467 + line = self.readline(sizehint)
1.468 + while line:
1.469 + yield line
1.470 + total += len(line)
1.471 + if 0 < sizehint <= total:
1.472 + break
1.473 + line = self.readline(sizehint)
1.474 +
1.475 +
1.476 +class HTTPRequest(object):
1.477 + """An HTTP Request (and response).
1.478 +
1.479 + A single HTTP connection may consist of multiple request/response pairs.
1.480 + """
1.481 +
1.482 + server = None
1.483 + """The HTTPServer object which is receiving this request."""
1.484 +
1.485 + conn = None
1.486 + """The HTTPConnection object on which this request connected."""
1.487 +
1.488 + inheaders = {}
1.489 + """A dict of request headers."""
1.490 +
1.491 + outheaders = []
1.492 + """A list of header tuples to write in the response."""
1.493 +
1.494 + ready = False
1.495 + """When True, the request has been parsed and is ready to begin generating
1.496 + the response. When False, signals the calling Connection that the response
1.497 + should not be generated and the connection should close."""
1.498 +
1.499 + close_connection = False
1.500 + """Signals the calling Connection that the request should close. This does
1.501 + not imply an error! The client and/or server may each request that the
1.502 + connection be closed."""
1.503 +
1.504 + chunked_write = False
1.505 + """If True, output will be encoded with the "chunked" transfer-coding.
1.506 +
1.507 + This value is set automatically inside send_headers."""
1.508 +
1.509 + def __init__(self, server, conn):
1.510 + self.server= server
1.511 + self.conn = conn
1.512 +
1.513 + self.ready = False
1.514 + self.started_request = False
1.515 + self.scheme = "http"
1.516 + if self.server.ssl_adapter is not None:
1.517 + self.scheme = "https"
1.518 + # Use the lowest-common protocol in case read_request_line errors.
1.519 + self.response_protocol = 'HTTP/1.0'
1.520 + self.inheaders = {}
1.521 +
1.522 + self.status = ""
1.523 + self.outheaders = []
1.524 + self.sent_headers = False
1.525 + self.close_connection = self.__class__.close_connection
1.526 + self.chunked_read = False
1.527 + self.chunked_write = self.__class__.chunked_write
1.528 +
1.529 + def parse_request(self):
1.530 + """Parse the next HTTP request start-line and message-headers."""
1.531 + self.rfile = SizeCheckWrapper(self.conn.rfile,
1.532 + self.server.max_request_header_size)
1.533 + try:
1.534 + self.read_request_line()
1.535 + except MaxSizeExceeded:
1.536 + self.simple_response("414 Request-URI Too Long",
1.537 + "The Request-URI sent with the request exceeds the maximum "
1.538 + "allowed bytes.")
1.539 + return
1.540 +
1.541 + try:
1.542 + success = self.read_request_headers()
1.543 + except MaxSizeExceeded:
1.544 + self.simple_response("413 Request Entity Too Large",
1.545 + "The headers sent with the request exceed the maximum "
1.546 + "allowed bytes.")
1.547 + return
1.548 + else:
1.549 + if not success:
1.550 + return
1.551 +
1.552 + self.ready = True
1.553 +
1.554 + def read_request_line(self):
1.555 + # HTTP/1.1 connections are persistent by default. If a client
1.556 + # requests a page, then idles (leaves the connection open),
1.557 + # then rfile.readline() will raise socket.error("timed out").
1.558 + # Note that it does this based on the value given to settimeout(),
1.559 + # and doesn't need the client to request or acknowledge the close
1.560 + # (although your TCP stack might suffer for it: cf Apache's history
1.561 + # with FIN_WAIT_2).
1.562 + request_line = self.rfile.readline()
1.563 +
1.564 + # Set started_request to True so communicate() knows to send 408
1.565 + # from here on out.
1.566 + self.started_request = True
1.567 + if not request_line:
1.568 + # Force self.ready = False so the connection will close.
1.569 + self.ready = False
1.570 + return
1.571 +
1.572 + if request_line == CRLF:
1.573 + # RFC 2616 sec 4.1: "...if the server is reading the protocol
1.574 + # stream at the beginning of a message and receives a CRLF
1.575 + # first, it should ignore the CRLF."
1.576 + # But only ignore one leading line! else we enable a DoS.
1.577 + request_line = self.rfile.readline()
1.578 + if not request_line:
1.579 + self.ready = False
1.580 + return
1.581 +
1.582 + if not request_line.endswith(CRLF):
1.583 + self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
1.584 + return
1.585 +
1.586 + try:
1.587 + method, uri, req_protocol = request_line.strip().split(" ", 2)
1.588 + rp = int(req_protocol[5]), int(req_protocol[7])
1.589 + except (ValueError, IndexError):
1.590 + self.simple_response("400 Bad Request", "Malformed Request-Line")
1.591 + return
1.592 +
1.593 + self.uri = uri
1.594 + self.method = method
1.595 +
1.596 + # uri may be an abs_path (including "http://host.domain.tld");
1.597 + scheme, authority, path = self.parse_request_uri(uri)
1.598 + if '#' in path:
1.599 + self.simple_response("400 Bad Request",
1.600 + "Illegal #fragment in Request-URI.")
1.601 + return
1.602 +
1.603 + if scheme:
1.604 + self.scheme = scheme
1.605 +
1.606 + qs = ''
1.607 + if '?' in path:
1.608 + path, qs = path.split('?', 1)
1.609 +
1.610 + # Unquote the path+params (e.g. "/this%20path" -> "/this path").
1.611 + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
1.612 + #
1.613 + # But note that "...a URI must be separated into its components
1.614 + # before the escaped characters within those components can be
1.615 + # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
1.616 + # Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
1.617 + try:
1.618 + atoms = [unquote(x) for x in quoted_slash.split(path)]
1.619 + except ValueError, ex:
1.620 + self.simple_response("400 Bad Request", ex.args[0])
1.621 + return
1.622 + path = "%2F".join(atoms)
1.623 + self.path = path
1.624 +
1.625 + # Note that, like wsgiref and most other HTTP servers,
1.626 + # we "% HEX HEX"-unquote the path but not the query string.
1.627 + self.qs = qs
1.628 +
1.629 + # Compare request and server HTTP protocol versions, in case our
1.630 + # server does not support the requested protocol. Limit our output
1.631 + # to min(req, server). We want the following output:
1.632 + # request server actual written supported response
1.633 + # protocol protocol response protocol feature set
1.634 + # a 1.0 1.0 1.0 1.0
1.635 + # b 1.0 1.1 1.1 1.0
1.636 + # c 1.1 1.0 1.0 1.0
1.637 + # d 1.1 1.1 1.1 1.1
1.638 + # Notice that, in (b), the response will be "HTTP/1.1" even though
1.639 + # the client only understands 1.0. RFC 2616 10.5.6 says we should
1.640 + # only return 505 if the _major_ version is different.
1.641 + sp = int(self.server.protocol[5]), int(self.server.protocol[7])
1.642 +
1.643 + if sp[0] != rp[0]:
1.644 + self.simple_response("505 HTTP Version Not Supported")
1.645 + return
1.646 + self.request_protocol = req_protocol
1.647 + self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
1.648 +
1.649 + def read_request_headers(self):
1.650 + """Read self.rfile into self.inheaders. Return success."""
1.651 +
1.652 + # then all the http headers
1.653 + try:
1.654 + read_headers(self.rfile, self.inheaders)
1.655 + except ValueError, ex:
1.656 + self.simple_response("400 Bad Request", ex.args[0])
1.657 + return False
1.658 +
1.659 + mrbs = self.server.max_request_body_size
1.660 + if mrbs and int(self.inheaders.get("Content-Length", 0)) > mrbs:
1.661 + self.simple_response("413 Request Entity Too Large",
1.662 + "The entity sent with the request exceeds the maximum "
1.663 + "allowed bytes.")
1.664 + return False
1.665 +
1.666 + # Persistent connection support
1.667 + if self.response_protocol == "HTTP/1.1":
1.668 + # Both server and client are HTTP/1.1
1.669 + if self.inheaders.get("Connection", "") == "close":
1.670 + self.close_connection = True
1.671 + else:
1.672 + # Either the server or client (or both) are HTTP/1.0
1.673 + if self.inheaders.get("Connection", "") != "Keep-Alive":
1.674 + self.close_connection = True
1.675 +
1.676 + # Transfer-Encoding support
1.677 + te = None
1.678 + if self.response_protocol == "HTTP/1.1":
1.679 + te = self.inheaders.get("Transfer-Encoding")
1.680 + if te:
1.681 + te = [x.strip().lower() for x in te.split(",") if x.strip()]
1.682 +
1.683 + self.chunked_read = False
1.684 +
1.685 + if te:
1.686 + for enc in te:
1.687 + if enc == "chunked":
1.688 + self.chunked_read = True
1.689 + else:
1.690 + # Note that, even if we see "chunked", we must reject
1.691 + # if there is an extension we don't recognize.
1.692 + self.simple_response("501 Unimplemented")
1.693 + self.close_connection = True
1.694 + return False
1.695 +
1.696 + # From PEP 333:
1.697 + # "Servers and gateways that implement HTTP 1.1 must provide
1.698 + # transparent support for HTTP 1.1's "expect/continue" mechanism.
1.699 + # This may be done in any of several ways:
1.700 + # 1. Respond to requests containing an Expect: 100-continue request
1.701 + # with an immediate "100 Continue" response, and proceed normally.
1.702 + # 2. Proceed with the request normally, but provide the application
1.703 + # with a wsgi.input stream that will send the "100 Continue"
1.704 + # response if/when the application first attempts to read from
1.705 + # the input stream. The read request must then remain blocked
1.706 + # until the client responds.
1.707 + # 3. Wait until the client decides that the server does not support
1.708 + # expect/continue, and sends the request body on its own.
1.709 + # (This is suboptimal, and is not recommended.)
1.710 + #
1.711 + # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
1.712 + # but it seems like it would be a big slowdown for such a rare case.
1.713 + if self.inheaders.get("Expect", "") == "100-continue":
1.714 + # Don't use simple_response here, because it emits headers
1.715 + # we don't want. See http://www.cherrypy.org/ticket/951
1.716 + msg = self.server.protocol + " 100 Continue\r\n\r\n"
1.717 + try:
1.718 + self.conn.wfile.sendall(msg)
1.719 + except socket.error, x:
1.720 + if x.args[0] not in socket_errors_to_ignore:
1.721 + raise
1.722 + return True
1.723 +
1.724 + def parse_request_uri(self, uri):
1.725 + """Parse a Request-URI into (scheme, authority, path).
1.726 +
1.727 + Note that Request-URI's must be one of::
1.728 +
1.729 + Request-URI = "*" | absoluteURI | abs_path | authority
1.730 +
1.731 + Therefore, a Request-URI which starts with a double forward-slash
1.732 + cannot be a "net_path"::
1.733 +
1.734 + net_path = "//" authority [ abs_path ]
1.735 +
1.736 + Instead, it must be interpreted as an "abs_path" with an empty first
1.737 + path segment::
1.738 +
1.739 + abs_path = "/" path_segments
1.740 + path_segments = segment *( "/" segment )
1.741 + segment = *pchar *( ";" param )
1.742 + param = *pchar
1.743 + """
1.744 + if uri == "*":
1.745 + return None, None, uri
1.746 +
1.747 + i = uri.find('://')
1.748 + if i > 0 and '?' not in uri[:i]:
1.749 + # An absoluteURI.
1.750 + # If there's a scheme (and it must be http or https), then:
1.751 + # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
1.752 + scheme, remainder = uri[:i].lower(), uri[i + 3:]
1.753 + authority, path = remainder.split("/", 1)
1.754 + return scheme, authority, path
1.755 +
1.756 + if uri.startswith('/'):
1.757 + # An abs_path.
1.758 + return None, None, uri
1.759 + else:
1.760 + # An authority.
1.761 + return None, uri, None
1.762 +
1.763 + def respond(self):
1.764 + """Call the gateway and write its iterable output."""
1.765 + mrbs = self.server.max_request_body_size
1.766 + if self.chunked_read:
1.767 + self.rfile = ChunkedRFile(self.conn.rfile, mrbs)
1.768 + else:
1.769 + cl = int(self.inheaders.get("Content-Length", 0))
1.770 + if mrbs and mrbs < cl:
1.771 + if not self.sent_headers:
1.772 + self.simple_response("413 Request Entity Too Large",
1.773 + "The entity sent with the request exceeds the maximum "
1.774 + "allowed bytes.")
1.775 + return
1.776 + self.rfile = KnownLengthRFile(self.conn.rfile, cl)
1.777 +
1.778 + self.server.gateway(self).respond()
1.779 +
1.780 + if (self.ready and not self.sent_headers):
1.781 + self.sent_headers = True
1.782 + self.send_headers()
1.783 + if self.chunked_write:
1.784 + self.conn.wfile.sendall("0\r\n\r\n")
1.785 +
1.786 + def simple_response(self, status, msg=""):
1.787 + """Write a simple response back to the client."""
1.788 + status = str(status)
1.789 + buf = [self.server.protocol + " " +
1.790 + status + CRLF,
1.791 + "Content-Length: %s\r\n" % len(msg),
1.792 + "Content-Type: text/plain\r\n"]
1.793 +
1.794 + if status[:3] in ("413", "414"):
1.795 + # Request Entity Too Large / Request-URI Too Long
1.796 + self.close_connection = True
1.797 + if self.response_protocol == 'HTTP/1.1':
1.798 + # This will not be true for 414, since read_request_line
1.799 + # usually raises 414 before reading the whole line, and we
1.800 + # therefore cannot know the proper response_protocol.
1.801 + buf.append("Connection: close\r\n")
1.802 + else:
1.803 + # HTTP/1.0 had no 413/414 status nor Connection header.
1.804 + # Emit 400 instead and trust the message body is enough.
1.805 + status = "400 Bad Request"
1.806 +
1.807 + buf.append(CRLF)
1.808 + if msg:
1.809 + if isinstance(msg, unicode):
1.810 + msg = msg.encode("ISO-8859-1")
1.811 + buf.append(msg)
1.812 +
1.813 + try:
1.814 + self.conn.wfile.sendall("".join(buf))
1.815 + except socket.error, x:
1.816 + if x.args[0] not in socket_errors_to_ignore:
1.817 + raise
1.818 +
1.819 + def write(self, chunk):
1.820 + """Write unbuffered data to the client."""
1.821 + if self.chunked_write and chunk:
1.822 + buf = [hex(len(chunk))[2:], CRLF, chunk, CRLF]
1.823 + self.conn.wfile.sendall("".join(buf))
1.824 + else:
1.825 + self.conn.wfile.sendall(chunk)
1.826 +
1.827 + def send_headers(self):
1.828 + """Assert, process, and send the HTTP response message-headers.
1.829 +
1.830 + You must set self.status, and self.outheaders before calling this.
1.831 + """
1.832 + hkeys = [key.lower() for key, value in self.outheaders]
1.833 + status = int(self.status[:3])
1.834 +
1.835 + if status == 413:
1.836 + # Request Entity Too Large. Close conn to avoid garbage.
1.837 + self.close_connection = True
1.838 + elif "content-length" not in hkeys:
1.839 + # "All 1xx (informational), 204 (no content),
1.840 + # and 304 (not modified) responses MUST NOT
1.841 + # include a message-body." So no point chunking.
1.842 + if status < 200 or status in (204, 205, 304):
1.843 + pass
1.844 + else:
1.845 + if (self.response_protocol == 'HTTP/1.1'
1.846 + and self.method != 'HEAD'):
1.847 + # Use the chunked transfer-coding
1.848 + self.chunked_write = True
1.849 + self.outheaders.append(("Transfer-Encoding", "chunked"))
1.850 + else:
1.851 + # Closing the conn is the only way to determine len.
1.852 + self.close_connection = True
1.853 +
1.854 + if "connection" not in hkeys:
1.855 + if self.response_protocol == 'HTTP/1.1':
1.856 + # Both server and client are HTTP/1.1 or better
1.857 + if self.close_connection:
1.858 + self.outheaders.append(("Connection", "close"))
1.859 + else:
1.860 + # Server and/or client are HTTP/1.0
1.861 + if not self.close_connection:
1.862 + self.outheaders.append(("Connection", "Keep-Alive"))
1.863 +
1.864 + if (not self.close_connection) and (not self.chunked_read):
1.865 + # Read any remaining request body data on the socket.
1.866 + # "If an origin server receives a request that does not include an
1.867 + # Expect request-header field with the "100-continue" expectation,
1.868 + # the request includes a request body, and the server responds
1.869 + # with a final status code before reading the entire request body
1.870 + # from the transport connection, then the server SHOULD NOT close
1.871 + # the transport connection until it has read the entire request,
1.872 + # or until the client closes the connection. Otherwise, the client
1.873 + # might not reliably receive the response message. However, this
1.874 + # requirement is not be construed as preventing a server from
1.875 + # defending itself against denial-of-service attacks, or from
1.876 + # badly broken client implementations."
1.877 + remaining = getattr(self.rfile, 'remaining', 0)
1.878 + if remaining > 0:
1.879 + self.rfile.read(remaining)
1.880 +
1.881 + if "date" not in hkeys:
1.882 + self.outheaders.append(("Date", rfc822.formatdate()))
1.883 +
1.884 + if "server" not in hkeys:
1.885 + self.outheaders.append(("Server", self.server.server_name))
1.886 +
1.887 + buf = [self.server.protocol + " " + self.status + CRLF]
1.888 + for k, v in self.outheaders:
1.889 + buf.append(k + ": " + v + CRLF)
1.890 + buf.append(CRLF)
1.891 + self.conn.wfile.sendall("".join(buf))
1.892 +
1.893 +
1.894 +class NoSSLError(Exception):
1.895 + """Exception raised when a client speaks HTTP to an HTTPS socket."""
1.896 + pass
1.897 +
1.898 +
1.899 +class FatalSSLAlert(Exception):
1.900 + """Exception raised when the SSL implementation signals a fatal alert."""
1.901 + pass
1.902 +
1.903 +
1.904 +class CP_fileobject(socket._fileobject):
1.905 + """Faux file object attached to a socket object."""
1.906 +
1.907 + def __init__(self, *args, **kwargs):
1.908 + self.bytes_read = 0
1.909 + self.bytes_written = 0
1.910 + socket._fileobject.__init__(self, *args, **kwargs)
1.911 +
1.912 + def sendall(self, data):
1.913 + """Sendall for non-blocking sockets."""
1.914 + while data:
1.915 + try:
1.916 + bytes_sent = self.send(data)
1.917 + data = data[bytes_sent:]
1.918 + except socket.error, e:
1.919 + if e.args[0] not in socket_errors_nonblocking:
1.920 + raise
1.921 +
1.922 + def send(self, data):
1.923 + bytes_sent = self._sock.send(data)
1.924 + self.bytes_written += bytes_sent
1.925 + return bytes_sent
1.926 +
1.927 + def flush(self):
1.928 + if self._wbuf:
1.929 + buffer = "".join(self._wbuf)
1.930 + self._wbuf = []
1.931 + self.sendall(buffer)
1.932 +
1.933 + def recv(self, size):
1.934 + while True:
1.935 + try:
1.936 + data = self._sock.recv(size)
1.937 + self.bytes_read += len(data)
1.938 + return data
1.939 + except socket.error, e:
1.940 + if (e.args[0] not in socket_errors_nonblocking
1.941 + and e.args[0] not in socket_error_eintr):
1.942 + raise
1.943 +
1.944 + if not _fileobject_uses_str_type:
1.945 + def read(self, size=-1):
1.946 + # Use max, disallow tiny reads in a loop as they are very inefficient.
1.947 + # We never leave read() with any leftover data from a new recv() call
1.948 + # in our internal buffer.
1.949 + rbufsize = max(self._rbufsize, self.default_bufsize)
1.950 + # Our use of StringIO rather than lists of string objects returned by
1.951 + # recv() minimizes memory usage and fragmentation that occurs when
1.952 + # rbufsize is large compared to the typical return value of recv().
1.953 + buf = self._rbuf
1.954 + buf.seek(0, 2) # seek end
1.955 + if size < 0:
1.956 + # Read until EOF
1.957 + self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
1.958 + while True:
1.959 + data = self.recv(rbufsize)
1.960 + if not data:
1.961 + break
1.962 + buf.write(data)
1.963 + return buf.getvalue()
1.964 + else:
1.965 + # Read until size bytes or EOF seen, whichever comes first
1.966 + buf_len = buf.tell()
1.967 + if buf_len >= size:
1.968 + # Already have size bytes in our buffer? Extract and return.
1.969 + buf.seek(0)
1.970 + rv = buf.read(size)
1.971 + self._rbuf = StringIO.StringIO()
1.972 + self._rbuf.write(buf.read())
1.973 + return rv
1.974 +
1.975 + self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
1.976 + while True:
1.977 + left = size - buf_len
1.978 + # recv() will malloc the amount of memory given as its
1.979 + # parameter even though it often returns much less data
1.980 + # than that. The returned data string is short lived
1.981 + # as we copy it into a StringIO and free it. This avoids
1.982 + # fragmentation issues on many platforms.
1.983 + data = self.recv(left)
1.984 + if not data:
1.985 + break
1.986 + n = len(data)
1.987 + if n == size and not buf_len:
1.988 + # Shortcut. Avoid buffer data copies when:
1.989 + # - We have no data in our buffer.
1.990 + # AND
1.991 + # - Our call to recv returned exactly the
1.992 + # number of bytes we were asked to read.
1.993 + return data
1.994 + if n == left:
1.995 + buf.write(data)
1.996 + del data # explicit free
1.997 + break
1.998 + assert n <= left, "recv(%d) returned %d bytes" % (left, n)
1.999 + buf.write(data)
1.1000 + buf_len += n
1.1001 + del data # explicit free
1.1002 + #assert buf_len == buf.tell()
1.1003 + return buf.getvalue()
1.1004 +
1.1005 + def readline(self, size=-1):
1.1006 + buf = self._rbuf
1.1007 + buf.seek(0, 2) # seek end
1.1008 + if buf.tell() > 0:
1.1009 + # check if we already have it in our buffer
1.1010 + buf.seek(0)
1.1011 + bline = buf.readline(size)
1.1012 + if bline.endswith('\n') or len(bline) == size:
1.1013 + self._rbuf = StringIO.StringIO()
1.1014 + self._rbuf.write(buf.read())
1.1015 + return bline
1.1016 + del bline
1.1017 + if size < 0:
1.1018 + # Read until \n or EOF, whichever comes first
1.1019 + if self._rbufsize <= 1:
1.1020 + # Speed up unbuffered case
1.1021 + buf.seek(0)
1.1022 + buffers = [buf.read()]
1.1023 + self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
1.1024 + data = None
1.1025 + recv = self.recv
1.1026 + while data != "\n":
1.1027 + data = recv(1)
1.1028 + if not data:
1.1029 + break
1.1030 + buffers.append(data)
1.1031 + return "".join(buffers)
1.1032 +
1.1033 + buf.seek(0, 2) # seek end
1.1034 + self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
1.1035 + while True:
1.1036 + data = self.recv(self._rbufsize)
1.1037 + if not data:
1.1038 + break
1.1039 + nl = data.find('\n')
1.1040 + if nl >= 0:
1.1041 + nl += 1
1.1042 + buf.write(data[:nl])
1.1043 + self._rbuf.write(data[nl:])
1.1044 + del data
1.1045 + break
1.1046 + buf.write(data)
1.1047 + return buf.getvalue()
1.1048 + else:
1.1049 + # Read until size bytes or \n or EOF seen, whichever comes first
1.1050 + buf.seek(0, 2) # seek end
1.1051 + buf_len = buf.tell()
1.1052 + if buf_len >= size:
1.1053 + buf.seek(0)
1.1054 + rv = buf.read(size)
1.1055 + self._rbuf = StringIO.StringIO()
1.1056 + self._rbuf.write(buf.read())
1.1057 + return rv
1.1058 + self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
1.1059 + while True:
1.1060 + data = self.recv(self._rbufsize)
1.1061 + if not data:
1.1062 + break
1.1063 + left = size - buf_len
1.1064 + # did we just receive a newline?
1.1065 + nl = data.find('\n', 0, left)
1.1066 + if nl >= 0:
1.1067 + nl += 1
1.1068 + # save the excess data to _rbuf
1.1069 + self._rbuf.write(data[nl:])
1.1070 + if buf_len:
1.1071 + buf.write(data[:nl])
1.1072 + break
1.1073 + else:
1.1074 + # Shortcut. Avoid data copy through buf when returning
1.1075 + # a substring of our first recv().
1.1076 + return data[:nl]
1.1077 + n = len(data)
1.1078 + if n == size and not buf_len:
1.1079 + # Shortcut. Avoid data copy through buf when
1.1080 + # returning exactly all of our first recv().
1.1081 + return data
1.1082 + if n >= left:
1.1083 + buf.write(data[:left])
1.1084 + self._rbuf.write(data[left:])
1.1085 + break
1.1086 + buf.write(data)
1.1087 + buf_len += n
1.1088 + #assert buf_len == buf.tell()
1.1089 + return buf.getvalue()
1.1090 + else:
1.1091 + def read(self, size=-1):
1.1092 + if size < 0:
1.1093 + # Read until EOF
1.1094 + buffers = [self._rbuf]
1.1095 + self._rbuf = ""
1.1096 + if self._rbufsize <= 1:
1.1097 + recv_size = self.default_bufsize
1.1098 + else:
1.1099 + recv_size = self._rbufsize
1.1100 +
1.1101 + while True:
1.1102 + data = self.recv(recv_size)
1.1103 + if not data:
1.1104 + break
1.1105 + buffers.append(data)
1.1106 + return "".join(buffers)
1.1107 + else:
1.1108 + # Read until size bytes or EOF seen, whichever comes first
1.1109 + data = self._rbuf
1.1110 + buf_len = len(data)
1.1111 + if buf_len >= size:
1.1112 + self._rbuf = data[size:]
1.1113 + return data[:size]
1.1114 + buffers = []
1.1115 + if data:
1.1116 + buffers.append(data)
1.1117 + self._rbuf = ""
1.1118 + while True:
1.1119 + left = size - buf_len
1.1120 + recv_size = max(self._rbufsize, left)
1.1121 + data = self.recv(recv_size)
1.1122 + if not data:
1.1123 + break
1.1124 + buffers.append(data)
1.1125 + n = len(data)
1.1126 + if n >= left:
1.1127 + self._rbuf = data[left:]
1.1128 + buffers[-1] = data[:left]
1.1129 + break
1.1130 + buf_len += n
1.1131 + return "".join(buffers)
1.1132 +
1.1133 + def readline(self, size=-1):
1.1134 + data = self._rbuf
1.1135 + if size < 0:
1.1136 + # Read until \n or EOF, whichever comes first
1.1137 + if self._rbufsize <= 1:
1.1138 + # Speed up unbuffered case
1.1139 + assert data == ""
1.1140 + buffers = []
1.1141 + while data != "\n":
1.1142 + data = self.recv(1)
1.1143 + if not data:
1.1144 + break
1.1145 + buffers.append(data)
1.1146 + return "".join(buffers)
1.1147 + nl = data.find('\n')
1.1148 + if nl >= 0:
1.1149 + nl += 1
1.1150 + self._rbuf = data[nl:]
1.1151 + return data[:nl]
1.1152 + buffers = []
1.1153 + if data:
1.1154 + buffers.append(data)
1.1155 + self._rbuf = ""
1.1156 + while True:
1.1157 + data = self.recv(self._rbufsize)
1.1158 + if not data:
1.1159 + break
1.1160 + buffers.append(data)
1.1161 + nl = data.find('\n')
1.1162 + if nl >= 0:
1.1163 + nl += 1
1.1164 + self._rbuf = data[nl:]
1.1165 + buffers[-1] = data[:nl]
1.1166 + break
1.1167 + return "".join(buffers)
1.1168 + else:
1.1169 + # Read until size bytes or \n or EOF seen, whichever comes first
1.1170 + nl = data.find('\n', 0, size)
1.1171 + if nl >= 0:
1.1172 + nl += 1
1.1173 + self._rbuf = data[nl:]
1.1174 + return data[:nl]
1.1175 + buf_len = len(data)
1.1176 + if buf_len >= size:
1.1177 + self._rbuf = data[size:]
1.1178 + return data[:size]
1.1179 + buffers = []
1.1180 + if data:
1.1181 + buffers.append(data)
1.1182 + self._rbuf = ""
1.1183 + while True:
1.1184 + data = self.recv(self._rbufsize)
1.1185 + if not data:
1.1186 + break
1.1187 + buffers.append(data)
1.1188 + left = size - buf_len
1.1189 + nl = data.find('\n', 0, left)
1.1190 + if nl >= 0:
1.1191 + nl += 1
1.1192 + self._rbuf = data[nl:]
1.1193 + buffers[-1] = data[:nl]
1.1194 + break
1.1195 + n = len(data)
1.1196 + if n >= left:
1.1197 + self._rbuf = data[left:]
1.1198 + buffers[-1] = data[:left]
1.1199 + break
1.1200 + buf_len += n
1.1201 + return "".join(buffers)
1.1202 +
1.1203 +
1.1204 +class HTTPConnection(object):
1.1205 + """An HTTP connection (active socket).
1.1206 +
1.1207 + server: the Server object which received this connection.
1.1208 + socket: the raw socket object (usually TCP) for this connection.
1.1209 + makefile: a fileobject class for reading from the socket.
1.1210 + """
1.1211 +
1.1212 + remote_addr = None
1.1213 + remote_port = None
1.1214 + ssl_env = None
1.1215 + rbufsize = DEFAULT_BUFFER_SIZE
1.1216 + wbufsize = DEFAULT_BUFFER_SIZE
1.1217 + RequestHandlerClass = HTTPRequest
1.1218 +
1.1219 + def __init__(self, server, sock, makefile=CP_fileobject):
1.1220 + self.server = server
1.1221 + self.socket = sock
1.1222 + self.rfile = makefile(sock, "rb", self.rbufsize)
1.1223 + self.wfile = makefile(sock, "wb", self.wbufsize)
1.1224 + self.requests_seen = 0
1.1225 +
1.1226 + def communicate(self):
1.1227 + """Read each request and respond appropriately."""
1.1228 + request_seen = False
1.1229 + try:
1.1230 + while True:
1.1231 + # (re)set req to None so that if something goes wrong in
1.1232 + # the RequestHandlerClass constructor, the error doesn't
1.1233 + # get written to the previous request.
1.1234 + req = None
1.1235 + req = self.RequestHandlerClass(self.server, self)
1.1236 +
1.1237 + # This order of operations should guarantee correct pipelining.
1.1238 + req.parse_request()
1.1239 + if self.server.stats['Enabled']:
1.1240 + self.requests_seen += 1
1.1241 + if not req.ready:
1.1242 + # Something went wrong in the parsing (and the server has
1.1243 + # probably already made a simple_response). Return and
1.1244 + # let the conn close.
1.1245 + return
1.1246 +
1.1247 + request_seen = True
1.1248 + req.respond()
1.1249 + if req.close_connection:
1.1250 + return
1.1251 + except socket.error, e:
1.1252 + errnum = e.args[0]
1.1253 + # sadly SSL sockets return a different (longer) time out string
1.1254 + if errnum == 'timed out' or errnum == 'The read operation timed out':
1.1255 + # Don't error if we're between requests; only error
1.1256 + # if 1) no request has been started at all, or 2) we're
1.1257 + # in the middle of a request.
1.1258 + # See http://www.cherrypy.org/ticket/853
1.1259 + if (not request_seen) or (req and req.started_request):
1.1260 + # Don't bother writing the 408 if the response
1.1261 + # has already started being written.
1.1262 + if req and not req.sent_headers:
1.1263 + try:
1.1264 + req.simple_response("408 Request Timeout")
1.1265 + except FatalSSLAlert:
1.1266 + # Close the connection.
1.1267 + return
1.1268 + elif errnum not in socket_errors_to_ignore:
1.1269 + if req and not req.sent_headers:
1.1270 + try:
1.1271 + req.simple_response("500 Internal Server Error",
1.1272 + format_exc())
1.1273 + except FatalSSLAlert:
1.1274 + # Close the connection.
1.1275 + return
1.1276 + return
1.1277 + except (KeyboardInterrupt, SystemExit):
1.1278 + raise
1.1279 + except FatalSSLAlert:
1.1280 + # Close the connection.
1.1281 + return
1.1282 + except NoSSLError:
1.1283 + if req and not req.sent_headers:
1.1284 + # Unwrap our wfile
1.1285 + self.wfile = CP_fileobject(self.socket._sock, "wb", self.wbufsize)
1.1286 + req.simple_response("400 Bad Request",
1.1287 + "The client sent a plain HTTP request, but "
1.1288 + "this server only speaks HTTPS on this port.")
1.1289 + self.linger = True
1.1290 + except Exception:
1.1291 + if req and not req.sent_headers:
1.1292 + try:
1.1293 + req.simple_response("500 Internal Server Error", format_exc())
1.1294 + except FatalSSLAlert:
1.1295 + # Close the connection.
1.1296 + return
1.1297 +
1.1298 + linger = False
1.1299 +
1.1300 + def close(self):
1.1301 + """Close the socket underlying this connection."""
1.1302 + self.rfile.close()
1.1303 +
1.1304 + if not self.linger:
1.1305 + # Python's socket module does NOT call close on the kernel socket
1.1306 + # when you call socket.close(). We do so manually here because we
1.1307 + # want this server to send a FIN TCP segment immediately. Note this
1.1308 + # must be called *before* calling socket.close(), because the latter
1.1309 + # drops its reference to the kernel socket.
1.1310 + if hasattr(self.socket, '_sock'):
1.1311 + self.socket._sock.close()
1.1312 + self.socket.close()
1.1313 + else:
1.1314 + # On the other hand, sometimes we want to hang around for a bit
1.1315 + # to make sure the client has a chance to read our entire
1.1316 + # response. Skipping the close() calls here delays the FIN
1.1317 + # packet until the socket object is garbage-collected later.
1.1318 + # Someday, perhaps, we'll do the full lingering_close that
1.1319 + # Apache does, but not today.
1.1320 + pass
1.1321 +
1.1322 +
1.1323 +_SHUTDOWNREQUEST = None
1.1324 +
1.1325 +class WorkerThread(threading.Thread):
1.1326 + """Thread which continuously polls a Queue for Connection objects.
1.1327 +
1.1328 + Due to the timing issues of polling a Queue, a WorkerThread does not
1.1329 + check its own 'ready' flag after it has started. To stop the thread,
1.1330 + it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
1.1331 + (one for each running WorkerThread).
1.1332 + """
1.1333 +
1.1334 + conn = None
1.1335 + """The current connection pulled off the Queue, or None."""
1.1336 +
1.1337 + server = None
1.1338 + """The HTTP Server which spawned this thread, and which owns the
1.1339 + Queue and is placing active connections into it."""
1.1340 +
1.1341 + ready = False
1.1342 + """A simple flag for the calling server to know when this thread
1.1343 + has begun polling the Queue."""
1.1344 +
1.1345 +
1.1346 + def __init__(self, server):
1.1347 + self.ready = False
1.1348 + self.server = server
1.1349 +
1.1350 + self.requests_seen = 0
1.1351 + self.bytes_read = 0
1.1352 + self.bytes_written = 0
1.1353 + self.start_time = None
1.1354 + self.work_time = 0
1.1355 + self.stats = {
1.1356 + 'Requests': lambda s: self.requests_seen + ((self.start_time is None) and 0 or self.conn.requests_seen),
1.1357 + 'Bytes Read': lambda s: self.bytes_read + ((self.start_time is None) and 0 or self.conn.rfile.bytes_read),
1.1358 + 'Bytes Written': lambda s: self.bytes_written + ((self.start_time is None) and 0 or self.conn.wfile.bytes_written),
1.1359 + 'Work Time': lambda s: self.work_time + ((self.start_time is None) and 0 or time.time() - self.start_time),
1.1360 + 'Read Throughput': lambda s: s['Bytes Read'](s) / (s['Work Time'](s) or 1e-6),
1.1361 + 'Write Throughput': lambda s: s['Bytes Written'](s) / (s['Work Time'](s) or 1e-6),
1.1362 + }
1.1363 + threading.Thread.__init__(self)
1.1364 +
1.1365 + def run(self):
1.1366 + self.server.stats['Worker Threads'][self.getName()] = self.stats
1.1367 + try:
1.1368 + self.ready = True
1.1369 + while True:
1.1370 + conn = self.server.requests.get()
1.1371 + if conn is _SHUTDOWNREQUEST:
1.1372 + return
1.1373 +
1.1374 + self.conn = conn
1.1375 + if self.server.stats['Enabled']:
1.1376 + self.start_time = time.time()
1.1377 + try:
1.1378 + conn.communicate()
1.1379 + finally:
1.1380 + conn.close()
1.1381 + if self.server.stats['Enabled']:
1.1382 + self.requests_seen += self.conn.requests_seen
1.1383 + self.bytes_read += self.conn.rfile.bytes_read
1.1384 + self.bytes_written += self.conn.wfile.bytes_written
1.1385 + self.work_time += time.time() - self.start_time
1.1386 + self.start_time = None
1.1387 + self.conn = None
1.1388 + except (KeyboardInterrupt, SystemExit), exc:
1.1389 + self.server.interrupt = exc
1.1390 +
1.1391 +
1.1392 +class ThreadPool(object):
1.1393 + """A Request Queue for the CherryPyWSGIServer which pools threads.
1.1394 +
1.1395 + ThreadPool objects must provide min, get(), put(obj), start()
1.1396 + and stop(timeout) attributes.
1.1397 + """
1.1398 +
1.1399 + def __init__(self, server, min=10, max=-1):
1.1400 + self.server = server
1.1401 + self.min = min
1.1402 + self.max = max
1.1403 + self._threads = []
1.1404 + self._queue = Queue.Queue()
1.1405 + self.get = self._queue.get
1.1406 +
1.1407 + def start(self):
1.1408 + """Start the pool of threads."""
1.1409 + for i in range(self.min):
1.1410 + self._threads.append(WorkerThread(self.server))
1.1411 + for worker in self._threads:
1.1412 + worker.setName("CP Server " + worker.getName())
1.1413 + worker.start()
1.1414 + for worker in self._threads:
1.1415 + while not worker.ready:
1.1416 + time.sleep(.1)
1.1417 +
1.1418 + def _get_idle(self):
1.1419 + """Number of worker threads which are idle. Read-only."""
1.1420 + return len([t for t in self._threads if t.conn is None])
1.1421 + idle = property(_get_idle, doc=_get_idle.__doc__)
1.1422 +
1.1423 + def put(self, obj):
1.1424 + self._queue.put(obj)
1.1425 + if obj is _SHUTDOWNREQUEST:
1.1426 + return
1.1427 +
1.1428 + def grow(self, amount):
1.1429 + """Spawn new worker threads (not above self.max)."""
1.1430 + for i in range(amount):
1.1431 + if self.max > 0 and len(self._threads) >= self.max:
1.1432 + break
1.1433 + worker = WorkerThread(self.server)
1.1434 + worker.setName("CP Server " + worker.getName())
1.1435 + self._threads.append(worker)
1.1436 + worker.start()
1.1437 +
1.1438 + def shrink(self, amount):
1.1439 + """Kill off worker threads (not below self.min)."""
1.1440 + # Grow/shrink the pool if necessary.
1.1441 + # Remove any dead threads from our list
1.1442 + for t in self._threads:
1.1443 + if not t.isAlive():
1.1444 + self._threads.remove(t)
1.1445 + amount -= 1
1.1446 +
1.1447 + if amount > 0:
1.1448 + for i in range(min(amount, len(self._threads) - self.min)):
1.1449 + # Put a number of shutdown requests on the queue equal
1.1450 + # to 'amount'. Once each of those is processed by a worker,
1.1451 + # that worker will terminate and be culled from our list
1.1452 + # in self.put.
1.1453 + self._queue.put(_SHUTDOWNREQUEST)
1.1454 +
1.1455 + def stop(self, timeout=5):
1.1456 + # Must shut down threads here so the code that calls
1.1457 + # this method can know when all threads are stopped.
1.1458 + for worker in self._threads:
1.1459 + self._queue.put(_SHUTDOWNREQUEST)
1.1460 +
1.1461 + # Don't join currentThread (when stop is called inside a request).
1.1462 + current = threading.currentThread()
1.1463 + if timeout and timeout >= 0:
1.1464 + endtime = time.time() + timeout
1.1465 + while self._threads:
1.1466 + worker = self._threads.pop()
1.1467 + if worker is not current and worker.isAlive():
1.1468 + try:
1.1469 + if timeout is None or timeout < 0:
1.1470 + worker.join()
1.1471 + else:
1.1472 + remaining_time = endtime - time.time()
1.1473 + if remaining_time > 0:
1.1474 + worker.join(remaining_time)
1.1475 + if worker.isAlive():
1.1476 + # We exhausted the timeout.
1.1477 + # Forcibly shut down the socket.
1.1478 + c = worker.conn
1.1479 + if c and not c.rfile.closed:
1.1480 + try:
1.1481 + c.socket.shutdown(socket.SHUT_RD)
1.1482 + except TypeError:
1.1483 + # pyOpenSSL sockets don't take an arg
1.1484 + c.socket.shutdown()
1.1485 + worker.join()
1.1486 + except (AssertionError,
1.1487 + # Ignore repeated Ctrl-C.
1.1488 + # See http://www.cherrypy.org/ticket/691.
1.1489 + KeyboardInterrupt), exc1:
1.1490 + pass
1.1491 +
1.1492 + def _get_qsize(self):
1.1493 + return self._queue.qsize()
1.1494 + qsize = property(_get_qsize)
1.1495 +
1.1496 +
1.1497 +
1.1498 +try:
1.1499 + import fcntl
1.1500 +except ImportError:
1.1501 + try:
1.1502 + from ctypes import windll, WinError
1.1503 + except ImportError:
1.1504 + def prevent_socket_inheritance(sock):
1.1505 + """Dummy function, since neither fcntl nor ctypes are available."""
1.1506 + pass
1.1507 + else:
1.1508 + def prevent_socket_inheritance(sock):
1.1509 + """Mark the given socket fd as non-inheritable (Windows)."""
1.1510 + if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
1.1511 + raise WinError()
1.1512 +else:
1.1513 + def prevent_socket_inheritance(sock):
1.1514 + """Mark the given socket fd as non-inheritable (POSIX)."""
1.1515 + fd = sock.fileno()
1.1516 + old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
1.1517 + fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
1.1518 +
1.1519 +
1.1520 +class SSLAdapter(object):
1.1521 + """Base class for SSL driver library adapters.
1.1522 +
1.1523 + Required methods:
1.1524 +
1.1525 + * ``wrap(sock) -> (wrapped socket, ssl environ dict)``
1.1526 + * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> socket file object``
1.1527 + """
1.1528 +
1.1529 + def __init__(self, certificate, private_key, certificate_chain=None):
1.1530 + self.certificate = certificate
1.1531 + self.private_key = private_key
1.1532 + self.certificate_chain = certificate_chain
1.1533 +
1.1534 + def wrap(self, sock):
1.1535 + raise NotImplemented
1.1536 +
1.1537 + def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
1.1538 + raise NotImplemented
1.1539 +
1.1540 +
1.1541 +class HTTPServer(object):
1.1542 + """An HTTP server."""
1.1543 +
1.1544 + _bind_addr = "127.0.0.1"
1.1545 + _interrupt = None
1.1546 +
1.1547 + gateway = None
1.1548 + """A Gateway instance."""
1.1549 +
1.1550 + minthreads = None
1.1551 + """The minimum number of worker threads to create (default 10)."""
1.1552 +
1.1553 + maxthreads = None
1.1554 + """The maximum number of worker threads to create (default -1 = no limit)."""
1.1555 +
1.1556 + server_name = None
1.1557 + """The name of the server; defaults to socket.gethostname()."""
1.1558 +
1.1559 + protocol = "HTTP/1.1"
1.1560 + """The version string to write in the Status-Line of all HTTP responses.
1.1561 +
1.1562 + For example, "HTTP/1.1" is the default. This also limits the supported
1.1563 + features used in the response."""
1.1564 +
1.1565 + request_queue_size = 5
1.1566 + """The 'backlog' arg to socket.listen(); max queued connections (default 5)."""
1.1567 +
1.1568 + shutdown_timeout = 5
1.1569 + """The total time, in seconds, to wait for worker threads to cleanly exit."""
1.1570 +
1.1571 + timeout = 10
1.1572 + """The timeout in seconds for accepted connections (default 10)."""
1.1573 +
1.1574 + version = "CherryPy/3.2.0"
1.1575 + """A version string for the HTTPServer."""
1.1576 +
1.1577 + software = None
1.1578 + """The value to set for the SERVER_SOFTWARE entry in the WSGI environ.
1.1579 +
1.1580 + If None, this defaults to ``'%s Server' % self.version``."""
1.1581 +
1.1582 + ready = False
1.1583 + """An internal flag which marks whether the socket is accepting connections."""
1.1584 +
1.1585 + max_request_header_size = 0
1.1586 + """The maximum size, in bytes, for request headers, or 0 for no limit."""
1.1587 +
1.1588 + max_request_body_size = 0
1.1589 + """The maximum size, in bytes, for request bodies, or 0 for no limit."""
1.1590 +
1.1591 + nodelay = True
1.1592 + """If True (the default since 3.1), sets the TCP_NODELAY socket option."""
1.1593 +
1.1594 + ConnectionClass = HTTPConnection
1.1595 + """The class to use for handling HTTP connections."""
1.1596 +
1.1597 + ssl_adapter = None
1.1598 + """An instance of SSLAdapter (or a subclass).
1.1599 +
1.1600 + You must have the corresponding SSL driver library installed."""
1.1601 +
1.1602 + def __init__(self, bind_addr, gateway, minthreads=10, maxthreads=-1,
1.1603 + server_name=None):
1.1604 + self.bind_addr = bind_addr
1.1605 + self.gateway = gateway
1.1606 +
1.1607 + self.requests = ThreadPool(self, min=minthreads or 1, max=maxthreads)
1.1608 +
1.1609 + if not server_name:
1.1610 + server_name = socket.gethostname()
1.1611 + self.server_name = server_name
1.1612 + self.clear_stats()
1.1613 +
1.1614 + def clear_stats(self):
1.1615 + self._start_time = None
1.1616 + self._run_time = 0
1.1617 + self.stats = {
1.1618 + 'Enabled': False,
1.1619 + 'Bind Address': lambda s: repr(self.bind_addr),
1.1620 + 'Run time': lambda s: (not s['Enabled']) and 0 or self.runtime(),
1.1621 + 'Accepts': 0,
1.1622 + 'Accepts/sec': lambda s: s['Accepts'] / self.runtime(),
1.1623 + 'Queue': lambda s: getattr(self.requests, "qsize", None),
1.1624 + 'Threads': lambda s: len(getattr(self.requests, "_threads", [])),
1.1625 + 'Threads Idle': lambda s: getattr(self.requests, "idle", None),
1.1626 + 'Socket Errors': 0,
1.1627 + 'Requests': lambda s: (not s['Enabled']) and 0 or sum([w['Requests'](w) for w
1.1628 + in s['Worker Threads'].values()], 0),
1.1629 + 'Bytes Read': lambda s: (not s['Enabled']) and 0 or sum([w['Bytes Read'](w) for w
1.1630 + in s['Worker Threads'].values()], 0),
1.1631 + 'Bytes Written': lambda s: (not s['Enabled']) and 0 or sum([w['Bytes Written'](w) for w
1.1632 + in s['Worker Threads'].values()], 0),
1.1633 + 'Work Time': lambda s: (not s['Enabled']) and 0 or sum([w['Work Time'](w) for w
1.1634 + in s['Worker Threads'].values()], 0),
1.1635 + 'Read Throughput': lambda s: (not s['Enabled']) and 0 or sum(
1.1636 + [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6)
1.1637 + for w in s['Worker Threads'].values()], 0),
1.1638 + 'Write Throughput': lambda s: (not s['Enabled']) and 0 or sum(
1.1639 + [w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
1.1640 + for w in s['Worker Threads'].values()], 0),
1.1641 + 'Worker Threads': {},
1.1642 + }
1.1643 + logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
1.1644 +
1.1645 + def runtime(self):
1.1646 + if self._start_time is None:
1.1647 + return self._run_time
1.1648 + else:
1.1649 + return self._run_time + (time.time() - self._start_time)
1.1650 +
1.1651 + def __str__(self):
1.1652 + return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
1.1653 + self.bind_addr)
1.1654 +
1.1655 + def _get_bind_addr(self):
1.1656 + return self._bind_addr
1.1657 + def _set_bind_addr(self, value):
1.1658 + if isinstance(value, tuple) and value[0] in ('', None):
1.1659 + # Despite the socket module docs, using '' does not
1.1660 + # allow AI_PASSIVE to work. Passing None instead
1.1661 + # returns '0.0.0.0' like we want. In other words:
1.1662 + # host AI_PASSIVE result
1.1663 + # '' Y 192.168.x.y
1.1664 + # '' N 192.168.x.y
1.1665 + # None Y 0.0.0.0
1.1666 + # None N 127.0.0.1
1.1667 + # But since you can get the same effect with an explicit
1.1668 + # '0.0.0.0', we deny both the empty string and None as values.
1.1669 + raise ValueError("Host values of '' or None are not allowed. "
1.1670 + "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
1.1671 + "to listen on all active interfaces.")
1.1672 + self._bind_addr = value
1.1673 + bind_addr = property(_get_bind_addr, _set_bind_addr,
1.1674 + doc="""The interface on which to listen for connections.
1.1675 +
1.1676 + For TCP sockets, a (host, port) tuple. Host values may be any IPv4
1.1677 + or IPv6 address, or any valid hostname. The string 'localhost' is a
1.1678 + synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
1.1679 + The string '0.0.0.0' is a special IPv4 entry meaning "any active
1.1680 + interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
1.1681 + IPv6. The empty string or None are not allowed.
1.1682 +
1.1683 + For UNIX sockets, supply the filename as a string.""")
1.1684 +
1.1685 + def start(self):
1.1686 + """Run the server forever."""
1.1687 + # We don't have to trap KeyboardInterrupt or SystemExit here,
1.1688 + # because cherrpy.server already does so, calling self.stop() for us.
1.1689 + # If you're using this server with another framework, you should
1.1690 + # trap those exceptions in whatever code block calls start().
1.1691 + self._interrupt = None
1.1692 +
1.1693 + if self.software is None:
1.1694 + self.software = "%s Server" % self.version
1.1695 +
1.1696 + # SSL backward compatibility
1.1697 + if (self.ssl_adapter is None and
1.1698 + getattr(self, 'ssl_certificate', None) and
1.1699 + getattr(self, 'ssl_private_key', None)):
1.1700 + warnings.warn(
1.1701 + "SSL attributes are deprecated in CherryPy 3.2, and will "
1.1702 + "be removed in CherryPy 3.3. Use an ssl_adapter attribute "
1.1703 + "instead.",
1.1704 + DeprecationWarning
1.1705 + )
1.1706 + try:
1.1707 + from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
1.1708 + except ImportError:
1.1709 + pass
1.1710 + else:
1.1711 + self.ssl_adapter = pyOpenSSLAdapter(
1.1712 + self.ssl_certificate, self.ssl_private_key,
1.1713 + getattr(self, 'ssl_certificate_chain', None))
1.1714 +
1.1715 + # Select the appropriate socket
1.1716 + if isinstance(self.bind_addr, basestring):
1.1717 + # AF_UNIX socket
1.1718 +
1.1719 + # So we can reuse the socket...
1.1720 + try: os.unlink(self.bind_addr)
1.1721 + except: pass
1.1722 +
1.1723 + # So everyone can access the socket...
1.1724 + try: os.chmod(self.bind_addr, 0777)
1.1725 + except: pass
1.1726 +
1.1727 + info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
1.1728 + else:
1.1729 + # AF_INET or AF_INET6 socket
1.1730 + # Get the correct address family for our host (allows IPv6 addresses)
1.1731 + host, port = self.bind_addr
1.1732 + try:
1.1733 + info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1.1734 + socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
1.1735 + except socket.gaierror:
1.1736 + if ':' in self.bind_addr[0]:
1.1737 + info = [(socket.AF_INET6, socket.SOCK_STREAM,
1.1738 + 0, "", self.bind_addr + (0, 0))]
1.1739 + else:
1.1740 + info = [(socket.AF_INET, socket.SOCK_STREAM,
1.1741 + 0, "", self.bind_addr)]
1.1742 +
1.1743 + self.socket = None
1.1744 + msg = "No socket could be created"
1.1745 + for res in info:
1.1746 + af, socktype, proto, canonname, sa = res
1.1747 + try:
1.1748 + self.bind(af, socktype, proto)
1.1749 + except socket.error:
1.1750 + if self.socket:
1.1751 + self.socket.close()
1.1752 + self.socket = None
1.1753 + continue
1.1754 + break
1.1755 + if not self.socket:
1.1756 + raise socket.error(msg)
1.1757 +
1.1758 + # Timeout so KeyboardInterrupt can be caught on Win32
1.1759 + self.socket.settimeout(1)
1.1760 + self.socket.listen(self.request_queue_size)
1.1761 +
1.1762 + # Create worker threads
1.1763 + self.requests.start()
1.1764 +
1.1765 + self.ready = True
1.1766 + self._start_time = time.time()
1.1767 + while self.ready:
1.1768 + self.tick()
1.1769 + if self.interrupt:
1.1770 + while self.interrupt is True:
1.1771 + # Wait for self.stop() to complete. See _set_interrupt.
1.1772 + time.sleep(0.1)
1.1773 + if self.interrupt:
1.1774 + raise self.interrupt
1.1775 +
1.1776 + def bind(self, family, type, proto=0):
1.1777 + """Create (or recreate) the actual socket object."""
1.1778 + self.socket = socket.socket(family, type, proto)
1.1779 + prevent_socket_inheritance(self.socket)
1.1780 + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1.1781 + if self.nodelay and not isinstance(self.bind_addr, str):
1.1782 + self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1.1783 +
1.1784 + if self.ssl_adapter is not None:
1.1785 + self.socket = self.ssl_adapter.bind(self.socket)
1.1786 +
1.1787 + # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
1.1788 + # activate dual-stack. See http://www.cherrypy.org/ticket/871.
1.1789 + if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
1.1790 + and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
1.1791 + try:
1.1792 + self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
1.1793 + except (AttributeError, socket.error):
1.1794 + # Apparently, the socket option is not available in
1.1795 + # this machine's TCP stack
1.1796 + pass
1.1797 +
1.1798 + self.socket.bind(self.bind_addr)
1.1799 +
1.1800 + def tick(self):
1.1801 + """Accept a new connection and put it on the Queue."""
1.1802 + try:
1.1803 + s, addr = self.socket.accept()
1.1804 + if self.stats['Enabled']:
1.1805 + self.stats['Accepts'] += 1
1.1806 + if not self.ready:
1.1807 + return
1.1808 +
1.1809 + prevent_socket_inheritance(s)
1.1810 + if hasattr(s, 'settimeout'):
1.1811 + s.settimeout(self.timeout)
1.1812 +
1.1813 + makefile = CP_fileobject
1.1814 + ssl_env = {}
1.1815 + # if ssl cert and key are set, we try to be a secure HTTP server
1.1816 + if self.ssl_adapter is not None:
1.1817 + try:
1.1818 + s, ssl_env = self.ssl_adapter.wrap(s)
1.1819 + except NoSSLError:
1.1820 + msg = ("The client sent a plain HTTP request, but "
1.1821 + "this server only speaks HTTPS on this port.")
1.1822 + buf = ["%s 400 Bad Request\r\n" % self.protocol,
1.1823 + "Content-Length: %s\r\n" % len(msg),
1.1824 + "Content-Type: text/plain\r\n\r\n",
1.1825 + msg]
1.1826 +
1.1827 + wfile = CP_fileobject(s, "wb", DEFAULT_BUFFER_SIZE)
1.1828 + try:
1.1829 + wfile.sendall("".join(buf))
1.1830 + except socket.error, x:
1.1831 + if x.args[0] not in socket_errors_to_ignore:
1.1832 + raise
1.1833 + return
1.1834 + if not s:
1.1835 + return
1.1836 + makefile = self.ssl_adapter.makefile
1.1837 + # Re-apply our timeout since we may have a new socket object
1.1838 + if hasattr(s, 'settimeout'):
1.1839 + s.settimeout(self.timeout)
1.1840 +
1.1841 + conn = self.ConnectionClass(self, s, makefile)
1.1842 +
1.1843 + if not isinstance(self.bind_addr, basestring):
1.1844 + # optional values
1.1845 + # Until we do DNS lookups, omit REMOTE_HOST
1.1846 + if addr is None: # sometimes this can happen
1.1847 + # figure out if AF_INET or AF_INET6.
1.1848 + if len(s.getsockname()) == 2:
1.1849 + # AF_INET
1.1850 + addr = ('0.0.0.0', 0)
1.1851 + else:
1.1852 + # AF_INET6
1.1853 + addr = ('::', 0)
1.1854 + conn.remote_addr = addr[0]
1.1855 + conn.remote_port = addr[1]
1.1856 +
1.1857 + conn.ssl_env = ssl_env
1.1858 +
1.1859 + self.requests.put(conn)
1.1860 + except socket.timeout:
1.1861 + # The only reason for the timeout in start() is so we can
1.1862 + # notice keyboard interrupts on Win32, which don't interrupt
1.1863 + # accept() by default
1.1864 + return
1.1865 + except socket.error, x:
1.1866 + if self.stats['Enabled']:
1.1867 + self.stats['Socket Errors'] += 1
1.1868 + if x.args[0] in socket_error_eintr:
1.1869 + # I *think* this is right. EINTR should occur when a signal
1.1870 + # is received during the accept() call; all docs say retry
1.1871 + # the call, and I *think* I'm reading it right that Python
1.1872 + # will then go ahead and poll for and handle the signal
1.1873 + # elsewhere. See http://www.cherrypy.org/ticket/707.
1.1874 + return
1.1875 + if x.args[0] in socket_errors_nonblocking:
1.1876 + # Just try again. See http://www.cherrypy.org/ticket/479.
1.1877 + return
1.1878 + if x.args[0] in socket_errors_to_ignore:
1.1879 + # Our socket was closed.
1.1880 + # See http://www.cherrypy.org/ticket/686.
1.1881 + return
1.1882 + raise
1.1883 +
1.1884 + def _get_interrupt(self):
1.1885 + return self._interrupt
1.1886 + def _set_interrupt(self, interrupt):
1.1887 + self._interrupt = True
1.1888 + self.stop()
1.1889 + self._interrupt = interrupt
1.1890 + interrupt = property(_get_interrupt, _set_interrupt,
1.1891 + doc="Set this to an Exception instance to "
1.1892 + "interrupt the server.")
1.1893 +
1.1894 + def stop(self):
1.1895 + """Gracefully shutdown a server that is serving forever."""
1.1896 + self.ready = False
1.1897 + if self._start_time is not None:
1.1898 + self._run_time += (time.time() - self._start_time)
1.1899 + self._start_time = None
1.1900 +
1.1901 + sock = getattr(self, "socket", None)
1.1902 + if sock:
1.1903 + if not isinstance(self.bind_addr, basestring):
1.1904 + # Touch our own socket to make accept() return immediately.
1.1905 + try:
1.1906 + host, port = sock.getsockname()[:2]
1.1907 + except socket.error, x:
1.1908 + if x.args[0] not in socket_errors_to_ignore:
1.1909 + # Changed to use error code and not message
1.1910 + # See http://www.cherrypy.org/ticket/860.
1.1911 + raise
1.1912 + else:
1.1913 + # Note that we're explicitly NOT using AI_PASSIVE,
1.1914 + # here, because we want an actual IP to touch.
1.1915 + # localhost won't work if we've bound to a public IP,
1.1916 + # but it will if we bound to '0.0.0.0' (INADDR_ANY).
1.1917 + for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
1.1918 + socket.SOCK_STREAM):
1.1919 + af, socktype, proto, canonname, sa = res
1.1920 + s = None
1.1921 + try:
1.1922 + s = socket.socket(af, socktype, proto)
1.1923 + # See http://groups.google.com/group/cherrypy-users/
1.1924 + # browse_frm/thread/bbfe5eb39c904fe0
1.1925 + s.settimeout(1.0)
1.1926 + s.connect((host, port))
1.1927 + s.close()
1.1928 + except socket.error:
1.1929 + if s:
1.1930 + s.close()
1.1931 + if hasattr(sock, "close"):
1.1932 + sock.close()
1.1933 + self.socket = None
1.1934 +
1.1935 + self.requests.stop(self.shutdown_timeout)
1.1936 +
1.1937 +
1.1938 +class Gateway(object):
1.1939 +
1.1940 + def __init__(self, req):
1.1941 + self.req = req
1.1942 +
1.1943 + def respond(self):
1.1944 + raise NotImplemented
1.1945 +
1.1946 +
1.1947 +# These may either be wsgiserver.SSLAdapter subclasses or the string names
1.1948 +# of such classes (in which case they will be lazily loaded).
1.1949 +ssl_adapters = {
1.1950 + 'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
1.1951 + 'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
1.1952 + }
1.1953 +
1.1954 +def get_ssl_adapter_class(name='pyopenssl'):
1.1955 + adapter = ssl_adapters[name.lower()]
1.1956 + if isinstance(adapter, basestring):
1.1957 + last_dot = adapter.rfind(".")
1.1958 + attr_name = adapter[last_dot + 1:]
1.1959 + mod_path = adapter[:last_dot]
1.1960 +
1.1961 + try:
1.1962 + mod = sys.modules[mod_path]
1.1963 + if mod is None:
1.1964 + raise KeyError()
1.1965 + except KeyError:
1.1966 + # The last [''] is important.
1.1967 + mod = __import__(mod_path, globals(), locals(), [''])
1.1968 +
1.1969 + # Let an AttributeError propagate outward.
1.1970 + try:
1.1971 + adapter = getattr(mod, attr_name)
1.1972 + except AttributeError:
1.1973 + raise AttributeError("'%s' object has no attribute '%s'"
1.1974 + % (mod_path, attr_name))
1.1975 +
1.1976 + return adapter
1.1977 +
1.1978 +# -------------------------------- WSGI Stuff -------------------------------- #
1.1979 +
1.1980 +
1.1981 +class CherryPyWSGIServer(HTTPServer):
1.1982 +
1.1983 + wsgi_version = (1, 0)
1.1984 +
1.1985 + def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
1.1986 + max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
1.1987 + self.requests = ThreadPool(self, min=numthreads or 1, max=max)
1.1988 + self.wsgi_app = wsgi_app
1.1989 + self.gateway = wsgi_gateways[self.wsgi_version]
1.1990 +
1.1991 + self.bind_addr = bind_addr
1.1992 + if not server_name:
1.1993 + server_name = socket.gethostname()
1.1994 + self.server_name = server_name
1.1995 + self.request_queue_size = request_queue_size
1.1996 +
1.1997 + self.timeout = timeout
1.1998 + self.shutdown_timeout = shutdown_timeout
1.1999 + self.clear_stats()
1.2000 +
1.2001 + def _get_numthreads(self):
1.2002 + return self.requests.min
1.2003 + def _set_numthreads(self, value):
1.2004 + self.requests.min = value
1.2005 + numthreads = property(_get_numthreads, _set_numthreads)
1.2006 +
1.2007 +
1.2008 +class WSGIGateway(Gateway):
1.2009 +
1.2010 + def __init__(self, req):
1.2011 + self.req = req
1.2012 + self.started_response = False
1.2013 + self.env = self.get_environ()
1.2014 + self.remaining_bytes_out = None
1.2015 +
1.2016 + def get_environ(self):
1.2017 + """Return a new environ dict targeting the given wsgi.version"""
1.2018 + raise NotImplemented
1.2019 +
1.2020 + def respond(self):
1.2021 + response = self.req.server.wsgi_app(self.env, self.start_response)
1.2022 + try:
1.2023 + for chunk in response:
1.2024 + # "The start_response callable must not actually transmit
1.2025 + # the response headers. Instead, it must store them for the
1.2026 + # server or gateway to transmit only after the first
1.2027 + # iteration of the application return value that yields
1.2028 + # a NON-EMPTY string, or upon the application's first
1.2029 + # invocation of the write() callable." (PEP 333)
1.2030 + if chunk:
1.2031 + if isinstance(chunk, unicode):
1.2032 + chunk = chunk.encode('ISO-8859-1')
1.2033 + self.write(chunk)
1.2034 + finally:
1.2035 + if hasattr(response, "close"):
1.2036 + response.close()
1.2037 +
1.2038 + def start_response(self, status, headers, exc_info = None):
1.2039 + """WSGI callable to begin the HTTP response."""
1.2040 + # "The application may call start_response more than once,
1.2041 + # if and only if the exc_info argument is provided."
1.2042 + if self.started_response and not exc_info:
1.2043 + raise AssertionError("WSGI start_response called a second "
1.2044 + "time with no exc_info.")
1.2045 + self.started_response = True
1.2046 +
1.2047 + # "if exc_info is provided, and the HTTP headers have already been
1.2048 + # sent, start_response must raise an error, and should raise the
1.2049 + # exc_info tuple."
1.2050 + if self.req.sent_headers:
1.2051 + try:
1.2052 + raise exc_info[0], exc_info[1], exc_info[2]
1.2053 + finally:
1.2054 + exc_info = None
1.2055 +
1.2056 + self.req.status = status
1.2057 + for k, v in headers:
1.2058 + if not isinstance(k, str):
1.2059 + raise TypeError("WSGI response header key %r is not a byte string." % k)
1.2060 + if not isinstance(v, str):
1.2061 + raise TypeError("WSGI response header value %r is not a byte string." % v)
1.2062 + if k.lower() == 'content-length':
1.2063 + self.remaining_bytes_out = int(v)
1.2064 + self.req.outheaders.extend(headers)
1.2065 +
1.2066 + return self.write
1.2067 +
1.2068 + def write(self, chunk):
1.2069 + """WSGI callable to write unbuffered data to the client.
1.2070 +
1.2071 + This method is also used internally by start_response (to write
1.2072 + data from the iterable returned by the WSGI application).
1.2073 + """
1.2074 + if not self.started_response:
1.2075 + raise AssertionError("WSGI write called before start_response.")
1.2076 +
1.2077 + chunklen = len(chunk)
1.2078 + rbo = self.remaining_bytes_out
1.2079 + if rbo is not None and chunklen > rbo:
1.2080 + if not self.req.sent_headers:
1.2081 + # Whew. We can send a 500 to the client.
1.2082 + self.req.simple_response("500 Internal Server Error",
1.2083 + "The requested resource returned more bytes than the "
1.2084 + "declared Content-Length.")
1.2085 + else:
1.2086 + # Dang. We have probably already sent data. Truncate the chunk
1.2087 + # to fit (so the client doesn't hang) and raise an error later.
1.2088 + chunk = chunk[:rbo]
1.2089 +
1.2090 + if not self.req.sent_headers:
1.2091 + self.req.sent_headers = True
1.2092 + self.req.send_headers()
1.2093 +
1.2094 + self.req.write(chunk)
1.2095 +
1.2096 + if rbo is not None:
1.2097 + rbo -= chunklen
1.2098 + if rbo < 0:
1.2099 + raise ValueError(
1.2100 + "Response body exceeds the declared Content-Length.")
1.2101 +
1.2102 +
1.2103 +class WSGIGateway_10(WSGIGateway):
1.2104 +
1.2105 + def get_environ(self):
1.2106 + """Return a new environ dict targeting the given wsgi.version"""
1.2107 + req = self.req
1.2108 + env = {
1.2109 + # set a non-standard environ entry so the WSGI app can know what
1.2110 + # the *real* server protocol is (and what features to support).
1.2111 + # See http://www.faqs.org/rfcs/rfc2145.html.
1.2112 + 'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
1.2113 + 'PATH_INFO': req.path,
1.2114 + 'QUERY_STRING': req.qs,
1.2115 + 'REMOTE_ADDR': req.conn.remote_addr or '',
1.2116 + 'REMOTE_PORT': str(req.conn.remote_port or ''),
1.2117 + 'REQUEST_METHOD': req.method,
1.2118 + 'REQUEST_URI': req.uri,
1.2119 + 'SCRIPT_NAME': '',
1.2120 + 'SERVER_NAME': req.server.server_name,
1.2121 + # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
1.2122 + 'SERVER_PROTOCOL': req.request_protocol,
1.2123 + 'SERVER_SOFTWARE': req.server.software,
1.2124 + 'wsgi.errors': sys.stderr,
1.2125 + 'wsgi.input': req.rfile,
1.2126 + 'wsgi.multiprocess': False,
1.2127 + 'wsgi.multithread': True,
1.2128 + 'wsgi.run_once': False,
1.2129 + 'wsgi.url_scheme': req.scheme,
1.2130 + 'wsgi.version': (1, 0),
1.2131 + }
1.2132 +
1.2133 + if isinstance(req.server.bind_addr, basestring):
1.2134 + # AF_UNIX. This isn't really allowed by WSGI, which doesn't
1.2135 + # address unix domain sockets. But it's better than nothing.
1.2136 + env["SERVER_PORT"] = ""
1.2137 + else:
1.2138 + env["SERVER_PORT"] = str(req.server.bind_addr[1])
1.2139 +
1.2140 + # Request headers
1.2141 + for k, v in req.inheaders.iteritems():
1.2142 + env["HTTP_" + k.upper().replace("-", "_")] = v
1.2143 +
1.2144 + # CONTENT_TYPE/CONTENT_LENGTH
1.2145 + ct = env.pop("HTTP_CONTENT_TYPE", None)
1.2146 + if ct is not None:
1.2147 + env["CONTENT_TYPE"] = ct
1.2148 + cl = env.pop("HTTP_CONTENT_LENGTH", None)
1.2149 + if cl is not None:
1.2150 + env["CONTENT_LENGTH"] = cl
1.2151 +
1.2152 + if req.conn.ssl_env:
1.2153 + env.update(req.conn.ssl_env)
1.2154 +
1.2155 + return env
1.2156 +
1.2157 +
1.2158 +class WSGIGateway_u0(WSGIGateway_10):
1.2159 +
1.2160 + def get_environ(self):
1.2161 + """Return a new environ dict targeting the given wsgi.version"""
1.2162 + req = self.req
1.2163 + env_10 = WSGIGateway_10.get_environ(self)
1.2164 + env = dict([(k.decode('ISO-8859-1'), v) for k, v in env_10.iteritems()])
1.2165 + env[u'wsgi.version'] = ('u', 0)
1.2166 +
1.2167 + # Request-URI
1.2168 + env.setdefault(u'wsgi.url_encoding', u'utf-8')
1.2169 + try:
1.2170 + for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
1.2171 + env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
1.2172 + except UnicodeDecodeError:
1.2173 + # Fall back to latin 1 so apps can transcode if needed.
1.2174 + env[u'wsgi.url_encoding'] = u'ISO-8859-1'
1.2175 + for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
1.2176 + env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
1.2177 +
1.2178 + for k, v in sorted(env.items()):
1.2179 + if isinstance(v, str) and k not in ('REQUEST_URI', 'wsgi.input'):
1.2180 + env[k] = v.decode('ISO-8859-1')
1.2181 +
1.2182 + return env
1.2183 +
1.2184 +wsgi_gateways = {
1.2185 + (1, 0): WSGIGateway_10,
1.2186 + ('u', 0): WSGIGateway_u0,
1.2187 +}
1.2188 +
1.2189 +class WSGIPathInfoDispatcher(object):
1.2190 + """A WSGI dispatcher for dispatch based on the PATH_INFO.
1.2191 +
1.2192 + apps: a dict or list of (path_prefix, app) pairs.
1.2193 + """
1.2194 +
1.2195 + def __init__(self, apps):
1.2196 + try:
1.2197 + apps = apps.items()
1.2198 + except AttributeError:
1.2199 + pass
1.2200 +
1.2201 + # Sort the apps by len(path), descending
1.2202 + apps.sort(cmp=lambda x,y: cmp(len(x[0]), len(y[0])))
1.2203 + apps.reverse()
1.2204 +
1.2205 + # The path_prefix strings must start, but not end, with a slash.
1.2206 + # Use "" instead of "/".
1.2207 + self.apps = [(p.rstrip("/"), a) for p, a in apps]
1.2208 +
1.2209 + def __call__(self, environ, start_response):
1.2210 + path = environ["PATH_INFO"] or "/"
1.2211 + for p, app in self.apps:
1.2212 + # The apps list should be sorted by length, descending.
1.2213 + if path.startswith(p + "/") or path == p:
1.2214 + environ = environ.copy()
1.2215 + environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
1.2216 + environ["PATH_INFO"] = path[len(p):]
1.2217 + return app(environ, start_response)
1.2218 +
1.2219 + start_response('404 Not Found', [('Content-Type', 'text/plain'),
1.2220 + ('Content-Length', '0')])
1.2221 + return ['']
1.2222 +