Few ideas on improving SockJS protocol

633 views
Skip to first unread message

Marek Majkowski

unread,
Dec 12, 2011, 12:09:34 PM12/12/11
to SockJS
I'd like to share few ideas about improving various low-level aspects
of SockJS. Some are quite ambitious. Feedback welcome.


1. Binary data.

Currently, sockjs doesn't do binary data. Fortunately, you can
just encode numbers as unicode and send it that way.
(I'm quite proud about that :P)

But this is inefficient - encoding 8 bits can take up to 6 bytes
(\uxxxx form for some chars) and usually 2 bytes (for utf-8 encoding).

I'm thinking that we can do better by trying to utilize
ISO-8859-1 encoding instead of UTF-8 for http based transports
(xhr/jsonp and others). Of course some native websockets
implementations support binary data out of the box.


2. Support command line clients for SockJS.

Currently, SockJS has a small bit of framing on top of websockets
protocol. That makes creating a standalone command-line
SockJS client difficult - it must understand SockJS framing.

As opposed to current websockets url:
/prefix/server_id/session_id/websocket
i'm thinking of creating a new websockets url, that will expose
websockets without any additional framing, for example:
/prefix/websocket

That would make it easier to connect to sockjs from other
environments than browsers.


3. Faster fallback mechanism

Currently, when websockets (or other transports) aren't working, SockJS
usually waits for a timeout to occur (this is oversimplification, if websockets
actively _fail_, the fallback will happen much faster).

We can get rid of the hardcoded timeout (5 seconds now) in favor of
a timeout related to estimated RTT (round trip time) between a server
and a client. In the end - why wait 5 seconds if we know that the server
should respond in under 100 ms?

In some rare circumstances, from the server point of view, we could end
up in having two sockjs sessions opened (due to an inevitable race condition).
But I think this is a cost worth taking.

(Doing this may require significant changes to the fallback-choosing mechanism).

What do you think?

Are there any other things that SockJS could improve on?

Marek

Vladimir Dronnikov

unread,
Dec 12, 2011, 1:14:42 PM12/12/11
to maj...@gmail.com, SockJS
> 1. Binary data.
>
> Currently, sockjs doesn't do binary data. Fortunately, you can
> just encode numbers as unicode and send it that way.
> (I'm quite proud about that :P)
>
> But this is inefficient - encoding 8 bits can take up to 6 bytes
> (\uxxxx form for some chars) and usually 2 bytes (for utf-8 encoding).
>
> I'm thinking that we can do better by trying to utilize
> ISO-8859-1 encoding instead of UTF-8 for http based transports
> (xhr/jsonp and others). Of course some native websockets
> implementations support binary data out of the box.

I was thinking of base64 which is fast, trivially coded and inflates
to only a 1/3 of initial message.

> 2. Support command line clients for SockJS.
>
> Currently, SockJS has a small bit of framing on top of websockets
> protocol. That makes creating a standalone command-line
> SockJS client difficult - it must understand SockJS framing.
>
> As opposed to current websockets url:
>  /prefix/server_id/session_id/websocket
> i'm thinking of creating a new websockets url, that will expose
> websockets without any additional framing, for example:
>   /prefix/websocket
>
> That would make it easier to connect to sockjs from other
> environments than browsers.

einaros has been working on https://github.com/einaros/ws -- i believe
we should look at that and may be adopt something

> 3. Faster fallback mechanism
>
> Currently, when websockets (or other transports) aren't working, SockJS
> usually waits for a timeout to occur (this is oversimplification, if websockets
> actively _fail_, the fallback will happen much faster).
>
> We can get rid of the hardcoded timeout (5 seconds now) in favor of
> a timeout related to estimated RTT (round trip time) between a server
> and a client. In the end - why wait 5 seconds if we know that the server
> should respond in under 100 ms?
>
> In some rare circumstances, from the server point of view, we could end
> up in having two sockjs sessions opened (due to an inevitable race condition).
> But I think this is a cost worth taking.
>
> (Doing this may require significant changes to the fallback-choosing mechanism).
>

I'd explore the possibility to upgrade instead of fallback -- i.e.
first connect with bullet-proof jsonp and then "may be reconnect"
using higher protocols, at idle

> What do you think?
>
> Are there any other things that SockJS could improve on?
>

I'd vote pro sheduling exploring another encoding/decoding techniques
(still need some insight how to cope with escaping of JSON in Lua).

--Vladimir

Sergey Koval

unread,
Dec 12, 2011, 1:35:49 PM12/12/11
to SockJS
Hi,

On Mon, Dec 12, 2011 at 7:09 PM, Marek Majkowski <maj...@gmail.com> wrote:
I'd like to share few ideas about improving various low-level aspects
of SockJS. Some are quite ambitious. Feedback welcome.


1. Binary data.
What are possible use cases for binary data?
 
2. Support command line clients for SockJS.

As opposed to current websockets url:
 /prefix/server_id/session_id/websocket
i'm thinking of creating a new websockets url, that will expose
websockets without any additional framing, for example:
  /prefix/websocket
I'm up for it - easy to support and won't break older clients. But how it'll handle heartbeats, using ping/pong messages from the websocket spec?
 
Serge.

Sergey Koval

unread,
Dec 12, 2011, 1:55:12 PM12/12/11
to SockJS
Hi,

On Mon, Dec 12, 2011 at 8:14 PM, Vladimir Dronnikov <dron...@gmail.com> wrote:
einaros has been working on https://github.com/einaros/ws -- i believe
we should look at that and may be adopt something
SockJS already includes websocket server, there's no need to use any other implementation. 
 
> 3. Faster fallback mechanism
I'd explore the possibility to upgrade instead of fallback -- i.e.
first connect with bullet-proof jsonp and then "may be reconnect"
using higher protocols, at idle
I am, personally, against this change. It will complicate things and won't solve the problem.

User will still have to wait and he won't receive or send any data during upgrade phase because of possible race conditions. Keeping that in mind, it is better to wait in the beginning and use best available transport instead of connecting with something and then block _active_ connection while trying to upgrade.

Forgot to mention - SockJS should put a cookie with last established transport to avoid probing on next run.
 
Serge.

Vladimir Dronnikov

unread,
Dec 12, 2011, 2:43:27 PM12/12/11
to serge...@gmail.com, SockJS
>> einaros has been working on https://github.com/einaros/ws -- i believe
>> we should look at that and may be adopt something
>
> SockJS already includes websocket server, there's no need to use any other
> implementation.
>

I wouldn't be so definite. Please, read its readme -- it's both server
_and client_, which is the point of the question.

>>
>> > 3. Faster fallback mechanism
>> I'd explore the possibility to upgrade instead of fallback -- i.e.
>> first connect with bullet-proof jsonp and then "may be reconnect"
>> using higher protocols, at idle
>
> I am, personally, against this change. It will complicate things and won't
> solve the problem.
>

One should always be capable of connecting via jsonp, providing stack
is ok. This will allow to establish connection as quick as possible
which is quite a killer feature of the most nowdays apps.

> User will still have to wait and he won't receive or send any data during
> upgrade phase because of possible race conditions.

That's why I used the word 'at idle'.

> Keeping that in mind, it
> is better to wait in the beginning and use best available transport instead
> of connecting with something and then block _active_ connection while trying
> to upgrade.

Upgrade should take place between polling cycles, hence with minimal penalty.

>
> Forgot to mention - SockJS should put a cookie with last established
> transport to avoid probing on next run.
>

Noway, man, it's way naive. It will as well remember jsonp.

Worth mentioning, that all these points have been investigated by
socket.io team, answers to be read in engine.io goals. No need to
repeat the same explorations.

> Serge.

--Vladimir

Sergey Koval

unread,
Dec 12, 2011, 3:18:02 PM12/12/11
to Vladimir Dronnikov, SockJS
Hi,

On Mon, Dec 12, 2011 at 9:43 PM, Vladimir Dronnikov <dron...@gmail.com> wrote:
I wouldn't be so definite. Please, read its readme -- it's both server
_and client_, which is the point of the question.
Idea was to avoid json escaping for websocket transport. Not sure how websocket client will help. Or you mean having websocket client available for sockjs-node developers?

> User will still have to wait and he won't receive or send any data during
> upgrade phase because of possible race conditions.
That's why I used the word 'at idle'.
Please define "idle". Only client can make decision when to switch to another transport. So, if server has some data to send, client won't be able to receive once upgrade completed, even though connection is established.

In SockJS (for example), it might take up to 5s to decide that transport timed out. In socket.io, with default settings, it will take up to 8 seconds to fail and switch back to jsonp transport.

So, from users perspective, there's no difference if client will wait for 5s before connecting or waiting data for 5 seconds after connecting. But this feature would require SockJS (or engine.io) developers to write code and test it.

Again, what's the point?
 
> Keeping that in mind, it
> is better to wait in the beginning and use best available transport instead
> of connecting with something and then block _active_ connection while trying
> to upgrade.
Upgrade should take place between polling cycles, hence with minimal penalty.
Right, and it will block any activity for established connection for duration of the upgrade.
 
> Forgot to mention - SockJS should put a cookie with last established
> transport to avoid probing on next run.
Noway, man, it's way naive. It will as well remember jsonp.
Ahem, if client decided that best available transport is xhr-streaming, why would you want user to probe for websockets again on next page load?
 
Worth mentioning, that all these points have been investigated by
socket.io team, answers to be read in engine.io goals. No need to
repeat the same explorations.
Yes, I'm well aware about engine.io, as I'm developer and maintainer of the TornadIO and TornadIO2 socket.io python integration libraries.

One again, my personal opinion, doing transport upgrade won't solve problem it is supposed to solve.
 
Serge.

Vladimir Dronnikov

unread,
Dec 12, 2011, 3:54:47 PM12/12/11
to Sergey Koval, SockJS
> Idea was to avoid json escaping for websocket transport. Not sure how
> websocket client will help. Or you mean having websocket client available
> for sockjs-node developers?

Yes. I was responding to "2. Support command line clients for SockJS."

> Please define "idle". Only client can make decision when to switch to
> another transport. So, if server has some data to send, client won't be able
> to receive once upgrade completed, even though connection is established.


>
> In SockJS (for example), it might take up to 5s to decide that transport
> timed out. In socket.io, with default settings, it will take up to 8 seconds
> to fail and switch back to jsonp transport.
>
> So, from users perspective, there's no difference if client will wait for 5s
> before connecting or waiting data for 5 seconds after connecting. But this
> feature would require SockJS (or engine.io) developers to write code and
> test it.
>
> Again, what's the point?
>
>>
>> > Keeping that in mind, it
>> > is better to wait in the beginning and use best available transport
>> > instead
>> > of connecting with something and then block _active_ connection while
>> > trying
>> > to upgrade.
>> Upgrade should take place between polling cycles, hence with minimal
>> penalty.
>
> Right, and it will block any activity for established connection for
> duration of the upgrade.
>

Upgrade should be conditional. Conditions should be under the control.
This would provide for tunability.
Noone said upgrade must be attempted nomatter the conditions are.

>> > Forgot to mention - SockJS should put a cookie with last established
>> > transport to avoid probing on next run.
>> Noway, man, it's way naive. It will as well remember jsonp.
>
> Ahem, if client decided that best available transport is xhr-streaming, why
> would you want user to probe for websockets again on next page load?
>

Client is app-level. Let's stay library. You can use whatever to pin
the transport but that's not sockjs's business, i presume.

>
> One again, my personal opinion, doing transport upgrade won't solve problem
> it is supposed to solve.
>
> Serge.

It's worth testing, as it's in principle makes connection more
flexible hence robust.

Vladimir Dronnikov

unread,
Dec 12, 2011, 4:11:39 PM12/12/11
to Sergey Koval, SockJS
>>> > Forgot to mention - SockJS should put a cookie with last established
>>> > transport to avoid probing on next run.

also please look at https://github.com/sockjs/sockjs-node/issues/35

--Vladimir

Sergey Koval

unread,
Dec 12, 2011, 4:18:26 PM12/12/11
to Vladimir Dronnikov, SockJS
Socket.io is doing it (though, it is possible to turn it off) and it really speeds up connection on page refresh. If cookies are disabled, so be it - it'll just try to find best transport again.

But thanks for the article, it is interesting read.

Serge.

Marek Majkowski

unread,
Dec 12, 2011, 5:18:23 PM12/12/11
to Shripad K, SockJS
On Mon, Dec 12, 2011 at 17:20, Shripad K <assortme...@gmail.com> wrote:

> On Mon, Dec 12, 2011 at 10:39 PM, Marek Majkowski <maj...@gmail.com> wrote:
>> 2. Support command line clients for SockJS.
>>
>> Currently, SockJS has a small bit of framing on top of websockets
>> protocol. That makes creating a standalone command-line
>> SockJS client difficult - it must understand SockJS framing.
>>
>> As opposed to current websockets url:
>>  /prefix/server_id/session_id/websocket
>> i'm thinking of creating a new websockets url, that will expose
>> websockets without any additional framing, for example:
>>   /prefix/websocket
>>
>> That would make it easier to connect to sockjs from other
>> environments than browsers.
>>
> I'm actually quite happy with the current websocket url. Is there any
> benefit to having one without server_id or session_id? Are you wanting to
> replace this functionality or a feature in itself?
> I find it much easier to run load tests with the current one. See an
> example: https://gist.github.com/1406702
>
> There is already a client by Worlize
> https://github.com/Worlize/WebSocket-Node.git that works really well with
> SockJS. But if the client you are planning to start working on is going to
> be a thin client (with no added extras that WebSocket-Node has) then I'm all
> for it :)

Wow. So how many concurrent connections were you able to squeeze out of SockJS?
Impressive :)

