P6SGI: More of a Journey than a Destination

When I started working on P6SGI, I thought, "Hey, I'll just update PSGI to use Perl 6, take advantage of some async data structures, and be done." That is not how this process has gone down. First, I learned that I needed to know more about Perl 6. Then, I found that I need to know more about HTTP/1.1 and more about PSGI. Most recently, I have been researching HTTP/2, Mojolicious, WebSockets, Akka, and a whole pile of other things.

So, here's the progress report on thing that have changed in the last week or so on our way toward a complete P6SGI standard, which is still a ways off.

Standardizing the application life cycle. I count this as the most significant change this week. An application server is now expected to call the application as soon as the request headers have been read. That is, it should not wait until the entire request body has been read from the incoming socket. This makes it possible for applications to respond to Expect: 100-continue headers. Furthermore, the application server should send the response headers back at the earliest opportunity. Therefore, it is not mandated, but strongly encouraged that application servers always run concurrently with their applications.

Using Supply for all the things. The p6sgi.input and p6sgi.errors streams have been replaced with Supply objects. The input stream comes from the server as a series of Blob objects while the application and middleware write Str objects to the error stream.

Also the output stream has been expanded to allow Lists of Pairs so that trailing headers or multiple-headers (as may occur handling 1xx responses) may be sent. In PSGI, this was handled just by writing out the headers as part of the content directly. This is a problem, however, if the application server wants to implement HTTP/2 or WebSockets as they have special framing requirements. This way the application needs to know a bit less about the protocol and can focus on the content more.

Another consequence of input streaming is that buffering is now gone from the spec. It will come be back as an extension of some kind to handle application servers that are inherent buffered (like CGI) or those that provide it at the server for convenience. Details TBD.

Additional promises have been added. I have specified new Promises to be kept by the server. The p6sgi.ready Promise is kept when the server has tapped and is ready to receive the output, just in case the application wants to deliver a live Supply for some reason. The p6sgix.header.done Promise is kept when the server has sent the headers and the p6sgix.body.done Promise is kept when the server has finished sending the body.

Extensions have been ported over from PSGI. I have ported the extensions of PSGI over without much modification or thought. These will likely change. The p6sgix.io extension, in particular, is controversial, problematic, and might not even be necessary. (Partly discussed below.)

Backpressure extensions have been added. I have added a set of "backpressure" extensions (also providing a Supply) that allow the application to monitor when the output socket is blocking. This allows an application that is sending a long stream to a slow connection to pause processing while waiting packets to clear the intertubes between peers. (Though, implementation of these will wait until non-blocking I/O is implemented.)

Protocol header extension. I am actively thinking through the first draft of this issue as I finish this post. One problem with PSGI is that is has a strong assumption that your application only cares about some flavor of HTTP/1.1. This is hardly likely in the modern web and a weakness of PSGI. PSGI provides a basic extension, psgi.io, that aims to allow the application to implement more advanced subprotocols. This has at least two major problems:

  1. The details of psgix.io is implementation-specific. This means that an application using it is tied to a specific implementation or has to provide multiple implementations.

  2. Why would you want protocol implementation details in the application? The reason you use an application server is so that the server takes care of those details on your behalf. The application should not be responsible for implementation something like WebSocket or HTTP/2 or Comet or whatever.

To address this issue, I am working on a mechanism that would allow the application server to specify additional protocols supported and then allow the application to request that the server negotiate the handshake for these. So far, this works for protocols like HTTP/2 or WebSocket, where the application can detect a client request to upgrade a connection for such (by the presence of an upgrade header). In those cases, the application could check an environment variable to see which protocols the server supports. If the desired protocol is supported, the application may tell the server to negotiate the handshake for that server and take any other actions required by including a special header, possible P6SGIx-Upgrade, which specifies the desired upgrade.

It is my hope that we can encourage servers to implement specialized protocols by providing upgrades like this or other forms of server-application communication rather than relying on a kludge like psgix.io.

In conclusion, it is my hope that more details of protocol handling can be kept to the server and the applications can focus more directly on the details of message content. It is my hope as well that through better asynchronous communication and concurrent operation of application and application server, we can resolve many of the weaknesses of PSGI and gain an overall more flexible and useful standard for the modern web.

