diff options
author | Walter Purcaro <vuolter@gmail.com> | 2014-03-28 22:32:14 +0100 |
---|---|---|
committer | Walter Purcaro <vuolter@gmail.com> | 2014-06-28 02:47:08 +0200 |
commit | b1fffc3a1b2dbbb807213b85f538e59251b9bf35 (patch) | |
tree | c373d3234dcb474bb424371a3d89341bed8a9e07 /module/lib/wsgiserver/__init__.py | |
parent | Plugins licensing doc (diff) | |
download | pyload-b1fffc3a1b2dbbb807213b85f538e59251b9bf35.tar.xz |
Remove bad whitespaces
Merged vuolter/pyload@00288e6
Diffstat (limited to 'module/lib/wsgiserver/__init__.py')
-rw-r--r-- | module/lib/wsgiserver/__init__.py | 347 |
1 files changed, 173 insertions, 174 deletions
diff --git a/module/lib/wsgiserver/__init__.py b/module/lib/wsgiserver/__init__.py index c380e18b0..f2c2e866a 100644 --- a/module/lib/wsgiserver/__init__.py +++ b/module/lib/wsgiserver/__init__.py @@ -4,28 +4,28 @@ Simplest example on how to use this module directly (without using CherryPy's application machinery): from cherrypy import wsgiserver - + def my_crazy_app(environ, start_response): status = '200 OK' response_headers = [('Content-type','text/plain')] start_response(status, response_headers) return ['Hello world!\n'] - + server = wsgiserver.CherryPyWSGIServer( ('0.0.0.0', 8070), my_crazy_app, server_name='www.cherrypy.example') - + The CherryPy WSGI server can serve as many WSGI applications as you want in one instance by using a WSGIPathInfoDispatcher: - + d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app}) server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d) - + Want SSL support? Just set these attributes: - + server.ssl_certificate = <filename> server.ssl_private_key = <filename> - + if __name__ == '__main__': try: server.start() @@ -109,7 +109,7 @@ import errno def plat_specific_errors(*errnames): """Return error numbers for all errors in errnames on this platform. - + The 'errno' module contains different global constants depending on the specific platform (OS). This function will return the list of numeric values for a given list of potential names. @@ -147,24 +147,24 @@ comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING', class WSGIPathInfoDispatcher(object): """A WSGI dispatcher for dispatch based on the PATH_INFO. - + apps: a dict or list of (path_prefix, app) pairs. """ - + def __init__(self, apps): try: apps = apps.items() except AttributeError: pass - + # Sort the apps by len(path), descending apps.sort() apps.reverse() - + # The path_prefix strings must start, but not end, with a slash. # Use "" instead of "/". self.apps = [(p.rstrip("/"), a) for p, a in apps] - + def __call__(self, environ, start_response): path = environ["PATH_INFO"] or "/" for p, app in self.apps: @@ -174,7 +174,7 @@ class WSGIPathInfoDispatcher(object): environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p environ["PATH_INFO"] = path[len(p):] return app(environ, start_response) - + start_response('404 Not Found', [('Content-Type', 'text/plain'), ('Content-Length', '0')]) return [''] @@ -185,29 +185,29 @@ class MaxSizeExceeded(Exception): class SizeCheckWrapper(object): """Wraps a file-like object, raising MaxSizeExceeded if too large.""" - + def __init__(self, rfile, maxlen): self.rfile = rfile self.maxlen = maxlen self.bytes_read = 0 - + def _check_length(self): if self.maxlen and self.bytes_read > self.maxlen: raise MaxSizeExceeded() - + def read(self, size=None): data = self.rfile.read(size) self.bytes_read += len(data) self._check_length() return data - + def readline(self, size=None): if size is not None: data = self.rfile.readline(size) self.bytes_read += len(data) self._check_length() return data - + # User didn't specify a size ... # We read the line in chunks to make sure it's not a 100MB line ! res = [] @@ -219,7 +219,7 @@ class SizeCheckWrapper(object): # See http://www.cherrypy.org/ticket/421 if len(data) < 256 or data[-1:] == "\n": return ''.join(res) - + def readlines(self, sizehint=0): # Shamelessly stolen from StringIO total = 0 @@ -232,13 +232,13 @@ class SizeCheckWrapper(object): break line = self.readline() return lines - + def close(self): self.rfile.close() - + def __iter__(self): return self - + def next(self): data = self.rfile.next() self.bytes_read += len(data) @@ -248,9 +248,9 @@ class SizeCheckWrapper(object): class HTTPRequest(object): """An HTTP Request (and response). - + A single HTTP connection may consist of multiple request/response pairs. - + send: the 'send' method from the connection's socket object. wsgi_app: the WSGI application to call. environ: a partial WSGI environ (server and connection entries). @@ -267,7 +267,7 @@ class HTTPRequest(object): version is less than or equal to the one received in the request. An HTTP server MUST NOT send a version for which it is not at least conditionally compliant." - + outheaders: a list of header tuples to write in the response. ready: when True, the request has been parsed and is ready to begin generating the response. When False, signals the calling Connection @@ -280,16 +280,16 @@ class HTTPRequest(object): transfer-coding. This value is set automatically inside send_headers. """ - + max_request_header_size = 0 max_request_body_size = 0 - + def __init__(self, wfile, environ, wsgi_app): self.rfile = environ['wsgi.input'] self.wfile = wfile self.environ = environ.copy() self.wsgi_app = wsgi_app - + self.ready = False self.started_response = False self.status = "" @@ -297,18 +297,18 @@ class HTTPRequest(object): self.sent_headers = False self.close_connection = False self.chunked_write = False - + def parse_request(self): """Parse the next HTTP request start-line and message-headers.""" self.rfile.maxlen = self.max_request_header_size self.rfile.bytes_read = 0 - + try: self._parse_request() except MaxSizeExceeded: self.simple_response("413 Request Entity Too Large") return - + def _parse_request(self): # HTTP/1.1 connections are persistent by default. If a client # requests a page, then idles (leaves the connection open), @@ -322,7 +322,7 @@ class HTTPRequest(object): # Force self.ready = False so the connection will close. self.ready = False return - + if request_line == "\r\n": # RFC 2616 sec 4.1: "...if the server is reading the protocol # stream at the beginning of a message and receives a CRLF @@ -332,32 +332,32 @@ class HTTPRequest(object): if not request_line: self.ready = False return - + environ = self.environ - + try: method, path, req_protocol = request_line.strip().split(" ", 2) except ValueError: self.simple_response(400, "Malformed Request-Line") return - + environ["REQUEST_METHOD"] = method - + # path may be an abs_path (including "http://host.domain.tld"); scheme, location, path, params, qs, frag = urlparse(path) - + if frag: self.simple_response("400 Bad Request", "Illegal #fragment in Request-URI.") return - + if scheme: environ["wsgi.url_scheme"] = scheme if params: path = path + ";" + params - + environ["SCRIPT_NAME"] = "" - + # Unquote the path+params (e.g. "/this%20path" -> "this path"). # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 # @@ -367,11 +367,11 @@ class HTTPRequest(object): atoms = [unquote(x) for x in quoted_slash.split(path)] path = "%2F".join(atoms) environ["PATH_INFO"] = path - + # Note that, like wsgiref and most other WSGI servers, # we unquote the path but not the query string. environ["QUERY_STRING"] = qs - + # Compare request and server HTTP protocol versions, in case our # server does not support the requested protocol. Limit our output # to min(req, server). We want the following output: @@ -393,23 +393,23 @@ class HTTPRequest(object): # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. environ["SERVER_PROTOCOL"] = req_protocol self.response_protocol = "HTTP/%s.%s" % min(rp, sp) - + # If the Request-URI was an absoluteURI, use its location atom. if location: environ["SERVER_NAME"] = location - + # then all the http headers try: self.read_headers() except ValueError, ex: self.simple_response("400 Bad Request", repr(ex.args)) return - + mrbs = self.max_request_body_size if mrbs and int(environ.get("CONTENT_LENGTH", 0)) > mrbs: self.simple_response("413 Request Entity Too Large") return - + # Persistent connection support if self.response_protocol == "HTTP/1.1": # Both server and client are HTTP/1.1 @@ -419,16 +419,16 @@ class HTTPRequest(object): # Either the server or client (or both) are HTTP/1.0 if environ.get("HTTP_CONNECTION", "") != "Keep-Alive": self.close_connection = True - + # Transfer-Encoding support te = None if self.response_protocol == "HTTP/1.1": te = environ.get("HTTP_TRANSFER_ENCODING") if te: te = [x.strip().lower() for x in te.split(",") if x.strip()] - + self.chunked_read = False - + if te: for enc in te: if enc == "chunked": @@ -439,7 +439,7 @@ class HTTPRequest(object): self.simple_response("501 Unimplemented") self.close_connection = True return - + # From PEP 333: # "Servers and gateways that implement HTTP 1.1 must provide # transparent support for HTTP 1.1's "expect/continue" mechanism. @@ -459,23 +459,23 @@ class HTTPRequest(object): # but it seems like it would be a big slowdown for such a rare case. if environ.get("HTTP_EXPECT", "") == "100-continue": self.simple_response(100) - + self.ready = True - + def read_headers(self): """Read header lines from the incoming stream.""" environ = self.environ - + while True: line = self.rfile.readline() if not line: # No more data--illegal end of headers raise ValueError("Illegal end of headers.") - + if line == '\r\n': # Normal end of headers break - + if line[0] in ' \t': # It's a continuation line. v = line.strip() @@ -483,20 +483,20 @@ class HTTPRequest(object): k, v = line.split(":", 1) k, v = k.strip().upper(), v.strip() envname = "HTTP_" + k.replace("-", "_") - + if k in comma_separated_headers: existing = environ.get(envname) if existing: v = ", ".join((existing, v)) environ[envname] = v - + ct = environ.pop("HTTP_CONTENT_TYPE", None) if ct is not None: environ["CONTENT_TYPE"] = ct cl = environ.pop("HTTP_CONTENT_LENGTH", None) if cl is not None: environ["CONTENT_LENGTH"] = cl - + def decode_chunked(self): """Decode the 'chunked' transfer coding.""" cl = 0 @@ -515,15 +515,15 @@ class HTTPRequest(object): "Bad chunked transfer coding " "(expected '\\r\\n', got %r)" % crlf) return - + # Grab any trailer headers self.read_headers() - + data.seek(0) self.environ["wsgi.input"] = data self.environ["CONTENT_LENGTH"] = str(cl) or "" return True - + def respond(self): """Call the appropriate WSGI app and write its iterable output.""" # Set rfile.maxlen to ensure we don't read past Content-Length. @@ -539,20 +539,20 @@ class HTTPRequest(object): else: self.rfile.maxlen = cl self.rfile.bytes_read = 0 - + try: self._respond() except MaxSizeExceeded: if not self.sent_headers: self.simple_response("413 Request Entity Too Large") return - + def _respond(self): if self.chunked_read: if not self.decode_chunked(): self.close_connection = True return - + response = self.wsgi_app(self.environ, self.start_response) try: for chunk in response: @@ -567,35 +567,35 @@ class HTTPRequest(object): finally: if hasattr(response, "close"): response.close() - + if (self.ready and not self.sent_headers): self.sent_headers = True self.send_headers() if self.chunked_write: self.wfile.sendall("0\r\n\r\n") - + def simple_response(self, status, msg=""): """Write a simple response back to the client.""" status = str(status) buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status), "Content-Length: %s\r\n" % len(msg), "Content-Type: text/plain\r\n"] - + if status[:3] == "413" and self.response_protocol == 'HTTP/1.1': # Request Entity Too Large self.close_connection = True buf.append("Connection: close\r\n") - + buf.append("\r\n") if msg: buf.append(msg) - + try: self.wfile.sendall("".join(buf)) except socket.error, x: if x.args[0] not in socket_errors_to_ignore: raise - + def start_response(self, status, headers, exc_info = None): """WSGI callable to begin the HTTP response.""" # "The application may call start_response more than once, @@ -603,7 +603,7 @@ class HTTPRequest(object): if self.started_response and not exc_info: raise AssertionError("WSGI start_response called a second " "time with no exc_info.") - + # "if exc_info is provided, and the HTTP headers have already been # sent, start_response must raise an error, and should raise the # exc_info tuple." @@ -612,36 +612,36 @@ class HTTPRequest(object): raise exc_info[0], exc_info[1], exc_info[2] finally: exc_info = None - + self.started_response = True self.status = status self.outheaders.extend(headers) return self.write - + def write(self, chunk): """WSGI callable to write unbuffered data to the client. - + This method is also used internally by start_response (to write data from the iterable returned by the WSGI application). """ if not self.started_response: raise AssertionError("WSGI write called before start_response.") - + if not self.sent_headers: self.sent_headers = True self.send_headers() - + if self.chunked_write and chunk: buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"] self.wfile.sendall("".join(buf)) else: self.wfile.sendall(chunk) - + def send_headers(self): """Assert, process, and send the HTTP response message-headers.""" hkeys = [key.lower() for key, value in self.outheaders] status = int(self.status[:3]) - + if status == 413: # Request Entity Too Large. Close conn to avoid garbage. self.close_connection = True @@ -660,7 +660,7 @@ class HTTPRequest(object): else: # Closing the conn is the only way to determine len. self.close_connection = True - + if "connection" not in hkeys: if self.response_protocol == 'HTTP/1.1': # Both server and client are HTTP/1.1 or better @@ -670,7 +670,7 @@ class HTTPRequest(object): # Server and/or client are HTTP/1.0 if not self.close_connection: self.outheaders.append(("Connection", "Keep-Alive")) - + if (not self.close_connection) and (not self.chunked_read): # Read any remaining request body data on the socket. # "If an origin server receives a request that does not include an @@ -687,13 +687,13 @@ class HTTPRequest(object): size = self.rfile.maxlen - self.rfile.bytes_read if size > 0: self.rfile.read(size) - + if "date" not in hkeys: self.outheaders.append(("Date", rfc822.formatdate())) - + if "server" not in hkeys: self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE'])) - + buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"] try: buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] @@ -1039,17 +1039,17 @@ else: break buf_len += n return "".join(buffers) - + class SSL_fileobject(CP_fileobject): """SSL file object attached to a socket object.""" - + ssl_timeout = 3 ssl_retry = .01 - + def _safe_call(self, is_reader, call, *args, **kwargs): """Wrap the given call with SSL error-trapping. - + is_reader: if False EOF errors will be raised. If True, EOF errors will return "" (to emulate normal sockets). """ @@ -1068,7 +1068,7 @@ class SSL_fileobject(CP_fileobject): except SSL.SysCallError, e: if is_reader and e.args == (-1, 'Unexpected EOF'): return "" - + errnum = e.args[0] if is_reader and errnum in socket_errors_to_ignore: return "" @@ -1076,20 +1076,20 @@ class SSL_fileobject(CP_fileobject): except SSL.Error, e: if is_reader and e.args == (-1, 'Unexpected EOF'): return "" - + thirdarg = None try: thirdarg = e.args[0][0][2] except IndexError: pass - + if thirdarg == 'http request': # The client is talking HTTP to an HTTPS server. raise NoSSLError() raise FatalSSLAlert(*e.args) except: raise - + if time.time() - start > self.ssl_timeout: raise socket.timeout("timed out") @@ -1102,7 +1102,7 @@ class SSL_fileobject(CP_fileobject): p = self._sock.pending() if not p: return "".join(buf) - + def sendall(self, *args, **kwargs): return self._safe_call(False, super(SSL_fileobject, self).sendall, *args, **kwargs) @@ -1112,15 +1112,15 @@ class SSL_fileobject(CP_fileobject): class HTTPConnection(object): """An HTTP connection (active socket). - + socket: the raw socket object (usually TCP) for this connection. wsgi_app: the WSGI application for this server/connection. environ: a WSGI environ template. This will be copied for each request. - + rfile: a fileobject for reading from the socket. send: a function for writing (+ flush) to the socket. """ - + rbufsize = -1 RequestHandlerClass = HTTPRequest environ = {"wsgi.version": (1, 0), @@ -1130,15 +1130,15 @@ class HTTPConnection(object): "wsgi.run_once": False, "wsgi.errors": sys.stderr, } - + def __init__(self, sock, wsgi_app, environ): self.socket = sock self.wsgi_app = wsgi_app - + # Copy the class environ into self. self.environ = self.environ.copy() self.environ.update(environ) - + if SSL and isinstance(sock, SSL.ConnectionType): timeout = sock.gettimeout() self.rfile = SSL_fileobject(sock, "rb", self.rbufsize) @@ -1148,13 +1148,13 @@ class HTTPConnection(object): else: self.rfile = CP_fileobject(sock, "rb", self.rbufsize) self.wfile = CP_fileobject(sock, "wb", -1) - + # Wrap wsgi.input but not HTTPConnection.rfile itself. # We're also not setting maxlen yet; we'll do that separately # for headers and body for each iteration of self.communicate # (if maxlen is 0 the wrapper doesn't check length). self.environ["wsgi.input"] = SizeCheckWrapper(self.rfile, 0) - + def communicate(self): """Read each request and respond appropriately.""" try: @@ -1165,16 +1165,16 @@ class HTTPConnection(object): req = None req = self.RequestHandlerClass(self.wfile, self.environ, self.wsgi_app) - + # This order of operations should guarantee correct pipelining. req.parse_request() if not req.ready: return - + req.respond() if req.close_connection: return - + except socket.error, e: errnum = e.args[0] if errnum == 'timed out': @@ -1201,13 +1201,13 @@ class HTTPConnection(object): except Exception, e: if req and not req.sent_headers: req.simple_response("500 Internal Server Error", format_exc()) - + linger = False - + def close(self): """Close the socket underlying this connection.""" self.rfile.close() - + if not self.linger: # Python's socket module does NOT call close on the kernel socket # when you call socket.close(). We do so manually here because we @@ -1239,25 +1239,25 @@ _SHUTDOWNREQUEST = None class WorkerThread(threading.Thread): """Thread which continuously polls a Queue for Connection objects. - + server: the HTTP Server which spawned this thread, and which owns the Queue and is placing active connections into it. ready: a simple flag for the calling server to know when this thread has begun polling the Queue. - + Due to the timing issues of polling a Queue, a WorkerThread does not check its own 'ready' flag after it has started. To stop the thread, it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue (one for each running WorkerThread). """ - + conn = None - + def __init__(self, server): self.ready = False self.server = server threading.Thread.__init__(self) - + def run(self): try: self.ready = True @@ -1265,7 +1265,7 @@ class WorkerThread(threading.Thread): conn = self.server.requests.get() if conn is _SHUTDOWNREQUEST: return - + self.conn = conn try: conn.communicate() @@ -1278,11 +1278,11 @@ class WorkerThread(threading.Thread): class ThreadPool(object): """A Request Queue for the CherryPyWSGIServer which pools threads. - + ThreadPool objects must provide min, get(), put(obj), start() and stop(timeout) attributes. """ - + def __init__(self, server, min=10, max=-1): self.server = server self.min = min @@ -1290,7 +1290,7 @@ class ThreadPool(object): self._threads = [] self._queue = Queue.Queue() self.get = self._queue.get - + def start(self): """Start the pool of threads.""" for i in xrange(self.min): @@ -1301,17 +1301,17 @@ class ThreadPool(object): for worker in self._threads: while not worker.ready: time.sleep(.1) - + def _get_idle(self): """Number of worker threads which are idle. Read-only.""" return len([t for t in self._threads if t.conn is None]) idle = property(_get_idle, doc=_get_idle.__doc__) - + def put(self, obj): self._queue.put(obj) if obj is _SHUTDOWNREQUEST: return - + def grow(self, amount): """Spawn new worker threads (not above self.max).""" for i in xrange(amount): @@ -1321,7 +1321,7 @@ class ThreadPool(object): worker.setName("CP WSGIServer " + worker.getName()) self._threads.append(worker) worker.start() - + def shrink(self, amount): """Kill off worker threads (not below self.min).""" # Grow/shrink the pool if necessary. @@ -1330,7 +1330,7 @@ class ThreadPool(object): if not t.isAlive(): self._threads.remove(t) amount -= 1 - + if amount > 0: for i in xrange(min(amount, len(self._threads) - self.min)): # Put a number of shutdown requests on the queue equal @@ -1338,13 +1338,13 @@ class ThreadPool(object): # that worker will terminate and be culled from our list # in self.put. self._queue.put(_SHUTDOWNREQUEST) - + def stop(self, timeout=5): # Must shut down threads here so the code that calls # this method can know when all threads are stopped. for worker in self._threads: self._queue.put(_SHUTDOWNREQUEST) - + # Don't join currentThread (when stop is called inside a request). current = threading.currentThread() while self._threads: @@ -1376,14 +1376,14 @@ class ThreadPool(object): class SSLConnection: """A thread-safe wrapper for an SSL.Connection. - + *args: the arguments to create the wrapped SSL.Connection(*args). """ - + def __init__(self, *args): self._ssl_conn = SSL.Connection(*args) self._lock = threading.RLock() - + for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', 'renegotiate', 'bind', 'listen', 'connect', 'accept', 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', @@ -1425,7 +1425,7 @@ else: class CherryPyWSGIServer(object): """An HTTP server for WSGI. - + bind_addr: The interface on which to listen for connections. For TCP sockets, a (host, port) tuple. Host values may be any IPv4 or IPv6 address, or any valid hostname. The string 'localhost' is a @@ -1433,7 +1433,7 @@ class CherryPyWSGIServer(object): The string '0.0.0.0' is a special IPv4 entry meaning "any active interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for IPv6. The empty string or None are not allowed. - + For UNIX sockets, supply the filename as a string. wsgi_app: the WSGI 'application callable'; multiple WSGI applications may be passed as (path_prefix, app) pairs. @@ -1444,47 +1444,46 @@ class CherryPyWSGIServer(object): request_queue_size: the 'backlog' argument to socket.listen(); specifies the maximum number of queued connections (default 5). timeout: the timeout in seconds for accepted connections (default 10). - + nodelay: if True (the default since 3.1), sets the TCP_NODELAY socket option. - + protocol: the version string to write in the Status-Line of all HTTP responses. For example, "HTTP/1.1" (the default). This also limits the supported features used in the response. - - + SSL/HTTPS --------- The OpenSSL module must be importable for SSL functionality. You can obtain it from http://pyopenssl.sourceforge.net/ - + ssl_certificate: the filename of the server SSL certificate. ssl_privatekey: the filename of the server's private key file. - + If either of these is None (both are None by default), this server will not use SSL. If both are given and are valid, they will be read on server start and used in the SSL context for the listening socket. """ - + protocol = "HTTP/1.1" _bind_addr = "127.0.0.1" version = "CherryPy/3.1.2" ready = False _interrupt = None - + nodelay = True - + ConnectionClass = HTTPConnection environ = {} - + # Paths to certificate and private key files ssl_certificate = None ssl_private_key = None - + def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5): self.requests = ThreadPool(self, min=numthreads or 1, max=max) - + if callable(wsgi_app): # We've been handed a single wsgi_app, in CP-2.1 style. # Assume it's mounted at "". @@ -1498,26 +1497,26 @@ class CherryPyWSGIServer(object): "include a WSGIPathInfoDispatcher instead.", DeprecationWarning) self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app) - + self.bind_addr = bind_addr if not server_name: server_name = socket.gethostname() self.server_name = server_name self.request_queue_size = request_queue_size - + self.timeout = timeout self.shutdown_timeout = shutdown_timeout - + def _get_numthreads(self): return self.requests.min def _set_numthreads(self, value): self.requests.min = value numthreads = property(_get_numthreads, _set_numthreads) - + def __str__(self): return "%s.%s(%r)" % (self.__module__, self.__class__.__name__, self.bind_addr) - + def _get_bind_addr(self): return self._bind_addr def _set_bind_addr(self, value): @@ -1538,16 +1537,16 @@ class CherryPyWSGIServer(object): self._bind_addr = value bind_addr = property(_get_bind_addr, _set_bind_addr, doc="""The interface on which to listen for connections. - + For TCP sockets, a (host, port) tuple. Host values may be any IPv4 or IPv6 address, or any valid hostname. The string 'localhost' is a synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6). The string '0.0.0.0' is a special IPv4 entry meaning "any active interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for IPv6. The empty string or None are not allowed. - + For UNIX sockets, supply the filename as a string.""") - + def start(self): """Run the server forever.""" # We don't have to trap KeyboardInterrupt or SystemExit here, @@ -1555,19 +1554,19 @@ class CherryPyWSGIServer(object): # If you're using this server with another framework, you should # trap those exceptions in whatever code block calls start(). self._interrupt = None - + # Select the appropriate socket if isinstance(self.bind_addr, basestring): # AF_UNIX socket - + # So we can reuse the socket... try: os.unlink(self.bind_addr) except: pass - + # So everyone can access the socket... try: os.chmod(self.bind_addr, 0777) except: pass - + info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)] else: # AF_INET or AF_INET6 socket @@ -1579,7 +1578,7 @@ class CherryPyWSGIServer(object): except socket.gaierror: # Probably a DNS issue. Assume IPv4. info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)] - + self.socket = None msg = "No socket could be created" for res in info: @@ -1594,14 +1593,14 @@ class CherryPyWSGIServer(object): break if not self.socket: raise socket.error, msg - + # Timeout so KeyboardInterrupt can be caught on Win32 self.socket.settimeout(1) self.socket.listen(self.request_queue_size) - + # Create worker threads self.requests.start() - + self.ready = True while self.ready: self.tick() @@ -1611,7 +1610,7 @@ class CherryPyWSGIServer(object): time.sleep(0.1) if self.interrupt: raise self.interrupt - + def bind(self, family, type, proto=0): """Create (or recreate) the actual socket object.""" self.socket = socket.socket(family, type, proto) @@ -1622,14 +1621,14 @@ class CherryPyWSGIServer(object): if self.ssl_certificate and self.ssl_private_key: if SSL is None: raise ImportError("You must install pyOpenSSL to use HTTPS.") - + # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.use_privatekey_file(self.ssl_private_key) ctx.use_certificate_file(self.ssl_certificate) self.socket = SSLConnection(ctx, self.socket) self.populate_ssl_environ() - + # If listening on the IPV6 any address ('::' = IN6ADDR_ANY), # activate dual-stack. See http://www.cherrypy.org/ticket/871. if (not isinstance(self.bind_addr, basestring) @@ -1640,9 +1639,9 @@ class CherryPyWSGIServer(object): # Apparently, the socket option is not available in # this machine's TCP stack pass - + self.socket.bind(self.bind_addr) - + def tick(self): """Accept a new connection and put it on the Queue.""" try: @@ -1652,7 +1651,7 @@ class CherryPyWSGIServer(object): return if hasattr(s, 'settimeout'): s.settimeout(self.timeout) - + environ = self.environ.copy() # SERVER_SOFTWARE is common for IIS. It's also helpful for # us to pass a default value for the "Server" response header. @@ -1663,7 +1662,7 @@ class CherryPyWSGIServer(object): # See http://www.faqs.org/rfcs/rfc2145.html. environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol environ["SERVER_NAME"] = self.server_name - + if isinstance(self.bind_addr, basestring): # AF_UNIX. This isn't really allowed by WSGI, which doesn't # address unix domain sockets. But it's better than nothing. @@ -1674,7 +1673,7 @@ class CherryPyWSGIServer(object): # Until we do DNS lookups, omit REMOTE_HOST environ["REMOTE_ADDR"] = addr[0] environ["REMOTE_PORT"] = str(addr[1]) - + conn = self.ConnectionClass(s, self.wsgi_app, environ) self.requests.put(conn) except socket.timeout: @@ -1698,7 +1697,7 @@ class CherryPyWSGIServer(object): # See http://www.cherrypy.org/ticket/686. return raise - + def _get_interrupt(self): return self._interrupt def _set_interrupt(self, interrupt): @@ -1708,11 +1707,11 @@ class CherryPyWSGIServer(object): interrupt = property(_get_interrupt, _set_interrupt, doc="Set this to an Exception instance to " "interrupt the server.") - + def stop(self): """Gracefully shutdown a server that is serving forever.""" self.ready = False - + sock = getattr(self, "socket", None) if sock: if not isinstance(self.bind_addr, basestring): @@ -1744,9 +1743,9 @@ class CherryPyWSGIServer(object): if hasattr(sock, "close"): sock.close() self.socket = None - + self.requests.stop(self.shutdown_timeout) - + def populate_ssl_environ(self): """Create WSGI environ entries to be merged into each request.""" cert = open(self.ssl_certificate, 'rb').read() @@ -1760,7 +1759,7 @@ class CherryPyWSGIServer(object): ## SSL_VERSION_INTERFACE string The mod_ssl program version ## SSL_VERSION_LIBRARY string The OpenSSL program version } - + # Server certificate attributes ssl_environ.update({ 'SSL_SERVER_M_VERSION': cert.get_version(), @@ -1768,17 +1767,17 @@ class CherryPyWSGIServer(object): ## 'SSL_SERVER_V_START': Validity of server's certificate (start time), ## 'SSL_SERVER_V_END': Validity of server's certificate (end time), }) - + for prefix, dn in [("I", cert.get_issuer()), ("S", cert.get_subject())]: # X509Name objects don't seem to have a way to get the # complete DN string. Use str() and slice it instead, # because str(dn) == "<X509Name object '/C=US/ST=...'>" dnstr = str(dn)[18:-2] - + wsgikey = 'SSL_SERVER_%s_DN' % prefix ssl_environ[wsgikey] = dnstr - + # The DN should be of the form: /k1=v1/k2=v2, but we must allow # for any value to contain slashes itself (in a URL). while dnstr: @@ -1789,6 +1788,6 @@ class CherryPyWSGIServer(object): if key and value: wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key) ssl_environ[wsgikey] = value - + self.environ.update(ssl_environ) |