Skip to content

ably-forks/sockjs-erlang

 
 

SockJS family:

SockJS-erlang server

SockJS server written in Erlang. Can run with Cowboy http server. SockJS-erlang is in core web-framework agnostic (up to version v0.2.1 we also supported Misultin). SockJS-erlang is compatible with SockJS client version 0.3. See https://github.com/sockjs/sockjs-client for more information on SockJS.

Show me the code!

A simplistic echo SockJS server using Cowboy may look more or less like this:

main(_) ->
    ok = application:start(xmerl),
    ok = application:start(sockjs),
    ok = application:start(ranch),
    ok = application:start(crypto),
    ok = application:start(cowlib),
    ok = application:start(cowboy),

    SockjsState = sockjs_handler:init_state(
                    <<"/echo">>, fun service_echo/3, state, []),

    Routes = [{'_',  [{<<"/echo/[...]">>,
                       sockjs_cowboy_handler, SockjsState}]}],
    Dispatch = cowboy_router:compile(Routes),

    cowboy:start_http(cowboy_test_http_listener, 100,
                      [{port, 8081}],
                      [{env, [{dispatch, Dispatch}]}]),
    receive
        _ -> ok
    end.

service_echo(_Conn, init, state)          -> {ok, state};
service_echo(Conn, {recv, Data}, state)   -> Conn:send(Data); % or: sockjs:send(Data, Conn)
service_echo(_Conn, {info, _Info}, state) -> {ok, state};
service_echo(_Conn, closed, state)        -> {ok, state}.

Dig into the examples directory to get working code:

How to run the examples?

You may need a recent version of Erlang/OTP, at least R14B is recommended.

To run Cowboy example:

cd sockjs-erlang
./rebar get-deps
./rebar compile
./examples/cowboy_echo.erl

This will start a simple /echo SockJS server on http://localhost:8081. Open this link in a browser and play around.

SockJS-erlang API

