<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: middleware</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/middleware.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2019-07-14T01:18:56+00:00</updated><author><name>Simon Willison</name></author><entry><title>Single sign-on against GitHub using ASGI middleware</title><link href="https://simonwillison.net/2019/Jul/14/sso-asgi/#atom-tag" rel="alternate"/><published>2019-07-14T01:18:56+00:00</published><updated>2019-07-14T01:18:56+00:00</updated><id>https://simonwillison.net/2019/Jul/14/sso-asgi/#atom-tag</id><summary type="html">
    &lt;p&gt;I released &lt;a href="https://datasette.readthedocs.io/en/stable/changelog.html#v0-29"&gt;Datasette 0.29&lt;/a&gt; last weekend, the first version of Datasette to be built on top of ASGI (discussed previously in &lt;a href="https://simonwillison.net/2019/Jun/23/datasette-asgi/"&gt;Porting Datasette to ASGI, and Turtles all the way down&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This also marked the introduction of the new &lt;a href="https://datasette.readthedocs.io/en/stable/plugins.html#plugin-asgi-wrapper"&gt;asgi_wrapper&lt;/a&gt; plugin hook, which allows plugins to wrap the entire Datasette application in their own piece of ASGI middleware.&lt;/p&gt;
&lt;p&gt;To celebrate this new capability, I also released two new plugins: &lt;a href="https://github.com/simonw/datasette-cors"&gt;datasette-cors&lt;/a&gt;, which provides fine-grained control over CORS headers (using my &lt;a href="https://github.com/simonw/asgi-cors"&gt;asgi-cors&lt;/a&gt; library from a few months ago) and &lt;a href="https://github.com/simonw/datasette-auth-github"&gt;datasette-auth-github&lt;/a&gt;, the first of hopefully many authentication plugins for Datasette.&lt;/p&gt;
&lt;h3&gt;&lt;a id="datasetteauthgithub_8"&gt;&lt;/a&gt;datasette-auth-github&lt;/h3&gt;
&lt;p&gt;The new plugin is best illustrated with a demo.&lt;/p&gt;
&lt;p&gt;Visit &lt;a href="https://datasette-auth-demo.now.sh/"&gt;https://datasette-auth-demo.now.sh/&lt;/a&gt; and you will be redirected to GitHub and asked to approve access to your account (just your e-mail address, not repository access).&lt;/p&gt;
&lt;p&gt;Agree, and you’ll be redirected back to the demo with a new element in the Datasette header: your GitHub username, plus a “log out” link in the navigation bar at the top of the screen.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Controlling_who_can_access_16"&gt;&lt;/a&gt;Controlling who can access&lt;/h3&gt;
&lt;p&gt;The default behaviour of the plugin is to allow in anyone with a GitHub account. Since the primary use-case for the plugin (at least for the moment) is restricting access to view data to a trusted subset of people,  the plugin lets you configure who is allowed to view your data in three different ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can restrict access to a specific list of GitHub accounts, using the &lt;code&gt;allow_users&lt;/code&gt; configuration option.&lt;/li&gt;
&lt;li&gt;You can restrict access to members of one or more GitHub &lt;a href="https://help.github.com/en/articles/about-organizations"&gt;organizations&lt;/a&gt;, with &lt;code&gt;allow_orgs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You can restrict access to members of specific &lt;a href="https://help.github.com/en/articles/about-teams"&gt;teams&lt;/a&gt; within an organization, using &lt;code&gt;allow_teams&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Datasette inherits quite a sophisticated user management system from GitHub, with very little effort required from the plugin. The &lt;code&gt;user_is_allowed()&lt;/code&gt; method that implements all three of the above options against the GitHub API in &lt;a href="https://github.com/simonw/datasette-auth-github/blob/f69781d11115b2685ff48bdaa2ab0367d4f8d306/datasette_auth_github/github_auth.py#L145-L188"&gt;just 40 lines of code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These options can be set using the &lt;code&gt;&amp;quot;plugins&amp;quot;&lt;/code&gt; section of the Datasette &lt;code&gt;metadata.json&lt;/code&gt; configuration file. Here’s an example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;plugins&amp;quot;: {
    &amp;quot;datasette-auth-github&amp;quot;: {
      &amp;quot;client_id&amp;quot;: {&amp;quot;$env&amp;quot;: &amp;quot;GITHUB_CLIENT_ID&amp;quot;},
      &amp;quot;client_secret&amp;quot;: {&amp;quot;$env&amp;quot;: &amp;quot;GITHUB_CLIENT_SECRET&amp;quot;},
      &amp;quot;allow_users&amp;quot;: [&amp;quot;simonw&amp;quot;]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This also illustrates a new Datasette feature: the ability to set &lt;a href="https://datasette.readthedocs.io/en/stable/plugins.html#secret-configuration-values"&gt;secret plugin configuration values&lt;/a&gt;. &lt;code&gt;{&amp;quot;$env&amp;quot;: &amp;quot;GITHUB_CLIENT_SECRET&amp;quot;}&lt;/code&gt; means &amp;quot;read this configuration option from the environment variable &lt;code&gt;GITHUB_CLIENT_SECRET&lt;/code&gt;&amp;quot;.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Automatic_log_in_40"&gt;&lt;/a&gt;Automatic log in&lt;/h3&gt;
&lt;p&gt;Like many OAuth providers, GitHub only asks the user for their approval the first time they log into a given app. Any subsequent times they are redirected to GitHub it will skip the permission screen and redirect them right back again with a token.&lt;/p&gt;
&lt;p&gt;This means we can implement automatic log in: any time a visitor arrives who does not have a cookie we can bounce them directly to GitHub, and if they have already consented they will be logged in instantly.&lt;/p&gt;
&lt;p&gt;This is a great user-experience - provided the user is logged into GitHub they will be treated as if they are logged into your application - but it does come with a downside: what if the user clicks the “log out” link?&lt;/p&gt;
&lt;p&gt;For the moment I’ve implemented this using another cookie: if the user clicks “log out”, I set an &lt;code&gt;asgi_auth_logout&lt;/code&gt; cookie marking the user as having explicitly logged out. While they have that cookie they won’t be logged in automatically, instead having to click an explicit link. See &lt;a href="https://githiub.com/simonw/datasette-auth-github/issues/41"&gt;issue 41&lt;/a&gt; for thoughts on how this could be further improved.&lt;/p&gt;
&lt;p&gt;One pleasant side-effect of all of this is that &lt;code&gt;datasette-auth-github&lt;/code&gt; doesn’t need to persist the users GitHub &lt;code&gt;access_token&lt;/code&gt; anywhere - it uses it during initil authentication check for any required organizations or teams, but then it deliberately forgets the token entirely.&lt;/p&gt;
&lt;p&gt;OAuth access tokens are like passwords, so the most resonsible thing for a piece of softare to do with them is avoid storing them anywhere at all unless they are explicitly needed.&lt;/p&gt;
&lt;h3&gt;&lt;a id="What_happens_when_a_user_leaves_an_organization_54"&gt;&lt;/a&gt;What happens when a user leaves an organization?&lt;/h3&gt;
&lt;p&gt;When building against a single sign-in provider, consideration needs to be given to offboarding: when a user is removed from a team or organization they should also lose access to their SSO applications.&lt;/p&gt;
&lt;p&gt;This is difficult when an application sets its own authentication cookies, like &lt;code&gt;datasette-auth-github&lt;/code&gt; does.&lt;/p&gt;
&lt;p&gt;One solution would be to make an API call on every request to the application, to verify that the user should still have access. This would slow everything down and is likely to blow through rate limits as well, so we need a more efficient solution.&lt;/p&gt;
&lt;p&gt;I ended up solving this with two mechanisms. Since we have automatic log in, our cookies don’t actually need to last very long - so by default the signed cookies set by the plugin last for just one hour. When a user’s cookie has expired they will be redirected back through GitHub - they probably won’t even notice the redirect, and their permissions will be re-verified as part of that flow.&lt;/p&gt;
&lt;p&gt;But what if you need to invalidate those cookies instantly?&lt;/p&gt;
&lt;p&gt;To cover that case, I’ve incorporated an optional &lt;code&gt;cookie_version&lt;/code&gt; configuration option into the signatures on the cookies. If you need to invalidate &lt;em&gt;every&lt;/em&gt; signed cookie that is out there - to lock out a compromised GitHub account owner for example - you can do so by changing the &lt;code&gt;cookie_version&lt;/code&gt; configuration option and restarting (or re-deploying) Datasette.&lt;/p&gt;
&lt;p&gt;These options are all described in detail in the &lt;a href="https://github.com/simonw/datasette-auth-github/blob/master/README.md"&gt;project README&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Integration_with_datasette_publish_71"&gt;&lt;/a&gt;Integration with datasette publish&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://datasette.readthedocs.io/en/stable/publish.html#datasette-publish"&gt;datasette publish&lt;/a&gt; command-line tool lets users instantly publish a SQLite database to the internet, using Heroku, Cloud Run or Zeit Now v1. I’ve added suppor for setting secret plugin configuration directly to that tool, which means you can publish an authentication-protected SQLite database to the internet with a shell one-liner, using &lt;code&gt;--install=datasette-auth-github&lt;/code&gt; to install the plugin and &lt;code&gt;--plugin-secret&lt;/code&gt; to configure it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ datasette publish cloudrun fixtures.db \
   --install=datasette-auth-github \
   --name datasette-auth-protected \
   --service datasette-auth-protected \
   --plugin-secret datasette-auth-github allow_users simonw \
   --plugin-secret datasette-auth-github client_id 85f6224cb2a44bbad3fa \
   --plugin-secret datasette-auth-github client_secret ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a Cloud Run instance which only allows GitHub user &lt;a href="https://github.com/simonw"&gt;simonw&lt;/a&gt; to log in. You could instead use &lt;code&gt;--plugin-secret datasette-auth-github allow_orgs my-org&lt;/code&gt; to allow any users from a specific GitHub organization.&lt;/p&gt;
&lt;p&gt;Note that Cloud Run does not yet give you full control over the URL that will be assigned to your deployment. In this case it gave me &lt;code&gt;https://datasette-auth-protected-j7hipcg4aq-uc.a.run.app&lt;/code&gt; - which works fine, but I needed to update my GitHub OAuth application’s callback URL manually to &lt;code&gt;https://datasette-auth-protected-j7hipcg4aq-uc.a.run.app/-/auth-callback&lt;/code&gt; after deploying the application in order to get the authentication flow to work correctly.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Add_GitHub_authentication_to_any_ASGI_application_87"&gt;&lt;/a&gt;Add GitHub authentication to any ASGI application!&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;datasette-auth-github&lt;/code&gt; isn’t just for Datasette: I deliberately wrote the plugin as ASGI middleware first, with only a very thin layer of extra code to turn it into an installable plugin.&lt;/p&gt;
&lt;p&gt;This means that if you are building any other kind of ASGI app (or using an ASGI-compatible framework such as Starlette or Sanic) you can wrap your application directly with the middleware and get the same authentication behaviour as when the plugin is added to Datasette!&lt;/p&gt;
&lt;p&gt;Here’s what that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;&lt;span class="hljs-keyword"&gt;from&lt;/span&gt; datasette_auth_github &lt;span class="hljs-keyword"&gt;import&lt;/span&gt; GitHubAuth
&lt;span class="hljs-keyword"&gt;from&lt;/span&gt; starlette.applications &lt;span class="hljs-keyword"&gt;import&lt;/span&gt; Starlette
&lt;span class="hljs-keyword"&gt;from&lt;/span&gt; starlette.responses &lt;span class="hljs-keyword"&gt;import&lt;/span&gt; HTMLResponse
&lt;span class="hljs-keyword"&gt;import&lt;/span&gt; uvicorn

app = Starlette(debug=&lt;span class="hljs-keyword"&gt;True&lt;/span&gt;)


&lt;span class="hljs-decorator"&gt;@app.route("/")&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;homepage&lt;/span&gt;&lt;span class="hljs-params"&gt;(request)&lt;/span&gt;:&lt;/span&gt;
    &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; HTMLResponse(&lt;span class="hljs-string"&gt;"Hello, {}"&lt;/span&gt;.format(
        repr(request.scope[&lt;span class="hljs-string"&gt;"auth"&lt;/span&gt;])
    ))


authenticated_app = GitHubAuth(
    app,
    client_id=&lt;span class="hljs-string"&gt;"986f5d837b45e32ee6dd"&lt;/span&gt;,
    client_secret=&lt;span class="hljs-string"&gt;"..."&lt;/span&gt;,
    require_auth=&lt;span class="hljs-keyword"&gt;True&lt;/span&gt;,
    allow_users=[&lt;span class="hljs-string"&gt;"simonw"&lt;/span&gt;],
)

&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; __name__ == &lt;span class="hljs-string"&gt;"__main__"&lt;/span&gt;:
    uvicorn.run(authenticated_app, host=&lt;span class="hljs-string"&gt;"0.0.0.0"&lt;/span&gt;, port=&lt;span class="hljs-number"&gt;8000&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The middleware adds a &lt;code&gt;scope[&amp;quot;auth&amp;quot;]&lt;/code&gt; key describing the logged in user, which is then passed through to your application. More on this &lt;a href="https://github.com/simonw/datasette-auth-github#using-this-as-asgi-middleware-without-datasette"&gt;in the README&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Your_security_reviews_needed_125"&gt;&lt;/a&gt;Your security reviews needed!&lt;/h3&gt;
&lt;p&gt;Since &lt;code&gt;datasette-auth-github&lt;/code&gt; adds authentication to Datasette, it is an extremely security-sensitive piece of code. So far I’m the only person who has looked at it: before I start widely recommending it to people I’d really like to get some more eyes on it to check for any potential security problems.&lt;/p&gt;
&lt;p&gt;I’ve opened &lt;a href="https://github.com/simonw/datasette-auth-github/issues/44"&gt;issue #44&lt;/a&gt; encouraging security-minded developers to have a dig through the code and see if there’s anything that can be tightened up or any potential vulnerabilities that need to be addressed. Please get involved!&lt;/p&gt;
&lt;p&gt;It’s a pretty small codebase, but here are some areas you might want to inspect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At a high level: is the way I’m verifying the user through the GitHub API and then storing their identity in a signed cookie the right way to go?&lt;/li&gt;
&lt;li&gt;The cookie signing secret is derived from the GitHub OAuth application’s &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; (because that secret is already meant to be a secret), combined with the &lt;code&gt;cookie_version&lt;/code&gt; option described above - &lt;a href="https://github.com/simonw/datasette-auth-github/blob/bf01f8f01b87a6cb09c47380ba0a86e0546ebb38/datasette_auth_github/github_auth.py#L81-L88"&gt;implementation here&lt;/a&gt;. Since this is a derived secret I’m using &lt;a href="https://docs.python.org/3/library/hashlib.html#hashlib.pbkdf2_hmac"&gt;pbkdf2_hmac&lt;/a&gt; with 100,000 iterations. This is by far the most cryptographically interesting part of the code, and could definitely do with some second opinions.&lt;/li&gt;
&lt;li&gt;The code used &lt;a href="https://github.com/simonw/datasette-auth-github/blob/bf01f8f01b87a6cb09c47380ba0a86e0546ebb38/datasette_auth_github/utils.py#L16-L36"&gt;to sign and verify cookies&lt;/a&gt; is based on Django’s (thoroughly reviewed) implementation, but could benefit from a sanity check.&lt;/li&gt;
&lt;li&gt;I wanted this library to work on &lt;a href="https://glitch.com/"&gt;Glitch&lt;/a&gt;, which currently &lt;a href="https://support.glitch.com/t/can-you-upgrade-python-to-latest-version/7980"&gt;only provides Python 3.5.2&lt;/a&gt;. Python’s asyncio HTTP librarys such as &lt;a href="https://github.com/encode/http3"&gt;http3&lt;/a&gt; and &lt;a href="https://aiohttp.readthedocs.io/en/stable/"&gt;aiohttp&lt;/a&gt; both require more modern Pythons, so I ended up &lt;a href="https://github.com/simonw/datasette-auth-github/pull/40"&gt;rolling my own&lt;/a&gt; very simple async HTTP function which uses &lt;code&gt;urllib.request&lt;/code&gt; inside a &lt;code&gt;loop.run_in_executor&lt;/code&gt; thread pool. Is that approach sound? Rolling my own HTTP client in this way feels a little hairy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This has been a really fun project so far, and I’m very excited about the potential for authenticated Datasette moving forward - not to mention the possibilites unlocked by an ASGI middleware ecosystem with strong support for wrapping any application in an authentication layer.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/asgi"&gt;asgi&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="github"/><category term="middleware"/><category term="projects"/><category term="security"/><category term="datasette"/><category term="asgi"/></entry><entry><title>Debugging Django in Production Revisited</title><link href="https://simonwillison.net/2009/Sep/7/debugging/#atom-tag" rel="alternate"/><published>2009-09-07T05:21:04+00:00</published><updated>2009-09-07T05:21:04+00:00</updated><id>https://simonwillison.net/2009/Sep/7/debugging/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://ericholscher.com/blog/2009/sep/5/debugging-django-production-revisited/"&gt;Debugging Django in Production Revisited&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Eric Holscher expands his show-technical-errors-to-superusers middleware to only show them to users in the group named “Technical Errors”.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/debugging"&gt;debugging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/eric-holscher"&gt;eric-holscher&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;&lt;/p&gt;



</summary><category term="debugging"/><category term="django"/><category term="eric-holscher"/><category term="middleware"/><category term="python"/></entry><entry><title>Finding and fixing memory leaks in Python</title><link href="https://simonwillison.net/2009/Apr/22/finding/#atom-tag" rel="alternate"/><published>2009-04-22T12:16:32+00:00</published><updated>2009-04-22T12:16:32+00:00</updated><id>https://simonwillison.net/2009/Apr/22/finding/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://amix.dk/blog/viewEntry/19420"&gt;Finding and fixing memory leaks in Python&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Using Dozer, a clever piece of WSGI middleware which displays sparklines of Python object counts and allows you to introspect them, using the gc module under the hood.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/amir-salihefendic"&gt;amir-salihefendic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/debugging"&gt;debugging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/memory"&gt;memory&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/memoryleaks"&gt;memoryleaks&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/wsgi"&gt;wsgi&lt;/a&gt;&lt;/p&gt;



</summary><category term="amir-salihefendic"/><category term="debugging"/><category term="memory"/><category term="memoryleaks"/><category term="middleware"/><category term="python"/><category term="wsgi"/></entry><entry><title>Integrating Facebook Connect with Django in 15 minutes</title><link href="https://simonwillison.net/2008/Dec/17/integrating/#atom-tag" rel="alternate"/><published>2008-12-17T13:18:38+00:00</published><updated>2008-12-17T13:18:38+00:00</updated><id>https://simonwillison.net/2008/Dec/17/integrating/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://nyquistrate.com/django/facebook-connect/"&gt;Integrating Facebook Connect with Django in 15 minutes&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Django authentication middleware that calls the Facebook REST API  using a cookie set by Facebook Connect and checks if that person is your Facebook friend. Despite most of the magic happening on the server you still need Facebook’s JavaScript to set that cookie in the first place.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cookies"&gt;cookies&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/facebook"&gt;facebook&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/facebookconnect"&gt;facebookconnect&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;&lt;/p&gt;



</summary><category term="cookies"/><category term="django"/><category term="facebook"/><category term="facebookconnect"/><category term="javascript"/><category term="middleware"/></entry><entry><title>csrf_protect.php</title><link href="https://simonwillison.net/2008/Sep/24/csrfprotect/#atom-tag" rel="alternate"/><published>2008-09-24T14:52:36+00:00</published><updated>2008-09-24T14:52:36+00:00</updated><id>https://simonwillison.net/2008/Sep/24/csrfprotect/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://simonwillison.net/static/2008/csrf_protect.php.txt"&gt;csrf_protect.php&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A PHP class for applying CSRF protection to existing PHP applications, using output buffering to rewrite any POST forms on a page. Heavily inspired by Django’s CSRF middleware. Tell me if you spot any bugs!


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/code"&gt;code&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/csrf"&gt;csrf&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/outputbuffering"&gt;outputbuffering&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/php"&gt;php&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;&lt;/p&gt;



</summary><category term="code"/><category term="csrf"/><category term="middleware"/><category term="outputbuffering"/><category term="php"/><category term="projects"/><category term="security"/></entry><entry><title>minidetector</title><link href="https://simonwillison.net/2008/Aug/15/minidetector/#atom-tag" rel="alternate"/><published>2008-08-15T08:21:59+00:00</published><updated>2008-08-15T08:21:59+00:00</updated><id>https://simonwillison.net/2008/Aug/15/minidetector/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://code.google.com/p/minidetector/"&gt;minidetector&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Neat piece of Django middleware that adds a “mobile = True” attribute to the request object if the request’s user-agent matches a list of strings of known low-power browsers in mobiles, PDAs or game consoles.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/minidetector"&gt;minidetector&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mobile"&gt;mobile&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/user-agents"&gt;user-agents&lt;/a&gt;&lt;/p&gt;



</summary><category term="django"/><category term="middleware"/><category term="minidetector"/><category term="mobile"/><category term="python"/><category term="user-agents"/></entry><entry><title>Super User Conditional Page Exception Reporting</title><link href="https://simonwillison.net/2008/Jul/31/django/#atom-tag" rel="alternate"/><published>2008-07-31T21:06:30+00:00</published><updated>2008-07-31T21:06:30+00:00</updated><id>https://simonwillison.net/2008/Jul/31/django/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.djangosnippets.org/snippets/935/"&gt;Super User Conditional Page Exception Reporting&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The name is almost as long as the code snippet: this serves Django’s debug page to logged in super-users, falling back to the default 500 template for everyone else.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/debugging"&gt;debugging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/exceptions"&gt;exceptions&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;&lt;/p&gt;



</summary><category term="debugging"/><category term="django"/><category term="exceptions"/><category term="middleware"/><category term="python"/></entry><entry><title>mod_rpaf for Apache</title><link href="https://simonwillison.net/2008/Jun/24/modrpaf/#atom-tag" rel="alternate"/><published>2008-06-24T17:02:55+00:00</published><updated>2008-06-24T17:02:55+00:00</updated><id>https://simonwillison.net/2008/Jun/24/modrpaf/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://stderr.net/apache/rpaf/"&gt;mod_rpaf for Apache&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A more secure alternative to Django’s equivalent middleware: sets the REMOTE_ADDR of incoming requests from whitelisted load balancers to the X-Forwarded-For header, without any risk that if the load balancers are missing attackers could abuse it to spoof their IP addresses.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/apache"&gt;apache&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/http"&gt;http&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/load-balancing"&gt;load-balancing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/modrpaf"&gt;modrpaf&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rpaf"&gt;rpaf&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/xforwardedfor"&gt;xforwardedfor&lt;/a&gt;&lt;/p&gt;



</summary><category term="apache"/><category term="django"/><category term="http"/><category term="load-balancing"/><category term="middleware"/><category term="modrpaf"/><category term="rpaf"/><category term="security"/><category term="xforwardedfor"/></entry><entry><title>DebugFooter middleware with Pygments sql syntax highlighting</title><link href="https://simonwillison.net/2008/Jun/14/django/#atom-tag" rel="alternate"/><published>2008-06-14T10:04:08+00:00</published><updated>2008-06-14T10:04:08+00:00</updated><id>https://simonwillison.net/2008/Jun/14/django/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.djangosnippets.org/snippets/799/"&gt;DebugFooter middleware with Pygments sql syntax highlighting&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Andreas Marr has enhanced my Django DebugFooter middleware with proper syntax highlighting for the logged SQL.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/andreas-marr"&gt;andreas-marr&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/debugfooter"&gt;debugfooter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;&lt;/p&gt;



</summary><category term="andreas-marr"/><category term="debugfooter"/><category term="django"/><category term="middleware"/><category term="python"/></entry><entry><title>Debugging Django</title><link href="https://simonwillison.net/2008/May/22/debugging/#atom-tag" rel="alternate"/><published>2008-05-22T00:35:40+00:00</published><updated>2008-05-22T00:35:40+00:00</updated><id>https://simonwillison.net/2008/May/22/debugging/#atom-tag</id><summary type="html">
    &lt;p&gt;I gave a talk on Debugging Django applications at &lt;a href="http://upcoming.yahoo.com/event/546918/"&gt;Monday's inaugural meeting&lt;/a&gt; of DJUGL, the London Django Users Group. I wanted to talk about something that wasn't particularly well documented elsewhere, so I pitched the talk as "Bug Driven Development" - what happens when Test Driven Development goes the way of &lt;a href="http://www.shipmentoffail.com/fails/2008/04/horse-vs-car-fail/"&gt;this unfortunate pony&lt;/a&gt;.&lt;/p&gt;

The slides &lt;a href="http://www.slideshare.net/simon/debugging-django/"&gt;are up on SlideShare&lt;/a&gt;, but don't provide quite enough context so I'm going to cover the tips in full here.

&lt;h4&gt;Making the most of the error page&lt;/h4&gt;

&lt;p&gt;Django's default error page is great - it provides a detailed traceback with local variables, lets you expand out the lines of code around the problem, provides a plain text exception suitable for e-mailing to colleagues and even a one-click button to send details to &lt;a href="http://dpaste.com/"&gt;http://dpaste.com/&lt;/a&gt; so you can go and talk about the error on IRC. It also serves the same purpose as &lt;a href="http://www.php.net/phpinfo"&gt;phpinfo()&lt;/a&gt; - it shows you your application's settings, the GET, POST and COOKIE data from the request and the all important META fields assembled from the HTTP environment (great for remembering how to miss-spell HTTP_REFERER).&lt;/p&gt;

&lt;p&gt;Useful tip number one is that you can trigger the error page from any view just by adding the following line:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;assert False&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can serve up an expression with the assertion as well; it will be displayed at the top of the error page:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;assert False, request.GET&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One particularly useful place to use this is when you are building a complex form. If you want to see the data that was submitted, drop an assert False in to the view that the form targets and use the error page to inspect the data.&lt;/p&gt;

&lt;h4&gt;Logging to the development server console&lt;/h4&gt;

&lt;p&gt;If you want to know what's going on inside a view, the quickest way is to drop in a print statement. The development server outputs any print statements directly to the terminal; it's the server-side alternative to a JavaScript alert().&lt;/p&gt;

&lt;p&gt;If you want to be a bit more sophisticated with your logging, it's worth turning to Python's logging module (part of the standard library). You can configure it in your settings.py:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import logging
logging.basicConfig(
    level = logging.DEBUG,
    format = '%(asctime)s %(levelname)s %(message)s',
)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then call it from any of your views:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;def my_view(request):
    import logging
    logging.debug("A log message")
    ...&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Again, this will log things to the terminal where the development server is running. If you want to log things to a file you can do so by extending the basicConfig call:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;logging.basicConfig(
    level = logging.DEBUG,
    format = '%(asctime)s %(levelname)s %(message)s',
    filename = '/tmp/myapp.log',
    filemode = 'w'
)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can then use &lt;samp&gt;tail -f /tmp/myapp.log&lt;/samp&gt; to see log lines being appended to that file in real-time. This can be used in production as well as development.&lt;/p&gt;

&lt;p&gt;The above just scratches the surface of Python's logging module; with a bit of &lt;a href="http://docs.python.org/lib/module-logging.html" title="Loggingi facility for Python"&gt;digging around in the documentation&lt;/a&gt; you can use it to rotate log files, send log messages over the network and even POST log events to an HTTP server somewhere.&lt;/p&gt;

&lt;p&gt;Often you find yourself dealing with an error that only occurs in certain circumstances - a function might be called from dozens of different places in your program but only runs in to trouble in a very specific case. You can use the &lt;a href="http://docs.python.org/lib/module-traceback.html"&gt;traceback module&lt;/a&gt; to log the current stack, which will allow you to tell how a function was called when something went wrong:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import logging, traceback, pprint

def my_buggy_function(arg):
    ...
    if error_condition:
        stack = pprint.pformat(traceback.extract_stack())
        logging.debug('An error occurred: %s' % stack)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The tuple returned by &lt;code class="python"&gt;traceback.extract_stack()&lt;/code&gt; includes line numbers, function names and paths to Python files so you can use it to reconstruct a good amount of information about your program.&lt;/p&gt;

&lt;h4&gt;Using the debugger&lt;/h4&gt;

&lt;p&gt;By far the most powerful weapon in my debugging toolkit is the Python debugger, &lt;a href="http://docs.python.org/lib/module-pdb.html"&gt;pdb&lt;/a&gt;. Again, this ships with the standard library so there's nothing extra to install. pdb is a command line debugger (if you want a GUI options include &lt;a href="http://sourceforge.net/projects/pyeclipse/"&gt;PyEclipse&lt;/a&gt; and &lt;a href="http://www.activestate.com/Products/komodo_ide/index.mhtml"&gt;Komodo&lt;/a&gt;, but I haven't used either myself). There are a bunch of ways to activate pdb, but the most straight forward is to simply drop the following line directly in to a Django view function:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import pdb; pdb.set_trace()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you try to load that page in your browser, the browser will hang - the page will appear to be loading extremely slowly. What's actually happened is the developer server has paused execution and thrown up the pdb interface - you can switch over to your console and start interacting directly with the server mid view.&lt;/p&gt;

&lt;p&gt;Did I mention you should never, ever leave this on in production?&lt;/p&gt;

&lt;p&gt;So, you've got a hung development server and a pdb prompt. What can you do with it? The answer is pretty much anything. I won't provide a full pdb tutorial here (&lt;a href="http://www.onlamp.com/pub/a/python/2005/09/01/debugger.html"&gt;this is a good introduction&lt;/a&gt;), but the commands I find most useful are the following:&lt;/p&gt;

&lt;dl&gt;
    &lt;dt&gt;list&lt;/dt&gt;
    &lt;dd&gt;Shows the lines of source code around your current point of execution. You can run it multiple times to increase the amount of source code displayed.&lt;/dd&gt;
    &lt;dt&gt;n&lt;/dt&gt;
    &lt;dd&gt;Execute the next line&lt;/dd&gt;
    &lt;dt&gt;s&lt;/dt&gt;
    &lt;dd&gt;Same as n, but steps in to any functions that are called. You can quickly get lost in a twisty maze of code with this command, but that's OK because...&lt;/dd&gt;
    &lt;dt&gt;r&lt;/dt&gt;
    &lt;dd&gt;Continues execution until the current function returns&lt;/dd&gt;
    &lt;dt&gt;u&lt;/dt&gt;
    &lt;dd&gt;Goes UP one level in the stack - so you can see the function that called the function you are currently in&lt;/dd&gt;
    &lt;dt&gt;d&lt;/dt&gt;
    &lt;dd&gt;Goes DOWN again&lt;/dd&gt;
    &lt;dt&gt;locals()&lt;/dt&gt;
    &lt;dd&gt;not a pdb command, but handy for seeing what's in your current scope&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;The pdb docs have &lt;a href="http://www.python.org/doc/current/lib/debugger-commands.html"&gt;a full list of commands&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The pdb prompt doubles up as a full Python interactive shell, so you can not only access variables but you can modify them, call functions and generally mess around with the internals of your application as much as you like, while it's running. It's kind of a poor man's imitation of being a Smalltalk developer.&lt;/p&gt;

&lt;p&gt;Remember though, the whole time you're messing around in pdb your browser is still stuck there, waiting for the HTTP request to come back. If you hit "c" (for continue) your application will kick in again, the request will be served and your browser will breathe a sigh of relief.&lt;/p&gt;

&lt;p&gt;Thankfully you don't have to use pdb in a way that freezes your development server; it also works great in the interactive shell. If you've got a buggy function, one way to explore it is to run it interactively, then use the following idiom:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; def function_that_raises_an_exception():
...   assert False
... 
&amp;gt;&amp;gt;&amp;gt; function_that_raises_an_exception()
Traceback (most recent call last):
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;
  File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 2, in function_that_raises_an_exception
AssertionError
&amp;gt;&amp;gt;&amp;gt; import pdb; pdb.pm()
&amp;gt; &amp;lt;stdin&amp;gt;(2)function_that_raises_an_exception()
(Pdb)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;pdb.pm()&lt;/code&gt; stands for post-mortem, and is probably my favourite feature of the debugger - it lets you jump back in to debug the most recently raised exception, even if you hadn't imported pdb at the time the exception was raised.&lt;/p&gt;

&lt;p&gt;One last pdb tip: you can use it to debug Python command line scripts such as Django's custom &lt;samp&gt;./manage.py&lt;/samp&gt; commands. The trick is to run the script like this:&lt;/p&gt;

&lt;p&gt;&lt;samp&gt;python -i manage.py buggy_command&lt;/samp&gt;&lt;/p&gt;

&lt;p&gt;The &lt;samp&gt;-i&lt;/samp&gt; argument causes Python to drop in to the interactive prompt after executing the script. If the script raised an exception, you can then use &lt;code&gt;pdb.pm()&lt;/code&gt; to debug it.&lt;/p&gt;

&lt;h4&gt;Handling errors in production&lt;/h4&gt;

&lt;p&gt;Django's default behaviour in production (that is, when the DEBUG setting is set to False) is to e-mail exception reports to anyone listed in the ADMINS section. You can also turn on e-mail reports on every 404 error with the SEND_BROKEN_LINK_EMAILS setting, which will send them to addresses in the MANAGERS setting. As far as I know these settings don't do anything else - they're a pretty ancient bit of Django.&lt;/p&gt;

&lt;p&gt;On a high traffic site you probably don't want to be e-mailed on every server error. One neat alternative is David Cramer's &lt;a href="http://code.google.com/p/django-db-log/"&gt;django-db-log&lt;/a&gt;, which logs exceptions to a database table. It cleverly uses an MD5 hash of the traceback to aggregate many reports of the same error. More importantly though, it acts as a really straight forward example of how to use Django middleware's process_exception hook to roll your own error reporting. Take a look &lt;a href="http://code.google.com/p/django-db-log/source/browse/trunk/djangodblog/__init__.py"&gt;at the code&lt;/a&gt; to see how simple this is.&lt;/p&gt;

&lt;h4&gt;More useful middleware&lt;/h4&gt;

&lt;p&gt;In the talk I demoed a couple of other handy pieces of middleware. The first was the &lt;a href="http://www.djangosnippets.org/snippets/727/"&gt;ProfilerMiddleware&lt;/a&gt; (one of several profiling tools on &lt;a href="http://www.djangosnippets.org/"&gt;Django Snippets&lt;/a&gt;) which allows you to add &lt;samp&gt;?prof&lt;/samp&gt; to the end of any URL to see the output of Python's &lt;a href="http://docs.python.org/lib/module-profile.html"&gt;cProfile module&lt;/a&gt; run against that request. The second is one that I've just released: &lt;a href="http://www.djangosnippets.org/snippets/766/"&gt;DebugFooter&lt;/a&gt;, which adds a footer showing exactly which templates were loaded from where (handy for debugging complex template paths) as well as every executed SQL query and how long each one took.&lt;/p&gt;

&lt;h4&gt;Abusing the test client&lt;/h4&gt;

&lt;p&gt;A final tip for exploring your application interactively is to learn to use &lt;a href="http://www.djangoproject.com/documentation/testing/"&gt;Django's TestClient&lt;/a&gt;. Although designed for use in unit tests, this tool is equally useful for use at the interactive prompt. It allows you to simulate an in-process request against your application from within your Python code. Here's an example:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; from django.test.client import Client
&amp;gt;&amp;gt;&amp;gt; c = Client()
&amp;gt;&amp;gt;&amp;gt; response = c.get(&amp;quot;/&amp;quot;) # The homepage
&amp;gt;&amp;gt;&amp;gt; response
&amp;lt;django.http.HttpResponse object at 0x2300470&amp;gt;
&amp;gt;&amp;gt;&amp;gt; print response
Vary: Cookie
Content-Type: text/html; charset=utf-8

&amp;lt;!DOCTYPE HTML PUBLIC &amp;quot;-//W3C//DTD HTML 4.01//EN&amp;quot;
    &amp;quot;http://www.w3.org/TR/html4/strict.dtd&amp;quot;&amp;gt;
&amp;lt;html&amp;gt;
...&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The response object you get back is the &lt;code class="python"&gt;HttpResponse&lt;/code&gt; returned by the view, ready to be explored interactively.&lt;/p&gt;

&lt;p&gt;There's another function from the unit testing tools that can help with interactively exploring an application: &lt;code class="python"&gt;setup_test_environment()&lt;/code&gt;. This function monkey-patches in some additional hooks used by the unit tests, including one that intercepts template render calls and adds information on them to the request object. Here's an example:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;&amp;gt;&amp;gt;&amp;gt; from django.test.utils import setup_test_environment
&amp;gt;&amp;gt;&amp;gt; setup_test_environment()
&amp;gt;&amp;gt;&amp;gt; from django.test.client import Client
&amp;gt;&amp;gt;&amp;gt; c = Client()
&amp;gt;&amp;gt;&amp;gt; response = c.get(&amp;quot;/&amp;quot;)
&amp;gt;&amp;gt;&amp;gt; response.template
[&amp;lt;django.template.Template object at 0x2723dd0&amp;gt;,
 &amp;lt;django.template.Template object at 0x2723f30&amp;gt;,
 &amp;lt;django.template.Template object at 0x273ee10&amp;gt;]
&amp;gt;&amp;gt;&amp;gt; response.context
[ list of Context objects ]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This allows you to explore not just the HTML returned by a view, but also the templates and contexts that were used to render it.&lt;/p&gt;

&lt;h4&gt;Your tips welcome&lt;/h4&gt;

&lt;p&gt;If you have any useful tips on debugging Django applications, please share them in the comments on this entry.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/debugging"&gt;debugging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/djugl"&gt;djugl&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/my-talks"&gt;my-talks&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/testing"&gt;testing&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="debugging"/><category term="django"/><category term="djugl"/><category term="middleware"/><category term="my-talks"/><category term="testing"/></entry><entry><title>django-db-log</title><link href="https://simonwillison.net/2008/May/13/djangodblog/#atom-tag" rel="alternate"/><published>2008-05-13T08:07:49+00:00</published><updated>2008-05-13T08:07:49+00:00</updated><id>https://simonwillison.net/2008/May/13/djangodblog/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.davidcramer.net/code/126/django-db-log.html"&gt;django-db-log&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Middleware that logs Django exceptions to the database, using a clever scheme based on an MD5 of the traceback text to group duplicate errors in to batches.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/david-cramer"&gt;david-cramer&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/djangodblog"&gt;djangodblog&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/exceptions"&gt;exceptions&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/logging"&gt;logging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;&lt;/p&gt;



</summary><category term="david-cramer"/><category term="django"/><category term="djangodblog"/><category term="exceptions"/><category term="logging"/><category term="middleware"/></entry><entry><title>Wait For It</title><link href="https://simonwillison.net/2007/Jun/9/wait/#atom-tag" rel="alternate"/><published>2007-06-09T16:53:14+00:00</published><updated>2007-06-09T16:53:14+00:00</updated><id>https://simonwillison.net/2007/Jun/9/wait/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://pythonpaste.org/waitforit/"&gt;Wait For It&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Neat WSGI middleware from Ian Bicking that launches a thread for every incoming request and watches for slow responses; if something is taking too long it returns a “please wait” page to the user and polls for completion.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://blog.ianbicking.org/what-im-up-to-jun07.html"&gt;Ian Bicking&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/ian-bicking"&gt;ian-bicking&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/wsgi"&gt;wsgi&lt;/a&gt;&lt;/p&gt;



</summary><category term="ian-bicking"/><category term="middleware"/><category term="python"/><category term="wsgi"/></entry><entry><title>Avoid IE Brokenness When using Vary and Attachments</title><link href="https://simonwillison.net/2007/Apr/9/vary/#atom-tag" rel="alternate"/><published>2007-04-09T09:41:29+00:00</published><updated>2007-04-09T09:41:29+00:00</updated><id>https://simonwillison.net/2007/Apr/9/vary/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.djangosnippets.org/snippets/157/"&gt;Avoid IE Brokenness When using Vary and Attachments&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Django middleware that works around a bug in IE where external applications fail to load content that was served with a Vary header.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://groups.google.com/group/django-developers/browse_thread/thread/391029b8b4c3ee0e/"&gt;django-developers&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/http"&gt;http&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/internet-explorer"&gt;internet-explorer&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;&lt;/p&gt;



</summary><category term="django"/><category term="http"/><category term="internet-explorer"/><category term="middleware"/><category term="python"/></entry></feed>