P.S. I will be at the Pittsburgh Perl Workshop in a couple weeks and giving a talk on the P6SGI process and where we're headed with it.

9 Comments

Ooh that's looking much better now than last time we spoke :) Nice work.

I wonder to what extent any of these things can be backported back to Perl 5, to allow P(5)SGI to handle the things you're newly adding to P6SGI.

The psgix.io refactor can almost certainly be backported, albeit with a clumsier API. A bigger issue is that then you’ll have to get the servers to support it.

Totally amazing you stuck with this, heard all the trouble and actually put the effort into figuring stuff out. It does look a lot more like what I wish we had for Perl5 ;)

Not all of this could be back ported to P5SGI but my understanding is there is some work being done on supporting some sort of supply like feature for P5, although perhaps not backcompat down to 5.8.8

Looking forward to seeing where this goes!

FWIW regarding psgix.io I was of two minds on it as well when I was proposing changes for an upgraded P5SGI. Part of me wanted to replace it with a minimum specification, or duck type, maybe something based on IO::Socket::INET since a survey of PSGI handlers on CPAN suggested that was the most common interface exposed (and those that did not expect that object, mostly exposed something somewhat similar).

I'm not sure how much of this variation stemmed from all the various prior art on CPAN around this type of bi directional socket and if the core stuff for sockets in P6 is common enough that everyone would just use it anyway. In any case I think many of the other supplies or promises you suggest could cover a lot of this. FWIW my single use case for psgix.io for Catalyst was to upgrade to a web socket protocol. If there were other and better approach to that, then the need to standardize psgix.io gets smaller (although I still don't think it would be a bad idea if there was a required 'duck type' for the object exposed, just for promoting interoperability.

FWIW the survey I did of what psgix.io was for most of the common servers are some variation of IO::Socket (either IO::Socket::INET or a thin wrapper on it). I don't think it would be that hard to define a minimum duck type for the interface, and perhaps an allowed extension to it when the underlying server is non blocking. It probably would not be too hard to patch existing

Something that occurs to me here:

For background, I am the author of Plack::Middleware::MockProxyFrontend. That module uses psgix.io to unwrap the SSL layer from CONNECT requests and then process the inner connection again. (See also)

So while the low abstraction level of psgix.io doesn’t make sense to expose to applications, it does make sense to expose to middlewares – which can then implement protocols on top of it so that not each server needs to reimplement every protocol, nor each application.

So a handshake protocol makes sense as the interface through which other wire protocols are exposed to the application, but a lower-level interface might be the more desirable option for middlewares.

It would be nice if these use cases could be reconciled somehow so that e.g. a server that implements HTTP/2 can handshake this with the application, but a middleware that implements WebSockets could also be loaded, with both integrating transparently at the handshake protocol level. As far as the app is concerned, it would ideally make no difference whether it’s running on a server that implements both HTTP/2 and WebSocket itself, or on a server with support for only one of them and the other being provided by a middleware, or a server that supports neither but they’re both provided by a middleware, or even separately by two middlewares.

So long as all this can be fit into a reasonably simple design…

Whether middleware implements some fancy protocol on top of HTTP or the application does it is not that important.

From the perspective of the server, that’s true. From the perspective of the (codebase of the) application, it’s the other way around. Basically middlewares are always part of “the other side”. The protocol design ought to account for that, and I think PSGI’s – for all its shortcomings (in fact partly because of them, probably) – does, or tried to.

In P6SGI it is possible to implement HTTP/2 (at least the h2c version) and WebSocket without making any use of psgix.io right on top of an HTTP/1.1 server.

I like what I’m hearing. :-) I realise now that I may have been unclear: I was not arguing, nor advocating psgix.io specifically. It just occurred to me (based on a use case for that extension in PSGI) that there ought to be an interface close enough to the wire that middlewares can implement extra protocols – not just servers. So I wanted to point out the requirement to make sure it’ll be considered. I was not making any claim as to whether it has been covered – I’m not very firm on the Perl 6 concurrency stuff beyond the basics and haven’t looked at the recent P6SGI revisions either. I would not be surprised if the Perl 6 concurrency stuff allows providing that capability natively.

Leave a comment

About Sterling Hanenkamp

user-pic Perl hacker by day, Perl 6 module contributor by night.