1. Getting Started¶
The AdvancedHTTPServer module is composed of two main classes which implement
the bulk of the provided functionality. These two classes are
AdvancedHTTPServer
and RequestHandler
. Just like
Python’s http.server
module, the server takes a class not an
instance of a class and is responsible for responding to individual requests
at the TCP connection level. The RequestHandler
instance is
initialized automatically by the server when a request is received.
The following sections outline how to accomplish common tasks using AdvancedHTTPServer.
1.1. Binding To Interfaces¶
Bind to a single interface using the address (singular) keyword argument to
the AdvancedHTTPServer.__init__()
method.
server = AdvancedHTTPServer(RequestHandler, address=('0.0.0.0', 8081))
Deprecated since version 2.0.12: The address keyword argument has been deprecated in favor of the addresses keyword argument. It should not be used in new code.
Bind to one or more interfaces using the addresses (plural) keyword argument
to the AdvancedHTTPServer.__init__()
method.
server = AdvancedHTTPServer(RequestHandler, addresses=(
# address, port, use ssl
('0.0.0.0', 80, False),
('0.0.0.0', 8080, False)
))
1.2. Enabling SSL¶
To enable SSL, pass a PEM file path using the ssl_certfile keyword argument to
the AdvancedHTTPServer.__init__()
method. This will be the default
certificate. Additional certificates can be configured with TLS’s Server Name
Indication (SNI) extension using the AdvancedHTTPServer.add_sni_cert()
method.
server = AdvancedHTTPServer(RequestHandler,
address=('0.0.0.0', 443),
ssl_certfile='/path/to/the/certificate.pem'
)
An insecure, self-signed certificate suitable for testing can be created using the following openssl command:
openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem
1.3. Enabling Basic Authentication¶
Basic authentication can be enabled by adding credentials to a
AdvancedHTTPServer
instance using its
AdvancedHTTPServer.auth_add_creds()
method which takes a username and
password. The pwtype keyword argument can optionally be used to specify that
the password is a hash.
server = AdvancedHTTPServer(RequestHandler)
server.auth_add_creds('admin', 'Sup3rS3cr3t!')
1.4. Using RPC¶
AdvancedHTTPServer supports a custom form of RPC over HTTP using the RPC
verb. To register RPC methods in a RequestHandler
they must be added
to the RequestHandler.rpc_handler_map
dictionary. Unlike standard
HTTP request handlers, RPC request handlers can take arbitrary arguments and key
word arguments.
To define an RPC capable RequestHandler
:
# define a custom RequestHandler inheriting from the original
class RPCHandler(RequestHandler):
def on_init(self):
# add to rpc_handler_map instead of handler_map
self.rpc_handler_map['/xor'] = self.rpc_xor
def rpc_xor(self, key, data):
return ''.join(map(lambda x: chr(ord(x) ^ key), data))
# initialize the server with the custom handler
server = AdvancedHTTPServer(RPCHandler)
To call methods from an RPC capable RequestHandler
:
# in this case the server is running at http://localhost:8080/
rpc = RPCClient(('localhost', 8080))
rpc('xor', 1, 'test')
1.5. Passing Variables To The Request Handler¶
The RequestHandler
instance is passed the instance of the
ServerNonThreaded
which received the request. This attribute can be
used to pass forward values from the top level AdvancedHTTPServer
object.
class DemoHandler(RequestHandler):
def do_init(self):
# access the value from the subserver instance
self.some_value = self.server.some_value
class DemoServer(AdvancedHTTPServer):
def __init__(self, some_value, *args, **kwargs):
# initialize the server first, this sets self.sub_servers
super(DemoServer, self).__init__(*args, **kwargs)
# iterate through self.sub_servers and set the attribute to forward
for server in self.sub_servers:
server.some_value = some_value
some_value = 'Hello World!'
server = DemoServer(some_value, DemoHandler)
1.6. Registering Request Handlers¶
AdvancedHTTPServer provides two distinct methods of registering methods to handle either HTTP or RPC requests. These methods are provided so the user may select the one they prefer to work with.
1.6.1. Modifying The Handler Map¶
The RequestHandler
class initializes the empty dictionaries for
RequestHandler.handler_map
and
RequestHandler.rpc_handler_map
. Both are keyed by a regular
expression which is applied to the path of the HTTP request to find a valid
handler method. These maps can be set by overriding the
RequestHandler.on_init()
method hook. The method must take a single
argument (in addition to the standard class method self
argument which goes
first) which is the parsed query string.
class DemoHandler(RequestHandler):
def on_init(self):
# over ride on_init and add a generic http request handler method
# this references a method which is defined later
self.handler_map['^hello-world$'] = self.res_hello_world
def res_hello_world(self, query):
# ...
return
1.6.2. Using RegisterPath¶
The RegisterPath
class can be used as a decorator to allow handler
methods to be registered in the handler map. This approach does not require
writing a RequestHandler
class and the handlers can be simple
functions. The functions must take two arguments, the first is the active
RequestHandler
instance and the second is the parsed query string.
The handler keyword argument to RegisterPath.__init__()
specifies an
optional RequestHandler
to register the handler method with. By
default, the handler is treated as a global handler and is registered for all
RequestHandler
instances. Alternatively, a specific handler can be
specified either by a reference to the class or by the class’s name.
# register a global handler for all RequestHandler instances
@RegisterPath('^register-path-global$')
def register_path_global(server, query):
# ...
return
# register a handler only for DemoHandler by it's name
@RegisterPath('^register-path-name$', 'DemoHandler')
def register_path_name(server, query):
# ...
return
# register a handler only for DemoHandler by it's class reference
@RegisterPath('^register-path-class$', DemoHandler)
def register_path_class(server, query):
# ...
return
1.6.2.1. Stacking RegisterPath¶
Since RegisterPath
does not modify or wrap the handler method it is
possible to “stack” the decorators to register a single handler for multiple
paths.
@RegisterPath('^register-path-class-double$', DemoHandler)
@RegisterPath('^register-path-class$', DemoHandler)
def register_path_class(server, query):
# ...
return
1.7. Handling Requests¶
HTTP requests (and RPC requests) are dispatched to handlers defined by the
RequestHandler
. Two dictionaries exist, one for dispatching HTTP
requests and another specifically for RPC requests. Both dictionaries use
regular expressions as keys and functions to be called as value.
Standard HTTP requests such as GET and POST use the following standard function signature:
def some_http_handler(self, query):
message = b'Hello World!\r\n\r\n'
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Length', len(message))
self.end_headers()
self.wfile.write(message)
return
RPC requests use an arbitrary function signature supporting both positional (required) and keyword (optional) arguments. The caller must then specify these arguments as necessary following the standard Python rules. The value returned by an RPC handler is returned to the remote caller.
# define an RPC handler method accepting two arguments
def some_rpc_handler(self, arg1, kwarg1=None):
# return None to the caller
return
1.7.1. Accessing Headers¶
Request headers can be accessed from both standard HTTP and RPC handlers through
the RequestHandler.headers
attribute. Header strings are case
insensitive.
def some_http_handler(self, query):
# get the Accept header if it exists, otherwise an empty string
accept_header = self.headers.get('Accept', '')
message = b'Accept Header: ' + accept_header.encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Length', len(message))
self.end_headers()
self.wfile.write(message)
return
1.7.2. Accessing Query Parameters¶
HTTP requests are passed the parsed query parameters in the query argument to the registered handler. This parameter is a dictionary keyed by the field name with a list of the values defined for the field name.
Note
The parsed query data uses an array for the value to store each occurrence of field. Usually it’s desirable to just access the first or last instance but it is important to note that all are available.
def some_http_handler(self, query):
# get the value of id from the query or a list containing an empty string
# so the first member can be referenced without raising an exception
id_value = query.get('id', [''])[0]
message = b'id value: ' + id_value.encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Length', len(message))
self.end_headers()
self.wfile.write(message)
return