No, I don't think of replacing /echo/x/x/websocket url. I'm just
thinking of adding
a new one /echo/websocket, that will be without any custom sockjs framing.
No JSON encoding, no heartbeats, no close frames. Pure websockets.
The point is to enable you to create stress test that actually sends/receives
data without making it custom for SockJS (and just keep it as a
generic websockets
stress test). Does it make sense?

Some people want to connect to SockJS from, for example node, thus the
custom SockJS framing and heartbeats aren't necessary.

Marek

Marek Majkowski

unread,
Dec 12, 2011, 5:21:26 PM12/12/11
to dron...@gmail.com, SockJS
On Mon, Dec 12, 2011 at 18:14, Vladimir Dronnikov <dron...@gmail.com> wrote:
>> 1. Binary data.

>
> I was thinking of base64 which is fast, trivially coded and inflates
> to only a 1/3 of initial message.

Good idea. I need to benchmark it.

>> 2. Support command line clients for SockJS.
>>
>> Currently, SockJS has a small bit of framing on top of websockets
>> protocol. That makes creating a standalone command-line
>> SockJS client difficult - it must understand SockJS framing.
>>
>> As opposed to current websockets url:
>>  /prefix/server_id/session_id/websocket
>> i'm thinking of creating a new websockets url, that will expose
>> websockets without any additional framing, for example:
>>   /prefix/websocket
>>
>> That would make it easier to connect to sockjs from other
>> environments than browsers.
>
> einaros has been working on https://github.com/einaros/ws -- i believe
> we should look at that and may be adopt something

