There may be a problem...

90 views
Skip to first unread message

Louis DeJardin

unread,
Aug 22, 2012, 2:24:15 AM8/22/12
to net-http-a...@googlegroups.com

 

Hello everyone!

 

Sorry for the TL;DR email. I’m awful at being simultaneously terse and understandable. Hell, half the time I can’t even manage understandable. :)

 

 

I hate to say it, especially because of all the great recent progress. There is a kind of serious problem I’m having with some of the web frameworks we’ve been writing adapters for...

 

It seems like all the frameworks fit into two categories, double-tap or single-tap. Double-tap: the framework assumes it provides a response with headers and will write to the body in a callback. Single-tap: the framework assumes it can set headers and start writing output from the same point in code without jumping the header/body gap via an async or callback of any kind.

 

Rack, Web API, OWIN, Nancy are all double-tap… Node, Razor, SignalR, FUBU are all single-tap…

 

=== what would a single-tap OWIN look like? ===

 

I think the thing that would change would be instead of returning a task of ResultParameters or Tuple - the server would provide the response fundamentals in the environment as env[“owin.StatusCode”], env[“owin.ResponseHeaders”], and env[“owin.ResponseBody”]. Probably also move the request headers and stream back into the environment to follow suit.

 

So on the plus side the func becomes the simplest it’s ever been:

 

using AppFunc = Func<IDictionary<string, object>, Task>;

 

env goes in, task goes out. The task is done-or-error and lets the middleware and fwks run async. This doesn’t affect anything else in terms of middleware pipeline building, passing the next app argument, calling the next in line with “return app(env)”, etc.

 

=== why is this a problem? ===

 

There are ways to run a single-tap web framework on a double-tap owin pipeline, but they all boil down to buffering write data in the web framework side outside of the owin pipeline until the point when initial call into the framework returns all the way back and the server can make the body callback. Increased complexity, more memory to hold the body until the fwk returns, and cpu spent copying that data… Plus for fully synchronous web frameworks it means every response body will be entirely buffered because there’s no way for the server to deliver the output stream until the initial call returns. Unless you always queueuserworkitem onto another thread to call the fwk in order to return the first thread. I mean – I’m usually the last person to complain about complexity – but it’s pretty extreme compared to just passing the server’s output stream and a response header idictionary in the original call.

 

Running a double-tap framework on a single-tap pipeline by comparison is easy – the adapter just calls framework then calls its callback with the output stream.

 

My fear is that any of the benefits of having a double-tap pipeline may be small compared to the complexity and overhead of needed to run single-tap frameworks…

 

Thoughts? Seem reasonable? Good topic for the call tomorrow?

 

Thanks!

-Lou

 

Louis DeJardin

unread,
Aug 22, 2012, 2:37:02 AM8/22/12
to Louis DeJardin, net-http-a...@googlegroups.com
Another "plus side" that func is so small and simple we could get rid of owin.dll and the remaining named AppDelegate altogether. :)

--
Louis - sent from mobile

From: Louis DeJardin
Sent: 8/21/2012 11:24 PM
To: net-http-a...@googlegroups.com
Subject: There may be a problem...

Roy Jacobs

