diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6147d1d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +bundler_args: --without development +language: ruby +env: COVERAGE=on +script: bundle exec rspec spec +rvm: + - 1.9.2 + - 1.9.3 diff --git a/README.textile b/README.textile index ba21d8c..3ddb6b2 100644 --- a/README.textile +++ b/README.textile @@ -116,3 +116,4 @@ h1. Links * "SockJS: WebSocket emulation":http://www.rabbitmq.com/blog/2011/09/13/sockjs-websocket-emulation * "SockJS: web messaging ain't easy":http://www.rabbitmq.com/blog/2011/08/22/sockjs-web-messaging-aint-easy * "PubSubHuddle Realtime Web talk":http://www.rabbitmq.com/blog/2011/09/26/pubsubhuddle-realtime-web-talk + diff --git a/lib/rack/sockjs.rb b/lib/rack/sockjs.rb index 444fc29..c51efbb 100644 --- a/lib/rack/sockjs.rb +++ b/lib/rack/sockjs.rb @@ -47,7 +47,7 @@ module Rack class SockJS SERVER_SESSION_REGEXP = %r{/([^/]*)/([^/]*)} DEFAULT_OPTIONS = { - :sockjs_url => "http://cdn.sockjs.org/sockjs-#{::SockJS::PROTOCOL_VERSION}.min.js" + :sockjs_url => "http://cdn.sockjs.org/sockjs-#{::SockJS::PROTOCOL_VERSION_STRING}.min.js" } diff --git a/lib/sockjs/faye.rb b/lib/sockjs/faye.rb index 7bbec92..0d3f87e 100644 --- a/lib/sockjs/faye.rb +++ b/lib/sockjs/faye.rb @@ -5,11 +5,4 @@ class Thin::Request WEBSOCKET_RECEIVE_CALLBACK = 'websocket.receive_callback'.freeze GET = 'GET'.freeze - - def websocket? - @env['REQUEST_METHOD'] == GET and - @env['HTTP_CONNECTION'] and - @env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and - ['WebSocket', 'websocket'].include?(@env['HTTP_UPGRADE']) - end end diff --git a/lib/sockjs/protocol.rb b/lib/sockjs/protocol.rb index 72a2d91..7e0b83c 100644 --- a/lib/sockjs/protocol.rb +++ b/lib/sockjs/protocol.rb @@ -1,6 +1,6 @@ # encoding: utf-8 -require "json" +require 'multi_json' module SockJS module Protocol @@ -30,7 +30,7 @@ class Frame # with Unicode surrogates 0xD800-0xDFFF: # http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates def escape(string) - string.gsub(CHARS_TO_BE_ESCAPED) do |match| + string.encode("UTF-8", :invalid => :replace, :undef => :replace).gsub(CHARS_TO_BE_ESCAPED) do |match| '\u%04x' % (match.ord) end end @@ -76,7 +76,7 @@ def initialize(array) attr_reader :array def to_s - "a#{escape(array.to_json)}" + "a#{escape(MultiJson.dump(array))}" end end diff --git a/lib/sockjs/servers/response.rb b/lib/sockjs/servers/response.rb index e4fab45..d457c5b 100644 --- a/lib/sockjs/servers/response.rb +++ b/lib/sockjs/servers/response.rb @@ -134,6 +134,10 @@ def set_no_cache self.set_header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") end + def set_allow_options_headers + self.set_header("Access-Control-Allow-Headers", "Content-Type") + end + CONTENT_TYPES ||= { plain: "text/plain; charset=UTF-8", html: "text/html; charset=UTF-8", diff --git a/lib/sockjs/session.rb b/lib/sockjs/session.rb index 56a15dd..8d0fb70 100644 --- a/lib/sockjs/session.rb +++ b/lib/sockjs/session.rb @@ -15,7 +15,7 @@ def initialize(response, transport) #Close the *response* not the *session* def disconnect - @response.finish + @response.finish if @response.respond_to?(:finish) end def heartbeat @@ -67,7 +67,7 @@ def send(*messages) @outbox += messages end - def close(status = nil, message = nil) + def close(status = 1002, message = "Connection closed") @close_status = status @close_message = message transition_to(:closed) @@ -102,7 +102,7 @@ def send_heartbeat @consumer.heartbeat end - def close(status = nil, message = nil) + def close(status = 1002, message = "Connection closed") @close_status = status @close_message = message @consumer.closing(@close_status, @close_message) @@ -117,6 +117,7 @@ def on_enter @close_message ||= "Go away!" clear_all_timers set_close_timer + on_close end def attach_consumer(response, transport) @@ -188,6 +189,9 @@ def opened def after_app_run end + def on_close + end + attr_accessor :disconnect_delay, :interval attr_reader :transport, :response, :outbox, :closing_frame, :data @@ -215,12 +219,6 @@ def alive? !!@alive end - #XXX This is probably important - need to examine this case - def on_close - SockJS.debug "The connection has been closed on the client side (current status: #{@status})." - close_session(1002, "Connection interrupted") - end - def max_permitted_content_length @max_permitted_content_length ||= ($DEBUG ? 4096 : 128_000) end @@ -230,9 +228,9 @@ def parse_json(data) return [] end - JSON.parse("[#{data}]")[0] - rescue JSON::ParserError => error - raise SockJS::InvalidJSON.new(500, "Broken JSON encoding.") + MultiJson.load("[#{data}]")[0] + rescue MultiJson::LoadError => error + raise SockJS::InvalidJSON.new(500, "Broken JSON encoding: #{error.message}") end #Timers: diff --git a/lib/sockjs/transport.rb b/lib/sockjs/transport.rb index 99f1cca..d13fada 100644 --- a/lib/sockjs/transport.rb +++ b/lib/sockjs/transport.rb @@ -127,7 +127,11 @@ def format_frame(session, payload) "#{payload}\n" end + attr_reader :remote_addr, :http_origin + def call(env) + @remote_addr = env["REMOTE_ADDR"] + @http_origin = env["HTTP_ORIGIN"] SockJS.debug "Request for #{self.class}: #{env["REQUEST_METHOD"]}/#{env["PATH_INFO"]}" request = ::SockJS::Request.new(env) EM.next_tick do diff --git a/lib/sockjs/transports/htmlfile.rb b/lib/sockjs/transports/htmlfile.rb index 987fd76..db8b449 100644 --- a/lib/sockjs/transports/htmlfile.rb +++ b/lib/sockjs/transports/htmlfile.rb @@ -1,6 +1,6 @@ # encoding: utf-8 -require "json" +require "multi_json" require "sockjs/transport" module SockJS @@ -62,7 +62,7 @@ def handle_http_error(request, error) def format_frame(response, frame) raise TypeError.new("Payload must not be nil!") if frame.nil? - "\r\n" + "\r\n" end end end diff --git a/lib/sockjs/transports/info.rb b/lib/sockjs/transports/info.rb index 6a0e798..aba5200 100644 --- a/lib/sockjs/transports/info.rb +++ b/lib/sockjs/transports/info.rb @@ -14,7 +14,7 @@ def setup_response(request, response) response.set_access_control(request.origin) response.set_allow_options_post response.set_no_cache - response.write(self.info.to_json) + response.write(MultiJson.dump(self.info)) end def info diff --git a/lib/sockjs/transports/jsonp.rb b/lib/sockjs/transports/jsonp.rb index c1ae0d7..bc3ab5c 100644 --- a/lib/sockjs/transports/jsonp.rb +++ b/lib/sockjs/transports/jsonp.rb @@ -33,7 +33,7 @@ def format_frame(response, payload) # Yes, JSONed twice, there isn't a better way, we must pass # a string back, and the script, will be evaled() by the browser. - "#{response.request.callback}(#{super.chomp.to_json});\r\n" + "#{response.request.callback}(#{MultiJson.dump(super.chomp)});\r\n" end end diff --git a/lib/sockjs/transports/websocket.rb b/lib/sockjs/transports/websocket.rb index 6382232..b678829 100644 --- a/lib/sockjs/transports/websocket.rb +++ b/lib/sockjs/transports/websocket.rb @@ -146,7 +146,7 @@ def heartbeat_frame(response) def extract_message(event) SockJS.debug "Received message event: #{event.data.inspect}" - event.data.to_json + MultiJson.dump(event.data) end def messages_frame(websocket, messages) diff --git a/lib/sockjs/transports/xhr.rb b/lib/sockjs/transports/xhr.rb index d95fa90..ef7834f 100644 --- a/lib/sockjs/transports/xhr.rb +++ b/lib/sockjs/transports/xhr.rb @@ -13,6 +13,7 @@ def setup_response(request, response) response.set_cache_control response.set_access_control(request.origin) response.set_session_id(request.session_id) + response.set_allow_options_headers end end diff --git a/lib/sockjs/version.rb b/lib/sockjs/version.rb index f4cf996..a920704 100644 --- a/lib/sockjs/version.rb +++ b/lib/sockjs/version.rb @@ -7,7 +7,7 @@ module SockJS PROTOCOL_VERSION_STRING = PROTOCOL_VERSION.join(".") # Patch version of the gem. - PATCH_VERSION = [] + PATCH_VERSION = ['beta', 3] GEM_VERSION = (PROTOCOL_VERSION + PATCH_VERSION).join(".") end diff --git a/sockjs.gemspec b/sockjs.gemspec index 30e6625..e5ef348 100755 --- a/sockjs.gemspec +++ b/sockjs.gemspec @@ -26,6 +26,15 @@ Gem::Specification.new do |s| s.add_dependency "faye-websocket", "~> 0.4.3" s.add_dependency "regin", "~> 0.3.8" s.add_dependency "rack-mount", "~> 0.8.3" + s.add_dependency "multi_json", ">=1.3.6" + + begin + if !defined?(JRUBY_VERSION) + s.add_dependency("oj", ">= 2.0.12") + end + rescue + p "Could not detect ruby version" + end # Files s.files = %w{ diff --git a/spec/sockjs/protocol_spec.rb b/spec/sockjs/protocol_spec.rb index 96f2197..016ac3c 100755 --- a/spec/sockjs/protocol_spec.rb +++ b/spec/sockjs/protocol_spec.rb @@ -24,6 +24,16 @@ SockJS::Protocol::ArrayFrame.new([1, 2, 3]).to_s.should eql("a[1,2,3]") SockJS::Protocol::ArrayFrame.new(["tests"]).to_s.should eql('a["tests"]') end + + it "should not blow up if passed invalid utf-8" do + expect { + SockJS::Protocol::ArrayFrame.new(["tests\255"]) + }.to_not raise_error + end + + it "should still handle utf-8 strings though" do + SockJS::Protocol::ArrayFrame.new(["lämpö"]).to_s.should eql('a["lämpö"]') + end end describe "ClosingFrame" do