Friday, November 15, 2013

A basic WSGI PDF server


By Vasudev Ram

Humpty Dumpty

While browsing the Python Reddit, I saw this post:

Python website tuts that don't use Django, in which the poster (user amnion) asked (excerpted for brevity):

"I'm trying to get a feel for how Python can be used for websites ... I want to familiarize myself with everything under the hood more. ... any resources that show how to make a website without using a framework?"

Many of the answers were interesting (and some had useful links to related reading), but I found this one particularly of interest (user name showed as [deleted] for some reason):


Here's a basic wsgi server (aka "enough to make a website without using a framework").

The variables path and method will contain the path portion of the requested url and the http verb (GET, POST...). "Real" web frameworks are built up around this interface, adding features for url routing, authentication, session management, persistence, etc.

Sans framework, you're free to produce html in any way you see fit. This ten line example runs on python's simple http server, but it will also run on apache/ mod_wsgi, google app engine, or anything supporting wsgi.

"""
basic_wsgi_server.py
Basic WSGI server in Python.
From: http://www.reddit.com/r/Python/comments/1eboql/python_website_tuts_that_dont_use_django/c9z3qyz
"""

from wsgiref.simple_server import make_server

host = 'localhost'
port = 8888

def app(environ, start_response):
    path = environ['PATH_INFO']
    method = environ['REQUEST_METHOD']

    response = 'This is the page for "{}"'.format(path)

    start_response('200 OK', [('Content-type', 'text/html')])
    return [response]

make_server(host, port, app).serve_forever()

I tried the above code (I named it basic_wsgi_server.py), and it worked.

Then, out of interest, I thought of modifying that basic WSGI server to make it serve PDF, using my xtopdf toolkit for PDF creation. Constant PDF content, though, not dynamically generated stuff. So here is the code of basic_wsgi_pdf_server.py:

# Basic WSGI PDF server in Python.
# Adapted from:

# http://www.reddit.com/r/Python/comments/1eboql/python_website_tuts_that_dont_use_django/c9z3qyz

from debug1 import debug1
from PDFWriter import PDFWriter
from wsgiref.simple_server import make_server

host = 'localhost'
port = 8888

def app(environ, start_response):
    debug1("Entered app")
    path = environ['PATH_INFO']
    method = environ['REQUEST_METHOD']
    print "path:", path
    print "method:", method

    #response = 'This is the page for "{}"'.format(path)

    lines = [
            "Jack and Jill went up the hill",
            "Humpty Dumpty sat on a wall,",
            "'You are old, Father William,' the young man said,",
            "Master of all masters"
            ]

    debug1("Before creating PDFWriter and setting its fields")
    pdf_filename = "Nursery-rhymes-and-stories.pdf"
    pw = PDFWriter(pdf_filename)
    pw.setFont("Courier", 12)
    pw.setHeader("Excerpts from nursery rhymes and stories")
    pw.setFooter("Generated by xtopdf and basic_wsgi_pdf_server")

    debug1("Before for loop to write the lines")
    for line in lines:
        pw.writeLine(line)
        pw.writeLine(" ")
    pw.close()

    debug1("Before with statement to read file contents")
    with open(pdf_filename, "rb") as fil:
        response = fil.read()

    #start_response('200 OK', [('Content-type', 'text/html')])
    debug1("Before start_response")
    start_response('200 OK', [('Content-type', 'application/pdf')])
    debug1("Before returning response")
    return [response]

debug1("Before make_server and serve_forever()")
make_server(host, port, app).serve_forever()

Ran it with:

python basic_wsgi_pdf_server.py

Note that I added a few calls to a function called debug1, which is a slightly improved version of the debugging function I mentioned in this post:

A simple Python debugging function

, in order to proactively get information about any problem with the program. But it turned out to have no issues - worked straightaway.

Here is a screenshot of the output:


References:

[1] Jack and Jill
[2] Humpty Dumpty
[3] Father William
[4] Master of all masters

Isn't my test data better than Lorem ipsum? :-)

- Vasudev Ram - Dancing Bison Enterprises





O'Reilly 50% Ebook Deal of the Day

2 comments:

Chris Arndt said...

Please note that in Python 3 the returned iterable needs to yield bytes objects, not (unicode) strings, so to make the example work under Python 2 and 3, you need to encode the response string and set the encoding in the Content-type header:

encoding = 'utf-8'
headers = [('Content-type', 'text/plain; charset={}'.format( encoding))]
response = ''This is the page for "{}"'.format(path)

start_response(status, headers)

return [response.encode(encoding)]

Vasudev Ram said...

Thanks for pointing that out.