Skip to content

dget/brubeck.io

 
 

Repository files navigation

<html>
  <head>
    <title>Brubeck: a Mongrel2 handler</title>
    <link href='https://fonts.googleapis.com/css?family=Josefin+Slab' rel='stylesheet' type='text/css'>
    <link rel=stylesheet type="text/css" href="media/style.css"> 
    <link rel=stylesheet type="text/css" href="media/prettify.css"> 
    <meta name="viewport" content="width=600;" /> 
    <script type="text/javascript" src="js/prettify.js"></script>
  </head>

  <body onload="prettyPrint()">
    <div id="title">
      <a href="index.html"><img class="logo" src="img/logo.png" /></a>
    </div>

    <p class="links"><a href="readme.html">Readme</a> &nbsp;|&nbsp; <a href="design.html">Design</a> &nbsp;|&nbsp; <a href="installing.html">Installing</a> &nbsp;|&nbsp; <a href="demos.html">Demos</a> &nbsp;|&nbsp; <a href="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/j2labs/brubeck">Code</a></p>

<h1>What Is Brubeck?</h1>

<p><strong>Brubeck</strong> is a flexible Python web framework that aims to make the process of building scalable web services easy.</p>

<p>The Brubeck model resembles what companies build when they operate at large scale, but working with it will feel like what you're used to from other frameworks.</p>

<ul>
<li>No confusing callbacks</li>
<li>No database opinions</li>
<li>Built-in distributed load balancing</li>
</ul>

<h1>Features</h1>

<p>Brubeck gets by with a little help from its friends:</p>

<ul>
<li><a href="https://mongrel2.org">Mongrel2</a>: lean &amp; fast, asynchronous web serving</li>
<li><a href="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/j2labs/dictshield">DictShield</a>: data modeling &amp; validation with no database opinions</li>
<li><a href="https://zeromq.org">ZeroMQ</a>: fast messaging &amp; supports most languages</li>
<li><a href="https://gevent.org">Gevent</a>: non-blocking I/O, coroutines &amp; implicit scheduling, mostly in C.</li>
<li><a href="https://eventlet.net">Eventlet</a>: like gevent but written mostly in Python.</li>
</ul>

<p>Please also see this completely unscientific comparison of Brubeck and Tornado:</p>

<ul>
<li><a href="https://gist.github.com/2252671">500 concurrent connections for 10 seconds</a></li>
</ul>

<h1>Examples</h1>

<p>Building a Brubeck app is essentially to write a MessageHander, open a connection to Mongrel2 and call <code>run()</code> on a Brubeck instance.</p>

<h2>Hello World</h2>

<p>This is a whole Brubeck application. </p>

<pre><code class="prettyprint">class DemoHandler(WebMessageHandler):
    def get(self):
        self.set_body('Hello world')
        return self.render()

urls = [(r'^/', DemoHandler)]
mongrel2_pair = ('ipc:https://127.0.0.1:9999', 'ipc:https://127.0.0.1:9998')

app = Brubeck(mongrel2_pair=mongrel2_pair,
              handler_tuples=urls)
app.run()
</code></pre>

<h2>Complete Examples</h2>

<p><strong>Listsurf</strong> is a simple to way to save links. Yeah... another delicious clone!</p>

<p>It serves as a basic demonstration of what a complete site looks like when you build with Brubeck. It has authentication with secure cookies, offers a JSON API, uses <a href="https://jinja.pocoo.org/">Jinja2</a> for templating, <a href="https://eventlet.net">Eventlet</a> for coroutines and stores data in <a href="https://mongodb.org">MongoDB</a>.</p>

<ul>
<li><a href="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/j2labs/listsurf">Listsurf Code</a></li>
</ul>

<p><strong>Readify</strong> is a more elaborate form of Listsurf.</p>

<p>User's have profiles, you can mark things as liked, archived (out of your stream, kept) or you can delete them. The links can also be tagged for easy finding. This project also splits the API out from the Web system into two separte processes, each reading from a single Mongrel2.</p>

<ul>
<li><a href="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/j2labs/readify">Readify Code</a></li>
</ul>

<p><strong>SpotiChat</strong> is a chat app for spotify user.</p>

