volt-sockjs 0.3.4.4

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