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,13 @@
1
+ # encoding: utf-8
2
+
3
+ module SockJS
4
+ # SockJS protocol version.
5
+ PROTOCOL_VERSION = [0, 2, 1]
6
+
7
+ PROTOCOL_VERSION_STRING = PROTOCOL_VERSION.join(".")
8
+
9
+ # Patch version of the gem.
10
+ PATCH_VERSION = []
11
+
12
+ GEM_VERSION = (PROTOCOL_VERSION + PATCH_VERSION).join(".")
13
+ end
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bundle exec rspec
2
+ # encoding: utf-8
3
+
4
+ require "spec_helper"
5
+ require "sockjs/protocol"
6
+
7
+ describe SockJS::Protocol do
8
+ it "OpeningFrame should be 'o'" do
9
+ described_class::OpeningFrame.instance.to_s.should eql("o")
10
+ end
11
+
12
+ it "HeartbeatFrame should be 'h'" do
13
+ described_class::HeartbeatFrame.instance.to_s.should eql("h")
14
+ end
15
+
16
+ describe "ArrayFrame" do
17
+ it "should take only an array as the first argument" do
18
+ expect {
19
+ SockJS::Protocol::ArrayFrame.new(Hash.new)
20
+ }.to raise_error(TypeError)
21
+ end
22
+
23
+ it "should return a valid array frame" do
24
+ SockJS::Protocol::ArrayFrame.new([1, 2, 3]).to_s.should eql("a[1,2,3]")
25
+ SockJS::Protocol::ArrayFrame.new(["tests"]).to_s.should eql('a["tests"]')
26
+ end
27
+ end
28
+
29
+ describe "ClosingFrame" do
30
+ it "should take only integer as the first argument" do
31
+ expect {
32
+ SockJS::Protocol::ClosingFrame.new("2010", "message")
33
+ }.to raise_error(TypeError)
34
+ end
35
+
36
+ it "should take only string as the second argument" do
37
+ expect {
38
+ SockJS::Protocol::ClosingFrame.new(2010, :message)
39
+ }.to raise_error(TypeError)
40
+ end
41
+
42
+ it "should return a valid closing frame" do
43
+ expect {
44
+ frame = SockJS::Protocol::ClosingFrame.new(2010, "message")
45
+ frame.to_s.should eql('c[2010,"message"]')
46
+ }.not_to raise_error
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bundle exec rspec
2
+ # encoding: utf-8
3
+
4
+ require "spec_helper"
5
+
6
+ require "sockjs"
7
+ require "sockjs/session"
8
+ require "sockjs/transports/xhr"
9
+
10
+ #Comment:
11
+ #These specs came to me way to interested in the internals of Session
12
+ #Policy has got to be that any spec that fails because e.g. it wants to know
13
+ #what state Session is in is a fail. Okay to spec that Session should be able
14
+ #to respond to message X after Y, though.
15
+
16
+ describe SockJS::Session do
17
+ around :each do |example|
18
+ EM.run do
19
+ example.run
20
+ EM.stop
21
+ end
22
+ end
23
+
24
+ let :connection do
25
+ SockJS::Connection.new {}
26
+ end
27
+
28
+ let :transport do
29
+ xprt = SockJS::Transports::XHRPost.new(connection, Hash.new)
30
+
31
+ def xprt.send
32
+ end
33
+
34
+ xprt
35
+ end
36
+
37
+ let :request do
38
+ FakeRequest.new.tap do|req|
39
+ req.data = ""
40
+ end
41
+ end
42
+
43
+ let :response do
44
+ transport.response_class.new(request, 200)
45
+ end
46
+
47
+ let :session do
48
+ sess = described_class.new({:open => []})
49
+ sess
50
+ end
51
+ end
@@ -0,0 +1,73 @@
1
+ require "spec_helper"
2
+
3
+ require "sockjs"
4
+ require "sockjs/transport"
5
+
6
+ describe SockJS::Transport do
7
+ describe "CONTENT_TYPES" do
8
+ [:plain, :html, :javascript, :event_stream].each do |type|
9
+ it "should define #{type}" do
10
+ described_class::CONTENT_TYPES[type].should_not be_nil
11
+ end
12
+ end
13
+ end
14
+
15
+ subject do
16
+ described_class.new(Object.new, Hash.new)
17
+ end
18
+
19
+ describe "#disabled?", :pending => "valid?" do
20
+ it "should be false if the current class isn't in disabled_transports" do
21
+ subject.should_not be_disabled
22
+ end
23
+
24
+ it "should be false if the current class is in disabled_transports" do
25
+ subject.options[:disabled_transports] = [subject.class]
26
+ subject.should be_disabled
27
+ end
28
+ end
29
+
30
+ describe "#session_class" do
31
+ it "should be a valid class" do
32
+ subject.session_class.should be_kind_of(Class)
33
+ end
34
+ end
35
+
36
+ describe "#response_class" do
37
+ it "should be a valid class" do
38
+ subject.response_class.should be_kind_of(Class)
39
+ end
40
+ end
41
+
42
+ let :session do
43
+ mock("Session")
44
+ end
45
+
46
+ describe "#format_frame(payload)" do
47
+ it "should fail if payload is nil" do
48
+ expect do
49
+ subject.format_frame(session, nil)
50
+ end.to raise_error(TypeError)
51
+ end
52
+
53
+ it "should return payload followed by \\n otherwise" do
54
+ subject.format_frame(session, "o").should eql("o\n")
55
+ end
56
+ end
57
+
58
+ describe "#response(request, status)" do
59
+ # TODO
60
+ end
61
+
62
+ describe "#respond(request, status, options = Hash.new, &block)" do
63
+ # TODO
64
+ end
65
+
66
+ describe "#error(http_status, content_type, body)" do
67
+ # TODO
68
+ end
69
+
70
+ describe "#get_session(request, response, preamble = nil)" do
71
+ # TODO
72
+ end
73
+ end
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bundle exec rspec
2
+ # encoding: utf-8
3
+
4
+ require "spec_helper"
5
+
6
+ require "sockjs"
7
+ require "sockjs/transports/eventsource"
8
+
9
+ describe SockJS::Transports::EventSource, :type => :transport, :em => true do
10
+ transport_handler_eql "/eventsource", "GET"
11
+
12
+ describe "#handle(request)" do
13
+ let(:request) do
14
+ @request ||= begin
15
+ request = FakeRequest.new
16
+ request.path_info = "/echo/a/b/eventsource"
17
+ request
18
+ end
19
+ end
20
+
21
+ let(:response) do
22
+ def transport.try_timer_if_valid(*)
23
+ end
24
+
25
+ transport.handle(request)
26
+ end
27
+
28
+ it "should respond with HTTP 200" do
29
+ response.status.should eql(200)
30
+ end
31
+
32
+ it "should respond with event stream MIME type" do
33
+ response.headers["Content-Type"].should match("text/event-stream")
34
+ end
35
+
36
+ it "should disable caching" do
37
+ response.headers["Cache-Control"].should eql("no-store, no-cache, must-revalidate, max-age=0")
38
+ end
39
+
40
+ it "should write two empty lines for Opera" do
41
+ response # Run the handler.
42
+
43
+ pending 'We do split("\r\n"), remember?' do
44
+ response.chunks[0].should eql("")
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "#format_frame(payload)" do
50
+ it "should format payload"
51
+ end
52
+
53
+ describe "#escape_selected(*args)" do
54
+ it "should escape given payload"
55
+ end
56
+ end
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bundle exec rspec
2
+ # encoding: utf-8
3
+
4
+ require "spec_helper"
5
+
6
+ require "sockjs"
7
+ require "sockjs/transports/htmlfile"
8
+
9
+ describe SockJS::Transports::HTMLFile, :em => true, :type => :transport do
10
+ transport_handler_eql "/htmlfile", "GET"
11
+
12
+ describe "#handle(request)" do
13
+ let(:request) do
14
+ FakeRequest.new
15
+ end
16
+
17
+ let(:response) do
18
+ def transport.try_timer_if_valid(*)
19
+ end
20
+
21
+ transport.handle(request)
22
+ end
23
+
24
+ context "with callback specified" do
25
+ let(:request) do
26
+ @request ||= FakeRequest.new.tap do |request|
27
+ request.query_string = {"c" => "clbk"}
28
+ request.path_info = '/a/b/htmlfile'
29
+ request.session_key = 'b'
30
+ end
31
+ end
32
+
33
+ it "should respond with HTTP 200" do
34
+ response.status.should eql(200)
35
+ end
36
+
37
+ it "should respond with HTML MIME type" do
38
+ response.headers["Content-Type"].should match("text/html")
39
+ end
40
+
41
+ it "should disable caching" do
42
+ response.headers["Cache-Control"].should eql("no-store, no-cache, must-revalidate, max-age=0")
43
+ end
44
+
45
+ it "should return HTML wrapper in the body" do
46
+ response.chunks.find{|chunk| chunk =~ (/document.domain = document.domain/)}.should_not be_nil
47
+ end
48
+
49
+ it "should have at least 1024 bytes"
50
+ it "should replace {{ callback }} with the actual callback name"
51
+ end
52
+
53
+ context "without callback specified" do
54
+ it "should respond with HTTP 500" do
55
+ response.status.should eql(500)
56
+ end
57
+
58
+ it "should respond with HTML MIME type" do
59
+ response.headers["Content-Type"].should match("text/html")
60
+ end
61
+
62
+ it "should return error message in the body" do
63
+ response # Run the handler.
64
+ response.chunks.last.should match(/"callback" parameter required/)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "#format_frame(payload)" do
70
+ it "should format payload"
71
+ end
72
+ end
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+ require "sockjs"
4
+ require "sockjs/transports/iframe"
5
+
6
+ describe SockJS::Transports::IFrame, :type => :transport do
7
+ describe "#handle(request)" do
8
+ let(:transport) do
9
+ described_class.new(connection, sockjs_url: "http://sock.js/sock.js")
10
+ end
11
+
12
+ let(:response) do
13
+ transport.handle(request)
14
+ end
15
+
16
+ context "If-None-Match header matches ETag of current body" do
17
+ let(:request) do
18
+ @request ||= FakeRequest.new.tap do |request|
19
+ etag = '"af0ca7deb5298aeb946c4f7b96d1501b"'
20
+ request.if_none_match = etag
21
+ request.path_info = "/iframe.html"
22
+ end
23
+ end
24
+
25
+ it "should respond with HTTP 304" do
26
+ response.status.should eql(304)
27
+ end
28
+ end
29
+
30
+ context "If-None-Match header doesn't match ETag of current body" do
31
+ let(:request) do
32
+ @request ||= FakeRequest.new.tap do |request|
33
+ request.path_info = "/iframe.html"
34
+ end
35
+ end
36
+
37
+ it "should respond with HTTP 200" do
38
+ response.status.should eql(200)
39
+ end
40
+
41
+ it "should respond with HTML MIME type" do
42
+ response.headers["Content-Type"].should match("text/html")
43
+ end
44
+
45
+ it "should set ETag header"
46
+
47
+ it "should set cache control to be valid for the next year" do
48
+ time = Time.now + 31536000
49
+
50
+ response.headers["Cache-Control"].should eql("public, max-age=31536000")
51
+ response.headers["Expires"].should eql(time.gmtime.to_s)
52
+ response.headers["Access-Control-Max-Age"].should eql("1000001")
53
+ end
54
+
55
+ it "should return HTML wrapper in the body" do
56
+ response # Run the handler.
57
+ response.chunks.last.should match(/document.domain = document.domain/)
58
+ end
59
+
60
+ it "should set sockjs_url" do
61
+ response # Run the handler.
62
+ response.chunks.last.should match(transport.options[:sockjs_url])
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env bundle exec rspec
2
+ # encoding: utf-8
3
+
4
+ require "spec_helper"
5
+
6
+ require "sockjs"
7
+ require "sockjs/transports/jsonp"
8
+
9
+ describe "JSONP", :em => true, :type => :transport do
10
+ let(:open_request) do
11
+ FakeRequest.new.tap do |request|
12
+ request.path_info = "/jsonp"
13
+ request.query_string = {"c" => "clbk"}
14
+ request.data = "ok"
15
+ request.session_key = "b"
16
+ end
17
+ end
18
+
19
+ describe SockJS::Transports::JSONP do
20
+ transport_handler_eql "/jsonp", "GET"
21
+
22
+ describe "#handle(request)" do
23
+ let(:request) do
24
+ FakeRequest.new.tap do |request|
25
+ request.path_info = "/echo/a/b/jsonp"
26
+ end
27
+ end
28
+
29
+ context "with callback specified" do
30
+ let(:request) do
31
+ FakeRequest.new.tap do |request|
32
+ request.path_info = "/jsonp"
33
+ request.query_string = {"c" => "clbk"}
34
+ request.session_key = "b"
35
+ end
36
+ end
37
+
38
+ context "with a session" do
39
+ let(:prior_transport) do
40
+ described_class.new(connection, {})
41
+ end
42
+
43
+ it "should respond with HTTP 200" do
44
+ response.status.should eql(200)
45
+ end
46
+
47
+ it "should respond with plain text MIME type" do
48
+ response.headers["Content-Type"].should match("application/javascript")
49
+ end
50
+
51
+ it "should respond with a body"
52
+ end
53
+
54
+ context "without any session" do
55
+ it "should respond with HTTP 200" do
56
+ response.status.should eql(200)
57
+ end
58
+
59
+ it "should respond with javascript MIME type" do
60
+ response.headers["Content-Type"].should match("application/javascript")
61
+ end
62
+
63
+ it "should set access control" do
64
+ response.headers["Access-Control-Allow-Origin"].should eql(request.origin)
65
+ response.headers["Access-Control-Allow-Credentials"].should eql("true")
66
+ end
67
+
68
+ it "should disable caching" do
69
+ response.headers["Cache-Control"].should eql("no-store, no-cache, must-revalidate, max-age=0")
70
+ end
71
+
72
+ it "should open a new session"
73
+ end
74
+ end
75
+
76
+ context "without callback specified" do
77
+ it "should respond with HTTP 500" do
78
+ response.status.should eql(500)
79
+ end
80
+
81
+ it "should respond with HTML MIME type" do
82
+ response.headers["Content-Type"].should match("text/html")
83
+ end
84
+
85
+ it "should return error message in the body" do
86
+ response.chunks.last.should match(/"callback" parameter required/)
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "#format_frame(payload)" do
92
+ it "should format payload"
93
+ end
94
+ end
95
+
96
+ describe SockJS::Transports::JSONPSend do
97
+ transport_handler_eql "/jsonp_send", "POST"
98
+
99
+ describe "#handle(request)" do
100
+ let(:request) do
101
+ FakeRequest.new.tap do |request|
102
+ request.path_info = "/a/_/jsonp_send"
103
+ end
104
+ end
105
+
106
+ context "with valid data" do
107
+ context "with application/x-www-form-urlencoded" do
108
+ # TODO: test with invalid data like d=sth, we should get Broken encoding.
109
+ context "with a valid session" do
110
+ before :each do
111
+ session
112
+ end
113
+
114
+ let(:request) do
115
+ FakeRequest.new.tap do |request|
116
+ request.path_info = "/jsonp_send"
117
+ request.session_key = existing_session_key
118
+ request.content_type = "application/x-www-form-urlencoded"
119
+ request.data = "d=%5B%22x%22%5D"
120
+ end
121
+ end
122
+
123
+ it "should respond with HTTP 200" do
124
+ response.status.should eql(200)
125
+ end
126
+
127
+ it "should set session ID" do
128
+ cookie = response.headers["Set-Cookie"]
129
+ cookie.should match("JSESSIONID=#{request.session_id}; path=/")
130
+ end
131
+
132
+ it "should write 'ok' to the body stream" do
133
+ response # Run the handler.
134
+ response.chunks.last.should eql("ok")
135
+ end
136
+ end
137
+
138
+ context "without a valid session" do
139
+ let :session do
140
+ end
141
+
142
+ let(:request) do
143
+ FakeRequest.new.tap do |request|
144
+ request.path_info = "/a/_/jsonp_send"
145
+ request.content_type = "application/x-www-form-urlencoded"
146
+ request.data = "d=sth"
147
+ end
148
+ end
149
+
150
+ it "should respond with HTTP 404" do
151
+ SockJS::debug!
152
+ response.status.should eql(404)
153
+ end
154
+
155
+ it "should respond with plain text MIME type" do
156
+ response.headers["Content-Type"].should match("text/plain")
157
+ end
158
+
159
+ it "should return error message in the body" do
160
+ response # Run the handler.
161
+ response.chunks.last.should match(/Session is not open\!/)
162
+ end
163
+ end
164
+ end
165
+
166
+ context "with any other MIME type" do
167
+ context "with a valid session" do
168
+ before :each do
169
+ session
170
+ end
171
+
172
+ let(:request) do
173
+ FakeRequest.new.tap do |request|
174
+ request.path_info = "/jsonp_send"
175
+ request.data = '["data"]'
176
+ request.session_key = existing_session_key
177
+ end
178
+ end
179
+
180
+ it "should respond with HTTP 200" do
181
+ response.status.should eql(200)
182
+ end
183
+
184
+ it "should set session ID" do
185
+ cookie = response.headers["Set-Cookie"]
186
+ cookie.should match("JSESSIONID=#{request.session_id}; path=/")
187
+ end
188
+
189
+ it "should write 'ok' to the body stream" do
190
+ response # Run the handler.
191
+ response.chunks.last.should eql("ok")
192
+ end
193
+ end
194
+
195
+ context "without a valid session" do
196
+ let :session do
197
+ end
198
+
199
+ let(:request) do
200
+ FakeRequest.new.tap do |request|
201
+ request.path_info = "/a/_/jsonp_send"
202
+ request.data = "data"
203
+ end
204
+ end
205
+
206
+ it "should respond with HTTP 404" do
207
+ response.status.should eql(404)
208
+ end
209
+
210
+ it "should respond with plain text MIME type" do
211
+ response.headers["Content-Type"].should match("text/plain")
212
+ end
213
+
214
+ it "should return error message in the body" do
215
+ response.chunks.last.should match(/Session is not open\!/)
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ [nil, "", "d=", "f=test"].each do |data|
222
+ context "with data = #{data.inspect}" do
223
+ before :each do
224
+ session
225
+ end
226
+
227
+ let(:request) do
228
+ FakeRequest.new.tap do |request|
229
+ request.path_info = "/jsonp_send"
230
+ request.session_key = "b"
231
+ request.content_type = "application/x-www-form-urlencoded"
232
+ request.data = data
233
+ end
234
+ end
235
+
236
+ it "should respond with HTTP 500" do
237
+ response.status.should eql(500)
238
+ end
239
+
240
+ it "should respond with HTML MIME type" do
241
+ response.headers["Content-Type"].should match("text/html")
242
+ end
243
+
244
+ it "should return error message in the body" do
245
+ response # Run the handler.
246
+ response.chunks.last.should match(/Payload expected./)
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end