volt-sockjs 0.3.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENCE +19 -0
- data/README.textile +118 -0
- data/lib/meta-state.rb +151 -0
- data/lib/rack/sockjs.rb +173 -0
- data/lib/sockjs.rb +59 -0
- data/lib/sockjs/callbacks.rb +19 -0
- data/lib/sockjs/connection.rb +45 -0
- data/lib/sockjs/delayed-response-body.rb +99 -0
- data/lib/sockjs/duck-punch-rack-mount.rb +12 -0
- data/lib/sockjs/duck-punch-thin-response.rb +15 -0
- data/lib/sockjs/examples/protocol_conformance_test.rb +73 -0
- data/lib/sockjs/faye.rb +18 -0
- data/lib/sockjs/protocol.rb +97 -0
- data/lib/sockjs/servers/request.rb +137 -0
- data/lib/sockjs/servers/response.rb +170 -0
- data/lib/sockjs/session.rb +492 -0
- data/lib/sockjs/transport.rb +357 -0
- data/lib/sockjs/transports/eventsource.rb +30 -0
- data/lib/sockjs/transports/htmlfile.rb +73 -0
- data/lib/sockjs/transports/iframe.rb +68 -0
- data/lib/sockjs/transports/info.rb +48 -0
- data/lib/sockjs/transports/jsonp.rb +84 -0
- data/lib/sockjs/transports/websocket.rb +198 -0
- data/lib/sockjs/transports/welcome_screen.rb +17 -0
- data/lib/sockjs/transports/xhr.rb +75 -0
- data/lib/sockjs/version.rb +13 -0
- data/spec/sockjs/protocol_spec.rb +49 -0
- data/spec/sockjs/session_spec.rb +51 -0
- data/spec/sockjs/transport_spec.rb +73 -0
- data/spec/sockjs/transports/eventsource_spec.rb +56 -0
- data/spec/sockjs/transports/htmlfile_spec.rb +72 -0
- data/spec/sockjs/transports/iframe_spec.rb +66 -0
- data/spec/sockjs/transports/jsonp_spec.rb +252 -0
- data/spec/sockjs/transports/websocket_spec.rb +101 -0
- data/spec/sockjs/transports/welcome_screen_spec.rb +36 -0
- data/spec/sockjs/transports/xhr_spec.rb +314 -0
- data/spec/sockjs/version_spec.rb +18 -0
- data/spec/sockjs_spec.rb +8 -0
- data/spec/spec_helper.rb +121 -0
- data/spec/support/async-test.rb +42 -0
- 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
|