<p>SpotiChat provides chat for users listening to the same song with Spotify. The chat is handled via request handlers that go to sleep until incoming messages need to be distributed to connect clients. The messages are backed by <a href="https://redis.io">Redis</a> too.</p>

<ul>
<li><a href="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/sethmurphy/SpotiChat-Server">SpotiChat Code</a></li>
</ul>

<p><strong>no.js</strong> is a javascript-free chat system.</p>

<p>It works by using the old META Refresh trick, combined with long-polling. It even works in IE4! </p>

<ul>
<li><a href="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/talos/no.js">No.js Code</a></li>
</ul>

<h1>Closer Look At The Code</h1>

<p>In this section we'll discuss writing a request handler, adding user authentication and rendering pages with templates.</p>

<h2>Handling Requests</h2>

<p>The framework can be used for different requirements. It can be lean and lightweight for high throughput or you can fatten it up and use it for rendering pages in a database backed CMS.</p>

<p>The general architecture of the system is to map requests for a specific URL to some <a href="https://docs.python.org/library/functions.html#callable">callable</a> for processing the request. The configuration attempts to match handlers to URL's by inspecting a list of <code>(url pattern, callable)</code> tuples. First regex to match provides the callable.</p>

<p>Some people like to use classes as handlers. Some folks prefer to use functions. Brubeck supports both.</p>

<h3>MessageHandler Classes</h3>

<p>When a class model is used, the class will be instantiated for the life of the request and then thrown away. This keeps our memory requirements nice and light.</p>

<p>Brubeck's <code>MessageHandler</code> design is similar to what you see in <a href="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/facebook/tornado">Tornado</a>, or <a href="https://webpy.org/">web.py</a>. </p>

<p>To answer HTTP GET requests, implement <code>get()</code> on a WebMessageHandler instance.</p>

<pre><code class="prettyprint">class DemoHandler(WebMessageHandler):
    def get(self):
        self.set_body('Take five!')
        return self.render()
</code></pre>

<p>Then we add <code>DemoHandler</code> to the routing config and instantiate a Brubeck instance. </p>

<pre><code class="prettyprint">urls = [(r'^/brubeck', DemoHandler)]
config = {
    'handler_tuples': urls,
    ...
}

Brubeck(**config).run()
</code></pre>

<p>Notice the url regex is <code>^/brubeck</code>. This will put our handler code on <code>https://hostname/brubeck</code>. </p>

<ul>
<li><a href="https://github.com/j2labs/brubeck/blob/master/demos/demo_minimal.py">Runnable demo</a></li>
</ul>

<h3>Functions and Decorators</h3>

<p>If you'd prefer to just use a simple function, you instantiate a Brubeck instance and wrap your function with the <code>add_route</code> decorator. </p>

<p>Your function will be given two arguments. First, is the <code>application</code> itself. This provides the function with a hook almost all the information it might need. The second argument, the <code>message</code>, provides all the information available about the request.</p>

<p>That looks like this:</p>

<pre><code class="prettyprint">app = Brubeck(mongrel2_pair=('ipc:https://127.0.0.1:9999', 
                             'ipc:https://127.0.0.1:9998'))

@app.add_route('^/brubeck', method='GET')
def foo(application, message):
    return http_response('Take five!', 200, 'OK', {})

app.run()
</code></pre>

<ul>
<li><a href="https://github.com/j2labs/brubeck/blob/master/demos/demo_noclasses.py">Runnable demo</a></li>
</ul>

<h2>Templates</h2>

<p>Brubeck currently supports <a href="https://jinja.pocoo.org/">Jinja2</a>, <a href="https://www.tornadoweb.org/documentation#templates">Tornado</a>, <a href="https://www.makotemplates.org/">Mako</a> or <a href="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/defunkt/pystache">Pystache</a> templates.</p>

<p>Template support is contained in <code>brubeck.templates</code> as rendering handlers. Each handler will attach a <code>render_template</code> function to your handler and overwrite the default <code>render_error</code> to produce templated errors messages.</p>

<p>Using a template system is then as easy as calling <code>render_template</code> with the template filename and some context, just like you're used to.</p>

<h3>Jinja2 Example</h3>

<p>Using Jinja2 template looks like this.</p>

