volt-sockjs 0.3.4.4

Sign up to get free protection for your applications and to get access to all the features.
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