Re: [mongrel2] Re: WebSocket support m2
- From:
- Alfred Tascon
- Date:
- 2011-02-10 @ 01:40
I vote you use the "Connection: Upgrade" test to set content length to 8.
On Thu, Feb 10, 2011 at 1:35 AM, Xavier Lange <xrlange@gmail.com> wrote:
> I think the websocket support falls on its face in the function
> int connection_http_to_handler(Connection *conn)
> specifically on the test
> if(content_len == 0)
>
> WebSocket don't set the content_len (they really should -- they have a
> well-defined body of 8 bytes...) but they do put on in the body. So
> yes, they're a stupid mess, but I think I can shoe-horn support in
> pretty easily by adding a new check for an 8 byte body.
>
> Add a websocket workaround:
> if(conn->req->parser.nread-conn->req->parser.body_start == 8){
> content_length = 8;
> }
>
> then continue with
> if(content_len == 0){
>
> If we don't want to do this kind of hack, maybe we can modify the HTTP
> parser to look for "Connection: Upgrade" header?
>
> I'm going to go forward with my quick workaround in the mean time. Let
> me know what you think.
>
> Xavier
>
> On Tue, Feb 8, 2011 at 7:22 PM, Xavier Lange <xrlange@gmail.com> wrote:
> > WebSockets suck -- moving target, poor support, etc. But, when they
> > work they're pretty useful for doing high-speed message relaying to
> > the browser. I'm using my libm2handler library
> > (https://github.com/derdewey/mongrel2_c_handler) to bridge some fancy
> > C libraries and I want websockets.
> >
> > I'm running into a problem where the body of the initial connection
> > from the websocket client does not get all the way to the handler.
> > I'll be adding debug statements into mongrel2 and seeing where to go
> > from there.
> >
> > After I figure out that tidbit, the C handler library will support
> > websockets. Or I will detail the changes made to mongre2.
> >
> > Purpose of this post: how should I do websocket? In the handler or
> > should I modify how new connections are modified (the bit which
> > determines if the connection is XML, JSON, or plain HTTP)?
> >
> > If you're curious how the upgrade 'challenge' works, snippets of code
> > for you to chew on:
> >
> > static int ws_reply_upgrade(mongrel2_request *req, mongrel2_socket
> *socket){
> > unsigned char response[16];
> > ws_handshake_response(req,response);
> > fprintf(stdout,"Headers: %s\n",bdata(req->headers));
> > fprintf(stdout,"Body: %s\n",bdata(req->body));
> > fprintf(stdout,"Challenge response: %s",response);
> >
> > // that response[16] will be sent to the client
> >
> > return 0;
> > }
> >
> > static int ws_handshake_response(mongrel2_request *req, unsigned char
> > response[16]){
> > const char* headers = bdata(req->headers);
> > json_object *json = json_tokener_parse(headers);
> >
> > json_object *seckey2_json =
> > json_object_object_get(json,"sec-websocket-key2");
> > const char* seckey2_raw = json_object_get_string(seckey2_json);
> > uint32_t seckey2;
> > ws_extract_seckey(seckey2_raw,&seckey2);
> > json_object_put(seckey2_json);
> >
> > json_object *seckey1_json =
> > json_object_object_get(json,"sec-websocket-key1");
> > const char* seckey1_raw = json_object_get_string(seckey1_json);
> > uint32_t seckey1;
> > ws_extract_seckey(seckey1_raw,&seckey1);
> > json_object_put(seckey1_json);
> >
> > // TODO : This guy will be throwing error in the near future.
> > ws_calculate_response(seckey1,seckey2,req->body, response);
> >
> > json_object_put(json);
> > return 0;
> > }
> >
> > /**
> > * Assumes little endian architecture!
> > * Takes the inputs and returns the challenge response necessary
> > * for completing a WS handshake.
> > * @param seckey1 - Concated number, divided by number of spaces
> > * @param seckey2 - Found same as seckey2
> > * @param body - Body from their request
> > * @param md5output - Room enough to store the md5'd result
> > * @return 0 on success
> > */
> > static int ws_calculate_response(uint32_t seckey1, uint32_t seckey2,
> > bstring body, unsigned char md5output[16]){
> > // TODO test if body is 8 bytes.
> > char* md5input = calloc(16,sizeof(char));
> > char* seckey1_bytes = (char*)&seckey1;
> > char* seckey2_bytes = (char*)&seckey2;
> > char* body_str = bdata(body);
> >
> > // TODO this assumes little endian arch. Do a runtime check in the
> > future to make it cross platform.
> > md5input[0] = seckey1_bytes[3];
> > md5input[1] = seckey1_bytes[2];
> > md5input[2] = seckey1_bytes[1];
> > md5input[3] = seckey1_bytes[0];
> >
> > md5input[4] = seckey2_bytes[3];
> > md5input[5] = seckey2_bytes[2];
> > md5input[6] = seckey2_bytes[1];
> > md5input[7] = seckey2_bytes[0];
> >
> > memcpy((void*)&md5input[8], body_str, 8);
> >
> > md5((const unsigned char *)md5input, 16, md5output);
> > free(md5input);
> > return 0;
> > }
> >
> > static int ws_extract_seckey(const char* seckey_str, uint32_t *seckey){
> > uint32_t seckey_concat = ws_concat_numbers(seckey_str);
> > uint32_t seckey_num_sp = ws_count_spaces(seckey_str);
> >
> > *seckey = seckey_concat/seckey_num_sp;
> > return 0;
> > }
> >
> > /**
> > * Take a normal C string, concats all the numbers characters
> > * and then turns that into a computer-native value.
> > * @param incoming
> > * @return
> > */
> > static uint32_t ws_concat_numbers(const char *incoming){
> > char* numbuff = calloc(16,sizeof(char));
> > int ni = 0;
> > for(int i=0; i<strlen(incoming); i++){
> > if(incoming[i]-'0' >= 0 && incoming[i]-'9' <= 0){
> > numbuff[ni] = incoming[i];
> > ni = ni + 1;
> > }
> > }
> > uint32_t extracted_number;
> > sscanf(numbuff,"%u",&extracted_number);
> > free(numbuff);
> > return extracted_number;
> > }
> >
> > static uint32_t ws_count_spaces(const char *incoming){
> > int count = 0;
> > for(int i=0; i<strlen(incoming); i++){
> > if(incoming[i] == ' '){
> > count = count + 1;
> > }
> > }
> > return count;
> > }
> >
>
Re: [mongrel2] Re: WebSocket support m2
- From:
- Zed A. Shaw
- Date:
- 2011-02-10 @ 06:53
On Wed, Feb 09, 2011 at 05:35:40PM -0800, Xavier Lange wrote:
> I think the websocket support falls on its face in the function
> int connection_http_to_handler(Connection *conn)
> specifically on the test
I'd say hook it in anyway you can, and then we can refine it and make it
better. There's also one extra thing to consider though: You'll want
to layer a chosen message format inside this. There's currently two
different ones in websockets, but maybe it can just be the same json
parsed stuff inside and pick that.
So with that in mind, I think you'd detect that it's a WebSocket in the
same place that XML and JSON are found, probably using a header, then
figure out what's inside.
Next thing is, you don't have to detect any special headers inside the
HTTP parser. You can actually just access the headers directly inside
the connection_* handlers. So where the other socket types are detected
in connection.c you'd just check for that header and say it's WEBSOCKET.
> If we don't want to do this kind of hack, maybe we can modify the HTTP
> parser to look for "Connection: Upgrade" header?
I'd go for that just to make easier, but I'd say, create a function that
abstracts this away so you can change it.
> I'm going to go forward with my quick workaround in the mean time. Let
> me know what you think.
Go for it. I'll check it out when you have something.
--
Zed A. Shaw
http://zedshaw.com/
Re: [mongrel2] Re: WebSocket support m2
- From:
- Xavier Lange
- Date:
- 2011-02-10 @ 20:17
WebSocket work around is limping along quite well. It checks headers
for connection/upgrade and overrides content_len to 8.
https://gist.github.com/821211#file_new_connection_http_to_handler
That function works most of the time. I think my handler is goofing on
reads because I lose the bytes from the body. I'll have to debug that
some more. Let me know what you think of the patch.
Zed: I'm using websockets because the jssocket wasn't responding to
some sends. I had it hooked up to a form and sometime a submit would
go the backend, sometimes it wouldn't. Plus, I control the stack so
requiring safari/chrome is not a problem.
Xavier
On Wed, Feb 9, 2011 at 10:53 PM, Zed A. Shaw <zedshaw@zedshaw.com> wrote:
> On Wed, Feb 09, 2011 at 05:35:40PM -0800, Xavier Lange wrote:
>> I think the websocket support falls on its face in the function
>> int connection_http_to_handler(Connection *conn)
>> specifically on the test
>
> I'd say hook it in anyway you can, and then we can refine it and make it
> better. There's also one extra thing to consider though: You'll want
> to layer a chosen message format inside this. There's currently two
> different ones in websockets, but maybe it can just be the same json
> parsed stuff inside and pick that.
>
> So with that in mind, I think you'd detect that it's a WebSocket in the
> same place that XML and JSON are found, probably using a header, then
> figure out what's inside.
>
> Next thing is, you don't have to detect any special headers inside the
> HTTP parser. You can actually just access the headers directly inside
> the connection_* handlers. So where the other socket types are detected
> in connection.c you'd just check for that header and say it's WEBSOCKET.
>
>> If we don't want to do this kind of hack, maybe we can modify the HTTP
>> parser to look for "Connection: Upgrade" header?
>
> I'd go for that just to make easier, but I'd say, create a function that
> abstracts this away so you can change it.
>
>> I'm going to go forward with my quick workaround in the mean time. Let
>> me know what you think.
>
> Go for it. I'll check it out when you have something.
>
> --
> Zed A. Shaw
> http://zedshaw.com/
>
Re: [mongrel2] Re: WebSocket support m2
- From:
- Zed A. Shaw
- Date:
- 2011-02-10 @ 20:27
On Thu, Feb 10, 2011 at 12:17:11PM -0800, Xavier Lange wrote:
> WebSocket work around is limping along quite well. It checks headers
> for connection/upgrade and overrides content_len to 8.
> https://gist.github.com/821211#file_new_connection_http_to_handler
I'll take a look and see what you have going.
> Zed: I'm using websockets because the jssocket wasn't responding to
> some sends. I had it hooked up to a form and sometime a submit would
> go the backend, sometimes it wouldn't. Plus, I control the stack so
> requiring safari/chrome is not a problem.
Well, I'd like to help if you've got a problem with it, since that's
probably easier to figure out than writing C code. :-)
--
Zed A. Shaw
http://zedshaw.com/
Re: [mongrel2] Re: WebSocket support m2
- From:
- Xavier Lange
- Date:
- 2011-02-10 @ 23:01
The websocket spec author has stated that, no, websockets will never
send content-length in the handshake. Workaround like this will be
necessary. Blah blah blah.
His reasoning: http://www.ietf.org/mail-archive/web/hybi/current/msg02274.html
Good news: I finagled things in to place and the handshake works.
Although I have not tested sending data...
Xavier
On Thu, Feb 10, 2011 at 12:27 PM, Zed A. Shaw <zedshaw@zedshaw.com> wrote:
> On Thu, Feb 10, 2011 at 12:17:11PM -0800, Xavier Lange wrote:
>> WebSocket work around is limping along quite well. It checks headers
>> for connection/upgrade and overrides content_len to 8.
>> https://gist.github.com/821211#file_new_connection_http_to_handler
>
> I'll take a look and see what you have going.
>
>> Zed: I'm using websockets because the jssocket wasn't responding to
>> some sends. I had it hooked up to a form and sometime a submit would
>> go the backend, sometimes it wouldn't. Plus, I control the stack so
>> requiring safari/chrome is not a problem.
>
> Well, I'd like to help if you've got a problem with it, since that's
> probably easier to figure out than writing C code. :-)
>
> --
> Zed A. Shaw
> http://zedshaw.com/
>
Re: [mongrel2] Re: WebSocket support m2
- From:
- Xavier Lange
- Date:
- 2011-02-11 @ 19:44
Aaaand it works now. Behold the power of websockets (cough,
handshakes) with some C handler goodness.
https://github.com/derdewey/mongrel2_c_handler/blob/master/lib/sample_handlers/ws_handshake.c
Zed, what do you think of that websocket patch?
Xavier
On Thu, Feb 10, 2011 at 3:01 PM, Xavier Lange <xrlange@gmail.com> wrote:
> The websocket spec author has stated that, no, websockets will never
> send content-length in the handshake. Workaround like this will be
> necessary. Blah blah blah.
>
> His reasoning: http://www.ietf.org/mail-archive/web/hybi/current/msg02274.html
>
> Good news: I finagled things in to place and the handshake works.
> Although I have not tested sending data...
>
> Xavier
>
> On Thu, Feb 10, 2011 at 12:27 PM, Zed A. Shaw <zedshaw@zedshaw.com> wrote:
>> On Thu, Feb 10, 2011 at 12:17:11PM -0800, Xavier Lange wrote:
>>> WebSocket work around is limping along quite well. It checks headers
>>> for connection/upgrade and overrides content_len to 8.
>>> https://gist.github.com/821211#file_new_connection_http_to_handler
>>
>> I'll take a look and see what you have going.
>>
>>> Zed: I'm using websockets because the jssocket wasn't responding to
>>> some sends. I had it hooked up to a form and sometime a submit would
>>> go the backend, sometimes it wouldn't. Plus, I control the stack so
>>> requiring safari/chrome is not a problem.
>>
>> Well, I'd like to help if you've got a problem with it, since that's
>> probably easier to figure out than writing C code. :-)
>>
>> --
>> Zed A. Shaw
>> http://zedshaw.com/
>>
>
Re: [mongrel2] Re: WebSocket support m2
- From:
- Zed A. Shaw
- Date:
- 2011-02-12 @ 18:13
On Fri, Feb 11, 2011 at 11:44:01AM -0800, Xavier Lange wrote:
> Aaaand it works now. Behold the power of websockets (cough,
> handshakes) with some C handler goodness.
>
>
https://github.com/derdewey/mongrel2_c_handler/blob/master/lib/sample_handlers/ws_handshake.c
>
> Zed, what do you think of that websocket patch?
Cool, where's your fossil so I can see what you've got going in a patch?
--
Zed A. Shaw
http://zedshaw.com/
Re: [mongrel2] Re: WebSocket support m2
- From:
- Xavier Lange
- Date:
- 2011-02-15 @ 16:23
Alright, I got a fossil server setup and committed the websocket code.
Took a little longer than expected.
http://mongrel2.tureus.com/timeline?r=trunk
Xavier
On Sat, Feb 12, 2011 at 10:13 AM, Zed A. Shaw <zedshaw@zedshaw.com> wrote:
> On Fri, Feb 11, 2011 at 11:44:01AM -0800, Xavier Lange wrote:
>> Aaaand it works now. Behold the power of websockets (cough,
>> handshakes) with some C handler goodness.
>>
>>
https://github.com/derdewey/mongrel2_c_handler/blob/master/lib/sample_handlers/ws_handshake.c
>>
>> Zed, what do you think of that websocket patch?
>
> Cool, where's your fossil so I can see what you've got going in a patch?
>
> --
> Zed A. Shaw
> http://zedshaw.com/
>
Re: [mongrel2] Re: WebSocket support m2
- From:
- Zed A. Shaw
- Date:
- 2011-02-15 @ 19:17
On Tue, Feb 15, 2011 at 08:23:50AM -0800, Xavier Lange wrote:
> Alright, I got a fossil server setup and committed the websocket code.
> Took a little longer than expected.
Alright, looking good so far. I did two commits so you can see how I
like the code in stages. The first one just changes your stuff to use
the check() macros we have:
http://mongrel2.org/info/01bc42cda5
The second one then refactors the code a bit so that we have a generic
is_websocket() that we can alter depending on how the standard changes:
http://mongrel2.org/info/682a5c2dd8
So now we need to figure out how this is actually used. It seems to me
that we'll need a sample that works with this, and there might need to
be a change to the connection state machine to deal with the socket.
What do you have working so far?
--
Zed A. Shaw
http://zedshaw.com/
Re: [mongrel2] Re: WebSocket support m2
- From:
- Xavier Lange
- Date:
- 2011-02-15 @ 19:34
To see a chrome compatible handshake and sending out data to the client,
take a look at my c lib. It has a WS handler demo.
Yes, good call on abstracting to is_socket. Although now thar the WS spec
is dropping the body nonce and providing an explicit protocol version
scheme, I'm going to consider handling handshakes and disconnects in the
http parser's state machine. It would make mongrel super general/useful
for ws no matter the adapter.
Would you be against such an addition to the http parser? And how about a
convention in the router similar to @ for messages?
Xavier
On Feb 15, 2011, at 11:17, "Zed A. Shaw" <zedshaw@zedshaw.com> wrote:
> On Tue, Feb 15, 2011 at 08:23:50AM -0800, Xavier Lange wrote:
>> Alright, I got a fossil server setup and committed the websocket code.
>> Took a little longer than expected.
>
> Alright, looking good so far. I did two commits so you can see how I
> like the code in stages. The first one just changes your stuff to use
> the check() macros we have:
>
> http://mongrel2.org/info/01bc42cda5
>
> The second one then refactors the code a bit so that we have a generic
> is_websocket() that we can alter depending on how the standard changes:
>
> http://mongrel2.org/info/682a5c2dd8
>
> So now we need to figure out how this is actually used. It seems to me
> that we'll need a sample that works with this, and there might need to
> be a change to the connection state machine to deal with the socket.
>
> What do you have working so far?
>
> --
> Zed A. Shaw
> http://zedshaw.com/
Re: [mongrel2] Re: WebSocket support m2
- From:
- Zed A. Shaw
- Date:
- 2011-02-15 @ 20:06
On Tue, Feb 15, 2011 at 11:34:40AM -0800, Xavier Lange wrote:
> To see a chrome compatible handshake and sending out data to the
> client, take a look at my c lib. It has a WS handler demo.
Ok I'll take a look tonight.
> Yes, good call on abstracting to is_socket. Although now thar the WS
> spec is dropping the body nonce and providing an explicit protocol
> version scheme, I'm going to consider handling handshakes and
> disconnects in the http parser's state machine. It would make mongrel
> super general/useful for ws no matter the adapter.
Doing this piece of code in the parser doesn't fit. But, doing the
framing parsing in the parser could work. Basically, if the parser is
checking headers then you're doing it wrong.
> Would you be against such an addition to the http parser? And how
> about a convention in the router similar to @ for messages?
Yep, that's my thinking. Use the code you have here for detecting the
websocket then just parse out the same kind of protocol as with the
jssocket.
--
Zed A. Shaw
http://zedshaw.com/
Re: [mongrel2] Re: WebSocket support m2
- From:
- Zed A. Shaw
- Date:
- 2011-02-15 @ 19:02
On Tue, Feb 15, 2011 at 08:23:50AM -0800, Xavier Lange wrote:
> Alright, I got a fossil server setup and committed the websocket code.
> Took a little longer than expected.
>
> http://mongrel2.tureus.com/timeline?r=trunk
Super duper cool. Let me pull this and then I have a couple of pointers
for improving the code.
Also do you have a test sample HTML and gear that works with this so I
can try it and think up a test case?
--
Zed A. Shaw
http://zedshaw.com/
Re: [mongrel2] Re: WebSocket support m2
- From:
- Alfred Tascon
- Date:
- 2011-02-11 @ 20:57
I have been waiting for Mongrel 2 support for WebSocket for a little while
now so thanks for your efforts - heres hoping it cuts the mustard.
But with fear on raining on your parade have you seen this?
http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-04
and the just released
http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-05
looks like they have removed the need for the 8 bytes
Also found this about IE9
http://blogs.msdn.com/b/interoperability/archive/2011/02/09/the-updated-websockets-prototype.aspx
Alfred
On Fri, Feb 11, 2011 at 7:44 PM, Xavier Lange <xrlange@gmail.com> wrote:
> Aaaand it works now. Behold the power of websockets (cough,
> handshakes) with some C handler goodness.
>
>
>
https://github.com/derdewey/mongrel2_c_handler/blob/master/lib/sample_handlers/ws_handshake.c
>
> Zed, what do you think of that websocket patch?
>
> Xavier
>
> On Thu, Feb 10, 2011 at 3:01 PM, Xavier Lange <xrlange@gmail.com> wrote:
> > The websocket spec author has stated that, no, websockets will never
> > send content-length in the handshake. Workaround like this will be
> > necessary. Blah blah blah.
> >
> > His reasoning:
> http://www.ietf.org/mail-archive/web/hybi/current/msg02274.html
> >
> > Good news: I finagled things in to place and the handshake works.
> > Although I have not tested sending data...
> >
> > Xavier
> >
> > On Thu, Feb 10, 2011 at 12:27 PM, Zed A. Shaw <zedshaw@zedshaw.com>
> wrote:
> >> On Thu, Feb 10, 2011 at 12:17:11PM -0800, Xavier Lange wrote:
> >>> WebSocket work around is limping along quite well. It checks headers
> >>> for connection/upgrade and overrides content_len to 8.
> >>> https://gist.github.com/821211#file_new_connection_http_to_handler
> >>
> >> I'll take a look and see what you have going.
> >>
> >>> Zed: I'm using websockets because the jssocket wasn't responding to
> >>> some sends. I had it hooked up to a form and sometime a submit would
> >>> go the backend, sometimes it wouldn't. Plus, I control the stack so
> >>> requiring safari/chrome is not a problem.
> >>
> >> Well, I'd like to help if you've got a problem with it, since that's
> >> probably easier to figure out than writing C code. :-)
> >>
> >> --
> >> Zed A. Shaw
> >> http://zedshaw.com/
> >>
> >
>
Re: [mongrel2] Re: WebSocket support m2
- From:
- Xavier Lange
- Date:
- 2011-02-11 @ 21:13
No raining on my parade. I'm glad they're changing the handshake
protocol... the one I implemented is goofy. The new spec looks
slightly better.
But I'm targeting the most current Chrome -- so once I get the framing
for messages down I'll be set. What's a protocol worth without any
clients? ;)
Xavier
On Fri, Feb 11, 2011 at 12:57 PM, Alfred Tascon <atascon@gmail.com> wrote:
> I have been waiting for Mongrel 2 support for WebSocket for a little while
> now so thanks for your efforts - heres hoping it cuts the mustard.
> But with fear on raining on your parade have you seen this?
> http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-04
> and the just released
> http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-05
> looks like they have removed the need for the 8 bytes
> Also found this about IE9
>
http://blogs.msdn.com/b/interoperability/archive/2011/02/09/the-updated-websockets-prototype.aspx
> Alfred
>
>
> On Fri, Feb 11, 2011 at 7:44 PM, Xavier Lange <xrlange@gmail.com> wrote:
>>
>> Aaaand it works now. Behold the power of websockets (cough,
>> handshakes) with some C handler goodness.
>>
>>
>>
https://github.com/derdewey/mongrel2_c_handler/blob/master/lib/sample_handlers/ws_handshake.c
>>
>> Zed, what do you think of that websocket patch?
>>
>> Xavier
>>
>> On Thu, Feb 10, 2011 at 3:01 PM, Xavier Lange <xrlange@gmail.com> wrote:
>> > The websocket spec author has stated that, no, websockets will never
>> > send content-length in the handshake. Workaround like this will be
>> > necessary. Blah blah blah.
>> >
>> > His reasoning:
>> > http://www.ietf.org/mail-archive/web/hybi/current/msg02274.html
>> >
>> > Good news: I finagled things in to place and the handshake works.
>> > Although I have not tested sending data...
>> >
>> > Xavier
>> >
>> > On Thu, Feb 10, 2011 at 12:27 PM, Zed A. Shaw <zedshaw@zedshaw.com>
>> > wrote:
>> >> On Thu, Feb 10, 2011 at 12:17:11PM -0800, Xavier Lange wrote:
>> >>> WebSocket work around is limping along quite well. It checks headers
>> >>> for connection/upgrade and overrides content_len to 8.
>> >>> https://gist.github.com/821211#file_new_connection_http_to_handler
>> >>
>> >> I'll take a look and see what you have going.
>> >>
>> >>> Zed: I'm using websockets because the jssocket wasn't responding to
>> >>> some sends. I had it hooked up to a form and sometime a submit would
>> >>> go the backend, sometimes it wouldn't. Plus, I control the stack so
>> >>> requiring safari/chrome is not a problem.
>> >>
>> >> Well, I'd like to help if you've got a problem with it, since that's
>> >> probably easier to figure out than writing C code. :-)
>> >>
>> >> --
>> >> Zed A. Shaw
>> >> http://zedshaw.com/
>> >>
>> >
>
>