Is WSGI Lite a Library or a Protocol? (And Why You Should Care)
In retrospect, my article yesterday about WSGI Lite made a rather glaring mistake: instead of carefully laying out the background rationale and explaining where WSGI Lite fits in to today's Python world, I threw a bunch of links at people and went "Whee! It's neat!"
So, in hindsight, I should've expected reactions like "huh?" "wha?" and "don't we already have WebOb and Werkzeug?"
My bad, guys. I totally failed to highlight the really crucial point about WSGI Lite, and that is the distinction between "wsgi_lite" (the proof-of-concept/future reference library) and "WSGI Lite" (the PEPpable protocol).
See, in my mind, "wsgi_lite" the library is no more a competitor to WebOb and Werkzeug than the standard library's "wsgiref" package is a competitor to mod_wsgi: just because it has a server in there, doesn't mean it competes with servers!
I think it's a pretty safe bet to say that most WSGI (protocol) code does not use wsgiref (library), except maybe indirectly via something else. And the same thing may well end up being true of wsgi_lite (the library) and WSGI Lite (the protocol).
Yeah, it's a little confusing. I get that now. When I was first writing the code, I called it "WSGI 2", and the decorators were "@wsgi2" and "@wsgi1", instead of "@lite" and "lighten()". I was even having the decorators change the "wsgi.version" environment key from (1,0) to (2,0) and back.
However, as the work progressed, the versioning didn't make a lot of sense to me, because in a sense, the core bits of the protocol weren't changing. Instead, there were a handful of small protocols that, put together, make a new way of doing WSGI. So I ended up deciding to call it "WSGI Lite", and dropped the version fudging.
But if you look at what is happening with the actual underlying protocol, I really am proposing something like a WSGI 2 here, or probably more like a 1.1. (Sort of.) The key point is that it's a protocol that can work in today's WSGI stacks, without needing a massive rewrite effort.
Granted, this means that if you have some pet gripes with WSGI, then Lite may or may not be able to solve them. A couple people have approached me privately about those issues, and I'd like to start hashing them out on the Web-SIG shortly.
But in the meantime, I'd like to take the rest of this article to lay out just what (and why) WSGI Lite, the protocol, is. (As opposed to wsgi_lite, the proof-of-concept implementation of the protocol.)
Why A New Protocol?
Because WSGI rots your brain.
Or, to put it less dramatically, it is damn near impossible to write correct WSGI middleware because there are too darn many things to think about.
In the Reddit thread about Armin's article, one person posted a bunch of links to the various patches they had to do to a piece of WSGI code in order to make it work correctly with various corner cases in the protocol, as bugs cropped up in interaction with other WSGI code.
And I took one quick look at one of those patches, and saw that it still had bugs.
Granted, it was a resource-leak bug, but that's not the point. It shouldn't be so frickin' easy to make that kind of mistake. (And the author was not exactly a newbie to either WSGI or web programming.)
And as I started writing my proof-of-concept (for what I originally thought of as "WSGI 2" rather than "WSGI Lite"), I discovered all kinds of other mistakes that people could make in their middleware, that had never even occurred to me before.
Even Ian Bicking, author of WebOb, realized after reading the WSGI Lite docs that WebOb contained a latent bug I described there!
So, something has to be done. WebOb and Werkzeug are great libraries, but if libraries could solve the problem, it would already be solved. That's why wsgi_lite (the library), is really just a test bed for WSGI Lite, (the protocol).
And the aim of WSGI Lite is not to solve all WSGI 1 problems, nor even the entire subset of WSGI 1 problems that can be addressed in a reasonably performant, backwards-compatible way using a pair of decorators.
Rather, the aim is to eliminate certain key obstacles to solving those problems.
Protocols, WSGI, and Game Theory
Back when I first proposed the idea that became WSGI (late 2003), the goal of the Web-SIG was to define standard "request" and "response" objects for the standard library.
So my counter-proposal to instead define a protocol, and not actually put any code for the protocol into the standard library, may have seemed a bit loopy to some folks. Perhaps a bit like, "let's solve this problem by not solving this problem!"
But the reason that I did it -- and the reason it ended up working so well that damn near every dynamic language ends up more-or-less cloning WSGI these days -- is because of game theory.
Essentially, there was never any serious chance that a bunch of web framework developers with investment in existing APIs were ever going to get together and agree on the One True Request and One True Response: there were just too many differences in fundamental approaches, and way too much opportunity for bikeshedding.
In game theory terms, you could say there was no Schelling Point. As Wikipedia puts it:
Consider a simple example: two people unable to communicate with each other are each shown a panel of four squares and asked to select one; if and only if they both select the same one, they will each receive a prize. Three of the squares are blue and one is red. Assuming they each know nothing about the other player, but that they each do want to win the prize, then they will, reasonably, both choose the red square. Of course, the red square is not in a sense a better square; they could win by both choosing any square.
In other words, in trying to design the One True Request and One True Response, there was no single obvious "square" to choose: everything was up for grabs, so nobody could win the "prize" (i.e., the benefits of having a One True anything in common).
So what I did with my WSGI proposal was deliberately create a Schelling Point: a single red square in a board full of blues.
And the way that I did it, was to specifically remove any semblance of an API that would make WSGI look like another blue square.
Voila: the Web-SIG was able to shift from discussions about what color to paint the bikeshed, to substantive discussions about the guts of HTTP and what requirements we had for interfacing with it.
Now, notice that I'm not saying that I came up with WSGI by myself and I was a genius. What I'm saying is, I gave the Web-SIG something to collaborate on, instead of something to compete over.
Let me repeat that: something to collaborate on, instead of something to compete over.
I could not have written the WSGI PEP by myself: I didn't have nearly enough information. But the Web-SIG, in collaboration mode, could.
So what does all this have to do with WSGI Lite?
Well, once again, the idea is to create a collaborative Schelling Point: a protocol, rather than an API. Because, once again, no one can agree on The One True WSGI Wrapper, when all we have are competing implementations with distinct APIs.
Granted, I may have shot myself in the foot this time, by starting with a proof-of-concept library rather than a PEP explaining the protocols!
Unfortunately, due to the nature of the requirements, I couldn't be sure the protocols would work without prototyping an implementation first, and still can't be sure the protocols really work without some community testing. (And the shape of the protocols themselves evolved considerably over the last three days of implementing, documenting, realizing something sucked, then fixing it and trying again!)
But what are these protocols exactly? What do they do, and why are they important?
The First Protocol: Calling Convention
The WSGI Lite protocol consists of a few basic elements working together:
- A revised calling convention and return protocol
- A server API extension for resource closing
- An "argument binding" protocol
The first of these things is something that's been proposed for a long time, and there seems to be fairly widespread consensus that a Rack-style calling convention is a good idea. WebOb, for example, already has some APIs that work on that calling convention, and I've never heard anybody saying that calling convention was bad, or that the current WSGI convention is better.
(Actually, the closest thing I've seen to somebody saying that, would be in the Hacker News thread about yesterday's article: somebody thought that WSGI Lite forces async code to use greenlets. But that's a mistake, because WSGI Lite only requires greenlets or threads for code that uses write(). WSGI Lite response bodies can still be produced just as asynchronously as a standard WSGI response body can.)
Anyway, so, the first protocol is well-known to WSGIans, and largely uncontroversial, hence the "uhh.. don't we already have that?" reaction from some quarters. What's been lacking is a co-ordinated way to move forward on that.
To put it another way, since that protocol lacks any "official" status or name, it's not really possible to use it as a Schelling Point of co-ordination between users and library authors. Ian can't point to WebOb and say, "WebOb lets you use the [thingy] protocol", instead, he has to say, "WebOb is cool, you should use it." Meanwhile, Armin is over there on the other side of the room, saying, "Werkzeug is cool, you should use it" too.
Meanwhile, the poor user is left in the middle of the room, scratching his or her head and going, "Uh, so what should I use now?", with respect to any "enhanced WSGI" APIs.
So, as far as this first sub-protocol is concerned, the ultimate point of WSGI Lite is going to be to nail down and "bless" a detailed and specific flavor of the calling protocol, to provide that co-ordination point for libraries to say what they offer to people, and for people to make choices about using them.
I seriously doubt that this is a very controversial proposal. After all, many people have said they want this calling protocol, and some leading WSGIans (hm, that term even has the word "Ian" in it!) have actually implemented more or less that protocol in their libraries.
What's more, people have been asking me to do something about getting this protocol "out there", reflecting their subconscious realization that a Schelling Point is indeed needed to do this, and that I'm the most obvious "red square" for co-ordination where WSGI is concerned.
So be it.
That's why Armin's article finally pushed me to actually implement something... and that's when I discovered the need for the other two sub-protocols in WSGI Lite.
The Second Protocol: Resource Management
See, as I was writing the decorators (called @wsgi2 and @wsgi1 at the time), I quickly began to notice that the "close()" part of WSGI was even more of a problem than I previously thought.
I won't go into detail here about the specific problems, or the protocol itself, as they're both laid out in the README file for the wsgi_lite library. Suffice to say here that under plain WSGI 1, resource closing is fragile because any one piece of middleware can inadvertently break the close() chain. This is likely more of a problem for WSGI code running on non-refcounting Pythons, but it can cause headaches even on CPython.
So, in order to solve that problem, I created a new resource closing protocol that allows applications to close multiple resources and to bypass broken WSGI 1 middleware.
This, I also expect to be a fairly uncontroversial protocol proposal. The problem it addresses is not widely understood, nor is there a big popular push for it, but it's an annoying little problem that can bite you in the butt and make debugging difficult, especially on "alternative implementations" of Python.
However, as I began trying to use this new protocol, and writing the early documentation for it, I discovered even more problems with WSGI!
Specifically, I noticed that it was damn hard to document my new closing protocol in such a way that it could actually be used correctly without having to learn even more arbitrary rules about what to call when and where to fetch it from.
Indeed, I ended up with something that looked just as hard to get right, as WSGI middleware was in the first place!
And when I looked at it more closely, I saw two things that were going on.
The first, was that most people don't realize when you pass a WSGI environment to a WSGI app, it's not yours any more. The application is allowed to clear it, put junk in it, or whatever. So you absolutely cannot use that environment dictionary once you pass it on.
And this put the closing protocol in a bit of a bind, because the closing protocol needed to be called late in an app or piece of middleware, but retrieved early.
So, if you wrote the natural thing, the obvious thing, and pulled the closing key out of the environment at the point nearest where you were going to use it, then your code would have a latent bug in it.
And that's just evil.
This is the point at which I realized just how much brain rot the bare WSGI protocol has in it: there are lots of little things like this that will bite you in the butt, punishing you for doing the simple, obvious, straightforward thing.
And so that's when I realized that I needed...
The Third Protocol: Argument/Extension Binding
See, the new resource closing protocol I came up with is not the only WSGI environment extension out there -- there are lots of others. But they share a few potential issues in common:
- Being pulled out of the environ at a point where they're no longer valid,
- Having to write boilerplate to check for their existence, and fall back to something else, and
- Mutually-incompatible decorators provided by libraries to fix problems 1 and 2!
That is, even if a library provides decorator support for its particular WSGI extension, you generally can't use more than one of them at a time.
And so, the argument/extension binding protocol fixes this by providing an argument-level decoration protocol, to replace function-level decoration as the way to solve problems 1 and 2 in existing libraries.
The idea here is that instead of trying to use a session decorator from library 1 and an authentication decorator from library 2, you can just use a single decorator with two keyword arguments.
This idea evolved gradually, as I first wrote a "@with_closing" decorator specifically to address the resource closing issue, and then noticed what having lots of decorators like that would lead to. (And, sure enough, existing WSGI library wrappers have mutually-incompatible decorators for these purposes.)
Anyway, the argument binding protocol is basically a way to map keyword arguments to things that are derived in some way from the request environment. It could be a parsed form, a session, an authenticated user, a cart... you name it, you can have it.
In other words, the idea is once again to have a Schelling Point where libraries can be used collaboratively, instead of having to compete for users. It also makes it easier for individual users to write one-offs for their particular application. Writing a WSGI Lite argument binding is a few lines of code over top of whatever kind of request-based object(s) you have in your application, and you can then use them anywhere.
Or... and this is the bigger point: you can then split out your nifty cart or session or whatever, and make it available to other users as a library, without needing to know dip about decorators.
And, again, it's a co-ordination point, because you can say, "Here's my new session library - and it supports WSGI Lite argument binding." The binding protocol becomes something that libraries have in common, allowing users to focus on functionality instead of screwing around with which color bikeshed the decorator is.
Now, if you're not clear on the technical bits of what I'm on about here, the argument binding protocol is explained on the wsgi_lite homepage. The basic idea, though, is that you can call @lite(keyword1=binding_rule1, keyword2=binding_rule2...), and bind your function's keyword arguments to objects like sessions, requests, carts, users, and arbitrary WSGI extensions. The binding rules can be strings, callables, or sequences of the above, and the first rule that yields a result from the environment gets passed in to your function as a keyword argument. And if no rule for that keyword yields a result, the keyword doesn't get passed to your function.
So, this allows you to use normal Python function argument defaults to fall back on if you don't get the object you're looking for, and it allows you to get a standard Python error when one of your arguments goes missing: you don't need to write code to check for the argument and raise your own error when it's missing.
Under Python 3, it might be that the decorator could just use argument annotations to do the same thing (instead of duplicating argument names in the decorator) but I haven't tried that yet.
The point, though, is that by defining a binding protocol, you can use it in lots of different ways. Given the protocol I've specified, you could go out there right now and write yourself a Python 3 decorator that looks for binding rules in argument annotations, and applies them according to the rules of the binding protocol. And users of your decorator would immediately be able to use anybody else's session, request, cart, or whatever other WSGI Lite argument bindings were out there, in their Python 3 argument annotations.
Likewise, you can, right now, write yourself a binding for your session, request, cart, or whatever objects, and be assured that people will be able to use them with any decorator (or other tool) that uses "WSGI Lite binding rules".
Even in tools that haven't been thought of yet, let alone implemented.
And that's the power of a protocol, versus a mere library.
Now, all in all, the argument binding sub-protocol is perhaps the most potentially-controversial part of the WSGI Lite protocol suite. It's totally new, and as far as I know, unprecedented in the WSGI world. And if you're the developer of a heavyweight WSGI library or framework, it might not seem very important to you.
However, the point of it isn't to re-solve a problem you've already solved for your own library, or to replace your API. Rather, it's a way to allow people to make smaller libraries, by 1) shrinking the unit of reuse to the argument, rather than the decorator, and 2) lowering the entry barrier for a library to be written, by removing the need for a big API or a complex decorator.
And of course, it also lets you add binding rules on top of your existing big library, to offer users an enticement or "gateway drug" to using the rest of your library. You can, in effect, begin advertising your library as a catalog of bindings, rather than trying to get people to drink all of your library's cool-aid at once.
So, Where Do We Go From Here?
Well, at this point, the protocols are out there, but they don't have any "official" standing, except for my attempt at declaring them "red squares". That is, they're potential points of co-ordination, and they have my backing as a potential "way forward" for the next-generation of WSGI.
But this doesn't mean they won't change between now and any real "official" status (like a PEP).
My original effort at WSGI -- originally called "WCI" -- was not very much like WSGI at all. The fundamental idea in WCI and WSGI was the same, sure, but the final implementation was very different.
And the same thing might happen with WSGI Lite, too.
Indeed, I've already gotten emails from a couple of big WSGIans about potential changes to WSGI Lite to fix other problems... and so some things may well happen there.
Mostly, though, what I want to do with WSGI Lite is create protocols that allow lightweight, collaborative solutions to those problems.
For example, rather than trying to fix all of the warts in "wsgi.input" in the core Lite protocol, I'd rather see some proposals for bindings that people can use to fix those problems.
Instead of us trying, yet again, to create the One True Input Object!
Now, is that really possible with wsgi.input, or any of the other warts that people would like to see fixed in the "next generation" of WSGI?
I don't know.
But I think it's worth a shot at finding out. And if there are some clear wins to be had by tweaking the three Lite sub-protocols, or adding some others to the mix, I'm all ears.
These are things that need to be hashed out a bit before the protocols are PEPpable, and yes, perhaps a bit of API bikeshedding may be needed as well.
And you know what?
I'm kind of looking forward to it.
See you on the Web-SIG!