volt-sockjs 0.3.4.4

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 (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENCE +19 -0
  3. data/README.textile +118 -0
  4. data/lib/meta-state.rb +151 -0
  5. data/lib/rack/sockjs.rb +173 -0
  6. data/lib/sockjs.rb +59 -0
  7. data/lib/sockjs/callbacks.rb +19 -0
  8. data/lib/sockjs/connection.rb +45 -0
  9. data/lib/sockjs/delayed-response-body.rb +99 -0
  10. data/lib/sockjs/duck-punch-rack-mount.rb +12 -0
  11. data/lib/sockjs/duck-punch-thin-response.rb +15 -0
  12. data/lib/sockjs/examples/protocol_conformance_test.rb +73 -0
  13. data/lib/sockjs/faye.rb +18 -0
  14. data/lib/sockjs/protocol.rb +97 -0
  15. data/lib/sockjs/servers/request.rb +137 -0
  16. data/lib/sockjs/servers/response.rb +170 -0
  17. data/lib/sockjs/session.rb +492 -0
  18. data/lib/sockjs/transport.rb +357 -0
  19. data/lib/sockjs/transports/eventsource.rb +30 -0
  20. data/lib/sockjs/transports/htmlfile.rb +73 -0
  21. data/lib/sockjs/transports/iframe.rb +68 -0
  22. data/lib/sockjs/transports/info.rb +48 -0
  23. data/lib/sockjs/transports/jsonp.rb +84 -0
  24. data/lib/sockjs/transports/websocket.rb +198 -0
  25. data/lib/sockjs/transports/welcome_screen.rb +17 -0
  26. data/lib/sockjs/transports/xhr.rb +75 -0
  27. data/lib/sockjs/version.rb +13 -0
  28. data/spec/sockjs/protocol_spec.rb +49 -0
  29. data/spec/sockjs/session_spec.rb +51 -0
  30. data/spec/sockjs/transport_spec.rb +73 -0
  31. data/spec/sockjs/transports/eventsource_spec.rb +56 -0
  32. data/spec/sockjs/transports/htmlfile_spec.rb +72 -0
  33. data/spec/sockjs/transports/iframe_spec.rb +66 -0
  34. data/spec/sockjs/transports/jsonp_spec.rb +252 -0
  35. data/spec/sockjs/transports/websocket_spec.rb +101 -0
  36. data/spec/sockjs/transports/welcome_screen_spec.rb +36 -0
  37. data/spec/sockjs/transports/xhr_spec.rb +314 -0
  38. data/spec/sockjs/version_spec.rb +18 -0
  39. data/spec/sockjs_spec.rb +8 -0
  40. data/spec/spec_helper.rb +121 -0
  41. data/spec/support/async-test.rb +42 -0
  42. metadata +154 -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
+ SockJS.debug("body: #{@body.inspect}")
32
+ response.status = 200
33
+ response.set_header("ETag", self.etag)
34
+ response.set_cache_control
35
+ response
36
+ end
37
+
38
+ def body
39
+ @body ||= BODY.gsub("{{ sockjs_url }}", options[:sockjs_url])
40
+ end
41
+
42
+ def digest
43
+ @digest ||= Digest::MD5.new
44
+ end
45
+
46
+ def etag
47
+ '"' + digest.hexdigest(body) + '"'
48
+ end
49
+
50
+ # Handler.
51
+ def handle_request(request)
52
+ if request.fresh?(etag)
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,198 @@
1
+
2
+
3
+ def activate
4
+ end# encoding: utf-8
5
+
6
+ require "forwardable"
7
+ require "sockjs/faye"
8
+ require "sockjs/transport"
9
+
10
+ # Raw WebSocket url: /websocket
11
+ # -------------------------------
12
+ #
13
+ # SockJS protocol defines a bit of higher level framing. This is okay
14
+ # when the browser using SockJS-client establishes the connection, but
15
+ # it's not really appropriate when the connection is being established
16
+ # from another program. Although SockJS focuses on server-browser
17
+ # communication, it should be straightforward to connect to SockJS
18
+ # from command line or some any programming language.
19
+ #
20
+ # In order to make writing command-line clients easier, we define this
21
+ # `/websocket` entry point. This entry point is special and doesn't
22
+ # use any additional custom framing, no open frame, no
23
+ # heartbeats. Only raw WebSocket protocol.
24
+
25
+ module SockJS
26
+ module Transports
27
+ module WSDebuggingMixin
28
+ def send_data(*args)
29
+ if args.length == 1
30
+ data = args.first
31
+ else
32
+ data = fix_buggy_input(*args)
33
+ end
34
+
35
+ SockJS.debug "WS#send #{data.inspect}"
36
+
37
+ super(data)
38
+ end
39
+
40
+ def fix_buggy_input(*args)
41
+ data = 'c[3000,"Go away!"]'
42
+ SockJS.debug "[ERROR] Incorrect input: #{args.inspect}, changing to #{data} for now"
43
+ return data
44
+ end
45
+
46
+ def close(*args)
47
+ SockJS.debug "WS#close(#{args.inspect[1..-2]})"
48
+ super(*args)
49
+ end
50
+ end
51
+
52
+ class WebSocket < ConsumingTransport
53
+ register 'GET', 'websocket'
54
+
55
+ def disabled?
56
+ !options[:websocket]
57
+ end
58
+
59
+ def session_key(ws)
60
+ ws.object_id.to_s
61
+ end
62
+
63
+ def handle_request(request)
64
+ if not @options[:websocket]
65
+ raise HttpError.new(404, "WebSockets Are Disabled")
66
+ elsif request.env["HTTP_UPGRADE"].to_s.downcase != "websocket"
67
+ SockJS.debug("Worng headers! HTTP_UPGRADE = #{request.env["HTTP_UPGRADE"].to_s}")
68
+ raise HttpError.new(400, 'Can "Upgrade" only to "WebSocket".')
69
+ elsif not ["Upgrade", "keep-alive, Upgrade"].include?(request.env["HTTP_CONNECTION"])
70
+ SockJS.debug("Worng headers! HTTP_CONNECTION = #{request.env["HTTP_CONNECTION"].to_s}")
71
+ raise HttpError.new(400, '"Connection" must be "Upgrade".')
72
+ end
73
+
74
+ super
75
+ end
76
+
77
+ def build_response(request)
78
+ SockJS.debug "Upgrading to WebSockets ..."
79
+
80
+ web_socket = Faye::WebSocket.new(request.env)
81
+
82
+ web_socket.extend(WSDebuggingMixin)
83
+
84
+ return web_socket
85
+ end
86
+
87
+ def build_error_response(request)
88
+ response = response_class.new(request)
89
+ end
90
+
91
+ def process_session(session, web_socket)
92
+ #XXX Facade around websocket?
93
+ @session = session
94
+ web_socket.on :open do |event|
95
+ begin
96
+ SockJS.debug "Attaching consumer"
97
+ session.attach_consumer(web_socket, self)
98
+ rescue Object => ex
99
+ SockJS::debug "Error opening (#{event.inspect[0..40]}) websocket: #{ex.inspect}"
100
+ end
101
+ end
102
+
103
+ web_socket.on :message do |event|
104
+ begin
105
+ session.receive_message(extract_message(event))
106
+ rescue Object => ex
107
+ SockJS::debug "Error receiving message on websocket (#{event.inspect[0..40]}): #{ex.inspect}"
108
+ web_socket.close
109
+ end
110
+ end
111
+
112
+ web_socket.on :close do |event|
113
+ begin
114
+ session.close(1000, "Session finished")
115
+ rescue Object => ex
116
+ SockJS::debug "Error closing websocket (#{event.inspect[0..40]}): #{ex.inspect} \n#{ex.message} \n#{ex.backtrace.join("\n")}"
117
+ end
118
+ end
119
+ end
120
+
121
+ def format_frame(response, frame)
122
+ frame.to_s
123
+ end
124
+
125
+ def send_data(web_socket, data)
126
+ web_socket.send(data)
127
+ return data.length
128
+ end
129
+
130
+ def finish_response(web_socket)
131
+ SockJS.debug "Finishing response"
132
+ web_socket.close
133
+ end
134
+
135
+ def extract_message(event)
136
+ SockJS.debug "Received message event: #{event.data.inspect}"
137
+ event.data
138
+ end
139
+
140
+ def heartbeat_frame(web_socket)
141
+ @pong = true if @pong.nil?
142
+
143
+ #no replay from last connection - susspend session
144
+ if !@pong
145
+ @session.suspend if @session
146
+ end
147
+ @pong = false
148
+ web_socket.ping("ping") do
149
+ SockJS.debug "pong"
150
+ @pong = true
151
+ @session.activate
152
+ end
153
+ super
154
+ end
155
+ end
156
+
157
+ class RawWebSocket < WebSocket
158
+ register 'GET', 'websocket'
159
+
160
+ def handle_request(request)
161
+ ver = request.env["sec-websocket-version"] || ""
162
+ unless ['8', '13'].include?(ver)
163
+ raise HttpError.new(400, 'Only supported WebSocket protocol is RFC 6455.')
164
+ end
165
+
166
+ super
167
+ end
168
+
169
+ def self.routing_prefix
170
+ "/" + self.prefix
171
+ end
172
+
173
+ def opening_frame(response)
174
+ end
175
+
176
+ def heartbeat_frame(response)
177
+ end
178
+
179
+ def extract_message(event)
180
+ SockJS.debug "Received message event: #{event.data.inspect}"
181
+ event.data.to_json
182
+ end
183
+
184
+ def messages_frame(websocket, messages)
185
+ messages.inject(0) do |sent_count, data|
186
+ send_data(websocket, data)
187
+ sent_count + data.length
188
+ end
189
+ rescue Object => ex
190
+ SockJS::debug "Error delivering messages to raw websocket: #{messages.inspect} #{ex.inspect}"
191
+ end
192
+
193
+ def closing_frame(response, status, message)
194
+ finish_response(response)
195
+ end
196
+ end
197
+ end
198
+ 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