ATTN Jack users: JSGI spec change, response array changed to object

9 views
Skip to first unread message

Tom Robinson

unread,
Sep 2, 2009, 3:47:07 AM9/2/09
to narw...@googlegroups.com, comm...@googlegroups.com
This is probably only of interest to people using Jack currently, but
I'm cc-ing CommonJS just in case.

I have updated the Jack codebase to reflect the recent change in the
JSGI spec. It is currently in the "jsgichange" branch:

http://github.com/tlrobinson/jack/tree/jsgichange

Please update your apps/frameworks then let me know how it works and
if you find any problems.


1) You need to convert your apps and middleware to return { status :
x, headers : y, body : z } instead of [x,y,z], and instances of
"response[0]", "response[1]", "response[2]" to "response.status",
"response.headers", "response.body". If you're only using the Jack
Response object then this will be automatic.

The following 3 regexes should help find most places where you'll need
to change code (though with some false positives and some missed,
probably):

\[[012]\]

return\s*\[

\[\s*[1-5][0-9][0-9]

(first matches "[0]", "[1]", or "[2]". the second matches "return [".
the third matches "[x" where x is an HTTP status code, 100-599)

You can even do find/replace with the following though I'd recommend
checking each match first:

\[\s*(\d+)\s*,\s*(\{[^}]+\})\s*,\s*(\[[^\]]+\])\s*\]

to

{\nstatus : $1,\nheaders : $2,\nbody : $3\n}

(it assumes no "}" in headers or "]" in body, and it won't indent
correctly)


2) I have not made the "jsgi." => "jsgi_" change proposed in
"outstanding jsgi spec issues" since there haven't been many responses
yet. However, I have fixed all instances of "jack.*" which should have
been "jsgi.*". This one is easy, use this to find/replace:

jack\.(version|url_scheme|input|errors|multithread|multiprocess|
run_once)

to

jsgi.$1


3) I'd like for instances of Jack's Response object to be valid JSGI
responses, instead of having to call finish(). However, finish() did
more than just return an array with the correct corresponding values:

Response.prototype.finish = function(block) {
this.block = block;

if (this.status == 204 || this.status == 304)
{
HashP.unset(this.headers, "Content-Type");
return {
status : this.status,
headers : this.headers,
body : []
};
}
else
{
return {
status : this.status,
headers : this.headers,
body : this
};
}
}

The first issue is that the body won't be cleared out if the status us
204 or 304. Whether that's actually problem or not depends if we want
this to be a feature of Response. The same effect could be achieved
through middleware, similar to the Head middleware.

It also uses the response object itself ("this") as the body, so the
Response object's "forEach" gets called, which in turns calls forEach
on this.body and the block.

Response.prototype.forEach = function(callback) {
this.body.forEach(callback);

this.writer = callback;
if (this.block)
this.block(this);
}

If a "block" has previously been specified (typically through
response.finish(block)) then from the block you can call write on the
argument passed in. This is basically a shortcut for { status: x,
headers: y, body: { forEach : function(callback) { ... } }}

We could store the data passed to response.write() somewhere other
than this.body (say this.written or something), then alias "this.body
= this" by default in the constructor, and in forEach check if
"this.body" still == "this", if so call forEach on "this.written",
otherwise call forEach on "this.body".

Eek. Perhaps I should just implement it and see how it works.

Now, I pretty much ported Rack's Response object directly, so it's
possible we want to rethink how this works to suit JavaScript a little
better.


Similarly, in Jack's Request I use getters for everything, rather than
straight properties (req.foo() instead of req.foo). This has the
advantage of making the initial constructor very fast since it's
basically just one or two property assignments, but it would be nice
to have properties instead of accessors.

In JS implementations that support accessors (Rhino, SM, all ES5) it
would be possible to get the best of both worlds, as in Ruby/Rack.
Perhaps that's a good enough reason to go with properties. Opinions?


-tom

George Moschovitis

unread,
Sep 2, 2009, 4:54:22 AM9/2/09
to narw...@googlegroups.com, comm...@googlegroups.com

I have updated the Jack codebase to reflect the recent change in the
JSGI spec. It is currently in the "jsgichange" branch:

       http://github.com/tlrobinson/jack/tree/jsgichange

Yes! ;-)

The first issue is that the body won't be cleared out if the status us
204 or 304. Whether that's actually problem or not depends if we want
this to be a feature of Response. The same effect could be achieved
through middleware, similar to the Head middleware.