That's a different story, but good to know. I was thinking about getting
rid of current (custom) websockets server in sockjs-node and
maybe replacing it with:
https://github.com/jcoglan/faye-websocket-node
but, your suggestion
https://github.com/einaros/ws
also looks interesting. I'll evaluate.

Marek

Marek Majkowski

unread,
Dec 12, 2011, 5:23:18 PM12/12/11
to serge...@gmail.com, SockJS

Maybe no heartbeats at all?

It's intended to be more local-network server-to-server thing,
so heartbeats may not be neccessary.

Or maybe we should send ping/pong sometimes.
Not a big deal IMO.

Marek

Marek Majkowski

unread,
Dec 12, 2011, 5:24:50 PM12/12/11
to Andrew Kimpton, SockJS
On Mon, Dec 12, 2011 at 18:50, Andrew Kimpton <awki...@gmail.com> wrote:
> On Dec 12, 2011, at 12:09 PM, Marek Majkowski wrote:
>
>> 1. Binary data.
>>
> The older browser implementations of the javascript API in the same era as Hixie (ie Safari 5 and Chrome <=13) didn't support binary transfers. My workaround was to base64 the binary data I needed to transmit (graphics data) which whilst a little wasteful was at least pretty efficient for my needs.
>
> Chrome 15 (but not IIRC Firefox 7,8) support binary transfers in the WebSocket API by allowing you to send/receive ArrayBuffers (and/or blobs). This is just what I need to avoid the Base64 encode (the wire format is HyBi with binary frames). I'd hope that SockJS could support the same API even if under the covers a base64 encode was still needed in certain cases for the transport (though I'd hope those cases would be minimal/edge cases).

