Serving Static Files and Routing with WSGI
You might have noticed, WSGI is awkward sometimes, lets fix that.

The Elephants in the Room

The World Wide Web languages (HTML/CSS/JavaScript) are all based around files. CSS definitions are supposed to be in an external file and there are HTML tags specifically for including CSS/JavaScript from an external file. However, WSGI breaks the correspondence between URLs and files (more accurately, it abstracts it). You hopefully noticed this problem when you did the last task from last week’s prac, you were not able to put your CSS in a separate file.

This all makes sense when you consider that we use WSGI to free ourselves from the one-to-one correspondence between URLs and files. However, we normally want to serve some files the simple way. Without this ability, every CSS file and every JavaScript file we want to include in our project needs a branch in our application function and a bit of python code to create it. This is a massive pain and a complete waste of time. It is a disaster for code complexity and makes the application brittle to changes. We need an alternative.

Three Separate Problems

There are three clear problems with “raw” WSGI which should be clear by now

Solutions

Routing

The problem of using multiple URLs is served with a simple snippet of python, we build a map of strings to functions and tell python to check each element in this map against the current URL to decide which function to run. By using the fnmatch module, we can use glob characters in the strings to match multiple paths.

routes = [('/static/*',      static_files.make_static_application('/static/', 'static', not_found)),
          ('/one',          one),
          ('/two',          two),
          ('/',             index),
         ]

def application(environ, start_response):
    for path, app in routes: 
        if fnmatch.fnmatch(environ['PATH_INFO'], path): 
            return app(environ, start_response)
    return not_found(environ, start_response)

You will notice that we have put a fail-over call to not_found if none of the paths match, this not_found function must be included

def not_found(environ, start_response):
    start_response('404 Not Found', [('content-type','text/html')])
    return ["""<html><h1>Page not Found</h1><p>
               That page is unknown. Return to 
               the <a href="/">home page</a></p>
               </html>""",]  

Serving Static Files

There no reason a WSGI program can’t act like it is serving static files. All it needs to do it look at the path info and fetch a file with that name.

Remember however, that the WSGI program only wants to serve some files in this way. In general we want it to act in a dynamic fashion so we have control over the request/response cycle. The common solution is to create a special directory called static into which you put files you would like to have served statically and to tell your WSGI program to serve these file directly. We have already done this in the above snippet when we told the application to route all request to static/* to the make_file_static application. This is a fairly simple piece of python code which you will need to include in your application, but you don’t need to understand it or write it yourself. It is available for you to use in any of your submissions in this course

Adding data to static files

Very often we have a file which is almost completely static but has one or two bits of python data in them. It is incredibly awkward to have to put the whole HTML file in a python string to achieve this. Instead, you can include templating in your application. You have available to you a python application which will replace any name preceeded by a % (i.e %foo or %bar) with a corresponding value from a dictionary. To use it, you call load_page passing in a dictionary which maps names to strings, a path to the file containing the %name parts, and which returns the contents of the file with all the names replaced by their values. For example, we can put the following content in a file called one.html

<html>
  <head>
    <title>One</title>
    <link rel="stylesheet" type="text/css" href="static/style.css"/>
  </head>

  <body>
    %content
  </body>
</html>

And use that file thusly

def one(environ, start_response):
    start_response('200 OK', [('content-type','text/html')])
    return [templating.load_page({'content': 'The Content Of Page One'}, "one.html"),]

Conclusion

I have provided a complete working WSGI program using the techniques above, I strongly recommend you get this WSGI program working and use it as the basis of future prac and assignment work.