unread,
Aug 22, 2012, 7:29:29 AM8/22/12
to net-http-a...@googlegroups.com, Louis DeJardin
Just my two cents (since I'm basically on the outside looking in): If you can move towards a single-tap pipeline that massively reduces complexity of the OWIN spec, that can only be a good thing.

A lot of the discussions in this mailing list relate to how to manage the complexity of all the delegates and whatnot, decisions on whether to hide this complexity in a Owin.dll, etc. If this complexity can be avoided at the cost of breaking integration with a few web frameworks, then I honestly believe that it's the right way to go. Especially considering how e.g. Node has built a rich ecosystem on a single-tap pipeline, there's no reason why the .NET variant would have to be more complicated.

Furthermore, massively reducing complexity increases the chance of OWIN being adopted and also implemented correctly. Less complexity means less ambiguity (hopefully) and that is also a win for everyone. OWIN as it is is already quite daunting from the outside what with all the different application layers involved, hosting running hosts, that kind of thing. That probably won't go away, but fewer hurdles = better.

TL;DR: yay!

Prabir Shrestha

unread,
Aug 22, 2012, 12:11:20 PM8/22/12
to net-http-a...@googlegroups.com
What about a func for response body instead with input as response stream and task as return value? This will not need bufferring.

using ResponseBodyFunc = Func<Stream, Task>; 

env[“owin.ResponseBodyFunc”] = async output => {
    await response.CopyToAsync(output);
};

Nancy uses Action<Stream>

or

using AppAction = Func<IDictionary<string, object>, Func<Stream, Task>>;

Chris R

unread,
Aug 22, 2012, 12:28:09 PM8/22/12
to net-http-a...@googlegroups.com
Yes, changing OWIN to single-tap would make it easier to integrate with single-tap servers and frameworks.  However, there would certainly be tradeoffs.
 
Quickly surveying the code base, here's a rough tally of things I'd expect to be easier or harder under a single-tap model:
 
Easier:
- Integrating single-tap frameworks - SignalR, AspNet, Razor, Fubu, Gate.Response, Node
- Integrating single-tap servers - AspNet, HttpListener
- Gate.Response
- Gate.Middleware.NotFound
- Gate.Middleware.ShowExceptions
- Gate.Middleware.Wilson
- Owin.dll/AppFunc/AppDelegate
 
Harder:
- Integrating double-tap frameworks? - WebApi, Nancy - We won't know for sure until we try.
- Integrating double-tap servers? - Firefly
- Gate.Middleware.Chunked
- Gate.Middleware.ContentLength
- Gate.Middleware.ContentType
- Gate.Middleware.PassiveValidator - Can't validate response, only request.
- Gate.Middleware.Logger/Tracer - Hard to coherantly trace/log response
 
No Change:
- Gate.Request
- Gate.Middleware.MethodOverride
- Gate.Middleware.Static
 
Not possible?
- WebApi/HttpMessageHandler middleware
- Gate.Middleware.Cascade
 
Unknown:
- WebSockets - This may need to be re-designed.
 
The most obvious change is that middleware loose their opportunity to examine and/or manipulate the response before the body starts.  For example the Chunked, ContentLength, and ContentType middleware currently examine the response and only take action if the Content-Length or Transfer-Encoding headers have not been set.  To do this with a single-tap model would require they insert their own stream and inspect headers on the first write.
 
The other difficulty that I see is that if middleware pre-emptively modify the response without completely short-circuiting, later changing to a 500 response would require clearing out any response headers and possibly other response properties from the Environment.
We need to look at these tradeoffs carefully before making a fundamental change like this.
~Chris

Date: Wed, 22 Aug 2012 04:29:29 -0700
From: roy.j...@gmail.com
To: net-http-a...@googlegroups.com
CC: lode...@microsoft.com
Subject: Re: There may be a problem...

Chris R

unread,
Aug 22, 2012, 12:38:38 PM8/22/12
to net-http-a...@googlegroups.com
Prabir,  I'm not sure I understand which polarity you're suggesting.  Who would provide the delegate and who would invoke it?
 
Today we already have a body delegate provided by the app and invoked by the server.  The problem is that the server only knows that it's time to invoke it when the initial AppFunc Task has completed.  That doesn't work well for apps that want to write headers and then immediately write the body (single-tap).
 
Or were you suggesting that the server provide the ResponseBodyFunc and the app invoke it?  That would require that the app provide a stream to the server, which is the opposite polarity from how most apps work and would require buffering in most cases.

Date: Wed, 22 Aug 2012 09:11:20 -0700
From: prabirs...@yahoo.com
To: net-http-a...@googlegroups.com

Subject: Re: There may be a problem...

Louis DeJardin

unread,
Aug 22, 2012, 1:04:28 PM8/22/12
to Prabir Shrestha, net-http-a...@googlegroups.com
Well, that's what the second tap is in owin today, and you're right that does work for fwks like nancy and webapi that have a total separation between the code that produces headers and the code that produces output. After setting that callback you need to be able to unwind the stack back to the web server before it is able to invoke it.

Problem comes in when you're trying to host a fwk that assumes it can, for example, set the content type and write output in the same function. For those fwks (more than half so far) they would need be reengineered w/out that assumption to run on owin.

The thing is - none of the hosting they would be on already (aspnet, httplistener) have that requirement. It's only present because the owin call has the double-tap - if the resp headers and output stream were in the environment that would work just fine.

Did some more reading also. Seems there is also a "jsgi" standard for javascript that has a double-tap pipeline like wsgi and rack. It appears the single-tap "connect" standard is what stuck, though.


--
Louis - sent from mobile

From: Prabir Shrestha
Sent: 8/22/2012 9:11 AM

Andreas Håkansson

unread,
Aug 25, 2012, 7:29:31 AM8/25/12
to net-http-a...@googlegroups.com
Just had time to respond here, and I've seen a bit out of the loop, so could you please elaborate what you mean with single- vs. double-tap frameworks?

Mark Rendle

unread,
Aug 25, 2012, 7:42:49 AM8/25/12
to net-http-a...@googlegroups.com

Double tap means the application writes all the headers, then yields to the server and waits to be asked for the content. Single tap means the application sets headers and writes content without yielding control to the server at any point, and could possibly continue to set headers after writing content, requiring the server to buffer the output stream.

Mark

Andreas Håkansson

unread,
Aug 25, 2012, 8:05:00 AM8/25/12
to net-http-a...@googlegroups.com
And Nancy is double-tab because out content it produced from a Func<Response> that the host has to invoke to pull it out and write it to the client?

Chris R

unread,
Aug 25, 2012, 10:49:52 AM8/25/12
to net-http-a...@googlegroups.com
Right.  Nancy has a clean separation between headers and body similar to Owin v0.14, WebApi, etc..  Some single tap examples are HttpListener, AspNet, Fubu, SignalR, etc..

What we realized the hard way with SignalR is that it's difficult for a single tap component to bridge that separation when building on top of a double tap component, requiring some strange thread hopping and/or buffering.  However, building a double tap component on top of a single tap component only requires delaying the exposure of the output stream.

When brainstorming lists of single and double tap components, there appear to be many more servers and frameworks using the single tap model.  In fact, the only servers using double tap I could think of were Kayak and Firefly, and Louis offered to re-write Firefly.  Conceptually I prefer the double tap model for a variety of reasons, but for interop purposes I don't think it's practical here.
________________________________
> Date: Sat, 25 Aug 2012 05:05:00 -0700
> From: and...@selfinflicted.org

Louis DeJardin

unread,
Aug 25, 2012, 12:34:37 PM8/25/12
to Chris R, net-http-a...@googlegroups.com
Yeah, exactly, in a nutshell single-tap owin means response headers,
status code, and output stream are in the env dictionary from the
start. Just like node - the headers are sent implicitly when you start
writing output.

So running on single-tap owin is exactly the same model as running on
aspnet or httplistener self-host. For double-tap or async fwks like
Nancy/SimpleWeb/WebApi what changes is the handler code isn't required
to unwind control to the server between executing the request, setting
response headers, and doing any rendering actions.

The owin func returns task, of course, so async fwks may run asyncly.
Differences being the output mechanism is available upfront, and the
fwk is in sync/async control the whole time so is in a better position
to cleanup/dispose at the end of the task.

--
Louis - sent from mobile
From: Chris R
Sent: 8/25/2012 7:49 AM
To: net-http-a...@googlegroups.com
Subject: RE: There may be a problem...

Grumpydev

unread,
Aug 29, 2012, 7:24:50 AM8/29/12
to net-http-a...@googlegroups.com, Chris R
Every now and then I bang my head on the deferred execution in Nancy, swear a lot at Andreas until he "has to go to a meeting", then eventually figure out how to work around it. I can't say I've ever been 100% sold on the benefits of the deferred execution, but in Nancy the key thing is that it *enables* the hosting to defer execution, but doesn't mandate it (for the same reasons mentioned above - it's easy to run a "double tap" on a "single tap" host).

I certainly wouldn't have a problem with it being single tap only, losing deferred execution of the body seems like the lesser of the two evils compared to having to buffer the output for (potentially) no benefit anyway.

Louis DeJardin

unread,
Aug 29, 2012, 4:43:10 PM8/29/12
to Grumpydev, net-http-a...@googlegroups.com, Chris R
Yeah, I could see that come up. But as you say, double-over-single doesn't force a mandate or require a buffer from either side to the other. It's entirely a design decision of nancy which model to execute.




--
Louis - sent from mobile

From: Grumpydev
Sent: 8/29/2012 4:24 AM
To: net-http-a...@googlegroups.com
Cc: Chris R

Subject: Re: There may be a problem...

Reply all
Reply to author
Forward
0 new messages