sockjs 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/LICENCE +19 -0
  2. data/README.textile +118 -0
  3. data/lib/meta-state.rb +151 -0
  4. data/lib/rack/sockjs.rb +173 -0
  5. data/lib/sockjs.rb +59 -0
  6. data/lib/sockjs/callbacks.rb +19 -0
  7. data/lib/sockjs/connection.rb +45 -0
  8. data/lib/sockjs/delayed-response-body.rb +99 -0
  9. data/lib/sockjs/duck-punch-rack-mount.rb +12 -0
  10. data/lib/sockjs/duck-punch-thin-response.rb +15 -0
  11. data/lib/sockjs/examples/protocol_conformance_test.rb +73 -0
  12. data/lib/sockjs/faye.rb +15 -0
  13. data/lib/sockjs/protocol.rb +97 -0
  14. data/lib/sockjs/servers/request.rb +136 -0
  15. data/lib/sockjs/servers/response.rb +169 -0
  16. data/lib/sockjs/session.rb +388 -0
  17. data/lib/sockjs/transport.rb +354 -0
  18. data/lib/sockjs/transports/eventsource.rb +30 -0
  19. data/lib/sockjs/transports/htmlfile.rb +69 -0
  20. data/lib/sockjs/transports/iframe.rb +68 -0
  21. data/lib/sockjs/transports/info.rb +48 -0
  22. data/lib/sockjs/transports/jsonp.rb +84 -0
  23. data/lib/sockjs/transports/websocket.rb +166 -0
  24. data/lib/sockjs/transports/welcome_screen.rb +17 -0
  25. data/lib/sockjs/transports/xhr.rb +75 -0
  26. data/lib/sockjs/version.rb +13 -0
  27. data/spec/sockjs/protocol_spec.rb +49 -0
  28. data/spec/sockjs/session_spec.rb +51 -0
  29. data/spec/sockjs/transport_spec.rb +73 -0
  30. data/spec/sockjs/transports/eventsource_spec.rb +56 -0
  31. data/spec/sockjs/transports/htmlfile_spec.rb +72 -0
  32. data/spec/sockjs/transports/iframe_spec.rb +66 -0
  33. data/spec/sockjs/transports/jsonp_spec.rb +252 -0
  34. data/spec/sockjs/transports/websocket_spec.rb +101 -0
  35. data/spec/sockjs/transports/welcome_screen_spec.rb +36 -0
  36. data/spec/sockjs/transports/xhr_spec.rb +314 -0
  37. data/spec/sockjs/version_spec.rb +18 -0
  38. data/spec/sockjs_spec.rb +8 -0
  39. data/spec/spec_helper.rb +121 -0
  40. data/spec/support/async-test.rb +42 -0
  41. metadata +171 -0
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+
3
+ require "digest/md5"
4
+ require "sockjs/transport"
5
+
6
+ module SockJS
7
+ module Transports
8
+ class IFrame < Endpoint
9
+ register 'GET', %r{/iframe[^/]*[.]html}
10
+
11
+ BODY = <<-EOB.freeze
12
+ <!DOCTYPE html>
13
+ <html>
14
+ <head>
15
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
16
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
17
+ <script>
18
+ document.domain = document.domain;
19
+ _sockjs_onload = function(){SockJS.bootstrap_iframe();};
20
+ </script>
21
+ <script src="{{ sockjs_url }}"></script>
22
+ </head>
23
+ <body>
24
+ <h2>Don't panic!</h2>
25
+ <p>This is a SockJS hidden iframe. It's used for cross domain magic.</p>
26
+ </body>
27
+ </html>
28
+ EOB
29
+
30
+ def setup_response(request, response)
31
+ response.status = 200
32
+ response.set_header("ETag", self.etag)
33
+ response.set_cache_control
34
+ response
35
+ end
36
+
37
+ def body
38
+ @body ||= BODY.gsub("{{ sockjs_url }}", options[:sockjs_url])
39
+ end
40
+
41
+ def digest
42
+ @digest ||= Digest::MD5.new
43
+ end
44
+
45
+ def etag
46
+ '"' + digest.hexdigest(body) + '"'
47
+ end
48
+
49
+ # Handler.
50
+ def handle_request(request)
51
+ if request.fresh?(etag)
52
+ SockJS.debug "Content hasn't been modified."
53
+ response = build_response(request)
54
+ response.status = 304
55
+ response.finish
56
+ return response
57
+ else
58
+ SockJS.debug "Deferring to Transport"
59
+ response = build_response(request)
60
+ response.set_content_type(:html)
61
+ response.write(body)
62
+ response.finish
63
+ return response
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ require "sockjs/transport"
4
+
5
+ module SockJS
6
+ module Transports
7
+ class Info
8
+ class Get < Endpoint
9
+ register 'GET', "info"
10
+ # Handler.
11
+ def setup_response(request, response)
12
+ response.status = 200
13
+ response.set_content_type(:json)
14
+ response.set_access_control(request.origin)
15
+ response.set_allow_options_post
16
+ response.set_no_cache
17
+ response.write(self.info.to_json)
18
+ end
19
+
20
+ def info
21
+ {
22
+ websocket: @options[:websocket],
23
+ origins: ["*:*"], # As specified by the spec, currently ignored.
24
+ cookie_needed: @options[:cookie_needed],
25
+ entropy: self.entropy
26
+ }
27
+ end
28
+
29
+ def entropy
30
+ rand(1 << 32)
31
+ end
32
+ end
33
+
34
+ class Options < Endpoint
35
+ register 'OPTIONS', 'info'
36
+
37
+ def setup_response(request, response)
38
+ response.status = 204
39
+ response.set_allow_options_get
40
+ response.set_cache_control
41
+ response.set_access_control(request.origin)
42
+ response.set_session_id(request.session_id)
43
+ response.write_head
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+
3
+ require "sockjs/transport"
4
+
5
+ module SockJS
6
+ module Transports
7
+ class JSONP < PollingConsumingTransport
8
+ register 'GET', 'jsonp'
9
+
10
+ def error_content_type
11
+ :html
12
+ end
13
+
14
+ def setup_response(request, response)
15
+ response.status = 200
16
+ response.set_content_type(:javascript)
17
+ response.set_access_control(request.origin)
18
+ response.set_no_cache
19
+ response.set_session_id(request.session_id)
20
+ return response
21
+ end
22
+
23
+ def get_session(response)
24
+ if response.request.callback
25
+ super
26
+ else
27
+ raise HttpError.new(500, '"callback" parameter required')
28
+ end
29
+ end
30
+
31
+ def format_frame(response, payload)
32
+ raise TypeError.new("Payload must not be nil!") if payload.nil?
33
+
34
+ # Yes, JSONed twice, there isn't a better way, we must pass
35
+ # a string back, and the script, will be evaled() by the browser.
36
+ "#{response.request.callback}(#{super.chomp.to_json});\r\n"
37
+ end
38
+ end
39
+
40
+ class JSONPSend < DeliveryTransport
41
+ register 'POST', 'jsonp_send'
42
+
43
+ # Handler.
44
+ def extract_message(request)
45
+ if request.content_type == "application/x-www-form-urlencoded"
46
+ raw_data = request.data.read || empty_payload
47
+ begin
48
+ data = Hash[URI.decode_www_form(raw_data)]
49
+
50
+ data = data.fetch("d")
51
+ rescue KeyError
52
+ empty_payload
53
+ end
54
+ else
55
+ data = request.data.read
56
+ end
57
+
58
+ if data.nil? or data.empty?
59
+ empty_payload
60
+ end
61
+ data
62
+ end
63
+
64
+ def setup_response(request, response)
65
+ response.status = 200
66
+ response.set_content_type(:plain)
67
+ response.set_session_id(request.session_id)
68
+ end
69
+
70
+ def successful_response(response)
71
+ response.write("ok")
72
+ response.finish
73
+ end
74
+
75
+ def error_content_type
76
+ :html
77
+ end
78
+
79
+ def empty_payload
80
+ raise SockJS::HttpError.new(500, "Payload expected.")
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,166 @@
1
+ # encoding: utf-8
2
+
3
+ require "forwardable"
4
+ require "sockjs/faye"
5
+ require "sockjs/transport"
6
+
7
+ # Raw WebSocket url: /websocket
8
+ # -------------------------------
9
+ #
10
+ # SockJS protocol defines a bit of higher level framing. This is okay
11
+ # when the browser using SockJS-client establishes the connection, but
12
+ # it's not really appropriate when the connection is being established
13
+ # from another program. Although SockJS focuses on server-browser
14
+ # communication, it should be straightforward to connect to SockJS
15
+ # from command line or some any programming language.
16
+ #
17
+ # In order to make writing command-line clients easier, we define this
18
+ # `/websocket` entry point. This entry point is special and doesn't
19
+ # use any additional custom framing, no open frame, no
20
+ # heartbeats. Only raw WebSocket protocol.
21
+
22
+ module SockJS
23
+ module Transports
24
+ module WSDebuggingMixin
25
+ def send_data(*args)
26
+ if args.length == 1
27
+ data = args.first
28
+ else
29
+ data = fix_buggy_input(*args)
30
+ end
31
+
32
+ SockJS.debug "WS#send #{data.inspect}"
33
+
34
+ super(data)
35
+ end
36
+
37
+ def fix_buggy_input(*args)
38
+ data = 'c[3000,"Go away!"]'
39
+ SockJS.debug "[ERROR] Incorrect input: #{args.inspect}, changing to #{data} for now"
40
+ return data
41
+ end
42
+
43
+ def close(*args)
44
+ SockJS.debug "WS#close(#{args.inspect[1..-2]})"
45
+ super(*args)
46
+ end
47
+ end
48
+
49
+ class WebSocket < ConsumingTransport
50
+ register 'GET', 'websocket'
51
+
52
+ def disabled?
53
+ !options[:websocket]
54
+ end
55
+
56
+ def session_key(ws)
57
+ ws.object_id.to_s
58
+ end
59
+
60
+ def handle_request(request)
61
+ if not @options[:websocket]
62
+ raise HttpError.new(404, "WebSockets Are Disabled")
63
+ elsif request.env["HTTP_UPGRADE"].to_s.downcase != "websocket"
64
+ raise HttpError.new(400, 'Can "Upgrade" only to "WebSocket".')
65
+ elsif not ["Upgrade", "keep-alive, Upgrade"].include?(request.env["HTTP_CONNECTION"])
66
+ raise HttpError.new(400, '"Connection" must be "Upgrade".')
67
+ end
68
+
69
+ super
70
+ end
71
+
72
+ def build_response(request)
73
+ SockJS.debug "Upgrading to WebSockets ..."
74
+
75
+ web_socket = Faye::WebSocket.new(request.env)
76
+
77
+ web_socket.extend(WSDebuggingMixin)
78
+
79
+ return web_socket
80
+ end
81
+
82
+ def build_error_response(request)
83
+ response = response_class.new(request)
84
+ end
85
+
86
+ def process_session(session, web_socket)
87
+ #XXX Facade around websocket?
88
+
89
+ web_socket.onopen = lambda do |event|
90
+ begin
91
+ session.attach_consumer(web_socket, self)
92
+ rescue Object => ex
93
+ SockJS::debug "Error opening (#{event.inspect[0..40]}) websocket: #{ex.inspect}"
94
+ end
95
+ end
96
+
97
+ web_socket.onmessage = lambda do |event|
98
+ begin
99
+ session.receive_message(extract_message(event))
100
+ rescue Object => ex
101
+ SockJS::debug "Error receiving message on websocket (#{event.inspect[0..40]}): #{ex.inspect}"
102
+ web_socket.close
103
+ end
104
+ end
105
+
106
+ web_socket.onclose = lambda do |event|
107
+ begin
108
+ session.close
109
+ rescue Object => ex
110
+ SockJS::debug "Error closing websocket (#{event.inspect[0..40]}): #{ex.inspect}"
111
+ end
112
+ end
113
+ end
114
+
115
+ def format_frame(response, frame)
116
+ frame.to_s
117
+ end
118
+
119
+ def send_data(web_socket, data)
120
+ web_socket.send(data)
121
+ return data.length
122
+ end
123
+
124
+ def finish_response(web_socket)
125
+ web_socket.close
126
+ end
127
+
128
+ def extract_message(event)
129
+ SockJS.debug "Received message event: #{event.data.inspect}"
130
+ event.data
131
+ end
132
+ end
133
+
134
+ class RawWebSocket < WebSocket
135
+ register 'GET', 'websocket'
136
+
137
+ def self.routing_prefix
138
+ "/" + self.prefix
139
+ end
140
+
141
+ def opening_frame(response)
142
+ end
143
+
144
+ def heartbeat_frame(response)
145
+ end
146
+
147
+ def extract_message(event)
148
+ SockJS.debug "Received message event: #{event.data.inspect}"
149
+ event.data.to_json
150
+ end
151
+
152
+ def messages_frame(websocket, messages)
153
+ messages.inject(0) do |sent_count, data|
154
+ send_data(websocket, data)
155
+ sent_count + data.length
156
+ end
157
+ rescue Object => ex
158
+ SockJS::debug "Error delivering messages to raw websocket: #{messages.inspect} #{ex.inspect}"
159
+ end
160
+
161
+ def closing_frame(response, status, message)
162
+ finish_response(response)
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ require "sockjs/transport"
4
+
5
+ module SockJS
6
+ module Transports
7
+ class WelcomeScreen < Endpoint
8
+ register 'GET', ''
9
+
10
+ def setup_response(request, response)
11
+ response.set_content_type(:plain)
12
+ response.status = 200
13
+ response.write("Welcome to SockJS!\n")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ require "sockjs/transport"
4
+
5
+ module SockJS
6
+ module Transports
7
+ class XHROptions < SessionEndpoint
8
+ register 'OPTIONS', 'xhr'
9
+
10
+ def setup_response(request, response)
11
+ response.status = 204
12
+ response.set_allow_options_post
13
+ response.set_cache_control
14
+ response.set_access_control(request.origin)
15
+ response.set_session_id(request.session_id)
16
+ end
17
+ end
18
+
19
+ class XHRSendOptions < XHROptions
20
+ register 'OPTIONS', 'xhr_send'
21
+ end
22
+
23
+ class XHRStreamingOptions < XHROptions
24
+ register 'OPTIONS', 'xhr_streaming'
25
+ end
26
+
27
+ class XHRSendPost < DeliveryTransport
28
+ register 'POST', 'xhr_send'
29
+
30
+ def setup_response(request, response)
31
+ response.status = 204
32
+
33
+ response.set_content_type(:plain)
34
+ response.set_access_control(request.origin)
35
+ response.set_session_id(request.session_id)
36
+ response
37
+ end
38
+ end
39
+
40
+ class XHRPost < PollingConsumingTransport
41
+ register 'POST', 'xhr'
42
+
43
+ def setup_response(request, response)
44
+ response.status = 200
45
+ response.set_content_type(:javascript)
46
+ response.set_access_control(request.origin)
47
+ response.set_session_id(request.session_id)
48
+ response
49
+ end
50
+ end
51
+
52
+ class XHRStreamingPost < ConsumingTransport
53
+ PREAMBLE ||= "h" * 2048 + "\n"
54
+
55
+ register 'POST', 'xhr_streaming'
56
+
57
+ def setup_response(request, response)
58
+ response.status = 200
59
+ response.set_content_type(:javascript)
60
+ response.set_access_control(request.origin)
61
+ response.set_session_id(request.session_id)
62
+ end
63
+
64
+ def response_beginning(response)
65
+ response.write_head
66
+ response.write(PREAMBLE)
67
+ end
68
+
69
+ def handle_session_unavailable(error, response)
70
+ response.write(PREAMBLE)
71
+ super(error, response)
72
+ end
73
+ end
74
+ end
75
+ end