Yep, that's the idea! The same API no matter what happens on the wire.

Base64 sounds like an option worth exploring.

Marek

Marek Majkowski

unread,
Dec 12, 2011, 5:34:24 PM12/12/11
to serge...@gmail.com, Vladimir Dronnikov, SockJS
On Mon, Dec 12, 2011 at 21:18, Sergey Koval <serge...@gmail.com> wrote:
> Socket.io is doing it (though, it is possible to turn it off) and it really
> speeds up connection on page refresh. If cookies are disabled, so be it -
> it'll just try to find best transport again.
>
> But thanks for the article, it is interesting read.

Two issues emerged:

1) SockJS should remember last working transports

I personally won't use cookie but window.localstorage, but it's a detail.
The big question, is how long to keep the data for. If we can be sure
that the computer wasn't moved (ie: network conditions didn't change much),
we can trust previously selected cookie.

On the other hand, on a laptop the network may change every
time I open the lid.

Maybe Sockjs-server should return a client IP (or hash of it),
which could be used as key to find the cookie/localStorage
with previously working transport?

Or maybe we should just regularry clean up the cookie/localStorage
(every 10 minutes?).


2) Upgrade of protocols vs downgrande.

SockJS currently does "downgrade". ie: we start with good transports
(native websockets) and when that doesn't work, we look for another
one (worse), and so on.

