| Development Status :: 3 - Alpha |
| Intended Audience :: Developers |
| License :: OSI Approved :: GNU General Public License (GPL) |
| Operating System :: OS Independent |
| Programming Language :: Python |
| Topic :: Internet :: WWW/HTTP :: WSGI |
Download: Morsel-0.1.tar.gz (Development Status :: 3 - Alpha)
The Web Server Gateway Interface (WSGI) is a specification for web servers and application servers to communicate with web applications. It is described in the PEP 333 Python standard.
For a moment think of a specification for connecting black boxes together. Some boxes have a plug, some have a socket, and some have a plug on one side and a socket on the other. This helps to connect lots of black boxes together and create chains.
+-------+ +-------+ +-------+ +-------+
| | | | | | | |
| Box 1 ===> > Box 2 ===> > Box 3 ===> > Box 4 |
| | | | | | | |
+-------+ +-------+ +-------+ +-------+
In the web development world, one may connect such software "boxes" to create full-blown web applications:
+------------+ +--------------------+ +----------------+ +-----+
| | | | | | | |
| Web Server ===> > Session management ===> > Authentication ===> > App |
| | | | | | | |
+------------+ +--------------------+ +----------------+ +-----+
WSGI then, is a specification for such box connectors. Think of the specification of how a web server will invoke a web application as a plug and the specification of how the web application will respond to the web server as a socket. Of course, there can be boxes that have a plug on one side and a socket on the other, thus appearing as web servers on one side and as applications on the other; these boxes we call "middleware."
WSGI is useful because people can create software boxes that perform a particular task and connect to other people's boxes. For example, some developers might create session management middleware, others authentication middleware, logging and reporting middleware, thus creating an ecosystem of these boxes which can be plugged together until a desired functionality is achieved. A web developer can mix, match, and create chains of these software boxes to create end-to-end web applications. If this doesn't sound like a useful thing, you're probably a Django developer.
Morsel is a WSGI web application project generator. It creates a minimal project directory and provides a few files that contain the code for gluing some basic WSGI components together.
Morsel is useful mainly for two reasons:
Use: python setup.py install and it will get installed in a directory in your
sys-path.
Morsel has the following dependencies:
You have to issue: python -m morsel/morsel -n <project
name> to create a WSGI web application project.
The "-m" option searches sys.path for the named module and runs the
corresponding .py file as a script.
The web application project Morsel generates is a WSGI application and can be
served by any WSGI server. By default, the Paste HTTP server is used to serve
the web application on port 9090. For this, go to the web application directory
and run python start.py.
Alternatively, you may use any WSGI aware web server like CherryPy, or Apache with mod_wsgi.
An HTTP request hits the Paste HTTP server and it invokes your WSGI
application. The first point of contact is the Routes middleware which
according to the routes (mappings of URLs to controllers) you have specified
dissects the URL and places the relevant information in the data structure
that is passed around from one component to the other (it is a dict
conventionally called environ). The next component in line
is the WSGI Dispatcher
which reads the information from Routes and goes and instantiates
the relevant controller to respond to the request. Schematically it looks like
this:
---------
|------------------- model/ |
| | |
|------------------- templates/ |
| | | - MVC style directory layout
| | |
| | |
controller1 controller2 |
| | ---------
`------------|
|
| ----
WSGI |
Dispatcher |
| |--------------------- WSGI Middleware Stack
| |
Routes |
| ----
|
Paste HTTPServer (or any other server you use)
The project layouts created are like the following:
<project name>/
|-- config
| |-- __init__.py
| `-- routing.py
|-- controllers
| |-- __init__.py
| `-- default.py
|-- lib
| |-- __init__.py
| `-- controller.py
|-- middleware
| `-- __init__.py
|-- model
| `-- __init__.py
|-- public
| `-- static
| `-- css
| `-- screen.css
|-- start.py
`-- templates
config is used for configuration files. The routing.py file under this
directory is used for the definition of some basic routes, i.e., mappings of
requested URLs to controllers. Instances of the controller classes or their
methods are WSGI applications. By default, Morsel creates a default
controller (in controllers/default.py) and sets a route for the root URL to
invoke the index() method of the default controller.
controllers is used for storing the application controllers. Morsel creates a
default controller, DefaultController and places it in default.py under this
directory. You are not limited to use only this directory (the WSGI
Dispatcher middleware accepts multiple controller directories).
lib contains library files useful for the application. By default it contains
controller.py, which defines a basic controller, Controller. This may be used
by your controllers as a base class (DefaultController inherits from it) and
it contains a useful method for rendering Mako templates
(_render_response()).
middleware is where the various WSGI middlware components are connected. By default, in
__init__.py, RoutesMiddleware and
WSGI Dispatcher middleware are wrapped
together to provide mapping of URLs to controllers/actions. For more
information on these two WSGI middleware components please refer to
Routes and WSGI
Dispatcher.
If you need to connect more WSGI components for
additional application functionality, this is the place to do so.
model may be used to store data representations such as ORM code.
public may be used for public files.
public/static may be used for static files such as CSS and JavaScript
files.
templates may be used to store template files. By default Morsel creates a
default.mako template file which is used by the default controller.
start.py is the file to invoke to start the Paste HTTP server at port
9090.
To create a controller you can either create a class for a callable object
which is a WSGI application, or a classwith methods which are WSGI applications
either by writing your own or inheriting from the Morsel Controller
class. Inheriting from Morsel's Controller class
provides a render_response method which is useful for rendering
Mako templates as part of your request response. Examples of all cases
follow:
# Class for a callable object.
class MyController(object):
"""A basic WSGI Controller"""
def __init__(self):
pass
def __call__(self, environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']
# Class with methods that are WSGI applications.
class MyControllerWithMethods(object):
def index(self, environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']
# Inheriting from Morsel's Controller class.
# Of course you can inherit from Controller without having to use
# render_response.
from lib.controller import Controller
class MyController(Controller):
def index(self, environ, start_response):
response = self._render_response("my_controller.mako",
{'some_template_var': 'some_value'})
return response(environ, start_response)
Morsel WSGI application projects use
Routes and
WSGI Dispatcher for URL parsing
and dispatching. As an example, assume that you have created a file called
controllers/hello.py in which you define a controller class called
HelloController. HelloController has an index() method which is a WSGI
application. The steps to route requests to this controller are as
follows:
<project name>/config/routing.py specify a URL, the controller to handle
it and the action (controller method to invoke). In this particular case:
mapper.connect('/hello', controller='hello', action='index'). (For more
mapper options please consult the Routes manual.) RoutesMiddleware will
create an environ['wsgiorg.routing_args'] entry of ((), {'controller':
'hello', 'action': 'index'}).
index() method passing it environ and start_response. Of course,
your controller class is called HelloController and so you need to specify a
controller class formating function in your dispatcher that capitalizes the
first controller letter so "hello" becomes "Hello" and a classname pattern so
that "Hello" matches HelloController.
The base controller class Controller has a render_response method that takes the
filename of a Mako template
and dictionary of arguments to pass to it. If your
controller classes inherit from it, all you need to do is call this method. For
example:
from lib.controller import Controller
class DefaultController(Controller):
def index(self, environ, start_response):
response = self._render_response("default.mako", {'title': 'webapp3'})
return response(environ, start_response)
You can either parse the GET variables directly from environ['QUERY_STRING'] or
import parse_formvars from paste.request in your controller and call it with
the arguments: qs = parse_formvars(environ,
include_get_vars=True).
If for example the quersy string was "var1=hello&var2=world"
qs will be a MultiDict
object MultiDict([('var1', 'hello'), ('var2', 'world')]).
You may use paste.request.parse_formvars as described in previous question.
For session support you may use the Beaker
session middleware. To do so, in
<project name>/middleware/__init__.py add the following:
# At imports
from beaker.session import SessionMiddleware
# ...
# After app = RoutesMiddleware(dispatcher, mapper)
app = SessionMiddleware(app)
In your controller you may then use:
session = environ['beaker.session']
if not session.has_key('value'):
session['value'] = 0
session['value'] += 1
session.save()
Paste comes to resque again:
# At imports
from paste.gzipper import make_gzip_middleware
#...
# Some point after app = RoutesMiddleware(dispatcher, mapper)
app = make_gzip_middleware(app)
You may use Alchemyware, an SQLAlchemy middleware.
In <project name>/middleware/__init__.py you may specify URLs that map to
certain directories like <project name>/public/static for static file
serving. For example, in __init__.py:
dispatcher = WSGIDispatcher(controller_dirs = ["controllers"])
dispatcher.add_static_dir('/javascript/', 'public/static/javascript/')
In <project name>/middleware/__init__.py you may specify more controller
directories when wrapping-in the WSGI Dispatcher:
dispatcher = WSGIDispatcher(controller_dirs = ["controllers", "other"])