yeah, a middleware can handle this.
 
We could store the data passed to response.write() somewhere other
than this.body (say this.written or something), then alias "this.body
= this" by default in the constructor, and in forEach check if
"this.body" still == "this", if so call forEach on "this.written",
otherwise call forEach on "this.body".

Eek. Perhaps I should just implement it and see how it works.

sounds nice..

In JS implementations that support accessors (Rhino, SM, all ES5) it
would be possible to get the best of both worlds, as in Ruby/Rack.
Perhaps that's a good enough reason to go with properties. Opinions?

We should use accessors (since they are in the ES5 spec).


-g.



--
blog.gmosx.com

Tom Robinson

unread,
Sep 2, 2009, 5:13:25 AM9/2/09
to narw...@googlegroups.com, comm...@googlegroups.com
On Sep 2, 2009, at 1:54 AM, George Moschovitis wrote:

In JS implementations that support accessors (Rhino, SM, all ES5) it
would be possible to get the best of both worlds, as in Ruby/Rack.
Perhaps that's a good enough reason to go with properties. Opinions?

We should use accessors (since they are in the ES5 spec).

While we have not specified that CommonJS implementations must be ES5 compliant (and probably won't in the near future), I think we can simply defer to the slower method of initializing the properties in the constructor, while using accessors for improved performance in ES5 compliant implementations.

In ES5 is it legal to overwrite an accessor with a value, for example:

Object.defineProperty(Foo.prototype, "bar", {
    get: function() {
        print("ok!");
        this.bar = "something";
        return this.bar;
    }
});

foo = new Foo();
foo.bar; // runs the getter, prints "ok!", returns "something"
foo.bar; // simply returns "something"



-tom

George Moschovitis

unread,
Sep 2, 2009, 6:29:34 AM9/2/09
to narw...@googlegroups.com, comm...@googlegroups.com
BTW,

I converted Nitro to use the jsgichange branch. Everything went smoothly.
My realworl jack apps are dramatically simplified after this change.

thank you,
-g.

Wes Garland

unread,
Sep 2, 2009, 10:15:00 AM9/2/09
to comm...@googlegroups.com
While we have not specified that CommonJS implementations must be ES5 compliant (and probably won't in the near future), I think we can simply defer to the slower method of initializing the properties in the constructor, while using accessors for improved performance in ES5 compliant implementations.

Agreed.  I also strongly suspect that using object.prop will be faster than object.getProp() in most implementations -- but the biggest win is that I think it is at the correct level of expressiveness.

Wes
--
Wesley W. Garland
Director, Product Development
PageMail, Inc.
+1 613 542 2787 x 102

Mark Miller

unread,
Sep 2, 2009, 11:53:07 AM9/2/09
to narw...@googlegroups.com, comm...@googlegroups.com
On Wed, Sep 2, 2009 at 2:13 AM, Tom Robinson<tlrob...@gmail.com> wrote:
> In ES5 is it legal to overwrite an accessor with a value, for example:
>
> Object.defineProperty(Foo.prototype, "bar", {
>     get: function() {
>         print("ok!");
>         this.bar = "something";
>         return this.bar;
>     }
> });
> foo = new Foo();
> foo.bar; // runs the getter, prints "ok!", returns "something"
> foo.bar; // simply returns "something"

Your conclusion is correct, but the code above won't work.

The first problem: Having defined bar as an accessor property, an
assignment to this property (as appears in your getter) will invoke
the setter. In this case there is no setter of course, meaning you
have a readonly accessor property. This means that if the assignment
appears in strict code it will throw, whereas if it appears in
nonstrict code it will silently fail -- just as if you were assigning
to a readonly data property.

To fix the first problem, change your getter to

get: function() {
print("ok!");

Object.defineProperty(this, 'bar', {value: "something"});
return this.bar;
}

The second problem is that your (now first) call to defineProperty
defined bar as a read-only non-configurable property. Therefore the
second defineProperty call will fail. Putting this together:

Object.defineProperty(Foo.prototype, "bar", {
get: function() {
print("ok!");

Object.defineProperty(this, 'bar', {value: "something"});
return this.bar;
},
configurable: true
});

The above code will still cause simple assignments to fail, both
before and after the first read. If this is not your intention, add a
setter to the first call and a {writable: true} to the second.
Likewise, if you want the property to be enumerable, add {enumerable:
true}.

--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM

Reply all
Reply to author
Forward
0 new messages