Permalink
Browse files

Started on Bullet example chat

  • Loading branch information...
1 parent add5fa5 commit ae4439c4420221d604e65cb9da3b4c671b8e7e44 @rambocoder committed Apr 24, 2012
Showing with 323 additions and 1 deletion.
  1. +6 −0 .gitignore
  2. 0 ebin/.gitignore
  3. +42 −0 priv/static/bullet.html
  4. +247 −0 priv/static/bullet.js
  5. +2 −1 rebar.config
  6. +8 −0 src/cowboy_examples.erl
  7. +17 −0 src/stream_handler.erl
  8. +1 −0 start.sh
View
@@ -0,0 +1,6 @@
+log/*
+ebin/*.beam
+ebin/*.app
+erl_crash.dump
+!.gitignore
+deps
View
No changes.
View
@@ -0,0 +1,42 @@
+<html><head>
+ <title>Bullet Awesomeness!</title>
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
+ <script src="bullet.js" type="text/javascript"></script>
+ <script>
+$(document).ready(function(){
+ var bullet = $.bullet('ws://localhost:8080/bullet');
+ bullet.onopen = function(){
+ console.log('WebSocket: opened');
+ };
+ bullet.onclose = function(){
+ console.log('WebSocket: closed');
+ };
+ bullet.onmessage = function(e){
+ console.log(e.data);
+ };
+ bullet.onheartbeat = function(){
+ bullet.send('ping');
+ };
+
+ $("#inputBox").focus();
+
+ // configure the Shout submit to publish a message
+ $("#chatForm").submit(function(event){
+ // prevent the submit button from doing a POST and causing a page reload by returning false from the submit event
+ bullet.send($('#inputBox').val());
+ return false;
+ });
+
+});
+
+ </script>
+</head>
+<body>
+<div id="inputArea">
+ <form id="chatForm" autocomplete="off">
+ <input type="text" name="inputBox" id="inputBox">
+ <input type="submit" class="submit" value="Fire!">
+ </form>
+</div>
+</body>
+</html>
View
@@ -0,0 +1,247 @@
+/*
+ Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ Bullet is a client-side javascript library AND server-side Cowboy handler
+ to manage continuous streaming. It selects the proper transport in a fully
+ automated way and makes sure to always reconnect to the server on any
+ disconnect. You only need to handle sending messages, receiving them,
+ and managing the heartbeat of the stream.
+
+ Usage: $.bullet(url);
+
+ Then you can register one of the 4 event handlers:
+ onopen, onmessage, onclose, onheartbeat.
+
+ onopen is called once right after starting the bullet stream.
+ onmessage is called once for each message receveid.
+ onclose is called once right after you voluntarily close the socket.
+ onheartbeat is called once every few seconds to allow you to easily setup
+ a ping/pong mechanism.
+*/
+(function($){$.extend({bullet: function(url){
+ var CONNECTING = 0;
+ var OPEN = 1;
+ var CLOSING = 2;
+ var CLOSED = 3;
+
+ var transports = {
+ /**
+ The websocket transport is disabled for Firefox 6.0 because it
+ causes a crash to happen when the connection is closed.
+ @see https://bugzilla.mozilla.org/show_bug.cgi?id=662554
+ */
+ websocket: function(){
+ var ret = false;
+
+ if (window.WebSocket){
+ ret = window.WebSocket;
+ }
+
+ if (window.MozWebSocket
+ && navigator.userAgent.indexOf("Firefox/6.0") == -1){
+ ret = window.MozWebSocket;
+ }
+
+ if (ret){
+ return {'heart': true, 'transport': ret};
+ }
+
+ return false;
+ },
+
+ xhrPolling: function(){
+ var timeout;
+ var xhr;
+
+ var fake = {
+ readyState: CONNECTING,
+ send: function(data){
+ if (this.readyState != CONNECTING && this.readyState != OPEN){
+ return false;
+ }
+
+ var fakeurl = url.replace('ws:', 'http:').replace('wss:', 'https:');
+
+ $.ajax({
+ async: false,
+ cache: false,
+ type: 'POST',
+ url: fakeurl,
+ data: data,
+ dataType: 'text',
+ contentType:
+ 'application/x-www-form-urlencoded; charset=utf-8',
+ headers: {'X-Socket-Transport': 'xhrPolling'},
+ success: function(data){
+ if (data.length != 0){
+ fake.onmessage({'data': data});
+ }
+ }
+ });
+
+ return true;
+ },
+ close: function(){
+ this.readyState = CLOSED;
+ xhr.abort();
+ clearTimeout(timeout);
+ fake.onclose();
+ },
+ onopen: function(){},
+ onmessage: function(){},
+ onerror: function(){},
+ onclose: function(){}
+ };
+
+ function poll(){
+ var fakeurl = url.replace('ws:', 'http:').replace('wss:', 'https:');
+
+ xhr = $.ajax({
+ type: 'GET',
+ cache: false,
+ url: fakeurl,
+ dataType: 'text',
+ data: {},
+ headers: {'X-Socket-Transport': 'xhrPolling'},
+ success: function(data){
+ if (fake.readyState == CONNECTING){
+ fake.readyState = OPEN;
+ fake.onopen(fake);
+ }
+ // Connection might have closed without a response body
+ if (data.length != 0){
+ fake.onmessage({'data': data});
+ }
+ if (fake.readyState == OPEN){
+ nextPoll();
+ }
+ },
+ error: function(xhr){
+ fake.onerror();
+ }
+ });
+ }
+
+ function nextPoll(){
+ timeout = setTimeout(function(){poll();}, 100);
+ }
+
+ nextPoll();
+
+ return {'heart': false, 'transport': function(){ return fake; }};
+ }
+ };
+
+ var tn = 0;
+ function next(){
+ var c = 0;
+
+ for (var f in transports){
+ if (tn == c){
+ var t = transports[f]();
+ if (t){
+ var ret = new t.transport(url);
+ ret.heart = t.heart;
+ return ret;
+ }
+
+ tn++;
+ }
+
+ c++;
+ }
+
+ return false;
+ }
+
+ var stream = new function(){
+ var readyState = CLOSED;
+ var heartbeat;
+ var delay = delayDefault = 80;
+
+ var transport = next();
+ function init(){
+ readyState = CONNECTING;
+
+ if (!transport){
+ // No transport, give up
+ // @todo Trigger a disconnect error
+ return false;
+ }
+
+ transport.onopen = function(){
+ // We got a connection, reset the poll delay
+ delay = delayDefault;
+
+ if (transport.heart){
+ heartbeat = setInterval(function(){stream.onheartbeat();}, 20000);
+ }
+
+ if (readyState != OPEN){
+ readyState = OPEN;
+ stream.onopen();
+ }
+ };
+ transport.onclose = function(){
+ clearInterval(heartbeat);
+
+ if (readyState == CLOSING){
+ readyState = CLOSED;
+ stream.onclose();
+ } else{
+ // Close happened on connect, select next transport
+ if (readyState == CONNECTING){
+ tn++;
+ }
+
+ delay *= 2;
+ if (delay > 10000){
+ delay = 10000;
+ }
+
+ setTimeout(function(){
+ transport = next();
+ init();
+ }, delay);
+ }
+ };
+ transport.onerror = transport.onclose;
+ transport.onmessage = function(e){
+ stream.onmessage(e);
+ };
+ }
+ init();
+
+ this.onopen = function(){};
+ this.onmessage = function(){};
+ this.onclose = function(){};
+ this.onheartbeat = function(){};
+
+ this.setURL = function(newURL){
+ url = newURL;
+ };
+ this.send = function(data){
+ return transport.send(data);
+ };
+ this.close = function(){
+ readyState = CLOSING;
+ transport.close();
+ };
+ };
+
+ return stream;
+}})})(jQuery);
View
@@ -1,4 +1,5 @@
%%-*- mode: erlang -*-
{deps, [
- {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}}
+ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}},
+ {bullet, ".*", {git, "https://github.com/extend/bullet.git", "master"}}
]}.
View
@@ -9,14 +9,22 @@ start() ->
application:start(public_key),
application:start(ssl),
application:start(cowboy),
+ application:start(bullet),
application:start(cowboy_examples).
start(_Type, _Args) ->
+ Mimetypes = {mimetypes,[
+ {<<".css">>, [<<"text/css">>]},
+ {<<".js">>, [<<"application/javascript">>]},
+ {<<".html">>, [<<"text/html">>]}]},
+ StaticDir = {directory, <<"./priv/static">>},
Dispatch = [
{'_', [
{[<<"websocket">>], websocket_handler, []},
{[<<"eventsource">>], eventsource_handler, []},
{[<<"eventsource">>, <<"live">>], eventsource_emitter, []},
+ {[<<"bullet">>], bullet_handler, [{handler, stream_handler}]},
+ {[<<"priv">>, <<"static">>, '...'], cowboy_http_static, [StaticDir, Mimetypes]},
{'_', default_handler, []}
]}
],
View
@@ -0,0 +1,17 @@
+-module(stream_handler).
+-export([init/4, stream/3, info/3, terminate/2]).
+
+init(_Transport, Req, _Opts, Active) ->
+ io:format("INIT. Active: ~p~n", [Active]),
+ {ok, Req, undefined_state}.
+
+stream(Data, Req, State) ->
+ io:format("STREAM. Data: ~p~n", [Data]),
+ {reply, Data, Req, State}.
+
+info(_Info, Req, State) ->
+ {ok, Req, State}.
+
+terminate(_Req, _State) ->
+ io:format("TERMINATE.~n"),
+ ok.
View
@@ -2,5 +2,6 @@
erl -sname cowboy_examples -pa ebin -pa deps/*/ebin -s cowboy_examples \
-eval "io:format(\"~n~nThe following examples are available:~n\")." \
-eval "io:format(\"* Hello world: http://localhost:8080~n\")." \
+ -eval "io:format(\"* Bullet: http://localhost:8080/priv/static/bullet.html~n\")." \
-eval "io:format(\"* Websockets: http://localhost:8080/websocket~n\")." \
-eval "io:format(\"* Eventsource: http://localhost:8080/eventsource~n\")."

0 comments on commit ae4439c

Please sign in to comment.