Bitbucket is a code hosting site with unlimited public and private repositories. We're also free for small teams!

Close
.. -*- restructuredtext -*-

########################################################################
servercontext - tools for writing tests for stuff relying on web servers
########################################################################

Introduction
------------

servercontext provides a context that can be used to easily start a HTTP
server in a thread, and make sure it's cleanly shut down when the context
is exited. This is useful for when you need to write tests for code that
requires

The general usage is:

    with servercontext.test_server():
        pass # do queries against http://localhost:8514

When the code inside the context runs, the server is guaranteed to be ready
to receive requests. When the context is exited, and before any other
code is run, the server is guaranteed to have been shut down. (But see the
notes section!)

Examples
---------

The easiest example is to serve files from directories. The following will
serve files from the current directory. The web server will run on port 8514,
which is the default for the library.

>>> from servercontext import test_server
>>> import urllib
>>> with test_server():
...     conn = urllib.urlopen("http://localhost:8514/README")
...     print conn.getcode()
200

It's easy to adjust the port and location from which files should be served:

>>> with test_server(port=6291, cwd="./testdocs"):
...     conn = urllib.urlopen("http://localhost:6291/hello_world.txt")
...     print conn.getcode()
200


The test server context returns a ServerContext instance you can optionally
use to get information about the server. The server context object contains
the attributes `address`, `port` and `baseurl`. Baseurl is perhaps the most
interesting, as tests can use it as the URL to connect to:

>>> with test_server(port="random") as server:
...     print conn.getcode()
200

For some kinds of tests it's not possible to reuse the same port over and
over. For example, if you're testing how some code deals with unexpectedly
shut down connections, the OS may hold on to the port for a while after the
test concluded. If another test is run just after this, it may fail because
the port isn't available. In these cases it's possible to assign a random
port to the server and access it from the server object:

>>> with test_server(port="random") as server:
...     conn = urllib.urlopen(server.baseurl)
...     print conn.getcode()
200

It's easy to provide other behaviours than just serving files. If a generator
is used as the request handler, any data that it yields will be returned to
the client. This example sends the current timestamp every second for 10
seconds:

>>> import time
>>> def slow_handler(request):
...     for n in xrange(10):
...         yield time.time()
...         time.sleep(1)
...
>>> with test_server(handler=slow_handler, port="random") as server:
...     start = time.time()
...     data = urllib.urlopen(server.baseurl).read()
...     print time.time()-start > 9
True

The request object that is passed as the argument to the handler is an object
implementing the same interface as BaseHTTPRequestHandler. The generator is
free to make calls on it, for instance to set a different status code than
the default 200.

A second alternative is to pass a callable that is not a generator as the
handler. This callable is also passed a request handler, and thus has complete
control over the response. The callable can send data with handler.wfile.write,
set headers and status codes and so on. It's also easy to return malformed
responses to test how code reacts to that .

This example simply returns a 401: not authorized response to all requests:

>>> def not_authorized(request):
...     request.send_error(401)
...
>>> import urllib2
>>> with test_server(handler=not_authorized, port="random") as server:
...     try:
...         conn = urllib2.urlopen(server.baseurl)
...     except urllib2.HTTPError, e:
...         print e.code
401

This example sets some custom headers and returns a string:

>>> def extra_headers_hello(request):
...     request.send_response(200)
...     request.send_header("X-flabaten", "magic")
...     request.end_headers()
...     request.wfile.write("Presto!")
...
>>> with test_server(handler=extra_headers_hello, port="random") as server:
...     conn = urllib2.urlopen(server.baseurl)
...     data = conn.read()
...     header = conn.headers["X-flabaten"]
...     print header, data
magic Presto!


A third possible type of handler is to write a class that inherits from
BaseHTTPRequestHandler. This will be called just as it would if you were
instanciating a server directly

Notes
-----

It's not a hard guarantee that the web server will be closed as soon as the
context is exited. If there is a buggy request handler, one that is hanging,
then the server can't be shut down properly. If a server can't be shut down in
5 seconds after a context was exited, a warning is raised.

Be aware of issues when testing on the same port multiple times. Even though
the library will try to reuse port numbers, this is not always possible.
Especially in cases where the previous connection was terminated abnormally,
there may be a timeout in place before


Todo
----

Supress logs from custom handler
with method twiddle
with trickle
with dying
auto response/headers
dumbhandler - returns contents of a file, no headers or anything. Can be used to
        replay previous requests

Contact
-------

The author of this module is Rune Halvorsen <runefh@gmail.com>. The code and
issue tracker is hosted on http://bitbucket.org/runeh/servercontext/ . Bug
reports, feature requests and patches are welcome.

Changelog
---------

See CHANGELOG in the top level directory of the distribution.

License
-------

See LICENSE in the top level directory of the distribution.

Recent activity

Nothing to see here, move along.

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.