Difference between revisions of "Mulib/Documentation"
Line 218: | Line 218: | ||
=== Content Negotiation Details === | === Content Negotiation Details === | ||
Once the leaf node has been reached (indicated by setting the WSGI environment variable 'mu.remaining_segments' to < | Once the leaf node has been reached (indicated by setting the WSGI environment variable 'mu.remaining_segments' to <code>()</code>), the Accept header is parsed and iterated. For each content type a handler is selected from 'mu.handler_adapters'. If a handler is available, that handler is the final WSGI application which will render the response. | ||
=== Parsers === | === Parsers === |
Revision as of 17:10, 24 January 1931
Mulib Documentation
Mulib is a Python web application library. Mulib provides various functions and classes which implement WSGI applications for various Python types and MIME content types. The two main features of Mulib are object traversal over arbitrary Python types (such as dict, list, and string) using an extensible handler adapter registry, and content negotiation to arbitrary MIME content types (such as text/html and application/json) using the same extensible handler adapter registry.
Basics
To use Mulib in a wsgi environment, there are two different options. mulib.mu.Publisher(any_obj) is a class which takes any Python object as it's only argument, whose instances are WSGI applications which will traverse over the object graph rooted in that object. mulib.mu.delegate(env, start_response, any_obj, path_segments=None) is a function which takes a WSGI request, any Python object, and a tuple or list of path segments to traverse over the object. If path_segments is ommitted, the path in the WSGI environment PATH_INFO variable will be split on '/' and used for traversal.
Perhaps some examples will help to clarify. To create a WSGI application object which responds to two urls, 'hello' and 'foo', the following would be used:
Publisher example
from eventlet import api, wsgi from mulib import mu application = mu.Publisher({'hello': 'world', 'foo': 'bar'}) wsgi.server(api.tcp_listener(('', 8080)), application)
eventlet.wsgi is used as an example wsgi server, but any wsgi server may be used to serve this WSGI application. The application will respond to two urls: the url '/hello', to which it will reply 'world', and the url '/foo', to which it will reply 'bar'. This is because the handler registered for type dict knows show to turn url segments into accesses of dictionary keys. Note that the url '/foo/bar' would not be valid because the handler for type str does not allow child access since strings do not have conceptual children.
This is what is meant by object traversal; we start with a root Python object and look for a handler based on the object's type. We then call this handler, which may call other handlers if there are more path segments to consume, until we reach the leaf node.
Delegate Example
Another usage of Mulib would be inside an existing WSGI application. In this case, the mulib.mu.delegate function can be quite useful.
from eventlet import api, wsgi from mulib import mu def application(env, start_response): return mu.delegate(env, start_response, "hello, world", ()) wsgi.server(api.tcp_listener(('', 8080)), application)
This will result in an application which responds to ALL urls with "hello, world". This is because we explicitly pass () as the path segments, which means mulib will ignore PATH_INFO entirely and assume the string "hello, world" is the leaf node immediately.
Content Negotiation Example
Here is another more complicated example, which highlights the content negotiation feature of mulib:
from eventlet import api, wsgi from mulib import mu def application(env, start_response): return mu.delegate(env, start_response, {"hello": "world"}, ()) wsgi.server(api.tcp_listener(('', 8080)), application)
In this case, we delegate to a Python dictionary, but we still don't pass any path segments. This application will also respond to any URL, but the actual bytes which are sent in the response will be based on the result of content negotiation. content negotiation is a process where the HTTP client sends an Accept header containing a list of acceptable mime types along with weights indicating the browser's preferences. Mulib will look at the Python type of the object being traversed (in this case, a dict) and determine which handler to pull out of the adapter registry based on the mime type of the handler.
In the case of dict, mulib has several handlers registered for different mime types. application/json, text/html, and text/plain. If the browser specifies application/json as the content type, the result will be
{"hello": "world"}
. However, if the browser specifies text/html as the content type, the result will be
<html><body><dl><dt>hello</dt><dd>world</dd></dl></body></html>
.
Subclassing mu.Handler
Sometimes, it is nice to write Python classes which implement a REST interface. mulib.mu.Handler provides an easy way to do this. There are three things you need to know about when subclassing mu.Handler.
First, by default mu.Handler will allow traversal to any attributes of self which do not start with an underscore (_).
Second, once an instance of mu.Handler has been selected as the leaf node, the REQUEST_METHOD is examined to determine which method to call on the Handler instance. If the REQUEST_METHOD is GET, _handle_GET(env, start_response) will be called. If the REQUEST_METHOD is PUT, _handle_PUT(env, start_response) will be called, and so on.
Implementing REST Example
import os.path from eventlet import api, wsgi from mulib import mu def relative(filename): return os.path.join(os.path.split(os.path.abspath(__file__))[0], filename) class Baz(mu.Handler): def _handle_GET(self, env, start_response): start_response('200 OK', [('Content-type', 'text/plain')]) return ["Baz!\n"] class Bar(mu.Handler): index = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Url Hierarchy Example</title> </head> <body> <a href="..">up</a> <h1>Bar!</h1> <a href="baz">baz</a> </body> </html>""" baz = Baz() class Foo(mu.Handler): def _handle_GET(self, env, start_response): start_response('200 OK', [('Content-type', 'text/plain')]) return ["Foo!\n"] class Root(mu.Handler): index = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Url Hierarchy Example</title> </head> <body> <a href="foo">foo</a> <a href="bar/">bar</a> </body> </html>""" foo = Foo() bar = Bar() wsgi.server( api.tcp_listener(('', 8080)), ## Socket to listen on mu.Publisher(Root())) ## wsgi app to run
Customizing Handler traversal example
Finally, if you wish to customize traversal over your Handler subclass, override the _traverse(env, start_response, segments) method. Although the traverse method has a signature which does not exactly match the WSGI application signature, it's semantics are the same. If there are additional unhandled segments below what your Handler wants to handle, it's convenient to call the delegate function to dispatch the request to another handler.
"""This is a port of the web.py example from the homepage. http://webpy.org/ Since mulib is now pure wsgi, it doesn't have nice request and response abstractions. This example would be a lot nicer if it used WebOb, a wsgi request/response abstraction. """ import cgi from eventlet import api, wsgi from mulib import mu, handlers class HelloServer(handlers.Handler): def _traverse(self, env, start_response, segments): return mu.delegate( env, start_response, Hello('/'.join(segments)), ## This object will handle the request. ()) ## This is a tuple of the unhandled segments. ## We handled them all, so this is an empty tuple. class Hello(handlers.Handler): def __init__(self, who): if not who: who = 'world' self.who = who def _handle_GET(self, env, start_response): times = cgi.parse_qs(env['QUERY_STRING']).get('times', [1])[0] start_response('200 OK', [('Content-type', 'text/plain')]) for c in range(int(times)): yield 'Hello, %s!\n' % self.who wsgi.server( api.tcp_listener(('', 8080)), ## Socket to listen on mu.Publisher(HelloServer())) ## wsgi app to run
Handler Adapters
A handler adapter is a WSGI application callable which is associated with a Python type using the mu.set_handler(typ, callable, content_type='text/plain') function. Whenever mulib is traversing an object of type typ, or an object of type typ has been selected as the leaf node and content negotiation has selected the content type text/plain, the WSGI application callable will be called.
Handler Adapters Example
import os.path from eventlet import api, wsgi from mulib import mu class MyClass(object): def __init__(self, name): self.name = name def handle_myclass(env, start_response): instance, path = mu.get_current(env) if env['PATH_INFO'] != '/': return mu.not_found(env, start_response) start_response('200 OK', [('Content-type', 'text/plain')]) return ["Hello, %s!" % instance.name] mu.set_handler(MyClass, handle_myclass) wsgi.server( api.tcp_listener(('', 8080)), ## Socket to listen on mu.Publisher(MyClass('World')) ## wsgi app to run
Object Traversal Details
First, the environment variable 'mu.remaining_segments' is calculated by splitting the url segments from PATH_INFO. Then, a handler for the type of the mu environment variable 'mu.current_node' is selected from the mu.adapters.Handlers instance from the environment variable 'mu.handler_adapters' and called, until the wsgi environment variable 'mu.remanining_segments' is (), indicating that we have reached the leaf node. Once we have reached the leaf node, content negotiation is performed to actually render the response to the request.
Content Negotiation Details
Once the leaf node has been reached (indicated by setting the WSGI environment variable 'mu.remaining_segments' to ()
), the Accept header is parsed and iterated. For each content type a handler is selected from 'mu.handler_adapters'. If a handler is available, that handler is the final WSGI application which will render the response.
Parsers
The mu.parsers WSGI environment variable should be set to a dictionary of MIME type to callable(str) function. If this environment variable is not set, the default parsers will be used (parsers for application/x-www-form-urlenpred and application/json). The function mu.parsed_body can be used to read the request body and parse it based on the Content-Type and the parsers dictionary.
The implementations of the handlers for dict and list use parsed_body to implement PUT, allowing dictionary and list values to be set to arbitrary objects.
Parsers Example
from mulib import mu from eventlet import api, wsgi root = {'hello':'Hello World', 'contacts': {}} wsgi.server(api.tcp_listener(('', 5000)), mu.SiteMap(root))
With the above server running, execute the following curl commands in a shell:
curl -X PUT -H "Content-type: application/json" -d '{"Chuck" : { "Name": "Chuck", "Age":99, "Languages": ["Python", "C++"]}}' http://localhost:5000/contacts
Then, visiting the url http://localhost:5000/contacts will display the newly-uploaded data either in JSON or HTML formats.
Glossary
object traversal: The process of recursively selecting handlers based on the types of the objects in the object graph, starting at the root node and traversing over the path segments split from PATH_INFO.
content negotiation: The process of selecting a handler based on the Accept header given by the browser once object traversal has been completed.
leaf node: The object which was selected as the target of rendering for a WSGI request. content negotiation will be performed and the negotiated handler will be called to finalize and render the response.