2012
4
Jun

Implementing An HTTP Server for Python 2 and 3

I've done some work recently to get the blogofile plugins branch working under Python 2.6, 2.7 and 3.2 from single codebase. The blogofile serve command runs an HTTP server on localhost to let you check the results of your blogofile build before you deploy it to the waiting world. In Python 3 (in which the plugins branch was initially implemented) the relevant bits of the implementation go like:

import http.server
import threading

class Server(threading.Thread):
    def __init__(self, port, address):
        HandlerClass = BlogofileRequestHandler
        ServerClass = http.server.HTTPServer
        self.httpd = ServerClass(('127.0.0.1', 8080), HandlerClass)

    def run(self):
        self.httpd.serve_forever()

    def shutdown(self):
        self.httpd.shutdown()
        self.httpd.socket.close()

class BlogofileRequestHandler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        http.server.SimpleHTTPRequestHandler.__init__(
            self, *args, **kwargs)

    def translate_path(self, path):
        ...

Some import acrobatics are required to get that to also run under Python 2:

try:
    from http.server import HTTPServer as http_server
except ImportError:
    from SocketServer import TCPServer as http_server
try:
    from http.server import SimpleHTTPRequestHandler \
        as http_handler
except ImportError:
    from SimpleHTTPServer import SimpleHTTPRequestHandler \
        as http_handler
import threading

class Server(threading.Thread):
    def __init__(self, port, address):
        HandlerClass = BlogofileRequestHandler
        ServerClass = http_server
        self.httpd = ServerClass(('127.0.0.1', 8080), HandlerClass)

    def run(self):
        self.httpd.serve_forever()

    def shutdown(self):
        self.httpd.shutdown()
        self.httpd.socket.close()

class BlogofileRequestHandler(http_handler):
    def __init__(self, *args, **kwargs):
        http_handler.__init__(self, *args, **kwargs)

    def translate_path(self, path):
        ...

The six library provides a nice way to clean up the hard to read try:... except ImportError:... blocks:

import threading
from six.moves import SimpleHTTPServer
from six.moves import socketserver

http_server = socketserver.TCPServer
http_handler = SimpleHTTPServer.SimpleHTTPRequestHandler

class Server(threading.Thread):
    def __init__(self, port, address):
        HandlerClass = BlogofileRequestHandler
        ServerClass = http_server
        self.httpd = ServerClass(('127.0.0.1', 8080), HandlerClass)

    def run(self):
        self.httpd.serve_forever()

    def shutdown(self):
        self.httpd.shutdown()
        self.httpd.socket.close()

class BlogofileRequestHandler(http_handler):
    def __init__(self, *args, **kwargs):
        http_handler.__init__(self, *args, **kwargs)

    def translate_path(self, path):
        ...

Note that this implementation steps back from the Python 3 http.server.HTTPServer as the server class to the underlying socketserver.TCPServer (or SocketServer.TCPServer in Python 2).

Read and Post Comments
2012
3
May

Blogofile Improvements

I finally got around to fixing a couple of minor annoyances I have with Blogofile. These fixes apply to the plugins development branch of Blogofile and the Blogofile_blog plugin, but they should be easily backport-able to the Blogofile master branch. I opened pull requests for these changes on Github and I'm happy to report that @EnigmaCurry merged the Blogofile_blog ones within hours! But since Blogofile development and this blog have been languishing for a while, I figured I should write about the changes here.

Using Python 2.7 and 3.2 with the PYTHONWARNINGS environment variable set to default reveals that both Blogofile and the Blogofile_blog plugin raise ResourceWarning exceptions when the blogofile build command is run. Admittedly, this is a really minor issue, but seeing a screen full of tracebacks every time I build my site is annoying, and it can obscure more serious problems. Those warnings are easily silenced by changing the offending open statements to use with statement context managers. The fix for Blogofile is in pull request 119 and the one for Blogofile_blog is in pull request 7.

The blogofile blog create post command creates a file with the extension .markdown by default but Blogofile also supports RST and Textile markup. I use RST and really want my newly created post files to have the extension .rst so that emacs goes to rst-mode automatically when I open a post file for editing. Again, a minor annoyance, and my fix was easily implemented. I chose to add a blog.post.default_markup config option. With blog.post.default_markup = 'rst' in my site's _config.py file my new posts get the .rst extension I want. If blog.post.default_markup is not set the created post file extension defaults to .markdown as before. This feature is in pull request 8.

I really like Blogofile and using it's plugins branch was my spur to get serious about using Python 3 and blogging again. So, I'm really happy to see @EnigmaCurry accepting pull requests again on the project. Hooray!!

Read and Post Comments