>> I've mentioned this before, but I still think it might be a good idea to >> standardize on a something quite different from JSGI, an event-oriented >> HTTP interface, more similar to Node's.
> This seems a promising approach. With the popularity of Comet, Web > Sockets, etc, I think it's better to start with a greenfield design > based on the Web application use cases a modern HTTP server interface > needs to support, rather than starting from JSGI and trying to make > incremental changes.
My intent with this was that JSGI and Node's API could co-exist as standards at different levels in the stack. Some servers could choose to implement Node's API, and one could use a Node-JSGI adapter, and others (like those running on GAE that couldn't really implement Node's API properly) could directly implement JSGI. Middleware may choose to target JSGI due to the ease of doing so, but code could still target the evented interface when needed. The nice thing is that we already have most in this place. Obviously we have an implementation of the Node API, and I don't think it would be too onerous to get another. We already have a JSGI-Node adapter [1], and we already have existing implementations of JSGI 0.3.
With this approach, there may still be some minor adjustments to be had. Having implemented the JSGI-Node adapter, I definitely feel the need for JSGI support for asynchronously reading the request body (perhaps mirroring the response body), and the character encoding in Node felt awkward and ethnocentric to me.
> Some of the more mature SSJS platforms have experience with what's > required here, while node has a powerful platform but no middleware > ecosystem to speak of yet. If we're going to end up with something > that works for everyone, it would help to start by finding what the > requirements are.
IMO, the encapsulation of asynchronicity with promises is part of what makes it so easy to pass around requests and responses in JSGI, and has made it possible to build a good library of JSGI middleware. I've certainly enjoyed using it, the Pintura framework has a stack of about a dozen middleware, easily handling asynchronous Comets applications on top of them. --
JSDB (www.jsdb.org) has been able to implement a web server for a few years now, and I've learned a few things about JSGI-type functions.
1. There's no one best way to do it. Sometimes, you want to cache the response body before you generate the response code and header. Sometimes, you don't. Sometimes, you can implement the body generator as an anonymous function. Sometimes, you'd rather not. Sometimes you want to transfer from disk to socket as fast as you can, without taking the time to create a JS String.
2. It can be really, really hard to debug server-side code, especially when the code would make more sense if it were written in Scheme. By separating the web server function from the CGI function, you're giving yourself the opportunity to test them separately. The interface wants to be simple (like Stream.write() on one end, and Stream.read() on the other). Anything fancy, and your test harness will work differently than your actual server.
3. HTTP can be a really simple protocol. One JSGI-like function form I use sometimes looks like this. It assumes the HTTP server, having found the function, already returned 200 OK and some minimal headers. I offer it as a demonstration of an effective pattern for using a Stream to communicate between a JSGI-like function and a HTTP server.
function foobar(client) { client.writeln("Content-type: text/html") client.writeln("Content-length: 12") client.writeln() client.writeln("Hello, world")
}
4. Then again, sometimes you want to assume less about what your HTTP server has already said to the client, and you don't know what or how much you're going to send until after you've done the processing. Then, returning a response object is the way to go.
Also, I already have a Stream class (http://www.jsdb.org/ jsdbhelp.html#Stream) and I'd hate for it to conflict with CommonJS's Stream.
Keep It Simple and Successful,
Shanti
On Jan 28, 5:53 pm, "Isaac Z. Schlueter" <i...@foohack.com> wrote:
> JSDB (www.jsdb.org) has been able to implement a web server for a few > years now, and I've learned a few things about JSGI-type functions.
> 1. There's no one best way to do it. Sometimes, you want to cache the > response body before you generate the response code and header. > Sometimes, you don't. Sometimes, you can implement the body generator > as an anonymous function. Sometimes, you'd rather not. Sometimes you > want to transfer from disk to socket as fast as you can, without > taking the time to create a JS String.
> 2. It can be really, really hard to debug server-side code, especially > when the code would make more sense if it were written in Scheme. By > separating the web server function from the CGI function, you're > giving yourself the opportunity to test them separately. The interface > wants to be simple (like Stream.write() on one end, and Stream.read() > on the other). Anything fancy, and your test harness will work > differently than your actual server.
> 3. HTTP can be a really simple protocol. One JSGI-like function form I > use sometimes looks like this. It assumes the HTTP server, having > found the function, already returned 200 OK and some minimal headers. > I offer it as a demonstration of an effective pattern for using a > Stream to communicate between a JSGI-like function and a HTTP server.
> 4. Then again, sometimes you want to assume less about what your HTTP > server has already said to the client, and you don't know what or how > much you're going to send until after you've done the processing. > Then, returning a response object is the way to go.
> Also, I already have a Stream class (http://www.jsdb.org/ > jsdbhelp.html#Stream) and I'd hate for it to conflict with CommonJS's > Stream.
> Keep It Simple and Successful,
> Shanti
> On Jan 28, 5:53 pm, "Isaac Z. Schlueter" <i...@foohack.com> wrote:
Ok. Seems like we've got a few different ideas here, and in the interest of moving this towards some kind of resolution, I thought we could have a show of hands about which direction you think makes the most sense regarding the open questions:
Regarding foreachable-with-promise vs body-is-a-stream, Kris and I have our preferences, and it's fairly trivial to do anything in one that you can do in the other, so as far as I can see, it's just a matter of getting some consensus around which one should be the spec and which one should be sugared on with middleware or by the server implementation.
Anything other open questions you think should be on that list?
I left the "what is a stream, exactly" question open for now, because I'd like to resolve whether or not it matters before we start debating the finer points. If there's a reasonable quorum in favor of that approach, we can move to working out exactly what the interface should look like. (I think it's pretty close now, just don't want to prematurely shut down that line of investigation.)
On Feb 1, 2:29 pm, "Isaac Z. Schlueter" <i...@foohack.com> wrote:
> Ok. Seems like we've got a few different ideas here, and in the > interest of moving this towards some kind of resolution, I thought we > could have a show of hands about which direction you think makes the > most sense regarding the open questions:
> Regarding foreachable-with-promise vs body-is-a-stream, Kris and I > have our preferences, and it's fairly trivial to do anything in one > that you can do in the other, so as far as I can see, it's just a > matter of getting some consensus around which one should be the spec > and which one should be sugared on with middleware or by the server > implementation.
+1 for stream.
> Anything other open questions you think should be on that list?
> I left the "what is a stream, exactly" question open for now, because > I'd like to resolve whether or not it matters before we start debating > the finer points. If there's a reasonable quorum in favor of that > approach, we can move to working out exactly what the interface should > look like. (I think it's pretty close now, just don't want to > prematurely shut down that line of investigation.)
I actually think this is hampering our efforts by not having stream specified. I'd like to do a new proposal for Streams which leverages the latest async discussions and learning. I'll post this in a day or so.
So I guess I need to actually make a proposal for forEach with promise and the rationale for it, so we know what we are voting on (to date it has just been some discussions). Also to clarify, I definitely want the body to be a stream. My assertion has been that forEach with promise is consistent simple mechanism for making the body a stream. So to be more accurate it seems that the if we were to vote with the information below it would be foreachable-with-promise-as-the-body-stream vs something-else-as-the-body-stream (and it seems a little poorly posed to be pitting a specific mechanism against a generic idea, if that is the intent). Anyway, I'll try to get a proposal up soon. Kris
> Ok. Seems like we've got a few different ideas here, and in the > interest of moving this towards some kind of resolution, I thought we > could have a show of hands about which direction you think makes the > most sense regarding the open questions:
> Regarding foreachable-with-promise vs body-is-a-stream, Kris and I > have our preferences, and it's fairly trivial to do anything in one > that you can do in the other, so as far as I can see, it's just a > matter of getting some consensus around which one should be the spec > and which one should be sugared on with middleware or by the server > implementation.
> Anything other open questions you think should be on that list?
> I left the "what is a stream, exactly" question open for now, because > I'd like to resolve whether or not it matters before we start debating > the finer points. If there's a reasonable quorum in favor of that > approach, we can move to working out exactly what the interface should > look like. (I think it's pretty close now, just don't want to > prematurely shut down that line of investigation.)
On Feb 1, 3:45 pm, Kris Zyp <kris...@gmail.com> wrote:
> Also to clarify, I definitely want the > body to be a stream. My assertion has been that forEach with promise is > consistent simple mechanism for making the body a stream.
I see. It was not my intent to try to pit a general concept against a specific implementation, but rather to pit a mostly-settled implementation against another mostly-settled implementation. (That is, even saying "with promises" leaves open the question as to exactly what interface a "promise" exposes.)
From the discussions floating about here, it was my understanding that a stream is pretty close to the specification in http://github.com/isaacs/ejsgi, with perhaps some room for some more bikeshedding.
In addition, "stream as response body" is intended to exploit the similarity between the request.input, request.jsgi.error, and response.body. That is, they should all be the same kind of thing, with the same API for reading/writing/pausing/etc.
ANYway, looks like you're feeling prompted to write up what "foreachable+promise" means exactly, and mob is drawing up plans for a "stream" interface proposal, and everyone seems to be on board about doing some kind of async something, so things are looking good :)
> ANYway, looks like you're feeling prompted to write up what > "foreachable+promise" means exactly, and mob is drawing up plans for a > "stream" interface proposal, and everyone seems to be on board about > doing some kind of async something, so things are looking good :)
Agreed. General direction == good.
However, I'm going to throw a big of a wrench with a Streams discussion. Hopefully I can keep it as a separate discussion and not slow down this discussion. We all use the term Stream, but there is quite a difference in what it means when you start to talk about async, full/half duplex, flow control, events etc. I think it is time to really nail this element down as it is de-stabalizing other higher order functions.
On Feb 1, 4:48 pm, mob <m...@embedthis.com> wrote:
> We all use the term Stream, but there is > quite a difference in what it means when you start to talk about > async, full/half duplex, flow control, events etc.
With that in mind, let's try to spec a simple generic API that can be as agnostic as possible as to the underlying architecture. IMO, the stream spec at http://github.com/isaacs/ejsgi is a pretty good start. ;)
> With that in mind, let's try to spec a simple generic API that can be > as agnostic as possible as to the underlying architecture. IMO, the > stream spec athttp://github.com/isaacs/ejsgiis a pretty good > start. ;)
Understand the ;-) But that does have some weaknesses with regard to flow control. I'll put my asbestos underpants on when I post the proposal.
On Mon, Feb 1, 2010 at 4:48 PM, mob <m...@embedthis.com> wrote: >> ANYway, looks like you're feeling prompted to write up what >> "foreachable+promise" means exactly, and mob is drawing up plans for a >> "stream" interface proposal, and everyone seems to be on board about >> doing some kind of async something, so things are looking good :)
> Agreed. General direction == good.
> However, I'm going to throw a big of a wrench with a Streams > discussion. Hopefully I can keep it as a separate discussion and not > slow down this discussion. We all use the term Stream, but there is > quite a difference in what it means when you start to talk about > async, full/half duplex, flow control, events etc. I think it is time > to really nail this element down as it is de-stabalizing other higher > order functions.
Glad to hear your opinion. I agree, one-way (in particular half-closed) streams are important to support. You probably already have some concrete ideas, but may I suggest looking at (if you haven't already) http://search.cpan.org/dist/AnyEvent/lib/AnyEvent/Handle.pm It's a little more specific ( for TCP streams) but I think it's a good API.
Isaac Z. Schlueter wrote: > On Feb 1, 4:48 pm, mob <m...@embedthis.com> wrote:
>> We all use the term Stream, but there is >> quite a difference in what it means when you start to talk about >> async, full/half duplex, flow control, events etc.
> With that in mind, let's try to spec a simple generic API that can be > as agnostic as possible as to the underlying architecture. IMO, the > stream spec at http://github.com/isaacs/ejsgi is a pretty good > start. ;)
I particularly spent a fair bit of time on the idea of a level 0 stream pattern that works abstractly of any implementation or which type of content comes out of the stream.
Also don't forget that high-level stream api with events and pauses won't make people trying to write very low level protocols that can switch between text and binary very happy.
Thanks for the links. I'll re-read them all to make sure.
I've got something in mind that evolves what we have. Works sync and async with events and flow control. It is a bit simpler than most of the prior proposals, but we'll see if it withstands the harsh glare of many eyes.
Here is my proposal for body streams as forEach-able objects returning promises (I tried to build upon and mix in some of the ideas from Isaac's EJSGI proposal): http://wiki.commonjs.org/wiki/JSGI/ForEachStreaming
Before we even try to compare non-forEachable streams with forEachable streams, I first wanted to make sure we are clear about something in the EJSGI proposal. This current design leads to buffering the entire response body for the most straightforward usage. This is a serious flaw. One can employ workarounds, but the most likely usage of EJSGI completely fails to meet its own design goal. One shouldn't have to resort to putting the response function in a setTimeout/queue function just avoid sinking one's own server. It is untenable to implement something with such negative consequences.
However, that being said, this is certainly a correctable issue with EJSGI. One simply needs to provide stream writing object through a callback, rather than through a direct constructor available on the request. The callback be can called by the server when it is ready to stream the response, and then response writing mechanism can begin. Such a structure is much easier to work with on the request input stream as well, because the reader calls the stream when it is ready to receive data, and it doesn't need to worry about missed events.
This correction actually would bring EJSGI closer in structure to forEachable streams. The crux differences to decide between would then basically boil down to whether the callback occurs through a forEach(writeFunction) that returns a promise, or whether it occurs through some startStream(writeObject) where writeObject holds the write and close methods. The functionality of either approach is identical, all the same streaming abilities: writing, closing, listening, pausing and resuming, can easily be achieved with either API. This would come down to simple API style preference, except for two important key advantages of the forEach design: forEach works with arrays. This not only makes it extremely easy to write very simple functions (since you can simply return an array), we should also be looking at how this design is going affect other parts of an application. As we discussed previously in this thread, it is very likely that one would use a similiar data structure to hold streaming objects that would end up being streamed in serialization. Being able to treat existing static arrays as a valid form of a streamed object data structure provides a much greater opportunity for code reuse. Compare the amount of existing code that uses JavaScript arrays to the amount of code that uses (or will ever use) EJSGI streams.
The second key advantage to forEach is that it is fully backwards compatible with JSGI 0.3. With a completely different API, we are forced to break compatibility or support two different paradigms at the same time, forEach and some other streaming API which induces unnecessary mental overload. I know some of you don't care about backwards compatibility, but the reality is that there are existing implementations, and the spec is in a much better position if can work with these implementations rather than telling them that they are now invalid.
Daniel Friesen pointed out that we do have already have a streaming proposal for I/O as well: http://wiki.commonjs.org/wiki/IO/B/Stream/Level1 Another possibility is that we follow this streaming API for JSGI. This may be a good idea for streaming. I would certainly prefer that our response bodies be as consistent as possible with other mechanisms. Having a streaming API that is inconsistent with both arrays and File I/O streaming seems like unnecessary fragmentation. However, I do think that having HTTP streaming match File streaming could be a red herring in terms of benefit. Matching these APIs certainly makes streaming files easier, but the reality is that application developers rarely need to actually implement file static handling. We implement it in a middleware or appliance provided by a framework or server, and/or setup a proxy to handle static files and we are done with. The real work of most web applications lies in taking data from a database and rendering/serializing it into a proper layout in HTML (or JSON or XML). And looking at the database drivers for Node (http://wiki.github.com/ry/node/modules#database), AFAICT, every one of them represents database query result sets as arrays (or an array within an object), not as some special object stream. Once again, if our data sources are forEach-able, it is most consistent to serialize with forEach-ables.
On Wednesday, February 3, 2010, Kris Zyp <kris...@gmail.com> wrote: > Here is my proposal for body streams as forEach-able objects returning > promises (I tried to build upon and mix in some of the ideas from > Isaac's EJSGI proposal): > http://wiki.commonjs.org/wiki/JSGI/ForEachStreaming
> Before we even try to compare non-forEachable streams with forEachable > streams, I first wanted to make sure we are clear about something in the > EJSGI proposal. This current design leads to buffering the entire > response body for the most straightforward usage. This is a serious > flaw. One can employ workarounds, but the most likely usage of EJSGI > completely fails to meet its own design goal. One shouldn't have to > resort to putting the response function in a setTimeout/queue function > just avoid sinking one's own server. It is untenable to implement > something with such negative consequences.
I've been following this discussion with a lot of interest. Most know I'm in the camp that we should not hamper our efforts to design a new spec for async web apps with a previous spec that is designed from a sync point of view.
W.r.t. Ejsgi having to buffer the response body in full, would you care to explain why you are under this impression?. I've written streamers using the interface that I don't believe buffers more that a few chunks of each response at any given time.
> However, that being said, this is certainly a correctable issue with > EJSGI. One simply needs to provide stream writing object through a > callback, rather than through a direct constructor available on the > request. The callback be can called by the server when it is ready to > stream the response, and then response writing mechanism can begin. Such > a structure is much easier to work with on the request input stream as > well, because the reader calls the stream when it is ready to receive > data, and it doesn't need to worry about missed events.
> This correction actually would bring EJSGI closer in structure to > forEachable streams. The crux differences to decide between would then > basically boil down to whether the callback occurs through a > forEach(writeFunction) that returns a promise, or whether it occurs > through some startStream(writeObject) where writeObject holds the write > and close methods. The functionality of either approach is identical, > all the same streaming abilities: writing, closing, listening, pausing > and resuming, can easily be achieved with either API. This would come > down to simple API style preference, except for two important key > advantages of the forEach design: forEach works with arrays. This not > only makes it extremely easy to write very simple functions (since you > can simply return an array), we should also be looking at how this > design is going affect other parts of an application. As we discussed > previously in this thread, it is very likely that one would use a > similiar data structure to hold streaming objects that would end up > being streamed in serialization. Being able to treat existing static > arrays as a valid form of a streamed object data structure provides a > much greater opportunity for code reuse. Compare the amount of existing > code that uses JavaScript arrays to the amount of code that uses (or > will ever use) EJSGI streams.
> The second key advantage to forEach is that it is fully backwards > compatible with JSGI 0.3. With a completely different API, we are forced > to break compatibility or support two different paradigms at the same > time, forEach and some other streaming API which induces unnecessary > mental overload. I know some of you don't care about backwards > compatibility, but the reality is that there are existing > implementations, and the spec is in a much better position if can work > with these implementations rather than telling them that they are now > invalid.
> Daniel Friesen pointed out that we do have already have a streaming > proposal for I/O as well: > http://wiki.commonjs.org/wiki/IO/B/Stream/Level1 > Another possibility is that we follow this streaming API for JSGI. This > may be a good idea for streaming. I would certainly prefer that our > response bodies be as consistent as possible with other mechanisms. > Having a streaming API that is inconsistent with both arrays and File > I/O streaming seems like unnecessary fragmentation. However, I do think > that having HTTP streaming match File streaming could be a red herring > in terms of benefit. Matching these APIs certainly makes streaming files > easier, but the reality is that application developers rarely need to > actually implement file static handling. We implement it in a middleware > or appliance provided by a framework or server, and/or setup a proxy to > handle static files and we are done with. The real work of most web > applications lies in taking data from a database and > rendering/serializing it into a proper layout in HTML (or JSON or XML). > And looking at the database drivers for Node > (http://wiki.github.com/ry/node/modules#database), AFAICT, every one of > them represents database query result sets as arrays (or an array within > an object), not as some special object stream. Once again, if our data > sources are forEach-able, it is most consistent to serialize with > forEach-ables.
> -- > Thanks, > Kris
> -- > You received this message because you are subscribed to the Google Groups "CommonJS" group. > To post to this group, send email to commonjs@googlegroups.com. > To unsubscribe from this group, send email to commonjs+unsubscribe@googlegroups.com. > For more options, visit this group at http://groups.google.com/group/commonjs?hl=en.
> On Wednesday, February 3, 2010, Kris Zyp <kris...@gmail.com> wrote:
>> Here is my proposal for body streams as forEach-able objects returning >> promises (I tried to build upon and mix in some of the ideas from >> Isaac's EJSGI proposal): >> http://wiki.commonjs.org/wiki/JSGI/ForEachStreaming
>> Before we even try to compare non-forEachable streams with forEachable >> streams, I first wanted to make sure we are clear about something in the >> EJSGI proposal. This current design leads to buffering the entire >> response body for the most straightforward usage. This is a serious >> flaw. One can employ workarounds, but the most likely usage of EJSGI >> completely fails to meet its own design goal. One shouldn't have to >> resort to putting the response function in a setTimeout/queue function >> just avoid sinking one's own server. It is untenable to implement >> something with such negative consequences.
> I've been following this discussion with a lot of interest. Most know > I'm in the camp that we should not hamper our efforts to design a new > spec for async web apps with a previous spec that is designed from a > sync point of view.
> W.r.t. Ejsgi having to buffer the response body in full, would you > care to explain why you are under this impression?. I've written > streamers using the interface that I don't believe buffers more that a > few chunks of each response at any given time.
The very simplest, most straightforward usage I can think of:
function(request){ var body = new request.jsgi.stream(); var response = { status: 200, headers: {}, body: }; body.write("hello world"); body.close(); return response;
}
With the body.write() call, Iits impossible to do anything except buffer, because the server has not yet received the status and headers to start the response. There is nothing wrong with the streaming interface itself in this situation, but the streaming interface should be provided with a callback when the server is ready to send something, otherwise you end up writing to a stream that isn't connected to anything yet.
On Tue, Feb 2, 2010 at 2:26 PM, Kris Zyp <kris...@gmail.com> wrote:
> On 2/2/2010 2:16 PM, Daniel N wrote: >> On Wednesday, February 3, 2010, Kris Zyp <kris...@gmail.com> wrote:
>>> Here is my proposal for body streams as forEach-able objects returning >>> promises (I tried to build upon and mix in some of the ideas from >>> Isaac's EJSGI proposal): >>> http://wiki.commonjs.org/wiki/JSGI/ForEachStreaming
>>> Before we even try to compare non-forEachable streams with forEachable >>> streams, I first wanted to make sure we are clear about something in the >>> EJSGI proposal. This current design leads to buffering the entire >>> response body for the most straightforward usage. This is a serious >>> flaw. One can employ workarounds, but the most likely usage of EJSGI >>> completely fails to meet its own design goal. One shouldn't have to >>> resort to putting the response function in a setTimeout/queue function >>> just avoid sinking one's own server. It is untenable to implement >>> something with such negative consequences.
>> I've been following this discussion with a lot of interest. Most know >> I'm in the camp that we should not hamper our efforts to design a new >> spec for async web apps with a previous spec that is designed from a >> sync point of view.
>> W.r.t. Ejsgi having to buffer the response body in full, would you >> care to explain why you are under this impression?. I've written >> streamers using the interface that I don't believe buffers more that a >> few chunks of each response at any given time.
> The very simplest, most straightforward usage I can think of:
> function(request){ > var body = new request.jsgi.stream(); > var response = { > status: 200, > headers: {}, > body: > }; > body.write("hello world"); > body.close(); > return response; > }
I liked mob's idea about just having one duplex object. So what about
I like Node's API a lot more than this, it makes clear that you are writing to response, not to the request (which obviously isn't possible). Do you have any objections to us creating a CommonJS HTTP Event Interface specification based on Node's API? (I am still suggesting that this would be distinct from JSGI since it is so radically different).
On Tue, Feb 2, 2010 at 2:47 PM, Kris Zyp <kris...@gmail.com> wrote:
> I like Node's API a lot more than this, it makes clear that you are > writing to response, not to the request (which obviously isn't > possible).
I think people will get used to it.
> Do you have any objections to us creating a CommonJS HTTP > Event Interface specification based on Node's API? (I am still > suggesting that this would be distinct from JSGI since it is so > radically different).
Yeah - I do mind. :) I want to change Node's HTTP API soon and I'm enjoying this discussion.
I like having a low level status, write and close API. Often it is easier to use than trying to force the code to return a {} like JSGI sync. this works well.
> I like Node's API a lot more than this, it makes clear that you are > writing to response, not to the request (which obviously isn't > possible). Do you have any objections to us creating a CommonJS HTTP > Event Interface specification based on Node's API? (I am still > suggesting that this would be distinct from JSGI since it is so > radically different).
I've got some issues with the Node API as currently stands. But we could use it as a starting point and evolve, but I think we should apply the best of recent learning to get this right. There are some error cases that are not coverable with the current Node API. But that is just my opinion.
If we start with Node, great. But lets fix the issues. If we are just going to standardize on the existing NodeAPI -- it will hurt in the long run. But I suppose it will short-circuit the need for CommonJS discussions ;-)
> I like having a low level status, write and close API. Often it is > easier to use than trying to force the code to return a {} like JSGI > sync. > this works well.
On the other hand I like the 'functional flavor' of the JSGI API: The app is a function, you pass an input (request/env) you get an output (response).