├── Makefile ├── .hgignore ├── LICENSE ├── package.mk ├── src ├── rabbit_socks_listener_sup.erl ├── rabbit_socks_echo.erl ├── rabbit_socks_socketio_sup.erl ├── rabbit_socks.erl ├── rabbit_socks_util.erl ├── rabbit_socks_stomp.erl ├── rabbit_socks_sup.erl ├── rabbit_socks_connection_sup.erl ├── rabbit_socks_ws.erl ├── rabbit_socks_xhrpolling.erl ├── rabbit_socks_ws_connection.erl ├── rabbit_socks_socketio.erl └── rabbit_socks_mochiweb.erl ├── ebin └── rabbit_socks.app.in ├── priv └── www │ ├── index.html │ ├── socketio.html │ └── stomp.html ├── README.md └── LICENSE-MPL /Makefile: -------------------------------------------------------------------------------- 1 | include ../umbrella.mk 2 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | ^tmp/ 2 | ^ebin/.*.beam$ 3 | ~$ 4 | ^dist/ 5 | ^build/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This package, rabbit-socks, is licensed under the MPL. For the 2 | MPL, please see LICENSE-MPL. 3 | 4 | If you have any questions regarding licensing, please contact us at 5 | info@rabbitmq.com. 6 | -------------------------------------------------------------------------------- /package.mk: -------------------------------------------------------------------------------- 1 | APP_NAME:=rabbit_socks 2 | DEPS:=rabbitmq-server rabbitmq-erlang-client rabbitmq-mochiweb 3 | 4 | WITH_BROKER_TEST_COMMANDS=eunit:test(rabbit_socks_test_util,[verbose]) 5 | 6 | define construct_app_commands 7 | cp -r $(PACKAGE_DIR)/priv $(APP_DIR) 8 | endef 9 | -------------------------------------------------------------------------------- /src/rabbit_socks_listener_sup.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_listener_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | -export([start_link/0, init/1]). 6 | 7 | start_link() -> 8 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 9 | 10 | init([]) -> 11 | {ok, {{one_for_one, 10, 10}, []}}. 12 | -------------------------------------------------------------------------------- /ebin/rabbit_socks.app.in: -------------------------------------------------------------------------------- 1 | {application, rabbit_socks, 2 | [{description, "Embedded browser sockets support"}, 3 | {vsn, "%%VSN%%"}, 4 | {modules, []}, 5 | {registered, []}, 6 | {mod, {rabbit_socks, []}}, 7 | {env, [{listeners, []}]}, 8 | %% {rabbit_mochiweb, 9 | %% [{protocol, rabbit_socks_echo}]}]}]}, 10 | %%{{"127.0.0.1", 5975}, []}, 11 | %%{{"127.0.0.1", 5976}, [{ssl, true}]}]}]}, 12 | {applications, [kernel, stdlib, rabbit, rabbitmq_mochiweb]}]}. 13 | -------------------------------------------------------------------------------- /src/rabbit_socks_echo.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_echo). 2 | 3 | %% Start the supervisor 4 | -export([start_link/0]). 5 | 6 | %% Callbacks 7 | -export([subprotocol_name/0, init/2, open/3, handle_frame/2, terminate/1]). 8 | 9 | subprotocol_name() -> "echo". 10 | 11 | start_link() -> 12 | rabbit_socks_connection_sup:start_link(?MODULE). 13 | 14 | init(_Path, []) -> 15 | {ok, undefined}. 16 | 17 | open(WriterModule, Writer, undefined) -> 18 | {ok, {WriterModule, Writer}}. 19 | 20 | terminate({_Module, _Writer}) -> 21 | ok. 22 | 23 | handle_frame(Frame = {utf8, _}, Framing = {WriterModule, Writer}) -> 24 | WriterModule:send_frame(Frame, Writer), 25 | {ok, Framing}. 26 | -------------------------------------------------------------------------------- /src/rabbit_socks_socketio_sup.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_socketio_sup). 2 | 3 | -behaviour(supervisor). 4 | -export([init/1]). 5 | 6 | %%------------------------- 7 | 8 | -export([start_link/0, start_child/1]). 9 | 10 | %%-------------------------------------------------------------------- 11 | 12 | init([]) -> 13 | {ok, {{simple_one_for_one, 0, 1}, 14 | [{undefined, {rabbit_socks_socketio, start_link, []}, 15 | transient, 50, worker, [rabbit_socks_socketio]}]}}. 16 | 17 | %%-------------------------------------------------------------------- 18 | 19 | start_link() -> 20 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 21 | 22 | start_child(Params) -> 23 | supervisor:start_child(?MODULE, [Params]). 24 | -------------------------------------------------------------------------------- /src/rabbit_socks.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks). 2 | 3 | -behaviour(application). 4 | 5 | -export([start/2, stop/1]). 6 | %% For other apps to drive rabbit-socks 7 | -export([start_listener/3]). 8 | 9 | start(normal, []) -> 10 | {ok, SupPid} = rabbit_socks_sup:start_link(), 11 | case application:get_env(listeners) of 12 | undefined -> 13 | throw({error, socks_no_listeners_given}); 14 | {ok, Listeners} -> 15 | error_logger:info_msg("Starting ~s~nbinding to:~n~p", 16 | ["Rabbit Socks", Listeners]), 17 | ok = rabbit_socks_mochiweb:start(Listeners) 18 | end, 19 | {ok, SupPid}. 20 | 21 | stop(_State) -> 22 | ok. 23 | 24 | start_listener(ListenerSpec, Subprotocol, Options) -> 25 | rabbit_socks_mochiweb:start_listener(ListenerSpec, Subprotocol, Options). 26 | -------------------------------------------------------------------------------- /src/rabbit_socks_util.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_util). 2 | 3 | -export([is_digit/1, binary_splitwith/2]). 4 | 5 | binary_splitwith(Pred, Bin) -> 6 | binary_splitwith1(Pred, Bin, 0). 7 | 8 | binary_splitwith1(Pred, Bin, Ind) -> 9 | case Bin of 10 | <> -> 11 | {<<>>, Take}; 12 | <> -> 13 | case Pred(Char) of 14 | true -> binary_splitwith1(Pred, Bin, Ind + 1); 15 | false -> {Take, <>} 16 | end 17 | end. 18 | 19 | is_digit($0) -> true; 20 | is_digit($1) -> true; 21 | is_digit($2) -> true; 22 | is_digit($3) -> true; 23 | is_digit($4) -> true; 24 | is_digit($5) -> true; 25 | is_digit($6) -> true; 26 | is_digit($7) -> true; 27 | is_digit($8) -> true; 28 | is_digit($9) -> true; 29 | is_digit(_) -> false. 30 | -------------------------------------------------------------------------------- /src/rabbit_socks_stomp.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_stomp). 2 | 3 | -export([start_link/0]). 4 | 5 | %% callbacks 6 | -export([subprotocol_name/0, init/2, open/3, handle_frame/2, terminate/1]). 7 | 8 | %% Start supervisor 9 | start_link() -> 10 | rabbit_socks_connection_sup:start_link(?MODULE). 11 | 12 | subprotocol_name() -> "STOMP". 13 | 14 | %% Spin up STOMP frame processor 15 | init(_Path, []) -> 16 | {ok, undefined}. 17 | 18 | open(Writer, WriterArg, undefined) -> 19 | gen_server:start_link(rabbit_stomp_processor, 20 | [{Writer, WriterArg}], []). 21 | 22 | terminate(Pid) -> 23 | %% FIXME 24 | exit(Pid, normal). 25 | 26 | handle_frame({utf8, Bin}, Pid) -> 27 | %% Expect a whole frame at a time, but not the terminator 28 | StompBin = unicode:characters_to_list(Bin), 29 | {ok, StompFrame, []} = rabbit_stomp_frame:parse( 30 | StompBin, rabbit_stomp_frame:initial_state()), 31 | rabbit_stomp_processor:process_frame(Pid, StompFrame), 32 | {ok, Pid}. 33 | -------------------------------------------------------------------------------- /src/rabbit_socks_sup.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | -export([start_link/0]). 6 | 7 | -export([init/1]). 8 | 9 | start_link() -> 10 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 11 | 12 | init([]) -> 13 | SocketioSup = {rabbit_socks_socketio_sup, 14 | {rabbit_socks_socketio_sup, start_link, []}, 15 | transient, 5, supervisor, [rabbit_socks_socketio]}, 16 | 17 | {ok, {{one_for_one, 10, 10}, 18 | [{rabbit_socks_listener_sup, 19 | {rabbit_socks_listener_sup, start_link, []}, 20 | transient, 5, supervisor, [rabbit_socks_listener_sup]}, 21 | {rabbit_socks_websockets, {rabbit_socks_connection_sup, start_link, [rabbit_socks_ws_connection]}, 22 | transient, 5, supervisor, [rabbit_socks_connection_sup]}, 23 | {rabbit_socks_xhrpolling, {rabbit_socks_connection_sup, start_link, [rabbit_socks_xhrpolling]}, 24 | transient, 5, supervisor, [rabbit_socks_connection_sup]}, 25 | SocketioSup 26 | ]}}. 27 | -------------------------------------------------------------------------------- /src/rabbit_socks_connection_sup.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_connection_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | % Behaviour 6 | -export([init/1]). 7 | 8 | % Interface 9 | -export([start_link/1, start_connection/3]). 10 | 11 | %% We have a supervisor for each front-end, i.e., each implementation 12 | %% of connection. This way we can parameterise on the connection 13 | %% module and use simple_one_for_one when starting connections. 14 | 15 | %% Callback for when one of these is started with supervisor:start_link/2 or/3 16 | %% Protocol is the protocol module. 17 | init([ConnectionType]) -> 18 | {ok, {{simple_one_for_one, 10, 10}, 19 | [{undefined, {ConnectionType, start_link, []}, 20 | transient, 50, worker, [ConnectionType]}]}}. 21 | 22 | %% Start a supervisor for connections using module ConnectionType 23 | start_link(ConnectionType) -> 24 | supervisor:start_link({local, ConnectionType}, ?MODULE, [ConnectionType]). 25 | 26 | %% Start a connection using the simple_one_for_one mechanism 27 | start_connection(ConnectionType, Protocol, Path) -> 28 | error_logger:info_msg("Starting connection ~p with ~p at ~p~n", [ConnectionType, Protocol, Path]), 29 | supervisor:start_child(ConnectionType, [Protocol, Path]). 30 | -------------------------------------------------------------------------------- /src/rabbit_socks_ws.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_ws). 2 | 3 | %% Parser 4 | -export([initial_parse_state/0, parse_frame/2]). 5 | 6 | %% Writer 7 | -export([send_frame/2, close_transport/1]). 8 | 9 | -record(parse, {type = unknown, fragments_rev = [], remaining = unknown}). 10 | 11 | -define(TEXT_FRAME_START, 0). 12 | -define(TEXT_FRAME_END, 255). 13 | 14 | initial_parse_state() -> 15 | #parse{}. 16 | 17 | parse_frame(<<>>, Parse) -> 18 | {more, Parse}; 19 | parse_frame(<<255, 0, _Rest/binary>>, Parse) -> 20 | {close, Parse}; 21 | parse_frame(<>, 22 | Parse = #parse{type = unknown, 23 | fragments_rev = [], 24 | remaining = unknown}) -> 25 | parse_frame(Rest, Parse#parse{type = utf8}); 26 | parse_frame(Bin, Parse = #parse{type = utf8}) -> 27 | parse_utf8_frame(Bin, Parse, 0); 28 | %% TODO binary frames 29 | parse_frame(Bin, Parse) -> 30 | {error, unrecognised_frame, {Bin, Parse}}. 31 | 32 | parse_utf8_frame(Bin, Parse = #parse{type = utf8, 33 | fragments_rev = Frags}, 34 | Index) -> 35 | case Bin of 36 | <> -> 37 | {frame, {utf8, lists:reverse([Head | Frags])}, Rest}; 38 | <> -> 39 | {more, Parse#parse{ fragments_rev = [ Whole | Frags] }}; 40 | Bin -> 41 | parse_utf8_frame(Bin, Parse, Index + 1) 42 | end. 43 | 44 | send_frame({utf8, Data}, Args) -> 45 | send_frame(Data, Args); 46 | send_frame(IoList, {Pid, Sock}) -> 47 | mochiweb_socket:send(Sock, [<>, IoList, <>]). 48 | 49 | close_transport({Pid, Sock}) -> 50 | rabbit_socks_ws_connection:close(Pid, "die!"). 51 | -------------------------------------------------------------------------------- /priv/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rabbit Socks 4 | 6 | 17 | 18 | 19 |

