While I was coding lnkckr, I needed to test for HTTP status code since its a library for link checking. I could use some common known and big and considerably stable website like Google. But that would never be reliable when running unittest with those.

A quick search I found is a great website: httpstat.us. I immediately used it in doctest at first, then I realized that its very slow, even with just a couple of checks. A single request to httpstat.us using curl took almost one second to finish. Too slow for testing, you dont want to wait for minutes for tons of tests.

I wrote a quick code as shown below, definitely inspired by httpstat.us, you can also find it on Gist.

#!/usr/bin/env python
# Simple HTTP Server which echos status code indicated by request path
#
# Copyright (C) 2013 by Yu-Jie Lin
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Gist: https://gist.github.com/4228803
# Code from lnkckr: https://bitbucket.org/livibetter/lnkckr
# Inspired by: http://httpstat.us


try:
  from http.server import BaseHTTPRequestHandler, HTTPServer
  from urllib.parse import unquote
except ImportError:
  from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
  from urlparse import unquote
import re

HOST = 'localhost'
PORT = 8000
H = 'http://%s:%s/' % (HOST, PORT)


class Handler(BaseHTTPRequestHandler):

  RE_CODE = re.compile(r'/(\d{3})')

  def do_GET(self):

    headers = {}
    body = None
    m = self.RE_CODE.match(self.path)
    code = int(m.group(1)) if m else None
    if self.path == '/' or code == 200:
      code = 200
    elif self.path == '/loop':
      code = 302
      headers['Location'] = '/loop'
    elif code in (301, 302):
      headers['Location'] = H
    else:
      code = 404
    if 'Location' not in headers:
      headers['Content-Type'] = 'text/html'
      body = unquote(self.headers.get('X-Echo'))

    self.send_response(code)
    for k, v in headers.items():
      self.send_header(k, v)
    self.end_headers()
    if body:
      self.wfile.write(body.encode('utf8'))

  def do_HEAD(self):

    self.do_GET()


def main():

  try:
    httpd = HTTPServer((HOST, PORT), Handler)
    httpd.serve_forever()
  except KeyboardInterrupt:
    pass


if __name__ == '__main__':
  main()

Example command, which returns "hello\nworld\n" from the server:

$ time curl -o - -v --header "X-Echo:hello%0aworld%0a" -L localhost:8000/200
* About to connect() to localhost port 8000 (#0)
*   Trying 127.0.0.1...
* connected
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /200 HTTP/1.1
> User-Agent: curl/7.26.0
> Host: localhost:8000
> Accept: */*
> X-Echo:hello%0aworld%0a
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: BaseHTTP/0.6 Python/3.2.3
< Date: Wed, 13 Feb 2013 16:44:31 GMT
< Content-Type: text/html
<
hello
world
* Closing connection #0

real    0m0.014s
user    0m0.006s
sys     0m0.004s

The code is very simple and probably not follow the HTTP specification in some situations. I want to post it as it could be a starting point if you need to develop your testing server which only respond simple stuff, it can run with Python 2 or Python 3. I use similar code in my unittest, running in thread, and its much faster, 0.01s for a request.

By the way, Python has a nice SimpleHTTPServer module, probably one of my favorite modules, it always comes in handy whenever I need to serve files from a directory. I even have a Bash alias set up for it.