Except for the web framework-specific API's, SockJS-erlang is rather simple. It has just a couple of methods:

  • sockjs_handler:init_state(Prefix, Callback, State, Options) -> service()

    Initializes the state of a SockJS service (ie: a thing you can access from the browser, it has an url and a code on the server side). prefix is a binary that must exacty match the url prefix of the service, for example, if service will be listening on '/echo', this parameter must be set to <<"/echo">>. callback function will be called when a new SockJS connection is established, data received or a connection is closed. The value of state will be passed to the callback and preserved if returned value has changed. Options is a proplist that can contain following tuples:

    • {sockjs_url, string()} - Transports which don't support cross-domain communication natively ('eventsource' to name one) use an iframe trick. A simple page is served from the SockJS server (using its foreign domain) and is placed in an invisible iframe. Code run from this iframe doesn't need to worry about cross-domain issues, as it's being run from domain local to the SockJS server. This iframe also does need to load SockJS javascript client library, and this option lets you specify its url (if you're unsure, point it to the latest minified SockJS client release, this is the default).
    • {websocket, boolean()} - are native websockets enabled? This can be usefull when your loadbalancer doesn't support them.
    • {cookie_needed, boolean()} - is your load balancer relying on cookies to get sticky sessions working?
    • {heartbeat_delay, integer()} - how often to send heartbeat packets (in ms).
    • {disconnect_delay, integer()} - how long to hold session state after the client was last connected (in ms).
    • {response_limit, integer()} - the maximum size of a single http streaming response (in bytes).
    • {logger, fun/3} - a function called on every request, used to print request to the logs (or on the screen by default).

    For more explanation, please do take a look at SockJS-node readme.

  • Connection:send(Payload) -> ok

  • sockjs:send(Payload, Connection) -> ok

    Send data over an active SockJS connection. Payload should be of iodata() type. Messages sent after connection gets closed will be lost.

  • Connection:close(Code, Reason) -> ok

  • sockjs:close(Code, Reason, Connection) -> ok

  • sockjs:close(Connection) -> ok

    Close an active SockJS connection with code and reason. If code and reason are skipped, the defaults are used.

  • Connection:info() -> proplist()

  • sockjs:info(Connection) -> proplist()

    Sometimes you may want to know more about the underlying connection. This method returns a proplist with few attributes extracted from the first HTTP/websocket request that was coming to this connection. You should see:

    * peername - ip address and port of the remote host
    * sockname - ip address and port of the local endpoint
    * path - the path used by the request that started the connection
    * headers - a set of headers extracted from the request that
      may be handy (don't expect to retrieve Cookie header).
    

The framework-specific calls are more problematic. Instead of trying to explain how to use them, please take a look at the examples.

  • type(req() :: {cowboy, request()})
  • sockjs_handler:handle_req(service(), req()) -> req()
  • sockjs_handler:handle_ws(service(), req()) -> req()

What's news in this fork?

API for multiplexing

  • sockjs:send(Payload, Channel) -> ok

    Send data over a channel. Payload should be of iodata() type. Messages sent after connection gets closed will be lost.

  • sockjs:close(Code, Reason, Channel) -> ok

  • sockjs:close(Channel) -> ok

    Close a channel with code and reason. If code and reason are skipped, the defaults are used. But actually, code and reason are not sent to client.

  • sockjs:info(Channel) -> proplist()

    Sometimes you may want to know more about the underlying connection. This method returns a proplist with few attributes extracted from the first HTTP/websocket request that was coming to this connection. You should see:

    * peername - ip address and port of the remote host
    * sockname - ip address and port of the local endpoint
    * path - the path used by the request that started the connection
    * headers - a set of headers extracted from the request that
      may be handy (don't expect to retrieve Cookie header).
    
  • sockjs:to_session(Channel) -> conn()

    Convert a channel to connection.

  • sockjs:to_channel(Conn, Topic) -> channel()

    Convert a connection to channel with specific topic (channel name).

  • sockjs_multiplex:init_state(Services, {AuthenCallback, Options})

    Sometimes you don't want client access directly your channel services and you want to do something with client first. If you use authentication callback, you can decide when allow client to use your services. It's quite simple, see example. Valid authentication callback options:

    * `{state, list()}` - initial state of authentication callback
    

    If you do not use multiplexing, you can also implement this mechanism, see example.

  • sockjs_multiplex:init_state(Services)

    Initialize state without authentication callback.

Stability

SockJS-erlang is quite new, but should be reasonably stable. Cowboy is passes all the SockJS-protocol tests.

Deployment and load balancing

SockJS servers should work well behind many load balancer setups, but it sometimes requres some additional twaks. For more details, please do take a look at the 'Deployment' section in SockJS-node readme.

Development and testing

You need rebar (instructions). Due to a bug in rebar config handling you need a reasonably recent version - newer than late Oct 2011. Alternatively, SockJS-erlang is bundeled with a recent rebar binary.

SockJS-erlang contains a test_server, a simple server used for testing.

To run Cowboy test_server:

cd sockjs-erlang
./rebar get-deps
./rebar compile
./examples/cowboy_test_server.erl

That should start test_server on port 8081. Currently, there are two separate test suits using test_server.

SockJS-protocol Python tests

Once test_server is listening on http://localhost:8081 you may test it using SockJS-protocol:

cd sockjs-protocol
make test_deps
./venv/bin/python sockjs-protocol-dev.py

For details see SockJS-protocol README.

SockJS-client QUnit tests

You need to start a second web server (by default listening on 8080) that is serving various static html and javascript files:

cd sockjs-client
make test

At that point you should have two web servers running: sockjs-erlang on 8081 and sockjs-client on 8080. When you open the browser on http://localhost:8080/ you should be able run the QUnit tests against your sockjs-node server.

For details see SockJS-client README.

Additionally, if you're doing more serious development consider using make serve, which will automatically the server when you modify the source code.

Packages

No packages published

Languages

  • Erlang 98.3%
  • Makefile 1.7%