20 | Rabbit Socks 21 |

22 |

Echo socket

23 |

24 | Enter text here; it will be echoed below. 25 |

26 |
27 | 28 | 29 |
30 |
31 |
32 | 58 | 59 | -------------------------------------------------------------------------------- /priv/www/socketio.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rabbit Socks 4 | 6 | 17 | 18 | 19 |

20 | Rabbit Socks 21 |

22 |

Echo over socket.io

23 |

24 | Enter text here; it will be echoed below. 25 |

26 |
27 | 28 | 29 |
30 |
31 |
32 | 59 | 60 | -------------------------------------------------------------------------------- /priv/www/stomp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rabbit Socks -- STOMP over websockets 4 | 7 | 20 | 21 | 22 |

23 | Rabbit Socks STOMP 24 |

25 |

STOMP socket

26 |

27 | We have a STOMP connection subscribed to a queue which outputs 28 | messages below; it will also send messages to the queue when you 29 | type something in the field. The protocol transcript appears in 30 | the box to the side. 31 |

32 |
33 | 34 | 35 |
36 |
37 |
38 |
39 |
40 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rabbit Socks 2 | 3 | Support for communicating with RabbitMQ via [Websockets][ws] (v75 and v76, 4 | covering Chrome 5+, Safari 5, and Firefox 4 beta) and Socket.IO 5 | clients (covering much of the rest of the browser world). 6 | 7 | ## We've only just begun 8 | 9 | Yes, it's early days. WebSockets draft 76 is supported; Socket.IO 10 | support is limited to XHR-polling. 11 | 12 | ## How to build it 13 | 14 | Follow the usual kerfuffle given on 15 | ; summary: 16 | 17 | $ hg clone http://hg.rabbitmq.com/rabbitmq-public-umbrella 18 | $ cd rabbitmq-public-umbrella 19 | $ make co 20 | $ make 21 | $ hg clone http://hg.rabbitmq.com/rabbit-socks 22 | $ cd rabbit-socks 23 | $ make 24 | $ cd ../rabbitmq-server 25 | $ mkdir -p plugins; cd plugins 26 | $ cp ../../rabbitmq-stomp/dist/*.ez ./ 27 | $ cp ../../rabbit-socks/dist/*.ez ./ 28 | $ rm rabbit_common.ez 29 | $ cd ..; make run 30 | 31 | There -- easy! 32 | 33 | ## How to use it 34 | 35 | By default there are no listeners configured. You can start one 36 | through configuration or programmatically (e.g., from your own Erlang 37 | app). 38 | 39 | ### Configuration 40 | 41 | Rabbit Socks will start listeners specified in the environment entry 42 | `'listeners'`. By default this is an empty list. The syntax for a 43 | listener is 44 | 45 | Listener = {Interface, Module, Options} 46 | 47 | Interface = rabbit_mochiweb 48 | | Port 49 | | {IpAddress, Port} 50 | 51 | Options = [] 52 | | [Option | Options] 53 | 54 | `Port` is a port number, of course; `Module` is a callback module, 55 | e.g., `'rabbit_socks_echo'`. The options are passed through to 56 | mochiweb. 57 | 58 | If `'rabbit_mochiweb'` is supplied as the interface, the listener will 59 | be registered with [RabbitMQ's Mochiweb 60 | plugin](http://www.rabbitmq.com//mochiweb.html) in the context 61 | `'socks'`. 62 | 63 | As usual, you can supply such a configuration on the command line: 64 | 65 | $ erl -rabbit_socks listeners [{rabbit_mochiweb, rabbit_socks_echo, []}] 66 | 67 | or in a [config file](http://www.erlang.org/doc/man/config.html). 68 | 69 | ### From code 70 | 71 | rabbit_socks:start_listener(Interface, Module, Options). 72 | 73 | with the meanings as given above. 74 | 75 | ## URLs 76 | 77 | A listener serves two kinds of path: paths starting with `/websocket/` 78 | will use bare WebSockets; paths starting with `/socket.io/` will use 79 | Socket.IO's protocol (which may also be via WebSockets). 80 | 81 | The path after that prefix is given to the callback module. 82 | 83 | ## Callback modules 84 | 85 | A callback module must define these procedures: 86 | 87 | - `subprotocol_name()`: the name of the subprotocol, to be sent in 88 | the connection establishment headers. 89 | 90 | - `init(Path, [])`: is given the path and, for a callback module, an 91 | empty list (yes this is a wart). It should return `{ok, State}` if 92 | the connection can go ahead; the State will be supplied on 93 | subsequent calls. 94 | 95 | - `open(WriterModule, WriterArg, State)`: this is called when the 96 | connection has been opened. `WriterModule` and `WriterArg` are used 97 | to send data, so you should probably keep them in the state. It 98 | should return `{ok, NewState}`. 99 | 100 | - `handle_frame(Frame, State)`: this is called when a frame is 101 | received, and should return `{ok, NewState}`. 102 | 103 | - `terminate(State)` is called if the connection is closed from the 104 | client end. It should return `'ok'`. 105 | 106 | Returning `{error, Reason}` at any point will shut the connection 107 | down. 108 | 109 | [ws]: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 "WebSockets draft v76" 110 | -------------------------------------------------------------------------------- /src/rabbit_socks_xhrpolling.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_xhrpolling). 2 | 3 | -behaviour(gen_server). 4 | 5 | -export([init/1, terminate/2, code_change/3]). 6 | -export([handle_info/2, handle_cast/2, handle_call/3]). 7 | 8 | -export([start_link/2, send_frame/2, close_transport/1]). 9 | 10 | -record(state, {protocol, protocol_args, protocol_state, 11 | pending_frames, pending_request}). 12 | 13 | %% Interface 14 | 15 | start_link(ProtocolMA, Path) -> 16 | gen_server:start_link(?MODULE, [ProtocolMA, Path], []). 17 | 18 | send_frame({utf8, Data}, Pid) -> 19 | gen_server:cast(Pid, {send, Data}), 20 | ok. 21 | 22 | close_transport(Pid) -> 23 | gen_server:cast(Pid, close_transport), 24 | ok. 25 | 26 | %% Callbacks 27 | 28 | init([{Protocol, Args}, Path]) -> 29 | %io:format("XHR polling started: ~p", [Protocol]), 30 | case Protocol:init(Path, Args) of 31 | {ok, ProtocolState} -> 32 | {ok, #state{protocol = Protocol, 33 | protocol_state = ProtocolState, 34 | pending_frames = [], 35 | pending_request = none}}; 36 | Err -> 37 | Err 38 | end. 39 | 40 | terminate(normal, #state{protocol = Protocol, protocol_state = PState}) -> 41 | ok = Protocol:terminate(PState), 42 | ok. 43 | 44 | code_change(_OldVsn, State, _Extra) -> 45 | {ok, State}. 46 | 47 | handle_info(Any, State) -> 48 | {stop, {unexpect_info, Any}, State}. 49 | 50 | handle_cast({send, Data}, State = #state{pending_frames = Frames, 51 | pending_request = Pending}) -> 52 | %io:format("Sent: ~p~n", [Data]), 53 | case Pending of 54 | none -> 55 | {noreply, State#state{pending_frames = [Data | Frames]}}; 56 | {Req, From} -> 57 | Req:respond({200, get_headers(), lists:reverse([ Data | Frames])}), 58 | gen_server:reply(From, ok), 59 | {noreply, State#state{pending_frames = [], 60 | pending_request = none}} 61 | end; 62 | 63 | handle_cast(close_transport, State = #state{pending_request = Pending}) -> 64 | case Pending of 65 | none -> ok; 66 | {OldReq, OldFrom} -> 67 | %% If the socket is Req: is closed, this command will 68 | %% trigger exit(normal). We hate mochwieb. Sorry. 69 | % OldReq:respond({200, get_headers(), []}), 70 | gen_server:reply(OldFrom, ok) 71 | end, 72 | {stop, normal, State#state{pending_request = none}}; 73 | 74 | handle_cast(Any, State) -> 75 | {stop, {unexpected_cast, Any}, State}. 76 | 77 | handle_call({open, _Req}, _From, 78 | State = #state{ protocol = Protocol, 79 | protocol_state = ProtocolState0 }) -> 80 | {ok, ProtocolState} = Protocol:open( 81 | rabbit_socks_xhrpolling, self(), ProtocolState0), 82 | {reply, ok, State#state{ protocol_state = ProtocolState }}; 83 | handle_call({data, Req, Data0}, _From, State = #state{protocol = Protocol, 84 | protocol_state = PState}) -> 85 | {_, Data} = proplists:lookup("data", mochiweb_util:parse_qs(Data0)), 86 | %io:format("Incoming: ~p~n", [Data]), 87 | {ok, PState1} = Protocol:handle_frame({utf8, Data}, PState), 88 | Req:respond({200, get_headers(), "ok"}), 89 | {reply, ok, State#state{protocol_state = PState1}}; 90 | handle_call({recv, Req}, From, State = #state{pending_frames = Frames, 91 | pending_request = Pending}) -> 92 | %io:format("Recv: ~p~n", [Frames]), 93 | case Pending of 94 | none -> ok; 95 | {OldReq, OldFrom} -> 96 | %% Although unlikely, it's possible to have more than one 97 | %% hanging request. We don't support that. Instead let's 98 | %% just finish the previous one with an empty response. 99 | OldReq:respond({200, get_headers(), []}), 100 | gen_server:reply(OldFrom, ok) 101 | end, 102 | case Frames of 103 | [] -> 104 | %% TODO: is it possible to catch socket-close event? 105 | {noreply, State#state{pending_request = {Req, From}}}; 106 | Frames -> 107 | Req:respond({200, get_headers(), lists:reverse(Frames)}), 108 | {reply, ok, State#state{pending_frames = [], 109 | pending_request = none}} 110 | end; 111 | handle_call(Any, From, State) -> 112 | {stop, {unexpected_call, Any, From}, State}. 113 | 114 | 115 | get_headers() -> 116 | [{"Access-Control-Allow-Origin", "*"}]. 117 | -------------------------------------------------------------------------------- /src/rabbit_socks_ws_connection.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_ws_connection). 2 | 3 | -behaviour(gen_fsm). 4 | 5 | %% A gen_fsm for managing a WS Connection. 6 | 7 | -export([init/1, handle_event/3, 8 | handle_sync_event/4, handle_info/3, 9 | terminate/3, code_change/4]). 10 | 11 | % Interface and states 12 | -export([start_link/2, close/2, wait_for_socket/2, wait_for_frame/2, 13 | close_sent/2]). 14 | 15 | -record(state, {protocol, protocol_state, socket, parse_state}). 16 | 17 | -define(CLOSE_TIMEOUT, 3000). 18 | 19 | start_link(Protocol, Path) -> 20 | gen_fsm:start_link(?MODULE, [Protocol, Path], []). 21 | 22 | close(Pid, Reason) -> 23 | gen_fsm:send_event(Pid, {close, Reason}). 24 | 25 | % States 26 | 27 | wait_for_socket({socket_ready, Sock}, 28 | State = #state{ protocol = Protocol, 29 | protocol_state = ProtocolState0 }) -> 30 | {ok, ProtocolState} = Protocol:open(rabbit_socks_ws, {self(), Sock}, 31 | ProtocolState0), 32 | mochiweb_socket:setopts(Sock, [{active, once}]), 33 | State1 = State#state{parse_state = rabbit_socks_ws:initial_parse_state(), 34 | protocol_state = ProtocolState, 35 | socket = Sock}, 36 | error_logger:info_msg("Connection started ~p~n", [i(State1)]), 37 | {next_state, wait_for_frame, State1}. 38 | 39 | wait_for_frame({data, Data}, State = #state{ 40 | protocol_state = ProtocolState, 41 | protocol = Protocol, 42 | socket = Sock, 43 | parse_state = ParseState}) -> 44 | case rabbit_socks_ws:parse_frame(Data, ParseState) of 45 | {more, ParseState1} -> 46 | mochiweb_socket:setopts(Sock, [{active, once}]), 47 | {next_state, wait_for_frame, State#state{parse_state = ParseState1}}; 48 | {close, _ParseState1} -> 49 | %% TODO really necessary to reset here? 50 | error_logger:info_msg("Client initiated close ~p~n", [i(State)]), 51 | State1 = State#state{ parse_state = rabbit_socks_ws:initial_parse_state() }, 52 | {stop, normal, close_connection(State1)}; 53 | {frame, Frame, Rest} -> 54 | {ok, ProtocolState1} = Protocol:handle_frame(Frame, ProtocolState), 55 | wait_for_frame({data, Rest}, 56 | State#state{ 57 | protocol_state = ProtocolState1, 58 | parse_state = rabbit_socks_ws:initial_parse_state()}) 59 | end; 60 | wait_for_frame({close, Reason}, State) -> 61 | error_logger:info_msg("Server initiated close ~p~n", [i(State)]), 62 | State1 = terminate_protocol(State), 63 | {next_state, close_sent, send_close(initiate_close(State1))}; 64 | wait_for_frame(_Other, State) -> 65 | {next_state, wait_for_frame, State}. 66 | 67 | close_sent({data, Data}, State = #state{ parse_state = ParseState, 68 | socket = Sock }) -> 69 | case rabbit_socks_ws:parse_frame(Data, ParseState) of 70 | {more, ParseState1} -> 71 | mochiweb_socket:setopts(Sock, [{active, once}]), 72 | {next_state, close_sent, State#state{ parse_state = ParseState1 }}; 73 | {close, _ParseState} -> 74 | {stop, normal, finalise_connection(State)}; 75 | {frame, _Frame, Rest} -> 76 | close_sent({data, Rest}, 77 | State#state{ 78 | parse_state = rabbit_socks_ws:initial_parse_state()}) 79 | end; 80 | close_sent({timeout, _Ref, close_handshake}, State) -> 81 | {stop, normal, finalise_connection(State)}. 82 | 83 | %% internal 84 | 85 | %% ff are state() -> state() 86 | 87 | initiate_close(State) -> 88 | _TimerRef = gen_fsm:start_timer(?CLOSE_TIMEOUT, close_handshake), 89 | State. 90 | 91 | send_close(State = #state { 92 | socket = Socket }) -> 93 | mochiweb_socket:send(Socket, <<255,0>>), 94 | State. 95 | 96 | close_connection(State) -> 97 | finalise_connection(send_close(State)). 98 | 99 | finalise_connection(State = #state{ socket = Socket }) -> 100 | mochiweb_socket:close(Socket), 101 | State#state{ socket = closed }. 102 | 103 | terminate_protocol(State = #state{ protocol_state = undefined }) -> 104 | State; 105 | terminate_protocol(State = #state{ protocol = Protocol, 106 | protocol_state = ProtocolState }) -> 107 | ok = Protocol:terminate(ProtocolState), 108 | State#state { protocol_state = undefined }. 109 | 110 | %% info for log messages 111 | 112 | i(#state{ socket = closed, protocol = Protocol }) -> 113 | { Protocol, closed }; 114 | i(#state{ socket = Socket, protocol = Protocol }) -> 115 | { Protocol, socket_info(Socket) }. 116 | 117 | socket_info(Socket) -> 118 | { mochiweb_socket:peername(Socket), 119 | mochiweb_socket:port(Socket)}. 120 | 121 | %% gen_fsm callbacks 122 | 123 | init([{Protocol, Args}, Path]) -> 124 | process_flag(trap_exit, true), 125 | case Protocol:init(Path, Args) of 126 | {ok, ProtocolState} -> 127 | State1 = #state{protocol = Protocol, 128 | protocol_state = ProtocolState}, 129 | {ok, wait_for_socket, State1}; 130 | Err -> 131 | Err 132 | end. 133 | 134 | handle_event(Event, StateName, StateData) -> 135 | {stop, {StateName, undefined_event, Event}, StateData}. 136 | handle_sync_event(Event, _From, StateName, StateData) -> 137 | {stop, {StateName, undefined_event, Event}, StateData}. 138 | 139 | handle_info({tcp, _Sock, Data}, State, StateData) -> 140 | ?MODULE:State({data, Data}, StateData); 141 | handle_info({tcp_closed, Socket}, StateName, 142 | #state{socket = Socket} = StateData) -> 143 | error_logger:warning_msg("Connection unexpectedly dropped (in ~p)", 144 | [StateName]), 145 | {stop, normal, terminate_protocol(StateData)}; 146 | handle_info({tcp_error, Socket, Reason}, StateName, 147 | #state{socket = Socket} = StateData) -> 148 | error_logger:warning_msg("Connection error reason=~p (in ~p)", 149 | [Reason, StateName]), 150 | {stop, normal, terminate_protocol(StateData)}; 151 | handle_info(Info, StateName, StateData) -> 152 | {stop, {unexpected_info, StateName, Info}, StateData}. 153 | 154 | %% If things happened cleanly, the protocol shoudl already be shut 155 | %% down. However, if we crashed, we should tell it. 156 | terminate(_Reason, _StateName, S = #state{socket = closed}) -> 157 | terminate_protocol(S); 158 | terminate(_Reason, _StateName, S = #state{socket = Socket}) -> 159 | terminate_protocol(S), 160 | case catch mochiweb_socket:close(Socket) of 161 | _ -> ok 162 | end. 163 | 164 | code_change(_OldVsn, StateName, StateData, _Extra) -> 165 | {ok, StateName, StateData}. 166 | -------------------------------------------------------------------------------- /src/rabbit_socks_socketio.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_socketio). 2 | 3 | %% Protocol 4 | -export([init/2, open/3, handle_frame/2, terminate/1]). 5 | 6 | %% Writer 7 | -export([send_frame/2, close_transport/1]). 8 | 9 | %% --------------------------- 10 | 11 | -export([start_link/1]). 12 | 13 | -behaviour(gen_server). 14 | -export([init/1, terminate/2, code_change/3]). 15 | -export([handle_call/3, handle_cast/2, handle_info/2]). 16 | 17 | %% --------------------------- 18 | 19 | -export([send_heartbeat/1]). 20 | 21 | -define(FRAME, "~m~"). 22 | -define(HEARTBEAT_DELAY, 10000). 23 | 24 | %% --------------------------- 25 | 26 | init(Path, [Session, RightProtocol]) -> 27 | process_flag(trap_exit, true), 28 | case RightProtocol:init(Path, []) of 29 | {ok, RightProtocolState} -> 30 | {ok, Pid} = rabbit_socks_socketio_sup:start_child( 31 | [Session, RightProtocol, RightProtocolState]), 32 | {ok, Pid}; % LeftState 33 | Err -> 34 | Err 35 | end. 36 | 37 | open(WriterModule, WriterArg, LeftState) -> 38 | gen_server:call(LeftState, {open, {WriterModule, WriterArg}}, infinity), 39 | {ok, LeftState}. 40 | 41 | handle_frame(Frame, LeftState) -> 42 | gen_server:cast(LeftState, {handle_frame, Frame}), 43 | {ok, LeftState}. 44 | 45 | terminate(LeftState) -> 46 | gen_server:call(LeftState, terminate, infinity), 47 | ok. 48 | 49 | send_frame(Frame, RightState) -> 50 | gen_server:cast(RightState, {send, Frame}), 51 | {ok, RightState}. 52 | 53 | close_transport(RightState) -> 54 | gen_server:cast(RightState, close_transport), 55 | {ok, RightState}. 56 | 57 | %% --------------------------- 58 | 59 | send_heartbeat(Pid) -> 60 | gen_server:cast(Pid, send_heartbeat). 61 | 62 | %% --------------------------- 63 | 64 | start_link(Params) -> 65 | gen_server:start_link(?MODULE, Params, []). 66 | 67 | %% --------------------------- 68 | 69 | -record(state, {session, right_protocol, right_protocol_state, left_callback, 70 | heartbeat_tref, heartbeat_expected}). 71 | 72 | init([Session, RightProtocol, RightProtocolState]) -> 73 | State0 = #state{session = Session, 74 | right_protocol = RightProtocol, 75 | right_protocol_state = RightProtocolState}, 76 | {ok, delay_heartbeat(State0)}. 77 | 78 | terminate(Reason, #state{heartbeat_tref = TRef}) -> 79 | case TRef of 80 | undefined -> 81 | ok; 82 | _ -> 83 | timer:cancel(TRef) 84 | end, 85 | error_logger:info_msg("~p died with ~p ~n", [?MODULE, Reason]), 86 | ok. 87 | 88 | code_change(_OldVsn, State, _Extra) -> 89 | {ok, State}. 90 | 91 | handle_info(Any, State) -> 92 | {stop, {unexpected_message, Any}, State}. 93 | 94 | handle_cast({handle_frame, {utf8, Bin}}, 95 | State = #state{right_protocol = RightProtocol, 96 | right_protocol_state = RightProtocolState0, 97 | heartbeat_expected = HeartbeatExpectedFrame}) -> 98 | Fun = fun (Frame, {Heartbeat, PState}) -> 99 | case Frame of 100 | Heartbeat -> 101 | {undefined, PState}; 102 | _ -> 103 | {ok, PState1} = 104 | RightProtocol:handle_frame(Frame, PState), 105 | {Heartbeat, PState1} 106 | end 107 | end, 108 | {Heartbeat1, RightProtocolState1} = 109 | lists:foldl(Fun, {HeartbeatExpectedFrame, RightProtocolState0}, 110 | unwrap_frames(Bin)), 111 | State1 = State#state{right_protocol_state = RightProtocolState1, 112 | heartbeat_expected = Heartbeat1}, 113 | {noreply, delay_heartbeat(State1)}; 114 | 115 | handle_cast({send, Frame}, State) -> 116 | do_send_frame(Frame, State), 117 | {noreply, State}; 118 | 119 | handle_cast(send_heartbeat, State = #state{heartbeat_expected = undefined}) -> 120 | Frame = construct_heartbeat_frame(), 121 | %io:format("send heartbeat ~p~n", [Frame]), 122 | do_send_frame(Frame, State), 123 | {noreply, State#state{heartbeat_expected = Frame}}; 124 | 125 | handle_cast(send_heartbeat, State = #state{heartbeat_expected = NotReceived}) -> 126 | error_logger:warning_msg("socketio:heartbeat not received ~p. closing. ~n", [NotReceived]), 127 | handle_cast(close_transport, State); 128 | 129 | handle_cast(close_transport, 130 | State = #state{left_callback = {WriterModule, WriterArg}}) -> 131 | WriterModule:close_transport(WriterArg), 132 | {noreply, State}; 133 | 134 | handle_cast(Any, State) -> 135 | {stop, {unexpected_message, Any}, State}. 136 | 137 | 138 | handle_call({open, LeftCallback}, _From, 139 | State = #state{session = Session, 140 | right_protocol = RightProtocol, 141 | right_protocol_state = RightProtocolState0}) -> 142 | {ok, RightProtocolState1} = RightProtocol:open(?MODULE, self(), % RightState 143 | RightProtocolState0), 144 | State1 = State#state{right_protocol_state = RightProtocolState1, 145 | left_callback = LeftCallback}, 146 | do_send_frame({utf8, list_to_binary(Session)}, State1), 147 | {reply, ok, State1}; 148 | 149 | handle_call(terminate, _From, 150 | State = #state{right_protocol = RightProtocol, 151 | right_protocol_state = RightProtocolState}) -> 152 | RightProtocol:terminate(RightProtocolState), 153 | {stop, normal, ok, State}; 154 | 155 | handle_call(Any, _From, State) -> 156 | {stop, {unexpected_message, Any}, State}. 157 | 158 | %% --------------------------- 159 | 160 | do_send_frame(Frame, #state{left_callback = {WriterModule, WriterArg}}) -> 161 | Wrapped = wrap_frame(Frame), 162 | WriterModule:send_frame({utf8, Wrapped}, WriterArg). 163 | 164 | 165 | delay_heartbeat(State = #state{heartbeat_tref = TRef0}) -> 166 | case TRef0 of 167 | undefined -> 168 | ok; 169 | _ -> 170 | timer:cancel(TRef0) 171 | end, 172 | {ok, TRef} = timer:apply_after(?HEARTBEAT_DELAY, 173 | ?MODULE, send_heartbeat, [self()]), 174 | State#state{heartbeat_tref = TRef}. 175 | 176 | construct_heartbeat_frame() -> 177 | %% Keep it small. 178 | R = random:uniform(9999), 179 | {utf8, list_to_binary("~h~" ++ integer_to_list(R))}. 180 | 181 | 182 | unwrap_frames(List) when is_list(List) -> 183 | unwrap_frames(iolist_to_binary(List)); 184 | unwrap_frames(Bin) -> 185 | unwrap_frames_unicode(unicode:characters_to_list(Bin, utf8), []). 186 | 187 | unwrap_frames_unicode([], Acc) -> 188 | lists:reverse(Acc); 189 | unwrap_frames_unicode(Frame, Acc) -> 190 | case Frame of 191 | ?FRAME ++ Rest -> 192 | {LenStr, Rest1} = lists:splitwith(fun rabbit_socks_util:is_digit/1, 193 | Rest), 194 | Length = list_to_integer(LenStr), 195 | case Rest1 of 196 | ?FRAME ++ Rest2 -> 197 | {Data, Rest3} = lists:split(Length, Rest2), 198 | BinData = unicode:characters_to_binary(Data, utf8), 199 | unwrap_frames_unicode(Rest3, [{utf8, BinData} | Acc]); 200 | _Else -> 201 | {error, malformed_frame, Frame} 202 | end; 203 | _Else -> 204 | {error, malformed_frame, Frame} 205 | end. 206 | 207 | wrap_frame({utf8, Bin}) -> 208 | case unicode:characters_to_list(Bin, utf8) of 209 | {error, _, _} -> 210 | {error, not_utf8_data, Bin}; 211 | {incomplete, _, _} -> 212 | {error, incomplete_utf8_data, Bin}; 213 | List -> 214 | LenStr = list_to_binary(integer_to_list(length(List))), 215 | [?FRAME, LenStr, ?FRAME, List] 216 | end; 217 | wrap_frame(IoList) -> 218 | wrap_frame({utf8, iolist_to_binary(IoList)}). 219 | -------------------------------------------------------------------------------- /src/rabbit_socks_mochiweb.erl: -------------------------------------------------------------------------------- 1 | -module(rabbit_socks_mochiweb). 2 | 3 | %% Start a mochiweb server and supply frames to registered interpreters. 4 | 5 | -export([start/1, start_listener/3]). 6 | %% For rabbit_socks_listener_sup callback 7 | -export([start_mochiweb_listener/5]). 8 | 9 | -define(CONNECTION_TABLE, socks_connections). 10 | -define(SESSION_PREFIX, "socks"). 11 | -define(DEFAULT_PREFIX, "socks"). 12 | -define(CONTEXT, socks). 13 | 14 | start(Listeners) -> 15 | %% FIXME each listener should have its own table 16 | ets:new(?CONNECTION_TABLE, [public, named_table]), 17 | start_listeners(Listeners). 18 | 19 | start_listeners([]) -> 20 | ok; 21 | start_listeners([{Interface, Options} | More]) -> 22 | case proplists:get_value(protocol, Options, undefined) of 23 | undefined -> 24 | exit({no_protocol, Options}); 25 | Subprotocol -> 26 | start_listener(Interface, Subprotocol, Options), 27 | start_listeners(More) 28 | end. 29 | 30 | start_listener(rabbit_mochiweb, Subprotocol, Options) -> 31 | start_listener({rabbit_mochiweb, ?DEFAULT_PREFIX}, 32 | Subprotocol, Options); 33 | start_listener({rabbit_mochiweb, Prefix}, Subprotocol, Options) -> 34 | register_with_rabbit_mochiweb(Prefix, Subprotocol); 35 | 36 | start_listener(Listener, Subprotocol, Options) -> 37 | Specs = rabbit_networking:check_tcp_listener_address( 38 | rabbit_socks_listener_sup, Listener), 39 | [supervisor:start_child(rabbit_socks_listener_sup, 40 | {Name, 41 | {?MODULE, start_mochiweb_listener, 42 | [Name, IPAddress, Port, Subprotocol, Options]}, 43 | transient, 10, worker, [rabbit_socks_mochiweb]}) 44 | || {IPAddress, Port, _Family, Name} <- Specs]. 45 | 46 | %% TODO: when using multi-context rabbit-mochi, account for not 47 | %% getting the supplied prefix 48 | register_with_rabbit_mochiweb(Path, Subprotocol) -> 49 | Name = Subprotocol:subprotocol_name(), 50 | rabbit_mochiweb:register_context_handler( 51 | ?CONTEXT, Path, makeloop_rabbit_mochiweb(Subprotocol), 52 | io_lib:format("Rabbit Socks (~p)", [Name])). 53 | 54 | start_mochiweb_listener(Name, IPAddress, Port, Subprotocol, Options) -> 55 | {ok, Pid} = mochiweb_http:start([{name, Name}, {ip, IPAddress}, 56 | {port, Port}, 57 | {loop, makeloop_just_mochiweb(Subprotocol)} 58 | | Options]), 59 | rabbit_networking:tcp_listener_started( 60 | case proplists:get_bool(ssl, Options) of 61 | true -> 'websocket/socket.io (SSL)'; 62 | false -> 'websocket/socket.io' 63 | end, IPAddress, Port), 64 | {ok, Pid}. 65 | 66 | %% URL scheme: 67 | %% 68 | %% Since our protocol is per-listener, we already know it at this 69 | %% point; all we can do is check that the client has the same idea of 70 | %% what it is. 71 | %% 72 | %% For websockets, we accept /websocket/, and the path is given to the 73 | %% protocol. 74 | %% 75 | %% For socket.io, the client library will append 76 | %% // to whatever prefix it is configured to 77 | %% use. Therefore our pattern, for connection initiation, is 78 | %% /socket.io///. 79 | 80 | makeloop_rabbit_mochiweb(Subprotocol) -> 81 | fun ({Prefix, _}, Req) -> 82 | Path = Req:get(path), 83 | dispatch(string:substr(Path, length(Prefix) + 2), Req, Subprotocol) 84 | end. 85 | 86 | makeloop_just_mochiweb(Subprotocol) -> 87 | fun (Req) -> 88 | dispatch(Req:get(path), Req, Subprotocol) 89 | end. 90 | 91 | dispatch(Path, Req, Subprotocol) -> 92 | case Path of 93 | "/socket.io/" ++ Rest -> 94 | rabbit_io(Req, Subprotocol, Rest); 95 | "/websocket" ++ Rest -> 96 | rabbit_ws(Req, Subprotocol, Rest); 97 | RelPath0 -> 98 | RelPath = case RelPath0 of 99 | "" -> ""; 100 | "/" ++ P -> P 101 | end, 102 | {file, Here} = code:is_loaded(?MODULE), 103 | ModuleRoot = filename:dirname(filename:dirname(Here)), 104 | Static = filename:join(filename:join(ModuleRoot, "priv"), "www"), 105 | Req:serve_file(RelPath, Static) 106 | end. 107 | 108 | fix_scheme(Scheme) -> 109 | case Scheme of 110 | http -> 111 | "ws"; 112 | https -> 113 | "wss" 114 | end. 115 | 116 | send_ajax_options(Req) -> 117 | Headers = [{"Access-Control-Allow-Origin", "*"}, 118 | {"Access-Control-Allow-Methods", "POST, GET, OPTIONS"}, 119 | {"Access-Control-Allow-Headers", 120 | "X-Requested-With, Content-Type, Accept"}], 121 | Req:respond({200, mochiweb_headers:from_list(Headers), ""}). 122 | 123 | rabbit_io(Req, Subprotocol, Rest) -> 124 | %io:format("Socket.IO ~p, ~p~n", [Req, Req:recv_body()]), 125 | case lists:reverse(re:split(Rest, "/", [{return, list}, trim])) of 126 | ["websocket" | PathElemsRev] -> %% i.e., in total /socket.io//websocket 127 | Session = new_session_id(), 128 | {ok, _Pid} = websocket(fix_scheme(Req:get(scheme)), Req, 129 | lists:reverse(PathElemsRev), 130 | Subprotocol, 131 | %% Wrap the subprotocol in Socket.IO framing 132 | {rabbit_socks_socketio, [Session, Subprotocol]}), 133 | exit(normal); 134 | [_Timestamp, "", PollingTransport | PathElemsRev] -> 135 | %io:format("New polling connection ~p ~p~n", [Protocol, PollingTransport]), 136 | case transport_module(PollingTransport) of 137 | {ok, Transport} -> 138 | Session = new_session_id(), 139 | {ok, _Pid} = 140 | polling(Transport, Req, lists:reverse(PathElemsRev), 141 | {rabbit_socks_socketio, [Session, Subprotocol]}, 142 | Session); 143 | {error, Err} -> 144 | Req:respond({404, [], "Bad transport"}) 145 | end; 146 | [Operation, Session, Transport | _PathElemsRev] -> 147 | %% io:format("Existing connection ~p~n", [Session]), 148 | case transport_module(Transport) of 149 | {ok, TransportModule} -> 150 | case ets:lookup(?CONNECTION_TABLE, Session) of 151 | [{Session, TransportModule, Pid}] -> 152 | case Operation of 153 | "send" -> 154 | %% NB: recv_body caches the body in the process 155 | %% dictionary. This must be done in the mochiweb 156 | %% process to have any chance of working. 157 | case Req:get(method) of 158 | 'OPTIONS' -> 159 | send_ajax_options(Req); 160 | _ -> 161 | gen_server:call(Pid, {data, Req, Req:recv_body()}) 162 | end; 163 | Timestamp -> 164 | %% Long poll. 165 | gen_server:call(Pid, {recv, Req}, infinity) 166 | end; 167 | [] -> 168 | Req:not_found() 169 | end; 170 | {error, Err} -> 171 | Req:respond({404, [], "Bad transport"}) 172 | end; 173 | Else -> 174 | Req:not_found() 175 | end. 176 | 177 | rabbit_ws(Req, Subprotocol, Rest0) -> 178 | Rest = case Rest0 of 179 | "/" ++ R -> R; 180 | R -> R 181 | end, 182 | PathElems = re:split(Rest, "/", [{return, list}, trim]), 183 | Scheme = case Req:get(socket) of 184 | {ssl, _Sock} -> "wss"; 185 | _Sock -> "ws" 186 | end, 187 | {ok, _} = websocket(Scheme, Req, PathElems, Subprotocol, {Subprotocol, []}), 188 | exit(normal). 189 | 190 | websocket(Scheme, Req, PathElems, Subprotocol, SubprotocolMA) -> 191 | case process_handshake(Scheme, Req, Subprotocol) of 192 | {error, DoesNotCompute} -> 193 | close_error(Req), 194 | error_logger:info_msg("Connection refused: ~p", [DoesNotCompute]); 195 | {response, Headers, Body} -> 196 | error_logger:info_msg("Connection accepted: ~p", [Req:get(peer)]), 197 | send_headers(Req, Headers), 198 | Req:send([Body]), 199 | start_ws_socket(SubprotocolMA, Req, PathElems) 200 | end. 201 | 202 | polling(TransportModule, Req, PathElems, SubprotocolMA, Session) -> 203 | {ok, Pid} = rabbit_socks_connection_sup:start_connection(TransportModule, 204 | SubprotocolMA, PathElems), 205 | register_polling_connection(Session, TransportModule, Pid), 206 | gen_server:call(Pid, {open, Req}), 207 | gen_server:call(Pid, {recv, Req}), 208 | {ok, Pid}. 209 | 210 | transport_module("xhr-polling") -> {ok, rabbit_socks_xhrpolling}; 211 | transport_module(Else) -> {error, {unsupported_transport, Else}}. 212 | 213 | register_polling_connection(Session, Transport, Pid) -> 214 | true = ets:insert_new(?CONNECTION_TABLE, 215 | {Session, Transport, Pid}). 216 | 217 | process_handshake(Scheme, Req, Subprotocol) -> 218 | ProtocolName = Subprotocol:subprotocol_name(), 219 | Origin = Req:get_header_value("Origin"), 220 | Location = make_location(Scheme, Req), 221 | FirstBit = [{"Upgrade", "WebSocket"}, 222 | {"Connection", "Upgrade"}], 223 | case Req:get_header_value("Sec-WebSocket-Key1") of 224 | undefined -> 225 | {response, 226 | FirstBit ++ 227 | [{"WebSocket-Origin", Origin}, 228 | {"WebSocket-Location", Location}, 229 | {"WebSocket-Protocol", ProtocolName}], 230 | <<>>}; 231 | Key1 -> 232 | Key2 = Req:get_header_value("Sec-WebSocket-Key2"), 233 | Key3 = Req:recv(8), 234 | Hash = handshake_hash(Key1, Key2, Key3), 235 | {response, 236 | FirstBit ++ 237 | [{"Sec-WebSocket-Origin", Origin}, 238 | {"Sec-WebSocket-Location", Location}, 239 | {"Sec-WebSocket-Protocol", ProtocolName}], 240 | Hash} 241 | end. 242 | 243 | handshake_hash(Key1, Key2, Key3) -> 244 | erlang:md5([reduce_key(Key1), reduce_key(Key2), Key3]). 245 | 246 | make_location(Scheme, Req) -> 247 | Host = Req:get_header_value("Host"), 248 | Resource = Req:get(raw_path), 249 | mochiweb_util:urlunsplit( 250 | {Scheme, Host, Resource, "", ""}). 251 | 252 | reduce_key(Key) when is_list(Key) -> 253 | {NumbersRev, NumSpaces} = lists:foldl( 254 | fun (32, {Nums, NumSpaces}) -> 255 | {Nums, NumSpaces + 1}; 256 | (Digit, {Nums, NumSpaces}) 257 | when Digit > 47 andalso Digit < 58 -> 258 | {[Digit | Nums], NumSpaces}; 259 | (_Other, Res) -> 260 | Res 261 | end, {[], 0}, Key), 262 | OriginalNum = list_to_integer(lists:reverse(NumbersRev)) div NumSpaces, 263 | <>. 264 | 265 | %% Write the headers to the response 266 | send_headers(Req, Headers) -> 267 | Req:start_raw_response({"101 Web Socket Protocol Handshake", 268 | mochiweb_headers:from_list(Headers)}). 269 | 270 | %% Close the connection with an error. 271 | close_error(Req) -> 272 | Req:respond({400, [], ""}). 273 | 274 | %% Spin up a socket to deal with frames 275 | start_ws_socket(SubprotocolMA, Req, PathElems) -> 276 | {ok, Pid} = rabbit_socks_connection_sup:start_connection( 277 | rabbit_socks_ws_connection, SubprotocolMA, PathElems), 278 | Sock = Req:get(socket), 279 | handover_socket(Sock, Pid), 280 | gen_fsm:send_event(Pid, {socket_ready, Sock}), 281 | {ok, Pid}. 282 | 283 | handover_socket({ssl, Sock}, Pid) -> 284 | ssl:controlling_process(Sock, Pid); 285 | handover_socket(Sock, Pid) -> 286 | gen_tcp:controlling_process(Sock, Pid). 287 | 288 | new_session_id() -> rabbit_guid:string_guid(?SESSION_PREFIX). 289 | -------------------------------------------------------------------------------- /LICENSE-MPL: -------------------------------------------------------------------------------- 1 | MOZILLA PUBLIC LICENSE 2 | Version 1.1 3 | 4 | --------------- 5 | 6 | 1. Definitions. 7 | 8 | 1.0.1. "Commercial Use" means distribution or otherwise making the 9 | Covered Code available to a third party. 10 | 11 | 1.1. "Contributor" means each entity that creates or contributes to 12 | the creation of Modifications. 13 | 14 | 1.2. "Contributor Version" means the combination of the Original 15 | Code, prior Modifications used by a Contributor, and the Modifications 16 | made by that particular Contributor. 17 | 18 | 1.3. "Covered Code" means the Original Code or Modifications or the 19 | combination of the Original Code and Modifications, in each case 20 | including portions thereof. 21 | 22 | 1.4. "Electronic Distribution Mechanism" means a mechanism generally 23 | accepted in the software development community for the electronic 24 | transfer of data. 25 | 26 | 1.5. "Executable" means Covered Code in any form other than Source 27 | Code. 28 | 29 | 1.6. "Initial Developer" means the individual or entity identified 30 | as the Initial Developer in the Source Code notice required by Exhibit 31 | A. 32 | 33 | 1.7. "Larger Work" means a work which combines Covered Code or 34 | portions thereof with code not governed by the terms of this License. 35 | 36 | 1.8. "License" means this document. 37 | 38 | 1.8.1. "Licensable" means having the right to grant, to the maximum 39 | extent possible, whether at the time of the initial grant or 40 | subsequently acquired, any and all of the rights conveyed herein. 41 | 42 | 1.9. "Modifications" means any addition to or deletion from the 43 | substance or structure of either the Original Code or any previous 44 | Modifications. When Covered Code is released as a series of files, a 45 | Modification is: 46 | A. Any addition to or deletion from the contents of a file 47 | containing Original Code or previous Modifications. 48 | 49 | B. Any new file that contains any part of the Original Code or 50 | previous Modifications. 51 | 52 | 1.10. "Original Code" means Source Code of computer software code 53 | which is described in the Source Code notice required by Exhibit A as 54 | Original Code, and which, at the time of its release under this 55 | License is not already Covered Code governed by this License. 56 | 57 | 1.10.1. "Patent Claims" means any patent claim(s), now owned or 58 | hereafter acquired, including without limitation, method, process, 59 | and apparatus claims, in any patent Licensable by grantor. 60 | 61 | 1.11. "Source Code" means the preferred form of the Covered Code for 62 | making modifications to it, including all modules it contains, plus 63 | any associated interface definition files, scripts used to control 64 | compilation and installation of an Executable, or source code 65 | differential comparisons against either the Original Code or another 66 | well known, available Covered Code of the Contributor's choice. The 67 | Source Code can be in a compressed or archival form, provided the 68 | appropriate decompression or de-archiving software is widely available 69 | for no charge. 70 | 71 | 1.12. "You" (or "Your") means an individual or a legal entity 72 | exercising rights under, and complying with all of the terms of, this 73 | License or a future version of this License issued under Section 6.1. 74 | For legal entities, "You" includes any entity which controls, is 75 | controlled by, or is under common control with You. For purposes of 76 | this definition, "control" means (a) the power, direct or indirect, 77 | to cause the direction or management of such entity, whether by 78 | contract or otherwise, or (b) ownership of more than fifty percent 79 | (50%) of the outstanding shares or beneficial ownership of such 80 | entity. 81 | 82 | 2. Source Code License. 83 | 84 | 2.1. The Initial Developer Grant. 85 | The Initial Developer hereby grants You a world-wide, royalty-free, 86 | non-exclusive license, subject to third party intellectual property 87 | claims: 88 | (a) under intellectual property rights (other than patent or 89 | trademark) Licensable by Initial Developer to use, reproduce, 90 | modify, display, perform, sublicense and distribute the Original 91 | Code (or portions thereof) with or without Modifications, and/or 92 | as part of a Larger Work; and 93 | 94 | (b) under Patents Claims infringed by the making, using or 95 | selling of Original Code, to make, have made, use, practice, 96 | sell, and offer for sale, and/or otherwise dispose of the 97 | Original Code (or portions thereof). 98 | 99 | (c) the licenses granted in this Section 2.1(a) and (b) are 100 | effective on the date Initial Developer first distributes 101 | Original Code under the terms of this License. 102 | 103 | (d) Notwithstanding Section 2.1(b) above, no patent license is 104 | granted: 1) for code that You delete from the Original Code; 2) 105 | separate from the Original Code; or 3) for infringements caused 106 | by: i) the modification of the Original Code or ii) the 107 | combination of the Original Code with other software or devices. 108 | 109 | 2.2. Contributor Grant. 110 | Subject to third party intellectual property claims, each Contributor 111 | hereby grants You a world-wide, royalty-free, non-exclusive license 112 | 113 | (a) under intellectual property rights (other than patent or 114 | trademark) Licensable by Contributor, to use, reproduce, modify, 115 | display, perform, sublicense and distribute the Modifications 116 | created by such Contributor (or portions thereof) either on an 117 | unmodified basis, with other Modifications, as Covered Code 118 | and/or as part of a Larger Work; and 119 | 120 | (b) under Patent Claims infringed by the making, using, or 121 | selling of Modifications made by that Contributor either alone 122 | and/or in combination with its Contributor Version (or portions 123 | of such combination), to make, use, sell, offer for sale, have 124 | made, and/or otherwise dispose of: 1) Modifications made by that 125 | Contributor (or portions thereof); and 2) the combination of 126 | Modifications made by that Contributor with its Contributor 127 | Version (or portions of such combination). 128 | 129 | (c) the licenses granted in Sections 2.2(a) and 2.2(b) are 130 | effective on the date Contributor first makes Commercial Use of 131 | the Covered Code. 132 | 133 | (d) Notwithstanding Section 2.2(b) above, no patent license is 134 | granted: 1) for any code that Contributor has deleted from the 135 | Contributor Version; 2) separate from the Contributor Version; 136 | 3) for infringements caused by: i) third party modifications of 137 | Contributor Version or ii) the combination of Modifications made 138 | by that Contributor with other software (except as part of the 139 | Contributor Version) or other devices; or 4) under Patent Claims 140 | infringed by Covered Code in the absence of Modifications made by 141 | that Contributor. 142 | 143 | 3. Distribution Obligations. 144 | 145 | 3.1. Application of License. 146 | The Modifications which You create or to which You contribute are 147 | governed by the terms of this License, including without limitation 148 | Section 2.2. The Source Code version of Covered Code may be 149 | distributed only under the terms of this License or a future version 150 | of this License released under Section 6.1, and You must include a 151 | copy of this License with every copy of the Source Code You 152 | distribute. You may not offer or impose any terms on any Source Code 153 | version that alters or restricts the applicable version of this 154 | License or the recipients' rights hereunder. However, You may include 155 | an additional document offering the additional rights described in 156 | Section 3.5. 157 | 158 | 3.2. Availability of Source Code. 159 | Any Modification which You create or to which You contribute must be 160 | made available in Source Code form under the terms of this License 161 | either on the same media as an Executable version or via an accepted 162 | Electronic Distribution Mechanism to anyone to whom you made an 163 | Executable version available; and if made available via Electronic 164 | Distribution Mechanism, must remain available for at least twelve (12) 165 | months after the date it initially became available, or at least six 166 | (6) months after a subsequent version of that particular Modification 167 | has been made available to such recipients. You are responsible for 168 | ensuring that the Source Code version remains available even if the 169 | Electronic Distribution Mechanism is maintained by a third party. 170 | 171 | 3.3. Description of Modifications. 172 | You must cause all Covered Code to which You contribute to contain a 173 | file documenting the changes You made to create that Covered Code and 174 | the date of any change. You must include a prominent statement that 175 | the Modification is derived, directly or indirectly, from Original 176 | Code provided by the Initial Developer and including the name of the 177 | Initial Developer in (a) the Source Code, and (b) in any notice in an 178 | Executable version or related documentation in which You describe the 179 | origin or ownership of the Covered Code. 180 | 181 | 3.4. Intellectual Property Matters 182 | (a) Third Party Claims. 183 | If Contributor has knowledge that a license under a third party's 184 | intellectual property rights is required to exercise the rights 185 | granted by such Contributor under Sections 2.1 or 2.2, 186 | Contributor must include a text file with the Source Code 187 | distribution titled "LEGAL" which describes the claim and the 188 | party making the claim in sufficient detail that a recipient will 189 | know whom to contact. If Contributor obtains such knowledge after 190 | the Modification is made available as described in Section 3.2, 191 | Contributor shall promptly modify the LEGAL file in all copies 192 | Contributor makes available thereafter and shall take other steps 193 | (such as notifying appropriate mailing lists or newsgroups) 194 | reasonably calculated to inform those who received the Covered 195 | Code that new knowledge has been obtained. 196 | 197 | (b) Contributor APIs. 198 | If Contributor's Modifications include an application programming 199 | interface and Contributor has knowledge of patent licenses which 200 | are reasonably necessary to implement that API, Contributor must 201 | also include this information in the LEGAL file. 202 | 203 | (c) Representations. 204 | Contributor represents that, except as disclosed pursuant to 205 | Section 3.4(a) above, Contributor believes that Contributor's 206 | Modifications are Contributor's original creation(s) and/or 207 | Contributor has sufficient rights to grant the rights conveyed by 208 | this License. 209 | 210 | 3.5. Required Notices. 211 | You must duplicate the notice in Exhibit A in each file of the Source 212 | Code. If it is not possible to put such notice in a particular Source 213 | Code file due to its structure, then You must include such notice in a 214 | location (such as a relevant directory) where a user would be likely 215 | to look for such a notice. If You created one or more Modification(s) 216 | You may add your name as a Contributor to the notice described in 217 | Exhibit A. You must also duplicate this License in any documentation 218 | for the Source Code where You describe recipients' rights or ownership 219 | rights relating to Covered Code. You may choose to offer, and to 220 | charge a fee for, warranty, support, indemnity or liability 221 | obligations to one or more recipients of Covered Code. However, You 222 | may do so only on Your own behalf, and not on behalf of the Initial 223 | Developer or any Contributor. You must make it absolutely clear than 224 | any such warranty, support, indemnity or liability obligation is 225 | offered by You alone, and You hereby agree to indemnify the Initial 226 | Developer and every Contributor for any liability incurred by the 227 | Initial Developer or such Contributor as a result of warranty, 228 | support, indemnity or liability terms You offer. 229 | 230 | 3.6. Distribution of Executable Versions. 231 | You may distribute Covered Code in Executable form only if the 232 | requirements of Section 3.1-3.5 have been met for that Covered Code, 233 | and if You include a notice stating that the Source Code version of 234 | the Covered Code is available under the terms of this License, 235 | including a description of how and where You have fulfilled the 236 | obligations of Section 3.2. The notice must be conspicuously included 237 | in any notice in an Executable version, related documentation or 238 | collateral in which You describe recipients' rights relating to the 239 | Covered Code. You may distribute the Executable version of Covered 240 | Code or ownership rights under a license of Your choice, which may 241 | contain terms different from this License, provided that You are in 242 | compliance with the terms of this License and that the license for the 243 | Executable version does not attempt to limit or alter the recipient's 244 | rights in the Source Code version from the rights set forth in this 245 | License. If You distribute the Executable version under a different 246 | license You must make it absolutely clear that any terms which differ 247 | from this License are offered by You alone, not by the Initial 248 | Developer or any Contributor. You hereby agree to indemnify the 249 | Initial Developer and every Contributor for any liability incurred by 250 | the Initial Developer or such Contributor as a result of any such 251 | terms You offer. 252 | 253 | 3.7. Larger Works. 254 | You may create a Larger Work by combining Covered Code with other code 255 | not governed by the terms of this License and distribute the Larger 256 | Work as a single product. In such a case, You must make sure the 257 | requirements of this License are fulfilled for the Covered Code. 258 | 259 | 4. Inability to Comply Due to Statute or Regulation. 260 | 261 | If it is impossible for You to comply with any of the terms of this 262 | License with respect to some or all of the Covered Code due to 263 | statute, judicial order, or regulation then You must: (a) comply with 264 | the terms of this License to the maximum extent possible; and (b) 265 | describe the limitations and the code they affect. Such description 266 | must be included in the LEGAL file described in Section 3.4 and must 267 | be included with all distributions of the Source Code. Except to the 268 | extent prohibited by statute or regulation, such description must be 269 | sufficiently detailed for a recipient of ordinary skill to be able to 270 | understand it. 271 | 272 | 5. Application of this License. 273 | 274 | This License applies to code to which the Initial Developer has 275 | attached the notice in Exhibit A and to related Covered Code. 276 | 277 | 6. Versions of the License. 278 | 279 | 6.1. New Versions. 280 | Netscape Communications Corporation ("Netscape") may publish revised 281 | and/or new versions of the License from time to time. Each version 282 | will be given a distinguishing version number. 283 | 284 | 6.2. Effect of New Versions. 285 | Once Covered Code has been published under a particular version of the 286 | License, You may always continue to use it under the terms of that 287 | version. You may also choose to use such Covered Code under the terms 288 | of any subsequent version of the License published by Netscape. No one 289 | other than Netscape has the right to modify the terms applicable to 290 | Covered Code created under this License. 291 | 292 | 6.3. Derivative Works. 293 | If You create or use a modified version of this License (which you may 294 | only do in order to apply it to code which is not already Covered Code 295 | governed by this License), You must (a) rename Your license so that 296 | the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", 297 | "MPL", "NPL" or any confusingly similar phrase do not appear in your 298 | license (except to note that your license differs from this License) 299 | and (b) otherwise make it clear that Your version of the license 300 | contains terms which differ from the Mozilla Public License and 301 | Netscape Public License. (Filling in the name of the Initial 302 | Developer, Original Code or Contributor in the notice described in 303 | Exhibit A shall not of themselves be deemed to be modifications of 304 | this License.) 305 | 306 | 7. DISCLAIMER OF WARRANTY. 307 | 308 | COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, 309 | WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 310 | WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF 311 | DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. 312 | THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE 313 | IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, 314 | YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE 315 | COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER 316 | OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF 317 | ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. 318 | 319 | 8. TERMINATION. 320 | 321 | 8.1. This License and the rights granted hereunder will terminate 322 | automatically if You fail to comply with terms herein and fail to cure 323 | such breach within 30 days of becoming aware of the breach. All 324 | sublicenses to the Covered Code which are properly granted shall 325 | survive any termination of this License. Provisions which, by their 326 | nature, must remain in effect beyond the termination of this License 327 | shall survive. 328 | 329 | 8.2. If You initiate litigation by asserting a patent infringement 330 | claim (excluding declatory judgment actions) against Initial Developer 331 | or a Contributor (the Initial Developer or Contributor against whom 332 | You file such action is referred to as "Participant") alleging that: 333 | 334 | (a) such Participant's Contributor Version directly or indirectly 335 | infringes any patent, then any and all rights granted by such 336 | Participant to You under Sections 2.1 and/or 2.2 of this License 337 | shall, upon 60 days notice from Participant terminate prospectively, 338 | unless if within 60 days after receipt of notice You either: (i) 339 | agree in writing to pay Participant a mutually agreeable reasonable 340 | royalty for Your past and future use of Modifications made by such 341 | Participant, or (ii) withdraw Your litigation claim with respect to 342 | the Contributor Version against such Participant. If within 60 days 343 | of notice, a reasonable royalty and payment arrangement are not 344 | mutually agreed upon in writing by the parties or the litigation claim 345 | is not withdrawn, the rights granted by Participant to You under 346 | Sections 2.1 and/or 2.2 automatically terminate at the expiration of 347 | the 60 day notice period specified above. 348 | 349 | (b) any software, hardware, or device, other than such Participant's 350 | Contributor Version, directly or indirectly infringes any patent, then 351 | any rights granted to You by such Participant under Sections 2.1(b) 352 | and 2.2(b) are revoked effective as of the date You first made, used, 353 | sold, distributed, or had made, Modifications made by that 354 | Participant. 355 | 356 | 8.3. If You assert a patent infringement claim against Participant 357 | alleging that such Participant's Contributor Version directly or 358 | indirectly infringes any patent where such claim is resolved (such as 359 | by license or settlement) prior to the initiation of patent 360 | infringement litigation, then the reasonable value of the licenses 361 | granted by such Participant under Sections 2.1 or 2.2 shall be taken 362 | into account in determining the amount or value of any payment or 363 | license. 364 | 365 | 8.4. In the event of termination under Sections 8.1 or 8.2 above, 366 | all end user license agreements (excluding distributors and resellers) 367 | which have been validly granted by You or any distributor hereunder 368 | prior to termination shall survive termination. 369 | 370 | 9. LIMITATION OF LIABILITY. 371 | 372 | UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT 373 | (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL 374 | DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, 375 | OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR 376 | ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY 377 | CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, 378 | WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER 379 | COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN 380 | INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF 381 | LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY 382 | RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW 383 | PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE 384 | EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO 385 | THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. 386 | 387 | 10. U.S. GOVERNMENT END USERS. 388 | 389 | The Covered Code is a "commercial item," as that term is defined in 390 | 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer 391 | software" and "commercial computer software documentation," as such 392 | terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 393 | C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), 394 | all U.S. Government End Users acquire Covered Code with only those 395 | rights set forth herein. 396 | 397 | 11. MISCELLANEOUS. 398 | 399 | This License represents the complete agreement concerning subject 400 | matter hereof. If any provision of this License is held to be 401 | unenforceable, such provision shall be reformed only to the extent 402 | necessary to make it enforceable. This License shall be governed by 403 | California law provisions (except to the extent applicable law, if 404 | any, provides otherwise), excluding its conflict-of-law provisions. 405 | With respect to disputes in which at least one party is a citizen of, 406 | or an entity chartered or registered to do business in the United 407 | States of America, any litigation relating to this License shall be 408 | subject to the jurisdiction of the Federal Courts of the Northern 409 | District of California, with venue lying in Santa Clara County, 410 | California, with the losing party responsible for costs, including 411 | without limitation, court costs and reasonable attorneys' fees and 412 | expenses. The application of the United Nations Convention on 413 | Contracts for the International Sale of Goods is expressly excluded. 414 | Any law or regulation which provides that the language of a contract 415 | shall be construed against the drafter shall not apply to this 416 | License. 417 | 418 | 12. RESPONSIBILITY FOR CLAIMS. 419 | 420 | As between Initial Developer and the Contributors, each party is 421 | responsible for claims and damages arising, directly or indirectly, 422 | out of its utilization of rights under this License and You agree to 423 | work with Initial Developer and Contributors to distribute such 424 | responsibility on an equitable basis. Nothing herein is intended or 425 | shall be deemed to constitute any admission of liability. 426 | 427 | 13. MULTIPLE-LICENSED CODE. 428 | 429 | Initial Developer may designate portions of the Covered Code as 430 | "Multiple-Licensed". "Multiple-Licensed" means that the Initial 431 | Developer permits you to utilize portions of the Covered Code under 432 | Your choice of the NPL or the alternative licenses, if any, specified 433 | by the Initial Developer in the file described in Exhibit A. 434 | 435 | EXHIBIT A -Mozilla Public License. 436 | 437 | ``The contents of this file are subject to the Mozilla Public License 438 | Version 1.1 (the "License"); you may not use this file except in 439 | compliance with the License. You may obtain a copy of the License at 440 | http://www.mozilla.org/MPL/ 441 | 442 | Software distributed under the License is distributed on an "AS IS" 443 | basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 444 | License for the specific language governing rights and limitations 445 | under the License. 446 | 447 | The Original Code is rabbit-socks. 448 | 449 | The Initial Developers of the Original Code are VMware Inc. 450 | 451 | (C) 2010 VMware Inc. 452 | 453 | All Rights Reserved. 454 | 455 | Contributor(s): ______________________________________.'' 456 | 457 | [NOTE: The text of this Exhibit A may differ slightly from the text of 458 | the notices in the Source Code files of the Original Code. You should 459 | use the text of this Exhibit A rather than the text found in the 460 | Original Code Source Code for Your Modifications.] 461 | --------------------------------------------------------------------------------