-
Notifications
You must be signed in to change notification settings - Fork 35
Design Proposal: Response Context
In an HTTP response body, there are actually several potentially useful values. We can define this as a pairs from the sets T = {raw, decoded, parsed}
and U = {stream, body}
. For example, we might be interested in the decoded response stream, rather than the body. Or we might just want the parsed response body.
This presents a difficulty in knowing what to return back to the caller. The HTTP IncomingMessage
itself is the raw stream. If the response has been encoded, however, this is almost never useful. Therefore, we might want to return the decoded stream. However, in many cases, the caller really just wants the parsed body. And so on.
We can't simply provide all the possible combinations, since some of them introduce overhead that the caller may not want. For example, providing a parsed body isn't useful if the caller wants to process the decoded stream themselves. There's no point in doing the same work twice.
Any interface must there provide each (t, u)
in such a way that they don't incur the processing overhead unless they use it.
One possible interface is to simply add three properties to the response context. (The context here is simply an object that includes the response itself as a property, but augments, much like we do with Patchboard/PBX requests.) These properties correspond to T
(we might just as well have used U
). In turn, each of these objects has a property corresponding to U
(again, we could have used T
).
For example, to get the decoded stream, you'd simply access the decoded.stream
property of the context object. If you wanted the raw body, you could access the raw.body
property. And so on.
To avoid unnecessary overhead, these properties would defined as getters to avoid introducing any overhead unless they're used. For example, the decoded.stream
property introduces overhead, since it has to set up the decoding. Therefore, we'd define that using a getter. The only exception to this, of course, would be the raw.stream
, which is the response object itself.
This still seems pretty awkward. We'd have to do something like this to get the parsed body.
data = yield ((yield resource.get()).parsed.body)
In truth, most of the time we either want the decoded stream, the decoded body, or the parsed body. And if you really want to the unencoded body, we can assume further that you won't mind getting it as a stream. We can introduce the following attributes to the context in order to handle the various cases, without fully normalizing them.
-
stream
: the decoded stream -
data
: the parsed body -
raw
: the raw, encoded stream -
body
: the decoded (unparsed) body
These four attributes account for four of the six possible (t, u)
pairs. The remaining two, the raw body and the parsed stream can be handled by virtue of the parser registered for a given content-type
.
For example, supposed we have a streaming JSON parser. We can register that for application/json
and have it return the parse stream instead of a JavaScript object. The developer still has complete control, but these cases are rare enough that we don't optimize for them.
Streaming a video now looks something like this.
(yield video.get()).stream.pipe player
On other hand, getting a JavaScript object looks something like this.
bar = yield (yield foo.get()).data
Additionally, we can extract common headers, like location
, as additional properties, which can simplify access to them.
{location} = yield monkeys.post()
And, of course, you can still get to the response object.
{response} = yield foo.get()