Browse files

Websocket working in Chrome, although posting data is still not. Poll…

…ing is working with timestamp sending
  • Loading branch information...
1 parent 667d2a1 commit 384bcd3695b8159d290a01e24bc3d6c10d5e8cba @akitaonrails committed Jan 12, 2010
View
3 app/controllers/chat_controller.rb
@@ -5,8 +5,9 @@ class ChatController < WebSocketApplicationController
def retrieve_messages
@last_message ||= (Time.now - 1.minute)
Chat.recent(@last_message).all do |messages|
- messages.each { |msg| render( formatted_msg(msg) ) }
+ list = messages.map { |msg| { "from" => msg.name, "msg" => msg.message, "sent" => msg.sent_at.to_formatted_s(:short) } }
@last_message = messages.first.try(:sent_at) || @last_message
+ render [list.to_json, "\n"]
end
end
View
9 app/controllers/retrieve_controller.rb
@@ -1,11 +1,18 @@
class RetrieveController < ApplicationController
+ before_start :check_last_timestamp
on_start :retrieve_messages
+ def check_last_timestamp
+ @last_message = Time.parse(request.params["last_timestamp"]) rescue nil
+ yield
+ end
+
def retrieve_messages
@last_message ||= (Time.now - 30.seconds)
Chat.recent(@last_message).all do |messages|
- messages.each { |msg| render( formatted_msg(msg) ) }
+ list = messages.map { |msg| { "from" => msg.name, "msg" => msg.message, "sent" => msg.sent_at.to_formatted_s(:short), "timestamp" => msg.sent_at } }
@last_message = messages.first.try(:sent_at) || @last_message
+ render [list.to_json, "\n"]
finish
end
end
View
13 app/helpers/application.rb
@@ -1,13 +1,10 @@
module ApplicationHelper
+ def respond_with
+ [200, {'Content-Type' => 'application/json'}]
+ end
+
private
def formatted_msg(message)
- if message.is_a? Chat
- ["<div>
- <span class=\"from\">At #{message.sent_at.to_formatted_s(:short)}, #{message.name} said:</span>
- <span class=\"msg\">#{message.message}</span>
- </div>", "\n"]
- else
- ["<p>#{message}</p>", "\n"]
- end
+ [[{ "message" => message }].to_json, "\n"]
end
end
View
1 chatserver.rb
@@ -5,6 +5,7 @@
require 'cramp/model'
require 'ruby-debug'
require 'active_support/all'
+require 'active_support/json'
require 'config/routes'
require 'app/helpers/application'
View
29 config/routes.rb
@@ -1,12 +1,12 @@
def app_routes
- Usher::Interface.for(:rack) do
- get('/').to(ChatController)
- get('/retrieve').to(RetrieveController)
- post('/receive').to(ReceiveController)
- end
+ # Usher::Interface.for(:rack) do
+ # get('/').to(ChatController)
+ # get('/retrieve').to(RetrieveController)
+ # post('/receive').to(ReceiveController)
+ # end
# Rack::Builder.new do
- # use Rack::Static, :urls => ["/demo"]
+ # use Rack::Static, :urls => ["/public"]
# use Rack::Session::Cookie
#
# routes = Usher::Interface.for(:rack) do
@@ -17,14 +17,13 @@ def app_routes
# run routes
# end
- # Rack::Builder.new do
- # use Rack::ContentType, "text/html"
- # use Rack::Static, :urls => ["/demo"]
- # use Rack::Session::Cookie
- #
- # map("/") { run RetrieveController }
- # map("/retrieve") { run RetrieveController }
- # map("/receive") { run ReceiveController }
- # end
+ Rack::Builder.new do
+ use Rack::Static, :urls => ["/public"]
+ use Rack::Session::Cookie
+
+ map("/websocket") { run ChatController }
+ map("/retrieve") { run RetrieveController }
+ map("/receive") { run ReceiveController }
+ end
end
View
2 demo.rb
@@ -3,7 +3,7 @@
require 'thin'
class MyApp < Sinatra::Base
- enable :static
+ set :static, true
end
Thin::Logging.trace = true
View
6 extra/crossdomain.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
+<!-- Policy file for xmlsocket://socks.example.com -->
+<cross-domain-policy>
+ <allow-access-from domain="*" to-ports="*" />
+</cross-domain-policy>
View
89 extra/flashpolicyd.pl
@@ -0,0 +1,89 @@
+#!/usr/bin/perl
+#
+# policyd.pl
+# Simple socket policy file server
+#
+# Usage: policyd.pl [-port=N] -file=FILE
+# Logs to stdout
+#
+
+use strict;
+use Socket;
+
+my $NULLBYTE = pack( 'c', 0 );
+
+my $port = 843;
+my $filePath;
+my $content;
+
+### READ ARGS
+
+while ( my $arg = shift @ARGV )
+{
+ if ( $arg =~ m/^--port=(\d+)$/ )
+ {
+ $port = $1;
+ }
+ elsif ( $arg =~ m/^--file=(.*)/ )
+ {
+ $filePath = $1;
+ }
+}
+
+unless ( $filePath )
+{
+ die "Usage: policyd.pl [--port=N] --file=FILE\n";
+}
+
+### READ FILE
+
+-f $filePath or die "No such file: '$filePath'\n";
+-s $filePath < 10_000 or die "File probably too large to be a policy file: '$filePath'\n";
+
+local $/ = undef;
+open POLICYFILE, "<$filePath" or die "Can't open '$filePath': $!\n";
+$content = <POLICYFILE>;
+close POLICYFILE;
+
+$content =~ m/cross-domain-policy/ or die "Not a valid policy file: '$filePath'\n";
+
+### BEGIN LISTENING
+
+socket( LISTENSOCK, PF_INET, SOCK_STREAM, getprotobyname( 'tcp' ) ) or die "socket() error: $!";
+setsockopt( LISTENSOCK, SOL_SOCKET, SO_REUSEADDR, pack( 'l', 1 ) ) or die "setsockopt() error: $!";
+bind( LISTENSOCK, sockaddr_in( $port, INADDR_ANY ) ) or die "bind() error: $!";
+listen( LISTENSOCK, SOMAXCONN ) or die "listen() error: $!";
+
+print STDOUT "\nListening on port $port\n\n";
+
+### HANDLE CONNECTIONS
+
+while ( my $clientAddr = accept( CONNSOCK, LISTENSOCK ) )
+{
+ my ( $clientPort, $clientIp ) = sockaddr_in( $clientAddr );
+ my $clientIpStr = inet_ntoa( $clientIp );
+ print STDOUT "Connection from $clientIpStr:$clientPort\n";
+
+ local $/ = $NULLBYTE;
+ my $request = <CONNSOCK>;
+ chomp $request;
+
+ if ( $request eq '<policy-file-request/>' )
+ {
+ print STDOUT "Valid request received\n";
+ }
+ else
+ {
+ print STDOUT "Unrecognized request: $request\n\n";
+ close CONNSOCK;
+ next;
+ }
+
+ print CONNSOCK $content;
+ print CONNSOCK $NULLBYTE;
+ close CONNSOCK;
+
+ print STDOUT "Sent policy file\n\n";
+}
+
+# End of file.
View
98 extra/flashpolicyd.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+#
+# flashpolicyd.py
+# Simple socket policy file server for Flash
+#
+# Usage: flashpolicyd.py [--port=N] --file=FILE
+#
+# Logs to stderr
+# Requires Python 2.5 or later
+
+from __future__ import with_statement
+
+import sys
+import optparse
+import socket
+import thread
+import exceptions
+import contextlib
+
+VERSION = 0.1
+
+class policy_server(object):
+ def __init__(self, port, path):
+ self.port = port
+ self.path = path
+ self.policy = self.read_policy(path)
+ self.log('Listening on port %d\n' % port)
+ try:
+ self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+ except AttributeError:
+ # AttributeError catches Python built without IPv6
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ except socket.error:
+ # socket.error catches OS with IPv6 disabled
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.sock.bind(('', port))
+ self.sock.listen(5)
+ def read_policy(self, path):
+ with file(path, 'rb') as f:
+ policy = f.read(10001)
+ if len(policy) > 10000:
+ raise exceptions.RuntimeError('File probably too large to be a policy file',
+ path)
+ if 'cross-domain-policy' not in policy:
+ raise exceptions.RuntimeError('Not a valid policy file',
+ path)
+ return policy
+ def run(self):
+ try:
+ while True:
+ thread.start_new_thread(self.handle, self.sock.accept())
+ except socket.error, e:
+ self.log('Error accepting connection: %s' % (e[1],))
+ def handle(self, conn, addr):
+ addrstr = '%s:%s' % (addr[0],addr[1])
+ try:
+ self.log('Connection from %s' % (addrstr,))
+ with contextlib.closing(conn):
+ # It's possible that we won't get the entire request in
+ # a single recv, but very unlikely.
+ request = conn.recv(1024).strip()
+ if request != '<policy-file-request/>\0':
+ self.log('Unrecognized request from %s: %s' % (addrstr, request))
+ return
+ self.log('Valid request received from %s' % (addrstr,))
+ conn.sendall(self.policy)
+ self.log('Sent policy file to %s' % (addrstr,))
+ except socket.error, e:
+ self.log('Error handling connection from %s: %s' % (addrstr, e[1]))
+ except Exception, e:
+ self.log('Error handling connection from %s: %s' % (addrstr, e[1]))
+ def log(self, str):
+ print >>sys.stderr, str
+
+def main():
+ parser = optparse.OptionParser(usage = '%prog [--port=PORT] --file=FILE',
+ version='%prog ' + str(VERSION))
+ parser.add_option('-p', '--port', dest='port', type=int, default=843,
+ help='listen on port PORT', metavar='PORT')
+ parser.add_option('-f', '--file', dest='path',
+ help='server policy file FILE', metavar='FILE')
+ opts, args = parser.parse_args()
+ if args:
+ parser.error('No arguments are needed. See help.')
+ if not opts.path:
+ parser.error('File must be specified. See help.')
+
+ try:
+ policy_server(opts.port, opts.path).run()
+ except Exception, e:
+ print >> sys.stderr, e
+ sys.exit(1)
+ except KeyboardInterrupt:
+ pass
+
+if __name__ == '__main__':
+ main()
View
2 extra/policyd
@@ -0,0 +1,2 @@
+#!/bin/bash
+sudo python flashpolicyd.py --port 843 --file crossdomain.xml
View
19 public/chat.html
@@ -46,18 +46,18 @@
function initConnection() {
// Connect to Web Socket.
// Change host/port here to your own Web Socket server.
- ws = new WebSocket("ws://foo.com.br:3000/");
+ ws = new WebSocket("ws://foo.com.br:3000/websocket");
// Set event handlers.
ws.onopen = function() {
- output("<p>Connection opened</p>");
+ output([{message: "Connection opened"}]);
};
ws.onmessage = function(e) {
// e.data contains received string.
- output(e.data);
+ output(eval(e.data));
};
ws.onclose = function() {
- output("<p>Connection closed</p>");
+ output([{message: "Connection closed"}]);
initConnection(); // reconnect
};
}
@@ -73,8 +73,15 @@
$("#msg").focus();
}
- function output(str) {
- $("#messages").append(str);
+ function output(data) {
+ $.each(data, function(i, item) {
+ if (item.from != null) {
+ $("#messages").append( "<div><span class=\"from\">" + item.from + ":</span>\n<span class=\"msg\">" + item.msg + "</span></div>");
+ }
+ if (item.message != null) {
+ $("#messages").append( "<p>" + item.message + "</p>");
+ }
+ })
var messages = document.getElementById("messages");
messages.scrollTop = messages.scrollHeight;
}
View
27 public/chat_poll.html
@@ -34,30 +34,45 @@
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
+ var lastTimestamp = {};
+
// method used to update element html
$(document).ready(function() {
$("#msg").focus();
setInterval(function() {
$.ajax({
url: "http://foo.com.br:3000/retrieve",
type: "GET",
- success: output,
+ data: lastTimestamp,
+ dataType: "json",
+ success: function(data) {
+ $.each(data, function(i, item) {
+ $("#messages").append( "<div><span class=\"from\">" + item.from + ":</span>\n<span class=\"msg\">" + item.msg + "</span></div>");
+ lastTimestamp = { last_timestamp: item.timestamp }
+ });
+ scrollDown();
+ },
})
- }, 3000);
+ }, 5000);
});
function onSubmit() {
$.ajax({
url: "http://foo.com.br:3000/receive",
- type: "GET",
+ type: "POST",
+ dataType: "json",
data: $("#chat-form").serialize(),
- success: output,
+ success: function(data) {
+ $.each(data, function(i, item){
+ $("#messages").append( "<p>" + item.message + "</p>");
+ })
+ scrollDown();
+ },
});
$("#msg").val("").focus();
}
- function output(str) {
- $("#messages").append(str);
+ function scrollDown() {
var messages = document.getElementById("messages");
messages.scrollTop = messages.scrollHeight;
}

0 comments on commit 384bcd3

Please sign in to comment.