On Feb 8, 2012, at February 8, 20126:07 PM, Isaac Schlueter wrote:

> I think I agree with pretty much all of this, Mikeal.  Exposing ctors
> really invites the wrong kind of composability, but we need to make
> the core systems nicer in the ways you describe.  The response header
> handling really needs to be cleaned up.  It mostly works, but has some
> odd edges where it doesn't.
> 
> Constraints:
> 1. We must maintain backwards compatibility unless it is completely
> unreasonable to do so.  This can often be done by re-implementing old
> API surfaces in terms of new ones, but please bear this in mind for
> any change.
> 2. API cleanup should generally not introduce new features, but merely
> make existing features more intuitive, organized, and consistent.
> 3. Performance regressions cannot be tolerated.  Express or request or
> fstream can do a bunch of extra junk to make a cuter API, and give up
> some performance in doing so, but node-core must be as fast as
> possible.

I would entirely agree with this except the fact that I know the code where our 
current writeHead bugs are that break composability and they are in optimized 
that landed over a year ago.

> 
> Here's a half-thought-out proposal.  Please shoot it down in
> constructive ways. :)
> 

I have a mostly-thought-out proposal https://github.com/joyent/node/issues/1448

I mainly wanted to get buy in among core that this is a goal and we should err 
on the side of exposing previously private state as well as composability 
before I dug in to fixing things.

> 
> Expose the actual headers as they will be written.  This must be a 2d
> array.  An object is not the right data structure, because there can
> be multiple values for the same key, and though it shouldn't, order
> does matter in some very rare edge cases.  Like module.exports, if
> this value is overwritten, or manually modified, then that's the
> "real" header.
> 
> response.headers = [ [key, value], ... ]
> 
> Sugar around response.headers.push([key, value]), but giving us a
> place to add intelligent defaults, validation, etc.  We should push
> this api everywhere as the default:
> 
> response.setHeader("key", "value") // Set a header, adding a second
> one if there's already one by that name.
> response.setHeader("key", "value", true) // Overwrite any existing
> header by that name.
> 
> Set the status as a property:
> response.status = 200;  // should this just default to 200?
> 
> Backwards-compatible shim method:
> response.writeHead = function (status, headers) {
>  this.status = status;
>  if (Array.isArray(headers)) {
>    this.headers = headers;
>  } else {
>    Object.keys(headers).forEach(function (k) {
>      this.setHeader(k, headers[k]);
>    });
>  }
> };
> 
> 
> Don't send the actual header on the socket until the first .write() or
> .end().  At that time, response.headerSent = true, and any calls to
> response.setHeader() trigger an error event.
> 
> 
> 
> 
> 
> On Wed, Feb 8, 2012 at 13:39, tjholowaychuk <tjholoway...@gmail.com> wrote:
>> Yeah, unless you're one of us writing these higher-level things you
>> probably wont even really be concerned about how node does things,
>> since if you're just using straight-up node you're probably happy with
>> writeHead() etc. It's awesome having a low-level core that is not
>> overly opinionated about this sort of thing but at the same time we
>> almost need that, otherwise the community just becomes a diverged
>> mess.
>> 
>> It's probably not worth talking about rack-like stuff since that'll
>> never be in core, but I guess the key thing is that we shouldn't need
>> something like that to build pluggable things. Say instead of manually
>> checking accept-encoding all over you want:
>> 
>> http.createServer(function(req, res){
>>  acceptEncoding(req, res);
>>  // go on with regular stuff here
>> });
>> 
>> IMHO we should at least supply something reasonable to make that
>> happen. In Connect I'm using a "header" event, it's not as "clean" as
>> something like rack but like I've said before I really dont want to
>> diverge from what core gives us otherwise it renders that stuff
>> useless to other people (like it does now) and splits the community
>> more than it should
>> 
>> On Feb 8, 1:35 pm, Mikeal Rogers <mikeal.rog...@gmail.com> wrote:
>>> Yup, none of this is new.
>>> 
>>> I think that we need to reach a consensus that this is our goal. I know 
>>> that in core there is a tendency to hold internal state and refrain from 
>>> exposure. I think that some people believe it is "safer" or more "secure" 
>>> and you can see this show a little in the thread about exposing core 
>>> constructors.
>>> 
>>> A pre-requisite to me writing patches that solve this is a cultural 
>>> consensus that we are working towards a composable core, and while I know 
>>> TJ is all for it :) I don't think this goal is currently shared across the 
>>> core community and the practice to date seems to lead towards writing 
>>> things initially very restrictive initially and then people like TJ and 
>>> Marco fighting to expose them.
>>> 
>>> On Feb 8, 2012, at February 8, 20124:18 PM, tjholowaychuk wrote:
>>> 
>>> 
>>> 
>>> 
>>> 
>>> 
>>> 
>>>> +1 been saying this for years
>>> 
>>>> On Feb 8, 1:04 pm, Mikeal Rogers <mikeal.rog...@gmail.com> wrote:
>>>>> All the streams in core are in some half state between declarative and 
>>>>> composable.
>>> 
>>>>> http.ServerResponse is probably the biggest/worst example where:
>>> 
>>>>> http.writeHead(status, headers)
>>> 
>>>>> can, in some cases, entirely opt out of the composable headers set:
>>> 
>>>>> http.setHeader(name, value)
>>> 
>>>>> in particular, this happens when you pass an array of tuples as the 
>>>>> headers argument to writeHead.
>>> 
>>>>> Then there is http.ClientRequest, which is not composable at all and has 
>>>>> no setHeader() method.
>>> 
>>>>> I have a similar problem with every stream in core except sockets. All of 
>>>>> the streams in core take, at some point, a set of parameters that set a 
>>>>> bunch of internal state and *start* an IO operation. Until that IO 
>>>>> operation hits a particular event, in the future, none of that internal 
>>>>> state is actually used. Some of that state can be mutated (is 
>>>>> composable), some currently isn't (is not composable), and some of it is 
>>>>> mutable if you access internal properties (ewwww).
>>> 
>>>>> If the goal of core is to reach API stability we need to get core to a 
>>>>> place where it's extensible. There is a similar thread to this effect 
>>>>> about exposing core constructors, but I'd like to stay on track in this 
>>>>> thread and just talk about composability because it doesn't have many of 
>>>>> the same concerns people have about exposing core constructors.
>>> 
>>>>> *Most* of the objects in core are accessed by userland as the subject of 
>>>>> an event. When those objects are not composable we incredibly limit the 
>>>>> extensibility of core, even when we expose the constructors, and it leads 
>>>>> to the worst kind of monkey patching. There are even cases where core 
>>>>> objects that get exposed by libraries have certain methods entirely 
>>>>> removed because they break composability (I know TJ was considering 
>>>>> removing writeHead when exposed by Express).
>>> 
>>>>> A few things would need to happen to make core more composable.
>>> 
>>>>> 1) Internal state needs to become external state.
>>>>> and
>>>>> 2) The removal, or "re-integration", of methods that currently break 
>>>>> composability.
>>> 
>>>>> We may also need to add more events to some objects. Some good examples, 
>>>>> which have been added, are the 'open' and 'socket' methods on 
>>>>> fs.ReadStream/fs.WriteStream and http.ServerResponse.
>>> 
>>>>> Anywhere we can expose internal state publicly we increase composability 
>>>>> because we allow new mutations that depend on that state the way internal 
>>>>> logic depends on it. As we do this we'll also realize more places where 
>>>>> we break that mutability.
>>> 
>>>>> Thoughts?

Reply via email to