summaryrefslogtreecommitdiffstats
path: root/module/lib/wsgiserver/__init__.py
diff options
context:
space:
mode:
authorGravatar Walter Purcaro <vuolter@gmail.com> 2014-03-28 22:32:14 +0100
committerGravatar Walter Purcaro <vuolter@gmail.com> 2014-06-28 02:47:08 +0200
commitb1fffc3a1b2dbbb807213b85f538e59251b9bf35 (patch)
treec373d3234dcb474bb424371a3d89341bed8a9e07 /module/lib/wsgiserver/__init__.py
parentPlugins licensing doc (diff)
downloadpyload-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__.py347
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)