Permalink
Browse files

New implementation based on Cramp

  • Loading branch information...
tarcieri committed Sep 10, 2011
1 parent 71c2ae4 commit 9c0003fe49a3a72592ced368a19be7cd4948f470
View
12 Gemfile
@@ -1,7 +1,9 @@
-source "http://rubygems.org"
+source :rubygems
-# Pull in Celluloid via git
-gem 'celluloid', :git => 'git://github.com/tarcieri/celluloid.git'
+gem 'cramp'
-# Specify your gem's dependencies in variety.gemspec
-gemspec
+gem 'thin'
+gem 'http_router'
+gem 'async-rack'
+gem 'json'
+gem 'celluloid'
View
@@ -1,40 +1,37 @@
-GIT
- remote: git://github.com/tarcieri/celluloid.git
- revision: c5a7d9ad784ba33994521266241c85a4e689896c
- specs:
- celluloid (0.0.1)
- celluloid
-
-PATH
- remote: .
- specs:
- variety (0.0.1)
-
GEM
remote: http://rubygems.org/
specs:
- diff-lcs (1.1.2)
- git (1.2.5)
- jeweler (1.5.2)
- bundler (~> 1.0.0)
- git (>= 1.2.5)
- rake
- rake (0.8.7)
- rspec (2.3.0)
- rspec-core (~> 2.3.0)
- rspec-expectations (~> 2.3.0)
- rspec-mocks (~> 2.3.0)
- rspec-core (2.3.1)
- rspec-expectations (2.3.0)
- diff-lcs (~> 1.1.2)
- rspec-mocks (2.3.0)
+ activesupport (3.0.10)
+ async-rack (0.5.1)
+ rack (~> 1.1)
+ celluloid (0.2.1)
+ cramp (0.15.1)
+ activesupport (~> 3.0.9)
+ eventmachine (~> 1.0.0.beta.3)
+ rack (~> 1.3.2)
+ thor (~> 0.14.6)
+ daemons (1.1.4)
+ eventmachine (1.0.0.beta.3)
+ http_router (0.10.2)
+ rack (>= 1.0.0)
+ url_mount (~> 0.2.1)
+ json (1.5.4)
+ rack (1.3.2)
+ thin (1.2.11)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
+ thor (0.14.6)
+ url_mount (0.2.1)
+ rack
PLATFORMS
ruby
DEPENDENCIES
- bundler (~> 1.0.0)
- celluloid!
- jeweler (~> 1.5.2)
- rspec (~> 2.3.0)
- variety!
+ async-rack
+ celluloid
+ cramp
+ http_router
+ json
+ thin
View
@@ -0,0 +1,33 @@
+class ChatClient < Cramp::Websocket
+ attr_reader :name
+
+ on_data :handle_data
+ on_finish :handle_leave
+
+ def handle_data(data)
+ msg = JSON.parse data
+ p msg
+
+ case msg['action']
+ when 'join' then handle_join(msg)
+ when 'message' then handle_message(msg)
+ end
+ end
+
+ def handle_join(msg)
+ @name = msg['user']
+ ChatServer.actor.register(@name, self)
+ end
+
+ def handle_leave
+ ChatServer.actor.unregister(@name)
+ end
+
+ def handle_message(msg)
+ ChatServer.actor.send_message @name, msg['message']
+ end
+
+ def send_message(msg)
+ render msg
+ end
+end
View
@@ -0,0 +1,39 @@
+require 'celluloid'
+
+class ChatServer
+ include Celluloid
+
+ def self.start
+ supervise_as :chat_server
+ end
+
+ def self.actor
+ Celluloid::Actor[:chat_server]
+ end
+
+ def initialize
+ @clients = {}
+ end
+
+ def register(name, client)
+ @clients[name] = client
+ publish :control, name, 'joined the chat room'
+ end
+
+ def unregister(name)
+ @clients.delete name
+ publish :control, name, 'left the chat room'
+ end
+
+ def send_message(user, str)
+ publish :message, user, str
+ end
+
+ def publish(type, user, str)
+ msg = {:action => type, :user => user, :message => str}.to_json
+
+ @clients.each do |_, client|
+ client.send_message msg
+ end
+ end
+end
@@ -0,0 +1,8 @@
+class HomeAction < Cramp::Action
+ @@template = ERB.new File.read(Variety::Application.root('app/views/index.erb'))
+
+ def start
+ render @@template.result(binding)
+ finish
+ end
+end
View
@@ -0,0 +1,114 @@
+<html>
+<head>
+<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js'></script>
+<script src='http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js'></script>
+<script src='http://datejs.googlecode.com/svn/trunk/build/date.js'></script>
+<script>
+$(document).ready(function(){
+ if (typeof(WebSocket) != 'undefined' || typeof(MozWebSocket)) {
+ $('#ask').show();
+ } else {
+ $('#error').show();
+ }
+
+ // join on enter
+ $('#ask input').keydown(function(event) {
+ if (event.keyCode == 13) {
+ $('#ask a').click();
+ }
+ })
+
+ // join on click
+ $('#ask a').click(function() {
+ join($('#ask input').val());
+ $('#ask').hide();
+ $('#channel').show();
+ $('input#message').focus();
+ });
+
+ function join(name) {
+ var host = window.location.host.split(':')[0];
+
+ var SocketKlass = "MozWebSocket" in window ? MozWebSocket : WebSocket;
+ var ws = new SocketKlass('ws://<%= request.host_with_port %>/websocket');
+
+ var container = $('div#msgs');
+ ws.onmessage = function(evt) {
+ var obj = $.evalJSON(evt.data);
+ if (typeof obj != 'object') return;
+
+ var action = obj['action'];
+ var struct = container.find('li.' + action + ':first');
+ if (struct.length < 1) {
+ console.log("Could not handle: " + evt.data);
+ return;
+ }
+
+ var msg = struct.clone();
+ msg.find('.time').text((new Date()).toString("HH:mm:ss"));
+
+ if (action == 'message') {
+ var matches;
+ if (matches = obj['message'].match(/^\s*[\/\\]me\s(.*)/)) {
+ msg.find('.user').text(obj['user'] + ' ' + matches[1]);
+ msg.find('.user').css('font-weight', 'bold');
+ } else {
+ msg.find('.user').text(obj['user']);
+ msg.find('.message').text(': ' + obj['message']);
+ }
+ } else if (action == 'control') {
+ msg.find('.user').text(obj['user']);
+ msg.find('.message').text(obj['message']);
+ msg.addClass('control');
+ }
+
+ if (obj['user'] == name) msg.find('.user').addClass('self');
+ container.find('ul').append(msg.show());
+ container.scrollTop(container.find('ul').innerHeight());
+ }
+
+ $('#channel form').submit(function(event) {
+ event.preventDefault();
+ var input = $(this).find(':input');
+ var msg = input.val();
+ ws.send($.toJSON({ action: 'message', message: msg }));
+ input.val('');
+ });
+
+ // send name when joining
+ ws.onopen = function() {
+ ws.send($.toJSON({ action: 'join', user: name }));
+ }
+ }
+});
+</script>
+ <link rel="stylesheet" type="text/css" media="screen" href="/stylesheets/style.css" />
+</head>
+<body>
+ <img src="/images/logo.png" alt="Variety" />
+ <div id="error" class="bordered" style="display: none;">
+ This browser has no native WebSocket support.<br/>
+ Use a WebKit nightly or Google Chrome.
+ </div>
+ <div id="ask" class="bordered" style="display: none;">
+ Name: <input type="text" id="name" /> <a href="#"><span class="join">Join!</span></a>
+ </div>
+ <div id="channel" class="bordered" style="display: none;">
+ <div id="msgs">
+ <ul>
+ <li class="message" style="display: none">
+ <span class="user"></span><span class="message"></span>
+ <span class="time"></span>
+ </li>
+ <li class="control" style="display: none">
+ <span class="user"></span>&nbsp;<span class="message"></span>
+ <span class="time"></span>
+ </li>
+ </ul>
+ </div>
+ <div id="input">
+ <form><input type="text" id="message" /></form>
+ </div>
+ </div>
+</body>
+</html>
View
@@ -0,0 +1,38 @@
+require 'rubygems'
+require 'bundler'
+
+module Variety
+ class Application
+
+ def self.root(path = nil)
+ @_root ||= File.expand_path(File.dirname(__FILE__))
+ path ? File.join(@_root, path.to_s) : @_root
+ end
+
+ def self.env
+ @_env ||= ENV['RACK_ENV'] || 'development'
+ end
+
+ def self.routes
+ @_routes ||= eval(File.read('./config/routes.rb'))
+ end
+
+ # Initialize the application
+ def self.initialize!
+ Cramp::Websocket.backend = :thin
+ end
+
+ end
+end
+
+Bundler.require(:default, Variety::Application.env)
+
+require 'erb'
+require 'json'
+require 'celluloid'
+
+# Preload application classes
+Dir['./app/**/*.rb'].each {|f| require f}
+
+# Launch the Celluloid chat server
+ChatServer.start
View
@@ -1,6 +0,0 @@
-#!/usr/bin/env ruby
-
-$LOAD_PATH << File.expand_path('../../lib', __FILE__)
-require 'variety'
-
-Variety::App.run!
View
@@ -1,4 +1,24 @@
-$LOAD_PATH << File.expand_path('../lib', __FILE__)
-require 'variety'
+require './application'
+Variety::Application.initialize!
-run Variety::App
+if Variety::Application.env == 'development'
+ use AsyncRack::CommonLogger
+
+ # Enable code reloading on every request
+ use Rack::Reloader, 0
+
+ # Serve assets from /public
+ use Rack::Static, :urls => ["/images", "/stylesheets"], :root => Variety::Application.root(:public)
+end
+
+# Running thin :
+#
+# bundle exec thin --max-persistent-conns 1024 --timeout 0 -R config.ru start
+#
+# Vebose mode :
+#
+# Very useful when you want to view all the data being sent/received by thin
+#
+# bundle exec thin --max-persistent-conns 1024 --timeout 0 -V -R config.ru start
+#
+run Variety::Application.routes
View
@@ -0,0 +1,5 @@
+# Check out https://github.com/joshbuddy/http_router for more information on HttpRouter
+HttpRouter.new do
+ add('/').to(HomeAction)
+ get('/websocket').to(ChatClient)
+end
View
@@ -1,12 +0,0 @@
-require 'sinatra'
-
-module Variety
- class App < Sinatra::Base
- set :static, true
- set :public, File.expand_path('../../public', __FILE__)
-
- get '/' do
- File.read File.expand_path('../../public/index.html', __FILE__)
- end
- end
-end
File renamed without changes.
View
@@ -1,9 +0,0 @@
-<html>
- <head>
- <link rel="stylesheet" href="style.css" type="text/css"/>
- <title>Variety: Sinatra and Celluloid-powered Chat</title>
- </head>
- <body>
- <h1><img alt="VARIETY" src="/logo.png"></h1>
- </body>
-</html>
Oops, something went wrong.

0 comments on commit 9c0003f

Please sign in to comment.