Here's the first task:
Add support for including/requiring modules from HTTP URLs. This can
be done completely in javascript. It requires understanding how to use
node.http.Client and node.fs.cat(). I suggest the following path (but
feel free to come up with your own solution):
1. add a new function called node.http.cat(url, encoding, callback)
which will send a GET request to the URL, buffer the body, and return
it to the callback.
2. add another function node.cat(url_or_path, encoding, callback)
which abstracts node.http.cat and node.fs.cat().
3. change include() and require() in node.js to use node.cat() instead
of node.fs.cat() to load modules.
> Add support for including/requiring modules from HTTP URLs. This can
> be done completely in javascript. It requires understanding how to use
> node.http.Client and node.fs.cat(). I suggest the following path (but
> feel free to come up with your own solution):
Just small enough for me to get something done. Here's the first part,
the test case the code should pass:
http://github.com/ujh/node/commit/6b9e5eedf2fadbd192e9be522ea51bd8fd02bb71
Urban
Looks good. I'll wait for the rest to merge it.
> Looks good. I'll wait for the rest to merge it.
http://github.com/ujh/node/commit/ce85f84d152dde9dc2cead5a8e4889710b3f0a69
This implementes node.http.cat(). It does miss a timeout, which would be
a good thing I guess. Do other parts of node.js provide timeout
functionality?
The question then becomes: Should load/require have a second argument
that is a timeout and what should the behaviour be? Should it call
onError after the timeout?
Also, is it OK that node.http.cat is inside the closure in http.js? It
doesn't seem necessary as it doesn't use any of the private stuff I guess.
Urban
Here you go:
http://github.com/ujh/node/commit/ea290e727de256a51f0a4ef9e4ff701da88f975c
The following things are of interest:
1. node.cat()'s callback has the status code as its first argument. For
files everything but 0 means failure. For HTTP everything but 200 (sort
of) means failure. Right now node.http.cat() returns the status 0 for
HTTP 200 and -1 for everything else. This of course masks the real
status code and therefore might not be ideal.
2. Module loading now uses "://" to distinguish remote modules from
local ones. Maybe someone can come up with a better distinction.
3. I've "fixed" parseUri() to exclude empty parts of the parsed URI (so
no more "" values).
I think that's it.
Urban
Sure :) What's the next thing on your list? I've been thinking cookies.
Something like req.cookies() and res.setCookie(). I guess that's low
level enough to go into http.Server and http.Client.
I'm thinking of fixing it like this: node.http.cat() will return the
HTTP status code and not 0 or -1. node.cat() will
a) have add a third argument to the callback that contains stuff like
the real return code and if it was a local or remote request.
b) do away with the status callback argument as it's not clear if we
should choose the http or file return codes and add the argument from a)
which may contain an additional field called success (or so).
c) the first argument to the callback (status) could just return true or
false. Any additional information can be retrieved from argument three
(see a) ).
What do you think? Right now I'm in favour of c)
Urban
Maybe deferreds (see below).
> I've been thinking cookies.
> Something like req.cookies() and res.setCookie(). I guess that's low
> level enough to go into http.Server and http.Client.
Cookies should be left to higher level code. The web server should
only deal with connection handling (e.g. keep-alive) and parsing the
message into its request line, header lines, and body. It should not
deal with header parsing (except for "Connection",
"Transfer-Encoding", and "Content-Length") or body parsing.
>>> 1. node.cat()'s callback has the status code as its first argument. For
>>> files everything but 0 means failure. For HTTP everything but 200 (sort
>>> of) means failure. Right now node.http.cat() returns the status 0 for
>>> HTTP 200 and -1 for everything else. This of course masks the real
>>> status code and therefore might not be ideal.
>>
>> Hm yeah - perhaps not ideal, but it works for now.
>
> I'm thinking of fixing it like this: node.http.cat() will return the
> HTTP status code and not 0 or -1. node.cat() will
>
> a) have add a third argument to the callback that contains stuff like
> the real return code and if it was a local or remote request.
>
> b) do away with the status callback argument as it's not clear if we
> should choose the http or file return codes and add the argument from a)
> which may contain an additional field called success (or so).
>
> c) the first argument to the callback (status) could just return true or
> false. Any additional information can be retrieved from argument three
> (see a) ).
>
> What do you think?
This the first situation I've seen where using Deferreds really makes
sense to me. Generally I've been wanting that to exist in higher level
libraries but maybe that needs to be reconsidered. Perhaps at the same
time we should think about having real DOM style events instead of
just callbacks. e.g.
https://developer.mozilla.org/en/DOM/element.addEventListener
>> I've been thinking cookies.
>> Something like req.cookies() and res.setCookie(). I guess that's low
>> level enough to go into http.Server and http.Client.
>
> Cookies should be left to higher level code. The web server should
> only deal with connection handling (e.g. keep-alive) and parsing the
> message into its request line, header lines, and body. It should not
> deal with header parsing (except for "Connection",
> "Transfer-Encoding", and "Content-Length") or body parsing.
All right. Would that be part of node itself or should that be a library?
>> I'm thinking of fixing it like this: node.http.cat() will return the
>> HTTP status code and not 0 or -1. node.cat() will
>>
>> a) have add a third argument to the callback that contains stuff like
>> the real return code and if it was a local or remote request.
>>
>> b) do away with the status callback argument as it's not clear if we
>> should choose the http or file return codes and add the argument from a)
>> which may contain an additional field called success (or so).
>>
>> c) the first argument to the callback (status) could just return true or
>> false. Any additional information can be retrieved from argument three
>> (see a) ).
>>
>> What do you think?
>
> This the first situation I've seen where using Deferreds really makes
> sense to me. Generally I've been wanting that to exist in higher level
> libraries but maybe that needs to be reconsidered. Perhaps at the same
> time we should think about having real DOM style events instead of
> just callbacks. e.g.
> https://developer.mozilla.org/en/DOM/element.addEventListener
I'm not sure I understand the connection here. Do you mean something
like having onSuccess and onError calls for the node.cat() call? And how
would you do it? I've seen stuff like node.cat(arguments, {onError:
function() {}, onSuccess: function() {}). Otherwise we need to have some
object here that can be observed (we really want the observer pattern
here, I guess).
Urban
That should be a library.
>> This the first situation I've seen where using Deferreds really makes
>> sense to me. Generally I've been wanting that to exist in higher level
>> libraries but maybe that needs to be reconsidered. Perhaps at the same
>> time we should think about having real DOM style events instead of
>> just callbacks. e.g.
>> https://developer.mozilla.org/en/DOM/element.addEventListener
>
> I'm not sure I understand the connection here. Do you mean something
> like having onSuccess and onError calls for the node.cat() call? And how
> would you do it? I've seen stuff like node.cat(arguments, {onError:
> function() {}, onSuccess: function() {}). Otherwise we need to have some
> object here that can be observed (we really want the observer pattern
> here, I guess).
I guess Dojo's syntax is okay
var d = node.cat("http://tinyclouds.org/mjsunit.js");
d.addCallback(onLoad);
d.addErrback(function () { puts("cannot load url"); node.exit(1); }
My timid objection to this has been that, in the name of minimalism,
it adds a lot of syntax, that for a low-level I/O infustructure, is
not necessary. However, I'm beginning to see for myself that having
Deferreds for the asynchronous functions is probably going to make
even the internal code simpler.
> I guess Dojo's syntax is okay
>
> var d = node.cat("http://tinyclouds.org/mjsunit.js");
> d.addCallback(onLoad);
> d.addErrback(function () { puts("cannot load url"); node.exit(1); }
>
> My timid objection to this has been that, in the name of minimalism,
> it adds a lot of syntax, that for a low-level I/O infustructure, is
> not necessary. However, I'm beginning to see for myself that having
> Deferreds for the asynchronous functions is probably going to make
> even the internal code simpler.
Well, how about this then:
var d = node.cat("http://tinyclouds.org/mjsunit.js");
d.addObserver({success: onLoad, error: function () { puts("cannot load
url"); node.exit(1); }})
Which is nice, because the observer can be any object that has the
required attributes.
And then there are no fixed names for the callbacks which means this can
be implemented as some kind of "mixin" (in Ruby terms) and you can use
it in all classes/objects by extending that object. Which is easy in JS
as you just have to implement something like Object.extend in Prototype.
Urban
I was thinking of something like this:
var Observable = {
addObserver: function() {},
removeObserver: function() {},
notify: function() {}
}
Then every class that wants to have this functionality can do something
like this:
Object.extend(node.http.Server.prototype, Observable)
Assuming that Object.extend() copies the fields from argument 2 into
argument 1 (as defined here: http://prototypejs.org/api/object/extend ).
That's how we used to do it in a (client side) JavaScript project I
worked on.
Urban
Yup, Thats what I was imagining. In jQuery theres a similar convention
($.extend()). So each of those methods would take a 'name' and a
callback? One of the cool things about the jQuery Event API (I think
Prototype has this too) is the ability to pass data to the bound
callback when triggering. It makes it easy to refactor a bunch of
similar callbacks into a single callback that can switch on the
triggered data.
Something I'm curious about - my C++ is rusty so I cant dive in myself
- If node is based on a full JS interpreter (V8), doesnt it inherently
include some kind of event handling mechanism? I see the issue that
there is no real DOM in node, but couldnt you somehow implement a
virtual DOM with just a 'body' and bind/trigger events off of that,
letting V8 handle the event model?
--AQ
> Yup, Thats what I was imagining. In jQuery theres a similar convention
> ($.extend()). So each of those methods would take a 'name' and a
> callback? One of the cool things about the jQuery Event API (I think
> Prototype has this too) is the ability to pass data to the bound
> callback when triggering. It makes it easy to refactor a bunch of
> similar callbacks into a single callback that can switch on the
> triggered data.
Not really. addObserver() would register an object. Then when an event
is triggered the controlling code would call notify(eventName,
argument1, argument2). notify() would the go through all objects that
want to be notified and look for a method called eventName(). If it's
present it would be called with the arguments. If it's not there, the
object isn't interested in that event. Is this what you are looking for?
Apart from the naming of course, that's not fixed, yet.
Urban
What would happen if multiple objects include the named function?
Would notify call all of them? If so, that works, too. I guess the
benefit of a named Event approach is that a single object can have
multiple callbacks for a single Event. In pseudo code:
var MyObject = {
...
}
extend(MyObject, Observable)
MyObject.addObserver('error', function() {
// show error message
})
MyObject.addObserver('error', function() {
// send error to the log
});
MyObject.notify('error');
// triggers both callbacks.
--AQ
> What would happen if multiple objects include the named function?
> Would notify call all of them? If so, that works, too. I guess the
> benefit of a named Event approach is that a single object can have
> multiple callbacks for a single Event. In pseudo code:
What I'm suggesting here is basically the observer pattern. So yes, all
observers would get called.
Your approach is modelled after the way the events in the DOM work, I
guess. Which is natural to JS programmers. So it should definitely be
considered as well.
I'm not sure what I like better right now. Does anybody else have an
opinion?
Urban
As I see it, there are two distinct situations in Node. They perhaps
should be handled with different APIs.
1) Simple asynchronous requests. For example, node.fs.cat (which cats
a file). The function runs in the background, returns some data or
possibly errors out. For this I think Dojo's deferreds are rather
nice.
(One use case in particular I need: a way to take N deferreds and wait
for their completion. I'm not sure how this is done in Dojo.)
2) Objects which emit events. An HTTP server gets a "request" event. A
HTTP request gets a "onBody" event. These are modeled better with
something like DOM events. The object emits events, any number of
callbacks can listen to such an event. Perhaps there could even be
event bubbling - but I can't think of what Node would use that for.
Whatever the choice, I'm most concerned about keeping it minimal.
Javascript object creation is a non-negligible performance penalty
here. I'd much rather have a fast web server than a featureful library
for event registration.
v8 doesn't have events. Node is v8's event model :)
The web server and other network objects can implement this interface.
Asynchronous functions (mostly those which execute system calls on the
thread pool) can return an object (a "promise" or "deferred") which
implements the EventTarget interface. The returned object issues the
"complete" and "error" events.
Perhaps that returned object can have special methods for registering
completion and error handlers. e.g.
var d = node.fs.open("filename");
d.addCallback(handleSuccess);
instead of
d.addEventListener("success", function (event) {
handleSuccess.apply(this, event.args);
});
Again, I hesitate because
new node.http.Server(handleRequest).listen(80);
is much simpler than
var s = new.http.Server();
s.addEventListener("request", function (event) {
handleRequest.apply(s, [event.req, event.res]);
});
s.listen(80);
True, but could the first argument to server be an object that defined
the default request handlers?
var s = new.http.Server({
request: function(req, res) {
....
}
});
s.listen(80);
Something cool about this is the ability to have a defined number of
default 'lifecycle' events and be able to observe/bind to them at
will.
I implemented something similar the front-end framework I've been
working with (sammy: http://code.quirkey.com/sammy) and it allows for
doing some nice things (easy logging of events, 'before' and 'after'
triggers, etc).
--AQ
I really like Sammy (and Sinatra). Would be great to have it ported to Node.
Thanks! Thats my goal - I started playing around this weekend and
didnt get to far, but the dream of a single API for both front end and
backend is two awesome to sleep on.
--AQ
> 1) Simple asynchronous requests. For example, node.fs.cat (which cats
> a file). The function runs in the background, returns some data or
> possibly errors out. For this I think Dojo's deferreds are rather
> nice.
>
> (One use case in particular I need: a way to take N deferreds and wait
> for their completion. I'm not sure how this is done in Dojo.)
>
> 2) Objects which emit events. An HTTP server gets a "request" event. A
> HTTP request gets a "onBody" event. These are modeled better with
> something like DOM events. The object emits events, any number of
> callbacks can listen to such an event. Perhaps there could even be
> event bubbling - but I can't think of what Node would use that for.
Event bubbling seems to be rather useless for this level of abstraction.
If there's a higher level server interface then it might sense there.
> Whatever the choice, I'm most concerned about keeping it minimal.
> Javascript object creation is a non-negligible performance penalty
> here. I'd much rather have a fast web server than a featureful library
> for event registration.
Hm. Is there a profiler for v8? I mean how to we know if the event
handling code is too slow?
Anyway: If we can find example code for both APIs mentioned, I'll
implement them and then we can see if we need to speed them up somehow
and if they are useful. Maybe even a hybrid of both APIs should be
possible, so that you can decide what kind of style you want. At least
before we start speeding things up ;)
Urban
Yes, I think you're right. OTH, I like the idea of implementing w3's
event spec - perhaps that would make it possible to use browser-side
libraries more with Node.
>> Whatever the choice, I'm most concerned about keeping it minimal.
>> Javascript object creation is a non-negligible performance penalty
>> here. I'd much rather have a fast web server than a featureful library
>> for event registration.
>
> Hm. Is there a profiler for v8? I mean how to we know if the event
> handling code is too slow?
Yes: ./node --prof script.js
It creates a file called v8.log which you can analyze using
deps/v8/tools/linux-tick-processor.py
I've experienced garbage collection being triggered too often because
of creating too many objects in the HTTP server. So it went from
http://s3.amazonaws.com/four.livejournal/20090529/timeseries6.png
to
http://s3.amazonaws.com/four.livejournal/20090617/timeseries11.png
histogram view (top):
http://s3.amazonaws.com/four.livejournal/20090617/hist10.png
I'm thinking about doing an javascript object pool for connection and
request objects at some point (once node stabilizes). I think it will
speed things up.
> Anyway: If we can find example code for both APIs mentioned, I'll
> implement them and then we can see if we need to speed them up somehow
> and if they are useful. Maybe even a hybrid of both APIs should be
> possible, so that you can decide what kind of style you want. At least
> before we start speeding things up ;)
I'm going to play around with this today. It would be nice to also do
some internal plumbing at the same time for event handling.