I don't really understand the "upgrade" idea, I can't see the benefits.
It looks that all it does is delays the inevitable - waiting for a timeout
in a pessimistic case. It makes sense for flash-fallback preloading though.

Marek

3rdEden

unread,
Dec 12, 2011, 5:46:43 PM12/12/11
to sockjs
We turned it off because it had to much issues like Vladimir
mentioned. There
is only a minor speedup and it was not worth the issues that it was
introducing.

On Dec 12, 10:18 pm, Sergey Koval <serge.ko...@gmail.com> wrote:
> Socket.io is doing it (though, it is possible to turn it off) and it really
> speeds up connection on page refresh. If cookies are disabled, so be it -
> it'll just try to find best transport again.
>
> But thanks for the article, it is interesting read.
>
> Serge.
>

Marek Majkowski

unread,
Dec 12, 2011, 5:57:19 PM12/12/11
to in...@3rd-eden.com, sockjs
On Mon, Dec 12, 2011 at 22:46, 3rdEden <in...@3rd-eden.com> wrote:
> We turned it off because it had to much issues like Vladimir
> mentioned. There is only a minor speedup and it was not worth the issues
> that it was introducing.

Interesting. I'm afraid of users behind misbehaving proxies or firewalls.

It's quite bad when on every request user needs to wait for a websocket
timeout to get downgraded to xhr or jsonp.

I'm thinking of implementing it as a preference. ie: if previous
connection to ws failed, and xhr succeeded, let's this time start
from xhr and try ws later.

Ie: push previously working transport to the top of the transports
queue.

Would that work better?

Marek

Arnout Kazemier

unread,
Dec 12, 2011, 6:08:28 PM12/12/11
to Marek Majkowski, sockjs
And that's one of the reasons why engine.io is starting with a "upgrade" protocol instead
of a "fallback / downgrade" protocol. Firewalls and anti-viruses are a night mere, the
research that I have done with my co-worker showed that at least 5% of all our users
are behind a corporate firewalls which already blocks non standard ports.

Other firewalls & virus scanners don't understand WebSockets and just drop the connection 
instead of closing it properly and then there are the bugs in the browser implementations of
WebSockets.

As for the cookies, sorting or pushing the last best used transport would be the best option
but you have to take network activity in to account, you don't downgrade even further if they
are on a unstable internet connection.

Sergey Koval

unread,
Dec 12, 2011, 6:22:13 PM12/12/11
to in...@3rd-eden.com, Marek Majkowski, sockjs
On Tue, Dec 13, 2011 at 1:08 AM, Arnout Kazemier <in...@3rd-eden.com> wrote:
And that's one of the reasons why engine.io is starting with a "upgrade" protocol instead
of a "fallback / downgrade" protocol. 
 
I had brief talk with Guillermo about engine.io other day and he said that it will be complete client responsibility to switch to other transport and will happen between polls.

Probably I'm missing something, but in worst case if websocket connection request times out there won't be any communication going till next poll. So, lets say, for interactive application that responds to user actions, it will look like user has some kind of network lag as application won't react to his actions.

Or approach to upgrade connection is different?

I don't know whats better - let user sit and wait for application to connect or try to upgrade connection behind the scenes, as user might not notice the delay.

Thanks,
Serge.
Reply all
Reply to author
Forward
0 new messages