Skip to content

Instantly share code, notes, and snippets.

@lehmannro
Created July 14, 2011 05:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lehmannro/1082003 to your computer and use it in GitHub Desktop.
Save lehmannro/1082003 to your computer and use it in GitHub Desktop.
Simple HTTP Server in 512 bytes
#!/usr/bin/env python2.6
"""
Task: implement a rudimentary HTTP server in Python. It needs to support the
GET method to serve static HTML files. Requests to ``/`` shall be
automatically resolved to ``/index.html``. All invalid requests shall be
answered with a ``400 Bad Request``. Queries on a non-existing file shall
terminate in a ``404 Not Found``. The serving port shall be passed through the
command line. The server must be threaded. Use the socket APIs.
This implementation is *just a single Python expression,* and only 512 bytes.
My favorite hacks:
* ``iter(s.accept,s.listen(8))`` actually uses the less known form of the
``iter()`` builtin: ``iter(callable, sentinel)``, where *the callable is
called until it returns the sentinel.* The ``socket.listen()`` method
handily returns ``None``, which means we will loop forever. I consume this
iterator with ``map()``.
* ``hasattr(type(k,(),{k:property(lambda x:open(d))})(),k)`` is a very
special beast of its own -- and already stripped down to only the relevant
parts! ``k`` is some string (any string basically, we don't really care for
this part of the code) and ``d`` is the file name we want to try to open.
The fun part about ``hasattr`` is that it was meant to do the following: try
to access the requested attribute. If it does not exist the corresponding
object will throw an ``AttributeError`` and the object does not have the
attribute in question. There is a long-standing quirk with its
implementation though: it catches *all* exceptions (which is basically fine
because you don't expect ``hasattr`` to ever raise/propagate an exception).
If the object now *computes a value* when accessed, eg. through a property,
we can handily check with ``hasattr()`` if that succeeded.
In the actual implementation it goes a step further: it has a *side effect,*
mutating another object *z* in case everything went fine.
* ``(s + ' '*2).split(' ', 2)`` is a nifty bit of code to make ``str.split``
always succeed. By default, ``s.split(' ', 2)`` would return *at most* three
strings delimited by a space -- but a list of one or two strings is fine too.
If we deliberately pad the string beforehand no such error can happen (but we
have to deal with the last element containing bogus trailing spaces
throughout the other parts of the code).
* ``~0`` is a neat obfuscation for ``-1``.
* Python's parser is actually quite forgiving in the way it requires
whitespace. Word boundaries are usually okay for terminating keywords and
identifiers, too, which allows squeezing the code quite a bit -- take
``d.strip("/")or"index.html"`` for example.
* Oh yeah, ``thread.start_new`` is a deprecated and undocumented shorthand for
``thread.start_new_thread``.
* ``socket.makefile()`` saves me some work when reading the first line from a
socket.
* A hack I did ultimately reject, in favor of spec conformance, is HTTP version
number trimming: Firefox and Chrome do accept ``HTTP/1.`` as a valid number,
saving another precious byte!
Okay, now for those variables:
``s``
The listening socket (bound through a leaking list comprehension).
``c``
Oh man, this is a bit tricky. It is generally bound to the recently
accepted (socket, addr) pair returned by ``socket.accept()``. In the
accepting *thread* it is immediately rebound to only the *socket.*
``z``
This hack I actually like very much. It is the response! Now by default, it
contains only the empty string. If reading the file and all that jazz
succeeds, the ``Content-Length`` header and the file contents are appended to
that list (see ``z.extend()``, around byte 271). When finally writing out
the response only the last element -- the contents or, if appending did not
happen for any reason, the empty string -- is sent.
``q``
The string ``"HTTP/1.`` to save some bytes when checking for ``HTTP/1.0`` and
``HTTP/1.1`` and writing out the response (``HTTP/1.1 200 OK``).
``y``
The file's contents.
``k``
A space. It is used as a dummy string and for ``str.split``ting the
request line.
``v``, ``d``, and ``p``
The parsed request: verb, queried document, and protocol version.
"""
[s.bind(('',int(__import__('sys').argv[1])))for(s)
in[__import__('socket').socket()]],map(lambda c,k=
' ':__import__('thread').start_new(lambda c,z=[""]
,q="HTTP/1.":[[c.send(q+"1 "+["400 Bad Request\n",[
"404 Not Found\n","200 OK\n"][hasattr(type(k,(),{k:
property(lambda x:z.extend("Content-Length: %d\n\n"
%len(y)+y for(y)in[open(d.strip("/")or"index.html")
.read()]))})(),k)]+z[~0]][v=='GET'and p.strip()in(q
+"1",q+"0")])for(v,d,p)in[(c.makefile().readline()+
k*2).split(k,2)]]],(c[0],)),iter(s.accept,s.listen(
8)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment