Permalink
Browse files

WS, XHR, XHR-Stream, ES, IFrame, HtmlFile

  • Loading branch information...
1 parent bfbf6b7 commit d8b0835d0afc501a03af4ca973b174a33ea52911 @marekjelen marekjelen committed Jan 20, 2012
Showing with 183 additions and 6 deletions.
  1. +1 −1 lib/wildcloud/websockets/engine.rb
  2. +179 −4 lib/wildcloud/websockets/web_sockets_server.rb
  3. +3 −1 samples/test.html
@@ -66,7 +66,7 @@ def remove_socket(socket_id, socket)
end
def publish(socket_id, message)
- @sockets[socket_id][:sockets].each { |socket| socket.publish(message) }
+ @sockets[socket_id][:sockets].each { |socket| socket.call(message) }
end
def on_message(socket_id, message)
@@ -28,6 +28,7 @@ module Websockets
java_import 'org.jboss.netty.handler.codec.http.DefaultHttpResponse'
java_import 'org.jboss.netty.handler.codec.http.HttpHeaders'
java_import 'org.jboss.netty.handler.codec.http.HttpChunkAggregator'
+ java_import 'org.jboss.netty.handler.codec.http.HttpMethod'
java_import 'org.jboss.netty.handler.codec.http.HttpRequest'
java_import 'org.jboss.netty.handler.codec.http.HttpRequestDecoder'
java_import 'org.jboss.netty.handler.codec.http.HttpResponseEncoder'
@@ -64,6 +65,10 @@ def start
class WebSocketsServerHandler < SimpleChannelUpstreamHandler
+ def self.xhrs
+ @xhrs ||= {}
+ end
+
def messageReceived(context, event)
message = event.message
case message
@@ -74,6 +79,10 @@ def messageReceived(context, event)
end
end
+ def channelClosed(context, event)
+ Engine.instance.remove_socket(@socket_id, self)
+ end
+
def handle_http(context, request)
case
when match = /^\/authorize\/([^\/]+)\/([^\/]+)$/.match(request.uri)
@@ -82,8 +91,20 @@ def handle_http(context, request)
handle_publish(context, request, match[1])
when match = /^\/([^\/]+)\/info$/.match(request.uri)
handle_info(context, request, match[1])
+ when match = /^\/([^\/]+)\/iframe.*\.html(\?t=(.*))?$/.match(request.uri)
+ handle_iframe(context, request, match[1])
when match = /^\/([^\/]+)\/([^\/]+)\/([^\/]+)\/websocket$/.match(request.uri)
handle_ws_handshake(context, request, match[1], match[2], match[3])
+ when match = /^\/([^\/]+)\/([^\/]+)\/([^\/]+)\/htmlfile\?c=(.*)$/.match(request.uri)
+ handle_htmlfile(context, request, match[1], match[2], match[3], match[4])
+ when match = /^\/([^\/]+)\/([^\/]+)\/([^\/]+)\/eventsource$/.match(request.uri)
+ handle_eventsource(context, request, match[1], match[2], match[3])
+ when match = /^\/([^\/]+)\/([^\/]+)\/([^\/]+)\/xhr_streaming$/.match(request.uri)
+ handle_xhr_streaming(context, request, match[1], match[2], match[3])
+ when match = /^\/([^\/]+)\/([^\/]+)\/([^\/]+)\/xhr$/.match(request.uri)
+ handle_xhr(context, request, match[1], match[2], match[3])
+ when match = /^\/([^\/]+)\/([^\/]+)\/([^\/]+)\/xhr_send$/.match(request.uri)
+ handle_xhr_send(context, request, match[1], match[2], match[3])
else
context.channel.write(DefaultHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::FORBIDDEN)).addListener(ChannelFutureListener.CLOSE)
end
@@ -121,6 +142,143 @@ def handle_publish(context, request, socket_id)
Engine.instance.publish(socket_id, data)
end
+ def handle_iframe(context, request, socket_id)
+ data = <<HTML
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <script>
+ document.domain = document.domain;
+ _sockjs_onload = function(){SockJS.bootstrap_iframe();};
+ </script>
+ <script src="http://cdn.sockjs.org/sockjs-0.2.min.js"></script>
+</head>
+<body>
+ <h2>Don't panic!</h2>
+ <p>This is a SockJS hidden iframe. It's used for cross domain magic.</p>
+</body>
+</html>
+HTML
+ response = DefaultHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::OK)
+ HttpHeaders.set_content_length(response, data.size)
+ response.set_content(ChannelBuffers.wrapped_buffer(data.to_java_bytes))
+ response.set_header('Access-Control-Allow-Credentials', 'true')
+ response.set_header('Access-Control-Allow-Origin', 'http://localhost')
+ response.set_header('Content-Type', 'text/html; charset=UTF-8')
+ context.channel.write(response).addListener(ChannelFutureListener.CLOSE)
+ end
+
+ def handle_htmlfile(context, request, socket_id, server_id, session_id, callback)
+ data = "<!doctype html>
+<html><head>
+<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />
+<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />
+</head><body><h2>Don't panic!</h2>
+<script>
+ document.domain = document.domain;
+ var c = parent.#{callback};
+ c.start();
+ function p(d) {c.message(d);};
+ window.onload = function() {c.stop();};
+</script>
+<script>\np(\"o\");\n</script>\r\n
+ "
+ response = DefaultHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::OK)
+ response.set_content(ChannelBuffers.wrapped_buffer(data.to_java_bytes))
+ response.set_header('Access-Control-Allow-Credentials', 'true')
+ response.set_header('Access-Control-Allow-Origin', 'http://localhost:*')
+ response.set_header('Content-Type', 'text/html; charset=UTF-8')
+ response.set_header('Connection', 'keep-alive')
+ context.channel.write(response)
+ @type = :htmlfile
+ @socket_id = socket_id
+ @channel = context.channel
+ Engine.instance.add_socket(@socket_id, self)
+
+ end
+
+
+ def handle_eventsource(context, request, socket_id, server_id, session_id)
+ response = DefaultHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::OK)
+ response.set_header('Access-Control-Allow-Credentials', 'true')
+ response.set_header('Access-Control-Allow-Origin', 'http://localhost')
+ response.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
+ response.set_header('Content-Type', 'text/event-stream; charset=UTF-8')
+ response.set_header('Connection', 'keep-alive')
+ data = "\r\n"
+ data << "data: o\r\n\r\n"
+ response.set_content(ChannelBuffers.wrapped_buffer(data.to_java_bytes))
+ context.channel.write(response)
+ @type = :eventsource
+ @socket_id = socket_id
+ @channel = context.channel
+ Engine.instance.add_socket(@socket_id, self)
+ end
+
+ def handle_xhr_streaming(context, request, socket_id, server_id, session_id)
+ response = DefaultHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::OK)
+ response.set_header('Access-Control-Allow-Credentials', 'true')
+ response.set_header('Access-Control-Allow-Origin', 'http://localhost')
+ response.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
+ response.set_header('Content-Type', 'application/json; charset=UTF-8')
+ response.set_header('Connection', 'keep-alive')
+ data = 'h' * 2048
+ data << "\no\n"
+ response.set_content(ChannelBuffers.wrapped_buffer(data.to_java_bytes))
+ context.channel.write(response)
+ @type = :xhr_stream
+ @socket_id = socket_id
+ @channel = context.channel
+ Engine.instance.add_socket(@socket_id, self)
+ end
+
+ def handle_xhr(context, request, socket_id, server_id, session_id)
+ response = DefaultHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::OK)
+ response.set_header('Access-Control-Allow-Credentials', 'true')
+ response.set_header('Access-Control-Allow-Origin', 'http://localhost')
+ response.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
+ response.set_header('Content-Type', 'application/json; charset=UTF-8')
+ response.set_header('Connection', 'keep-alive')
+ if WebSocketsServerHandler.xhrs.key?(session_id)
+ context.channel.write(response)
+ @channel = context.channel
+ @socket_id = socket_id
+ Engine.instance.add_socket(@socket_id, self)
+ @type = :xhr
+ else
+ WebSocketsServerHandler.xhrs[session_id] = true
+ data = "o\n"
+ HttpHeaders.set_content_length(response, data.size)
+ response.set_content(ChannelBuffers.wrapped_buffer(data.to_java_bytes))
+ context.channel.write(response).addListener(ChannelFutureListener.CLOSE)
+ end
+ end
+
+ def handle_xhr_send(context, request, socket_id, server_id, session_id)
+ close = true
+ response = DefaultHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::OK)
+ response.set_header('Access-Control-Allow-Credentials', 'true')
+ response.set_header('Access-Control-Allow-Origin', 'http://localhost')
+ case request.get_method
+ when HttpMethod::OPTIONS
+ response.status = HttpResponseStatus::NO_CONTENT
+ response.set_header('Access-Control-Allow-Headers', 'Allow,Content-type')
+ response.set_header('Allow', 'OPTIONS,POST')
+ when HttpMethod::POST
+ response.set_header('Content-Type', 'text/plain; charset=UTF-8')
+ response.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
+ data = request.content.to_string(Charset.for_name('UTF-8'))
+ data = MultiJson.decode(data)
+ data.each do |message|
+ Engine.instance.on_message(socket_id, message)
+ end
+ end
+ future = context.channel.write(response)
+ future.addListener(ChannelFutureListener.CLOSE) if close
+ end
+
def handle_ws_handshake(context, request, socket_id, server_id, session_id)
unless Engine.instance.validate(socket_id)
return context.channel.write(DefaultHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::FORBIDDEN)).addListener(ChannelFutureListener.CLOSE)
@@ -133,6 +291,7 @@ def handle_ws_handshake(context, request, socket_id, server_id, session_id)
@socket_id = socket_id
Engine.instance.add_socket(@socket_id, self)
@channel.write(TextWebSocketFrame.new("o\n"))
+ @type = :ws
end
def handle_ws(context, frame)
@@ -149,15 +308,31 @@ def handle_ws(context, frame)
end
end
- def publish(message)
+ def encode_message(message)
message = [message.to_s] unless message.kind_of?(Array)
- message = "a#{MultiJson.encode(message)}\n"
- @channel.write(TextWebSocketFrame.new(message))
+ "a#{MultiJson.encode(message)}\n"
+ end
+
+ def call(message)
+ case @type
+ when :ws
+ @channel.write(TextWebSocketFrame.new(encode_message(message)))
+ when :xhr_stream
+ @channel.write(ChannelBuffers.wrapped_buffer(encode_message(message).to_java_bytes))
+ when :eventsource
+ message = "data: #{encode_message(message)}\r\n\r\n"
+ @channel.write(ChannelBuffers.wrapped_buffer(message.to_java_bytes))
+ when :htmlfile
+ message ="<script>\np('#{encode_message(message).chop}');\n</script>\r\n"
+ @channel.write(ChannelBuffers.wrapped_buffer(message.to_java_bytes))
+ when :xhr
+ @channel.write(ChannelBuffers.wrapped_buffer(encode_message(message).to_java_bytes)).addListener(ChannelFutureListener.CLOSE)
+ end
end
def close(reason)
message = "c[3000,\"#{reason}\"]"
- @channel.write(TextWebSocketFrame.new(message))
+ @channel.write(TextWebSocketFrame.new(message)).addListener(ChannelFutureListener.CLOSE)
end
def handle_ws_text_frame(context, frame)
View
@@ -4,7 +4,9 @@
<script src="http://cdn.sockjs.org/sockjs-0.2.min.js"></script>
<link rel="stylesheet" href="http://localhost:4000/authorize/app/client">
<script>
- var sock = new SockJS('http://localhost:4000/appclient', null, {debug:true, devel:true});
+ // ['websocket', 'xdr-streaming', 'xhr-streaming', 'iframe-eventsource', 'iframe-htmlfile', 'xdr-polling', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling']
+ // ['websocket', 'xhr-streaming', 'iframe-eventsource', 'iframe-htmlfile', 'xhr-polling', 'iframe-xhr-polling']
+ var sock = new SockJS('http://localhost:4000/appclient', null, {debug:true, devel:true, protocols_whitelist: ['websocket', 'xhr-streaming', 'iframe-eventsource', 'iframe-htmlfile', 'xhr-polling', 'iframe-xhr-polling']});
sock.onopen = function () {
console.log('open');
};

0 comments on commit d8b0835

Please sign in to comment.