volt-sockjs 0.3.4.4

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 (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,357 @@
1
+ # encoding: utf-8
2
+
3
+ require "sockjs/session"
4
+ require "sockjs/servers/request"
5
+ require "sockjs/servers/response"
6
+
7
+ require 'rack/mount'
8
+
9
+ module SockJS
10
+ class SessionUnavailableError < StandardError
11
+ end
12
+
13
+ #If I had it to do over again, Endpoint wouldn't have subclasses - we'd
14
+ #subclass Response and instances of Endpoint would know what kind of Response
15
+ #to create for their mount point
16
+ class Endpoint
17
+ class MethodMap
18
+ def initialize(map)
19
+ @method_map = map
20
+ end
21
+ attr_reader :method_map
22
+
23
+ def call(env)
24
+ app = @method_map.fetch(env["REQUEST_METHOD"])
25
+ app.call(env)
26
+ rescue KeyError
27
+ ::SockJS.debug "Method not supported!"
28
+ [405, {"Allow" => methods_map.keys.join(", ") }, []]
29
+ end
30
+ end
31
+
32
+ class MethodNotSupportedApp
33
+ def initialize(methods)
34
+ @allowed_methods = methods
35
+ end
36
+
37
+ def response
38
+ ::SockJS.debug "Method not supported! (app)"
39
+ @response ||=
40
+ [405, {"Allow" => @allowed_methods.join(",")}, []].freeze
41
+ end
42
+
43
+ def call(env)
44
+ return response
45
+ end
46
+ end
47
+
48
+ #XXX Remove
49
+ # @deprecated: See response.rb
50
+ CONTENT_TYPES ||= {
51
+ plain: "text/plain; charset=UTF-8",
52
+ html: "text/html; charset=UTF-8",
53
+ javascript: "application/javascript; charset=UTF-8",
54
+ event_stream: "text/event-stream; charset=UTF-8"
55
+ }
56
+
57
+ module ClassMethods
58
+ def add_routes(route_set, connection, options)
59
+ method_catching = Hash.new{|h,k| h[k] = []}
60
+ endpoints.each do |endpoint_class|
61
+ endpoint_class.add_route(route_set, connection, options)
62
+ method_catching[endpoint_class.routing_prefix] << endpoint_class.method
63
+ end
64
+ method_catching.each_pair do |prefix, methods|
65
+ route_set.add_route(MethodNotSupportedApp.new(methods), {:path_info => prefix}, {})
66
+ end
67
+ end
68
+
69
+ def routing_prefix
70
+ case prefix
71
+ when String
72
+ "/" + prefix
73
+ when Regexp
74
+ prefix
75
+ end
76
+ end
77
+
78
+ def route_conditions
79
+ {
80
+ :request_method => self.method,
81
+ :path_info => self.routing_prefix
82
+ }
83
+ end
84
+
85
+ def add_route(route_set, connection, options)
86
+ #SockJS.debug "Adding route for #{self} on #{route_conditions.inspect}"
87
+ route_set.add_route(self.new(connection, options), route_conditions, {})
88
+ end
89
+
90
+ def endpoints
91
+ @endpoints ||= []
92
+ end
93
+
94
+ def register(method, prefix)
95
+ @prefix = prefix
96
+ @method = method
97
+ Endpoint.endpoints << self
98
+ end
99
+ attr_reader :prefix, :method
100
+ end
101
+ extend ClassMethods
102
+
103
+ # Instance methods.
104
+ attr_reader :connection, :options
105
+ def initialize(connection, options)
106
+ @connection, @options = connection, options
107
+ options[:websocket] = true unless options.has_key?(:websocket)
108
+ options[:cookie_needed] = true unless options.has_key?(:cookie_needed)
109
+ end
110
+
111
+ def inspect
112
+ "<<#{self.class.name} #{options.inspect}>>"
113
+ end
114
+
115
+ def response_class
116
+ SockJS::Response
117
+ end
118
+
119
+ # Used for pings.
120
+ def empty_string
121
+ "\n"
122
+ end
123
+
124
+ def format_frame(session, payload)
125
+ raise TypeError.new("Payload must not be nil!") if payload.nil?
126
+
127
+ "#{payload}\n"
128
+ end
129
+
130
+ attr_reader :remote_addr, :http_origin
131
+ def call(env)
132
+ @remote_addr = env["REMOTE_ADDR"]
133
+ @http_origin = env["HTTP_ORIGIN"]
134
+ SockJS.debug "Request for #{self.class}: #{env["REQUEST_METHOD"]}/#{env["PATH_INFO"]}"
135
+ request = ::SockJS::Request.new(env)
136
+ EM.next_tick do
137
+ handle(request)
138
+ end
139
+ return Thin::Connection::AsyncResponse
140
+ end
141
+
142
+ def handle(request)
143
+ handle_request(request)
144
+ rescue SockJS::HttpError => error
145
+ SockJS.debug "HttpError while handling request: #{([error.inspect] + error.backtrace).join("\n")}"
146
+ handle_http_error(request, error)
147
+ rescue Object => error
148
+ SockJS.debug "Error while handling request: #{([error.inspect] + error.backtrace).join("\n")}"
149
+ begin
150
+ response = response_class.new(request, 500)
151
+ response.write(error.message)
152
+ response.finish
153
+ return response
154
+ rescue Object => ex
155
+ SockJS.debug "Error while trying to send error HTTP response: #{ex.inspect}"
156
+ end
157
+ end
158
+
159
+ def handle_request(request)
160
+ response = build_response(request)
161
+ response.finish
162
+ return response
163
+ end
164
+
165
+ def error_content_type
166
+ :plain
167
+ end
168
+
169
+ def handle_http_error(request, error)
170
+ response = build_error_response(request)
171
+ response.status = error.status
172
+
173
+ response.set_content_type(error_content_type)
174
+ SockJS::debug "Built error response: #{response.inspect}"
175
+ response.write(error.message)
176
+ response
177
+ end
178
+
179
+ def build_response(request)
180
+ response = response_class.new(request)
181
+ setup_response(request, response)
182
+ return response
183
+ end
184
+
185
+ def build_error_response(request)
186
+ build_response(request)
187
+ end
188
+
189
+ def setup_response(request, response)
190
+ response.status = 200
191
+ end
192
+ end
193
+
194
+ class SessionEndpoint < Endpoint
195
+ def self.routing_prefix
196
+ legal_key_regexp = %r{[^./]+}
197
+ ::Rack::Mount::Strexp.new("/:server_key/:session_key/#{self.prefix}", {:server_key => legal_key_regexp, :session_key => legal_key_regexp})
198
+ end
199
+ end
200
+
201
+ class Transport < SessionEndpoint
202
+ def handle_request(request)
203
+ SockJS::debug({:Request => request, :Transport => self}.inspect)
204
+
205
+ response = build_response(request)
206
+ session = get_session(response)
207
+
208
+ process_session(session, response)
209
+
210
+ return response
211
+ rescue SockJS::InvalidJSON => error
212
+ exception_response(request, error, 500)
213
+ rescue SockJS::SessionUnavailableError => error
214
+ handle_session_unavailable(request)
215
+ end
216
+
217
+ def response_beginning(response)
218
+ end
219
+
220
+ def exception_response(request, error, status)
221
+ SockJS::debug("Handling error #{error.inspect}")
222
+ response = build_response(request)
223
+ response.status = status
224
+ response.set_content_type(:plain)
225
+ response.set_session_id(request.session_id)
226
+ response.write(error.message)
227
+ SockJS::debug("Error response: #{response.inspect}")
228
+ return response
229
+ end
230
+
231
+ def handle_session_unavailable(request)
232
+ SockJS::debug("Handling missing session for #{request.inspect}")
233
+ response = build_response(request)
234
+ response.status = 404
235
+ response.set_content_type(:plain)
236
+ response.set_session_id(request.session_id)
237
+ response.write("Session is not open!")
238
+ return response
239
+ end
240
+
241
+ def server_key(response)
242
+ request = response.request
243
+ (request.env['rack.routing_args'] || {})[:server_key]
244
+ end
245
+
246
+ def session_key(response)
247
+ request = response.request
248
+ (request.env['rack.routing_args'] || {})[:session_key]
249
+ end
250
+
251
+ def request_data(request)
252
+ request.data.string
253
+ end
254
+ end
255
+
256
+ class ConsumingTransport < Transport
257
+ def process_session(session, response)
258
+ session.attach_consumer(response, self)
259
+ response.request.on_close do
260
+ begin
261
+ request_closed(session)
262
+ rescue Object => ex
263
+ SockJS::debug "Exception when closing request: #{ex.inspect}"
264
+ end
265
+ end
266
+ end
267
+
268
+ def request_closed(session)
269
+ session.detach_consumer
270
+ end
271
+
272
+ def finish_response(response)
273
+ response.finish
274
+ end
275
+
276
+ def opening_frame(response)
277
+ send_data(response, format_frame(response, Protocol::OpeningFrame.instance))
278
+ end
279
+
280
+ def heartbeat_frame(response)
281
+ send_data(response, format_frame(response, Protocol::HeartbeatFrame.instance))
282
+ end
283
+
284
+ def messages_frame(response, messages)
285
+ send_data(response, format_frame(response, Protocol::ArrayFrame.new(messages)))
286
+ end
287
+
288
+ def closing_frame(response, status, message)
289
+ send_data(response, format_frame(response, Protocol::ClosingFrame.new(status, message)))
290
+ finish_response(response)
291
+ end
292
+
293
+ #TODO: Consider absorbing format_frame into send_data
294
+ def send_data(response, data)
295
+ response.write(data)
296
+ return data.length
297
+ end
298
+
299
+ def format_frame(response, frame)
300
+ frame.to_s + "\n"
301
+ end
302
+
303
+ def get_session(response)
304
+ begin
305
+ session = connection.get_session(session_key(response))
306
+ response_beginning(response)
307
+ return session
308
+ rescue KeyError
309
+ SockJS::debug("Missing session for #{session_key(response)} - creating new")
310
+ session = connection.create_session(session_key(response))
311
+ response_beginning(response)
312
+ opening_frame(response)
313
+ return session
314
+ end
315
+ end
316
+ end
317
+
318
+ class PollingConsumingTransport < ConsumingTransport
319
+ def process_session(session, response)
320
+ super
321
+ #response.finish
322
+ end
323
+ end
324
+
325
+ class DeliveryTransport < Transport
326
+ def process_session(session, response)
327
+ session.receive_message(extract_message(response.request))
328
+
329
+ successful_response(response)
330
+ end
331
+
332
+ def extract_message(request)
333
+ body = request.data.read
334
+ raise "Payload expected." if body.empty?
335
+ return body
336
+ end
337
+
338
+ def setup_response(response)
339
+ response.status = 204
340
+ end
341
+
342
+ def successful_response(response)
343
+ response.finish
344
+ end
345
+
346
+ def get_session(response)
347
+ begin
348
+ session = connection.get_session(session_key(response))
349
+ response_beginning(response)
350
+ return session
351
+ rescue KeyError
352
+ SockJS::debug("Missing session for #{session_key(response)} - invalid request")
353
+ raise SessionUnavailableError
354
+ end
355
+ end
356
+ end
357
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ require "sockjs/transport"
4
+
5
+ module SockJS
6
+ module Transports
7
+ class EventSource < ConsumingTransport
8
+ register 'GET', 'eventsource'
9
+
10
+ def setup_response(request, response)
11
+ response.status = 200
12
+
13
+ response.set_content_type(:event_stream)
14
+ response.set_session_id(request.session_id)
15
+ response.set_no_cache
16
+ response.write_head
17
+
18
+ # Opera needs to hear two more initial new lines.
19
+ response.write("\r\n")
20
+ response
21
+ end
22
+
23
+ def format_frame(response, frame)
24
+ raise TypeError.new("Payload must not be nil!") if frame.nil?
25
+
26
+ ["data: ", frame.to_s, "\r\n\r\n"].join
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,73 @@
1
+ # encoding: utf-8
2
+
3
+ require "json"
4
+ require "sockjs/transport"
5
+
6
+ module SockJS
7
+ module Transports
8
+ class HTMLFile < ConsumingTransport
9
+ register 'GET', 'htmlfile'
10
+
11
+ HTML_PREFIX = <<-EOT.chomp.freeze
12
+ <!doctype html>
13
+ <html><head>
14
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
15
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
16
+ </head><body><h2>Don't panic!</h2>
17
+ <script>
18
+ document.domain = document.domain;
19
+ var c = parent.
20
+ EOT
21
+
22
+ HTML_POSTFIX = (<<-EOH + (" " * (1024 - HTML_PREFIX.bytesize)) + "\r\n\r\n").freeze
23
+ ;
24
+ c.start();
25
+ function p(d) {c.message(d);};
26
+ window.onload = function() {c.stop();};
27
+ </script>
28
+ EOH
29
+
30
+ def setup_response(request, response)
31
+ response.status = 200
32
+ response.set_content_type(:html)
33
+ response.set_no_cache
34
+ response.set_session_id(request.session_id)
35
+
36
+ response
37
+ end
38
+
39
+ def get_session(response)
40
+ if response.request.callback.nil? or response.request.callback.empty?
41
+ raise SockJS::HttpError.new(500, '"callback" parameter required')
42
+ end
43
+
44
+ if !!/[^a-zA-Z0-9\-_.]/.match(response.request.callback)
45
+ raise SockJS::HttpError.new(500, 'invalid "callback" parameter')
46
+ end
47
+
48
+ super
49
+ end
50
+
51
+ def response_beginning(response)
52
+ response.write(HTML_PREFIX + response.request.callback + HTML_POSTFIX)
53
+ end
54
+
55
+ def handle_http_error(request, error)
56
+ response = build_response(request)
57
+ response.status = error.status
58
+ response.set_no_cache
59
+ response.set_content_type(:html)
60
+
61
+ SockJS::debug "Built error response: #{response.inspect}"
62
+ response.write(error.message)
63
+ response
64
+ end
65
+
66
+ def format_frame(response, frame)
67
+ raise TypeError.new("Payload must not be nil!") if frame.nil?
68
+
69
+ "<script>\np(#{frame.to_s.to_json});\n</script>\r\n"
70
+ end
71
+ end
72
+ end
73
+ end