[Web-SIG] Standardized configuration

Ian Bicking ianb at colorstudy.com
Tue Jul 19 19:56:02 CEST 2005


Phillip J. Eby wrote:
>> In many cases, the middleware is modifying or watching the 
>> application's output.  For instance, catching a 401 and turning that 
>> into the appropriate login -- which might mean producing a 401, a 
>> redirect, a login page via internal redirect, or whatever.
> 
> 
> And that would be legitimate middleware, except I don't think that's 
> what you really want for that use case.  What you want is an 
> "authentication service" that you just call to say, "I need a login" and 
> get the login information from, and return its return value so that it 
> does start_response for you and sends the right output.

Like I mentioned in my response to Chris, this kind of contract about 
return values is a difficult one to implement.  A "return 401 status" 
contract is pretty simple, in that it's vague in a way that fits with 
typical frameworks -- they all have a way of changing the status, and 
most have a way of aborting with that kind of error.

> The difference is obliviousness; if you want to *wrap* an application 
> not written to use WSGI services, then it makes sense to make it 
> middleware.  If you're writing a new application, just have it use 
> components instead of mocking up a 401 just so you can use the existing 
> middleware.

Who's writing new applications?  OK... I guess a lot of people are.  I 
may be more focused on retrofitting compared to other people.

> Notice, by the way, that it's trivial to create middleware that detects 
> the 401 and then *invokes the service*.  So, it's more reusable to make 
> services be services, and middleware be wrappers to apply services to 
> oblivious applications.

Yes, this would be the single-middleware-multiple-service model.  I 
don't understand exactly how services work myself, so I can't write 
that, but I'm certainly interested in examples.  Well... I'll throw out 
one just for the heck of it:

class ServiceMiddleware(object):

     def __init__(self, app):
         self.app = app
     def __call__(self, environ, start_response):
         context = environ['webapp.service_context'] = ServiceContext()
         # You could also do some thread-local registering of this
         # context at this point
         def replacement_start_response(status, headers):
             status, headers, writer = context.start_response(
                 start_response, status, headers)
             return writer
         app_iter = self.app(environ, start_response)
         return context.app_iter(app_iter)

class ServiceContext(object):
     def __init__(self):
         self.services = []
     def get_service(self, name):
         ... something I don't understand ...
         self.services.append(service)
         return service
     def start_response(self, start_response, status, headers):
         for service in self.services:
             if hasattr(service, 'munge_start_response'):
                 status, headers = service.munge_start_response(status, 
headers)
         return start_response(status, headers)
     def app_iter(self, app_iter):
         return app_iter


And ServiceContext should also ask services if they care to munge_body 
or something, and then pipe all calls to the writer and all the parts of 
app_iter into that service if so.  And it should let services catch 
exceptions.

>> I guess you could make one Uber Middleware that could handle the 
>> services' needs to rewrite output, watch for errors and finalize 
>> resources, etc.
> 
> 
> Um, it's called a library of functions.  :)  WSGI was designed to make 
> it easy to use library calls to do stuff.  If you don't need the 
> obliviousness, then library calls (or service calls) are the Obvious Way 
> To Do It.

I do use library calls when possible; and even when not possible I 
(generally) try to make the middleware as small as possible, just 
handling the logic of the transformation.  But mostly libraries don't 
need to be discussed here, because they are simple ;)

There are perhaps a few places where standardization of some library 
manipulations would be useful.  E.g., get_cookies() and 
parse_querystring() in paste.wsgilib 
(http://svn.pythonpaste.org/Paste/trunk/paste/wsgilib.py) could be 
standardized, and then WSGI-based libraries that were interested in the 
request could probably retrieve the frameworks' parsed version of URL 
and cookie parameters.

>>> Really, the only stuff that actually needs to be middleware, is stuff 
>>> that wraps an *oblivious* application; i.e., the application doesn't 
>>> know it's there.  If it's a service the application uses, then it 
>>> makes more sense to create a service management mechanism for 
>>> configuration and deployment of WSGI applications.
>>
>>
>> Applications always care about the things around them, so any 
>> convention that middleware and applications be unaware of each other 
>> would rule out most middleware.
> 
> 
> Yes, exactly!  Now you understand me.  :)  If the application is what 
> wants the service, let it just call the service.  Middleware is 
> *overhead* in that case.

Well, no, I don't really understand you, but if it makes you feel 
better... ;)

For instance, applications may be interested to know there's a piece of 
middleware that will catch unexpected exceptions.  For instance, it 
might see that and reraise unexpected exceptions instead of providing 
its own error report.  But it's not "overhead" or something the 
application wants handled lazily.  It's just useful information about 
the environment.

>>> I hope this isn't too vague; I've been wanting to say something about 
>>> this since I saw your blog post about doing transaction services in 
>>> WSGI, as that was when I first understood why you were making 
>>> everything into middleware.  (i.e., to create a poor man's substitute 
>>> for "placeful" services and utilities as found in PEAK and Zope 3.)
>>
>>
>> What do they provide that middleware does not?
> 
> 
> Well, some services may be things the application needs only when it's 
> being initially configured.  Or maybe the service is something like a 
> scheduler that gives timed callbacks.  There are lots of non-per-request 
> services that make sense, so forcing service access to be only through 
> the environment makes for cruftier code, since you now have to keep 
> track of whether you've been called before, and then do any setup during 
> your first web hit.  For that matter, some service configuration might 
> need to be dynamically determined, based on the application object 
> requesting it.
> 
> But the main thing they provide that middleware does not is simplicity 
> and ease of use.  I understand your desire to preserve the appearance of 
> neutrality, but you are creating new web frameworks here, and making 
> them ugly doesn't make them any less of a framework.  :)
> 
> What's worse is that by tying the service access mechanism to the 
> request environment, you're effectively locking out frameworks like PEAK 
> and Zope 3 from being able to play, and that goes against (IMO) the 
> goals of WSGI, which is to get more and more frameworks to be able to 
> play, and give them *incentive* to merge and dissolve and be assimilated 
> into the primordial soup of WSGI-based integration, or at least to be 
> competitors for various implementation/use case niches in the WSGI 
> ecosystem.

How is being request-oriented locking them out?  To me this mostly seems 
like an aesthetics and implementation discussion; mapping from one to 
the other doesn't seem that hard.  If you map from request to service, 
you do it by putting a little proxy in the request that calls the 
service.  If mapping from service to request, you keep the request 
around somewhere (threadlocal or something) and the service is 
implemented in terms of things found in the request.

> See also my message to Chris just now about why a WSGI service spec can 
> and should follow different rules of engagement than the WSGI spec did; 
> it really isn't necessary to make services ugly for applications in 
> order to make it easy for server implementors, as it was for the WSGI 
> core spec.  In fact, the opposite condition applies: the service stack 
> should make it easy and clean for applications to use WSGI services, 
> because they're the things that will let them hide WSGI implementation 
> details in the absence of an existing web framework.

With perhaps a couple exceptions, I don't think WSGI is that bad for the 
application side.  Not that you'll write to WSGI directly most of the 
time, but if you do it's still not that bad.  WSGI is dumb and crude, 
which is a feature.

-- 
Ian Bicking  /  ianb at colorstudy.com  /  http://blog.ianbicking.org


More information about the Web-SIG mailing list