"""This is a replacement for ``BaseHTTPServer.HTTPServer``."""
__docformat__ = "restructuredtext"
import errno
import exceptions
import socket
from SocketServer import TCPServer
import sys
import aquarium.util.Ports as ports
class _HTTPServer(TCPServer):
"""This is a replacement for ``BaseHTTPServer.HTTPServer``.
It does all the same work ``BaseHTTPServer.HTTPServer`` does. However,
instead of inheriting from something like
``SocketServer.ThreadingTCPServer``, it inherits directly from
``SocketServer.TCPServer`` and accepts a servertype for creating sockets
and threads. This is necessary to support new threading models and socket
types.
This whole class is a move away from the design espoused by
``SocketServer`` toward a design suggested by Paul Clegg. Specifically,
instead of using mixins for the various threading models, factories are
used for both socket and thread creation.
The following attributes are used:
RequestHandlerClass
By default, this is ``HTTPHandler``.
server_ctx
This is ``ServerContext``.
servertype
This is an instance of some class from the servertype package.
is_secure
Return ``getattr(self.servertype.create_socket, "is_secure", False)``.
mounts
This is configured using ``server_ctx.mounts``. It is a list of tuples
of the form ``[(url_path, vfs)]``. Each ``vfs`` is a virtual filesystem
object (i.e. an instance of some class from the ``glass.vfs`` package).
Abstracting the filesystem allows me to do things like use a zip file for
a document root.
available_vfs_modules
This is a list of the names of the available vfs modules. When creating
``self.mounts``, I'll try to instantiate each vfs to see if it can handle
the given ``path_translated``.
"""
allow_reuse_address = 1 # Taken from BaseHTTPServer.
available_vfs_modules = ["Standard", "Zip"]
def __init__(self, server_address=('', ports.HTTP_PORT),
RequestHandlerClass=None, server_ctx=None, servertype=None):
"""Against doctor's orders, I must override (not extend) this.
Specifically, I need to accept and make use of additional parameters as
well as split part of ``__init__`` into ``__call__`` for coro safety
reasons. Otherwise, this is pretty much like ``TCPServer.__init__``.
server_address
By default, this is ``('', 80)``.
RequestHandlerClass
By default, this is ``HTTPHandler``.
server_ctx
By default, this is an instance of ``ServerContext``.
servertype
By default, this is an instance of the ``Standard`` servertype.
"""
if not RequestHandlerClass:
from HTTPHandler import HTTPHandler
RequestHandlerClass = HTTPHandler
if not server_ctx:
from ServerContext import ServerContext
server_ctx = ServerContext()
self.request_queue_size = server_ctx.request_queue_size
if not servertype:
from servertype.Standard import Standard
servertype = Standard()
self.server_address = server_address
self.RequestHandlerClass = RequestHandlerClass
self.server_ctx = server_ctx
self.servertype = servertype
self.init_mounts()
def __call__(self):
"""Create, bind, and activate the socket."""
self.socket = self.servertype.create_socket()
self.server_bind()
self.server_activate()
def init_mounts(self):
"""Setup ``self.mounts``.
Implicitly add the document root as the first member. Pick the right
vfs for each ``path_translated``. Make sure each ``url_path`` has a
trailing slash. If not, add one.
"""
configured = [("/", self.server_ctx.doc_root)] + \
self.server_ctx.mounts
self.mounts = []
for (url_path, path_translated) in configured:
if not url_path.endswith("/"):
url_path += "/"
for vfs_name in self.available_vfs_modules:
try:
module_name = "glass.vfs." + vfs_name
module = __import__(module_name, {}, {}, [vfs_name])
vfs = getattr(module, vfs_name)(path_translated)
self.mounts.append((url_path, vfs))
break
except ValueError:
continue
else:
raise ValueError("""\
Could not find an appropriate vfs module for: %s
The simplest cause for this would be a misspelled doc_root.""" %
path_translated)
def server_bind(self):
"""Extend ``server_bind`` to store the server name and port."""
TCPServer.server_bind(self)
self.server_name, self.server_port = self.socket.getsockname()
def handle_request(self):
"""Handle early socket exceptions.
Call ``handle_early_socket_error``.
"""
request, client_address = None, None
try:
request, client_address = self.get_request()
except socket.error, e:
self.handle_early_socket_error(e, request, client_address)
return
if self.verify_request(request, client_address):
self.process_request(request, client_address)
def process_request(self, request, client_address):
"""Call ``finish_request_threaded`` in a new "thread"."""
self.servertype.create_thread(self.finish_request_threaded, request,
client_address)
def finish_request_threaded(self, request, client_address):
"""Call ``finish_request``, but handle exceptions.
This is nice because I don't won't exceptions from one "thread" being
handled in another "thread".
"""
try:
self.finish_request(request, client_address)
except Exception, e:
self.handle_error(e, request, client_address)
self.close_request(request)
def finish_request(self, request, client_address):
"""Instantiate the ``RequestHandlerClass`` and run it.
In ``TCPServer.finish_request``, instantiating the
``RequestHandlerClass`` is sufficient to run it. However, in coro, you
can't have anything in an ``__init__`` that may lead to a context
switch. ``SocketServer.BaseRequestHandler.__init__``, unsurprisingly,
is ignorant of this rule. Hence, we require that the
``RequestHandlerClass`` passed to us implement a separate ``__init__``
*and* ``__call__`` method, both of which will be called here, in order
to work around this problem. This is an API change that must apply to
coro and non-coro alike since it affects the request handlers.
"""
self.RequestHandlerClass(request, client_address, self,
self.server_ctx)()
self.close_request(request)
def close_request(self, request):
"""HACK: Call ``TCPServer.close_request``, if defined.
It isn't defined in Python 2.0, but it must be done in Python 2.3.
"""
if hasattr(TCPServer, "close_request"):
TCPServer.close_request(self, request)
def handle_early_socket_error(self, e, request, client_address):
"""Override this to handle socket errors from ``get_request``.
By default, I output to stderr just to make sure you see something if
things are completely messed up.
Beware, request and client_address might each be None.
"""
print >> sys.stderr, """\
HTTPServer: early socket error encountered--dropping request: %s""" % `e`
def handle_error(self, e, request, client_address):
"""Differentiate errors.
Differentiate ``handle_disconnect_error`` and ``handle_other_error``.
"""
if self.is_disconnect_error(e):
self.handle_disconnect_error(e, request, client_address)
else:
self.handle_other_error(e, request, client_address)
def is_disconnect_error(self, e):
"""Is ``e`` an error like ``errno.EPIPE`` or ``errno.ECONNRESET``?
Various types of sockets might represent disconnect errors differently.
Hopefully, one of the ways I've coded will apply.
"""
if (isinstance(e, socket.error) or
isinstance(e, exceptions.OSError) or
isinstance(e, exceptions.IOError)):
errnos = [errno.EPIPE, errno.ECONNRESET]
if len(e.args) and e.args[0] in errnos:
return True
if getattr(e, "errno", None) in errnos:
return True
return False
def handle_disconnect_error(self, e, request, client_address):
"""Handle errors such as ``errno.EPIPE`` or ``errno.ECONNRESET``.
By default, they are ignored.
"""
pass
def handle_other_error(self, e, request, client_address):
"""Handle all other errors that ``handle_disconnect_error`` doesn't.
Since I don't know what's appropriate for your app, I'll just call
``TCPServer.handle_error``.
"""
TCPServer.handle_error(self, request, client_address)
def __getattr__(self, attr):
"""Handle the ``is_secure`` attribute.
``is_secure`` returns
``getattr(self.servertype.create_socket, "is_secure", False)``.
"""
if attr == "is_secure":
return getattr(self.servertype.create_socket, "is_secure", False)
raise AttributeError(attr)
def HTTPServer(*args, **kargs):
"""HACK: This is a wrapper around ``_HTTPServer`` to make it coro safe.
Existing code does not know that ``__init__`` has been broken into
``__init__`` and ``__call__``. Hence, this wrapper abstracts that detail.
"""
http_server = _HTTPServer(*args, **kargs)
http_server()
return http_server
def test(ServerClass=HTTPServer, RequestHandlerClass=None, server_ctx=None,
servertype=None, protocol="HTTP/1.0"):
"""Test the HTTP request handler class.
This runs an HTTP server on port 8000 (or the first command line argument).
This code is a modified version of ``BaseHTTPServer.test``. I had to
modify it due to extra parameters passed to the ``ServerClass``. Each of
the above arguments has a reasonable default.
"""
if sys.argv[1:]:
port = int(sys.argv[1])
else:
port = 8000
server_address = ('', port)
httpd = ServerClass(server_address, RequestHandlerClass, server_ctx,
servertype)
httpd.RequestHandlerClass.protocol_version = protocol
sa = httpd.socket.getsockname()
print "Serving HTTP on", sa[0], "port", sa[1], "..."
httpd.serve_forever()
# Do some testing.
if __name__ == "__main__":
test()
|