sockjs 0.2.1

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