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.
- 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 +15 -0
- data/lib/sockjs/protocol.rb +97 -0
- data/lib/sockjs/servers/request.rb +136 -0
- data/lib/sockjs/servers/response.rb +169 -0
- data/lib/sockjs/session.rb +388 -0
- data/lib/sockjs/transport.rb +354 -0
- data/lib/sockjs/transports/eventsource.rb +30 -0
- data/lib/sockjs/transports/htmlfile.rb +69 -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 +166 -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 +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
|