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:
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.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.
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.
As Perl 5 lacks the thread model and composable concurrency objects of Perl 6, I am not sure how you would back port some of these bits to PSGI. It is an interesting problem to consider, though.
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 fromCONNECT
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…
A few comments:
1. In some ways the middleware bits are irrelevant to the P6SGI spec. The important bits to the spec are naming how the application ultimately communicates with the server. What happens between the middleware and application is generally the business of the application author and administrators. Whether middleware implements some fancy protocol on top of HTTP or the application does it is not that important.
In fact, I have one middleware already written for testing that implements a PSGI-to-P6SGI API mapping, which allows applications that do not even obey the P6SGI spec to be run. As long as what the server is given to run adhere's to the application side of the spec, what happens inside it is irrelevant.
2. 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 need to put more thought into how SSL/TLS impact things, but I suspect it will change how this works only very slightly. Since servers stream the input and the application streams the output, implementing this in middleware may be a simple matter of applying a map to the streams. And I intend to provide an HTTP/2 and WebSocket middleware as part of the reference implementation to do just this.
There may still be some reason to implement to implement psgix.io, but I'm not sure what it is yet.
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.
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.