<pre><code class="prettyprint">from brubeck.templating import Jinja2Rendering

class DemoHandler(WebMessageHandler, Jinja2Rendering):
    def get(self):
        context = {
            'name': 'J2 D2',
        }
        return self.render_template('success.html', **context)
</code></pre>

<ul>
<li><a href="https://github.com/j2labs/brubeck/blob/master/demos/demo_jinja2.py">Runnable demo</a></li>
<li><a href="https://github.com/j2labs/brubeck/tree/master/demos/templates/jinja2">Demo templates</a></li>
</ul>

<h3>Template Loading</h3>

<p>In addition to using a rendering handler, you need to provide the path to your
templates.</p>

<p>That looks like this:</p>

<pre><code class="prettyprint">from brubeck.templating import load_jinja2_env

config = {
    template_loader=load_jinja2_env('./templates/jinja2')
    ...
}
</code></pre>

<p>Using a function here keeps the config lightweight and flexible.
<code>template_loader</code> needs to be some function that returns an environment. The
details of how that works are up to you, if you want to change it.</p>

<h2>Auth</h2>

<p>Authentication can be provided by decorating functions with the <code>@web_authenticated</code> decorator. This decorator expects the handler to have a <code>current_user</code> property that returns either an authenticated <code>User</code> model or None. </p>

<p>The <code>UserHandlingMixin</code> provides functionality for authenticating a user and creating the <code>current_user</code> property. </p>

<p>The work that's required will depend on how you build your system. The authentication framework uses a DictShield Document to create the <code>User</code> model, so you can implement a database query or check user information in a sorted CSV. Either way, you still get the authentication framework you need.</p>

<p>In practice, this is what your code looks like.</p>

<pre><code class="prettyprint">from brubeck.auth import web_authenticated, UserHandlingMixin

class DemoHandler(WebMessageHandler, UserHandlingMixin):
    @web_authenticated
    def post(self):
        ...
</code></pre>

<p>The <code>User</code> model in brubeck.auth will probably serve as a good basis for your needs. A Brubeck user looks roughly like below.</p>

<pre><code class="prettyprint">class User(Document):
    """Bare minimum to have the concept of a User.
    """
    username = StringField(max_length=30, required=True)
    email = EmailField(max_length=100)
    password = StringField(max_length=128)
    is_active = BooleanField(default=False)
    last_login = LongField(default=curtime)
    date_joined = LongField(default=curtime)        
    ...
</code></pre>

<ul>
<li><a href="https://github.com/j2labs/brubeck/blob/master/demos/demo_auth.py">Runnable demo</a></li>
</ul>

<h2>Database Connections</h2>

<p>Database connectivity is provided in the form of a <code>db_conn</code> member on the <code>MessageHandler</code> instances when a <code>db_conn</code> flag is passed to the Brubeck instance.</p>

<p>That looks like this:</p>

<pre><code class="prettyprint">config = {
    'db_conn': db_conn,
    ...
}

app = Brubeck(**config)
</code></pre>

<p>For people using <code>MessageHandler</code> instances, the database connection is available as <code>self.db_conn</code>.</p>

<p>For people using the function and decorator approach, you can get the database connection off the <code>application</code> argument, <code>application.db_conn</code>.</p>

<p>Query code then looks like this with the database connection as the first argument.</p>

<pre><code class="prettyprint">user = load_user(self.db_conn, username='jd')
</code></pre>

<h2>Secure Cookies</h2>

<p>If you need a session to persist, you can use Brubeck's secure cookies to track users.</p>

<p>You first add the cookie secret to your Brubeck config.</p>

<pre><code class="prettyprint">config = {
    'cookie_secret': 'OMGSOOOOSECRET',
    ...
}
</code></pre>

<p>Then retrieve the cookie value by passing the application's secret key into the <code>get_cookie</code> function.</p>

<pre><code class="prettyprint"># Try loading credentials from secure cookie
user_id = self.get_cookie('user_id',
                          secret=self.application.cookie_secret)
</code></pre>

<p>What you do from there is up to you, but you'll probably be loading the user_id from a database or cache to get the rest of the account info. </p>

  <div id="footer">
    <img class="footer" src="img/footer.png">
  </div>
  </body>
</html>

About

the html behind brubeck.io

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published