rails_grip 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 543fe29bbb33ab3fa68744043d958b0ff417561f
4
+ data.tar.gz: 2a4261dbe4d4fe36b271fde6391e05a8cadd9e48
5
+ SHA512:
6
+ metadata.gz: 0549e500baf1100baaec263cf4725d486af5cee8dae9ecff8d83ddfa6747d72c63795ca37777f60f20c10134cf50efa637f0890658af22ccc64ff0a3f1e06b57
7
+ data.tar.gz: 2c365e32cd0e2b06e9410a4941f7bbea5e9bdbde563e93db0c9658ef28b144708d71134f79bb9ee12b05c9716a19d3eb23c4fea1f44ecfbf337664c84fb9c92d
@@ -0,0 +1,193 @@
1
+ # gripmiddleware.rb
2
+ # ~~~~~~~~~
3
+ # This module implements the GripMiddleware class.
4
+ # :authors: Konstantin Bokarius.
5
+ # :copyright: (c) 2015 by Fanout, Inc.
6
+ # :license: MIT, see LICENSE for more details.
7
+
8
+ require 'set'
9
+ require 'gripcontrol'
10
+ require_relative 'websocketcontext.rb'
11
+ require_relative 'nonwebsocketrequesterror.rb'
12
+
13
+ class GripMiddleware
14
+ def initialize(app)
15
+ @app = app
16
+ end
17
+
18
+ # TODO: Add a mechanism to set to websocket-only.
19
+ def call(env)
20
+ env['grip_proxied'] = false
21
+ env['grip_wscontext'] = nil
22
+ grip_signed = false
23
+ grip_proxies = RailsSettings.get_grip_proxies
24
+ if env.key?('HTTP_GRIP_SIG') and !grip_proxies.nil?
25
+ grip_proxies.each do |entry|
26
+ if GripControl.validate_sig(env['HTTP_GRIP_SIG'], entry['key'])
27
+ grip_signed = true
28
+ break
29
+ end
30
+ end
31
+ end
32
+ content_type = nil
33
+ if env.key?('CONTENT_TYPE')
34
+ content_type = env['CONTENT_TYPE']
35
+ at = content_type.index(';')
36
+ if !at.nil?
37
+ content_type = content_type[0..at-1]
38
+ end
39
+ end
40
+ accept_types = nil
41
+ if env.key?('HTTP_ACCEPT')
42
+ accept_types = env['HTTP_ACCEPT']
43
+ tmp = accept_types.split(',')
44
+ accept_types = []
45
+ tmp.each do |s|
46
+ accept_types.push(s.strip)
47
+ end
48
+ end
49
+ wscontext = nil
50
+ if env['REQUEST_METHOD'] == 'POST' and ((content_type ==
51
+ 'application/websocket-events') or (!accept_types.nil? and
52
+ accept_types.include?('application/websocket-events')))
53
+ cid = nil
54
+ if env.key?('HTTP_CONNECTION_ID')
55
+ cid = env['HTTP_CONNECTION_ID']
56
+ end
57
+ meta = {}
58
+ env.each do |k, v|
59
+ if k.start_with?('HTTP_META_')
60
+ meta[convert_header_name(k[10..-1])] = v
61
+ end
62
+ end
63
+ events = nil
64
+ begin
65
+ events = GripControl.decode_websocket_events(env["rack.input"].read)
66
+ rescue
67
+ return [ 400, {}, ["Error parsing WebSocket events.\n"]]
68
+ end
69
+ wscontext = WebSocketContext.new(cid, meta, events)
70
+ end
71
+ env['grip_proxied'] = grip_signed
72
+ env['grip_wscontext'] = wscontext
73
+ begin
74
+ status, headers, response = @app.call(env)
75
+ rescue NonWebSocketRequestError => e
76
+ return [400, {}, [e.message + "\n"]]
77
+ end
78
+ if !env['grip_wscontext'].nil? and status == 200
79
+ wscontext = env['grip_wscontext']
80
+ meta_remove = Set.new
81
+ wscontext.orig_meta.each do |k, v|
82
+ found = false
83
+ wscontext.meta.each do |nk, nv|
84
+ if nk.downcase == k
85
+ found = true
86
+ break
87
+ end
88
+ end
89
+ if !found
90
+ meta_remove.add(k)
91
+ end
92
+ end
93
+ meta_set = {}
94
+ wscontext.meta.each do |k, v|
95
+ lname = k.downcase
96
+ need_set = true
97
+ wscontext.orig_meta.each do |ok, ov|
98
+ if lname == ok and v == ov
99
+ need_set = false
100
+ break
101
+ end
102
+ end
103
+ if need_set
104
+ meta_set[lname] = v
105
+ end
106
+ end
107
+ events = []
108
+ if wscontext.accepted
109
+ events.push(WebSocketEvent.new('OPEN'))
110
+ end
111
+ events.push(*wscontext.out_events)
112
+ if wscontext.closed
113
+ events.push(WebSocketEvent.new('CLOSE',
114
+ [wscontext.out_close_code].pack('S_')))
115
+ end
116
+ if response.respond_to?(:content_type)
117
+ response.body = GripControl.encode_websocket_events(events)
118
+ response.content_type = 'application/websocket-events'
119
+ else
120
+ response = [GripControl.encode_websocket_events(events)]
121
+ end
122
+ headers['Content-Type'] = 'application/websocket-events'
123
+ if wscontext.accepted
124
+ headers['Sec-WebSocket-Extensions'] = 'grip'
125
+ end
126
+ meta_remove.each do |k, v|
127
+ headers['Set-Meta-' + k] = ''
128
+ end
129
+ meta_set.each do |k, v|
130
+ headers['Set-Meta-' + k] = v
131
+ end
132
+ elsif !env['grip_hold'].nil?
133
+ if !env['grip_proxied'] and RailsSettings.get_grip_proxy_required
134
+ return [ 501, {}, ["Not implemented.\n"]]
135
+ end
136
+ channels = env['grip_channels']
137
+ prefix = RailsSettings.get_prefix
138
+ if prefix != ''
139
+ channels.each do |channel|
140
+ channel.name = prefix + channel.name
141
+ end
142
+ end
143
+ if status == 304
144
+ iheaders = headers.clone
145
+ if !iheaders.key?('Location') and response.respond_to?(:location) and
146
+ !response.location.nil?
147
+ iheaders['Location'] = response.location
148
+ end
149
+ if response.respond_to?(:body)
150
+ orig_body = response.body
151
+ else
152
+ orig_body = response.to_s
153
+ end
154
+ iresponse = Response.new(status, nil, iheaders, orig_body)
155
+ timeout = nil
156
+ if !env['grip_timeout'].nil?
157
+ timeout = env['grip_timeout']
158
+ end
159
+ if response.respond_to?(:content_type)
160
+ response.body = GripControl.create_hold(env['grip_hold'],
161
+ channels, iresponse, timeout)
162
+ response.content_type = 'application/grip-instruct'
163
+ else
164
+ response = [GripControl.create_hold(env['grip_hold'],
165
+ channels, iresponse, timeout)]
166
+ end
167
+ headers = {'Content-Type' => 'application/grip-instruct'}
168
+ else
169
+ headers['Grip-Hold'] = env['grip_hold']
170
+ headers['Grip-Channel'] = GripControl.create_grip_channel_header(
171
+ channels)
172
+ if !env['grip_timeout'].nil?
173
+ headers['Grip-Timeout'] = env['grip_timeout'].to_s
174
+ end
175
+ end
176
+ end
177
+ return [status, headers, response]
178
+ end
179
+
180
+ private
181
+
182
+ def convert_header_name(name)
183
+ out = ''
184
+ name.each_char do |c|
185
+ if c == '_'
186
+ out += '-'
187
+ else
188
+ out += c.downcase
189
+ end
190
+ end
191
+ return out
192
+ end
193
+ end
@@ -0,0 +1,12 @@
1
+ # nonwebsocketrequesterror.rb
2
+ # ~~~~~~~~~
3
+ # This module implements the NonWebSocketRequestError class.
4
+ # :authors: Konstantin Bokarius.
5
+ # :copyright: (c) 2015 by Fanout, Inc.
6
+ # :license: MIT, see LICENSE for more details.
7
+
8
+ class NonWebSocketRequestError < StandardError
9
+ def message
10
+ "This endpoint only allows WebSocket requests."
11
+ end
12
+ end
data/lib/rails_grip.rb ADDED
@@ -0,0 +1,92 @@
1
+ # rails_grip.rb
2
+ # ~~~~~~~~~
3
+ # This module implements the RailsGrip class.
4
+ # :authors: Konstantin Bokarius.
5
+ # :copyright: (c) 2015 by Fanout, Inc.
6
+ # :license: MIT, see LICENSE for more details.
7
+
8
+ require 'pubcontrol'
9
+ require 'gripcontrol'
10
+
11
+ require_relative 'railssettings.rb'
12
+ require_relative 'gripmiddleware.rb'
13
+
14
+ class RailsGrip
15
+ def self.publish(channel, formats, id=nil, prev_id=nil)
16
+ pub = RailsGrip.get_pubcontrol
17
+ pub.publish(RailsSettings.get_prefix + channel, Item.new(
18
+ formats, id,prev_id))
19
+ end
20
+
21
+ def self.publish_async(channel, formats, id=nil, prev_id=nil, callback=nil)
22
+ pub = RailsGrip.get_pubcontrol
23
+ pub.publish_async(RailsSettings.get_prefix + channel,
24
+ Item.new(formats, id, prev_id), callback)
25
+ end
26
+
27
+ def self.set_hold_longpoll(request, channels, timeout=nil)
28
+ request.env['grip_hold'] = 'response'
29
+ request.env['grip_channels'] = RailsGrip.convert_channels(channels)
30
+ request.env['grip_timeout'] = timeout
31
+ end
32
+
33
+ def self.set_hold_stream(request, channels)
34
+ request.env['grip_hold'] = 'stream'
35
+ request.env['grip_channels'] = RailsGrip.convert_channels(channels)
36
+ end
37
+
38
+ def self.is_grip_proxied(request)
39
+ if request.env.key?('grip_proxied')
40
+ return request.env['grip_proxied']
41
+ end
42
+ return false
43
+ end
44
+
45
+ def self.get_wscontext(request)
46
+ if request.env.key?('grip_wscontext')
47
+ return request.env['grip_wscontext']
48
+ end
49
+ return nil
50
+ end
51
+
52
+ def self.verify_is_websocket(request)
53
+ if !RailsGrip.get_wscontext(request)
54
+ raise NonWebSocketRequestError
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def self.get_pubcontrol
61
+ if Thread.current['pubcontrol'].nil?
62
+ pub = GripPubControl.new
63
+ grip_proxies = RailsSettings.get_grip_proxies
64
+ publish_servers = RailsSettings.get_publish_servers
65
+ if !grip_proxies.nil?
66
+ pub.apply_grip_config(grip_proxies)
67
+ end
68
+ if !publish_servers.nil?
69
+ pub.apply_config(publish_servers)
70
+ end
71
+ at_exit { pub.finish }
72
+ Thread.current['pubcontrol'] = pub
73
+ end
74
+ return Thread.current['pubcontrol']
75
+ end
76
+
77
+ def self.convert_channels(channels)
78
+ if channels.is_a?(Channel) or channels.is_a?(String)
79
+ channels = [channels]
80
+ end
81
+ out = []
82
+ channels.each do |channel|
83
+ if channel.is_a?(String)
84
+ channel = Channel.new(channel)
85
+ end
86
+ out.push(channel)
87
+ end
88
+ return out
89
+ end
90
+ end
91
+
92
+ require_relative 'railtie.rb' if defined? Rails::Railtie
@@ -0,0 +1,36 @@
1
+ # railssettings.rb
2
+ # ~~~~~~~~~
3
+ # This module implements the RailsSettings class.
4
+ # :authors: Konstantin Bokarius.
5
+ # :copyright: (c) 2015 by Fanout, Inc.
6
+ # :license: MIT, see LICENSE for more details.
7
+
8
+ class RailsSettings
9
+ def self.get_prefix
10
+ if Rails.application.config.respond_to?(:grip_prefix)
11
+ return Rails.application.config.grip_prefix
12
+ end
13
+ return ''
14
+ end
15
+
16
+ def self.get_grip_proxies
17
+ if Rails.application.config.respond_to?(:grip_proxies)
18
+ return Rails.application.config.grip_proxies
19
+ end
20
+ return nil
21
+ end
22
+
23
+ def self.get_publish_servers
24
+ if Rails.application.config.respond_to?(:publish_servers)
25
+ return Rails.application.config.publish_servers
26
+ end
27
+ return nil
28
+ end
29
+
30
+ def self.get_grip_proxy_required
31
+ if Rails.application.config.respond_to?(:grip_proxy_required)
32
+ return Rails.application.config.grip_proxy_required
33
+ end
34
+ return false
35
+ end
36
+ end
data/lib/railtie.rb ADDED
@@ -0,0 +1,12 @@
1
+ # railtie.rb
2
+ # ~~~~~~~~~
3
+ # This module implements the Railtie class.
4
+ # :authors: Konstantin Bokarius.
5
+ # :copyright: (c) 2015 by Fanout, Inc.
6
+ # :license: MIT, see LICENSE for more details.
7
+
8
+ class Railtie < Rails::Railtie
9
+ initializer "rails_grip.configure_rails_initialization" do
10
+ Rails.application.middleware.use GripMiddleware
11
+ end
12
+ end
@@ -0,0 +1,107 @@
1
+ # websocketcontext.rb
2
+ # ~~~~~~~~~
3
+ # This module implements the WebSocketContext class.
4
+ # :authors: Konstantin Bokarius.
5
+ # :copyright: (c) 2015 by Fanout, Inc.
6
+ # :license: MIT, see LICENSE for more details.
7
+
8
+ require 'gripcontrol'
9
+
10
+ class WebSocketContext
11
+ attr_accessor :orig_meta
12
+ attr_accessor :meta
13
+ attr_accessor :accepted
14
+ attr_accessor :out_events
15
+ attr_accessor :closed
16
+ attr_accessor :out_close_code
17
+ attr_accessor :close_code
18
+
19
+ def initialize(id, meta, in_events)
20
+ @id = id
21
+ @in_events = in_events
22
+ @read_index = 0
23
+ @accepted = false
24
+ @close_code = nil
25
+ @closed = false
26
+ @out_close_code = nil
27
+ @out_events = []
28
+ @orig_meta = meta
29
+ @meta = Marshal.load(Marshal.dump(meta))
30
+ end
31
+
32
+ def is_opening
33
+ return (!@in_events.nil? and @in_events.length > 0 and
34
+ @in_events[0].type == 'OPEN')
35
+ end
36
+
37
+ def accept
38
+ @accepted = true
39
+ end
40
+
41
+ def close(code=nil)
42
+ @closed = true
43
+ if !code.nil?
44
+ @out_close_code = code
45
+ else
46
+ @out_close_code = 0
47
+ end
48
+ end
49
+
50
+ def can_recv
51
+ for n in @read_index..@in_events.length-1 do
52
+ if ['TEXT', 'BINARY', 'CLOSE', 'DISCONNECT'].include?(
53
+ @in_events[n].type)
54
+ return true
55
+ end
56
+ end
57
+ return false
58
+ end
59
+
60
+ def recv
61
+ e = nil
62
+ while e.nil? and @read_index < @in_events.length do
63
+ if ['TEXT', 'BINARY', 'CLOSE', 'DISCONNECT'].include?(
64
+ @in_events[@read_index].type)
65
+ e = @in_events[@read_index]
66
+ elsif @in_events[@read_index].type == 'PING'
67
+ @out_events.push(WebSocketEvent.new('PONG'))
68
+ end
69
+ @read_index += 1
70
+ end
71
+ if e.nil?
72
+ raise 'read from empty buffer'
73
+ end
74
+ if e.type == 'TEXT' or e.type == 'BINARY'
75
+ return e.content
76
+ elsif e.type == 'CLOSE'
77
+ if !e.content.nil? and e.content.length == 2
78
+ @close_code = e.content.unpack('S_')[0]
79
+ end
80
+ return nil
81
+ else
82
+ raise 'client disconnected unexpectedly'
83
+ end
84
+ end
85
+
86
+ def send(message)
87
+ @out_events.push(WebSocketEvent.new('TEXT', 'm:' + message))
88
+ end
89
+
90
+ def send_control(message)
91
+ @out_events.push(WebSocketEvent.new('TEXT', 'c:' + message))
92
+ end
93
+
94
+ def subscribe(channel)
95
+ send_control(GripControl.websocket_control_message(
96
+ RailsSettings.get_prefix + 'subscribe', {'channel' => channel}))
97
+ end
98
+
99
+ def unsubscribe(channel)
100
+ send_control(GripControl.websocket_control_message(
101
+ RailsSettings.get_prefix + 'unsubscribe', {'channel' => channel}))
102
+ end
103
+
104
+ def detach(channel)
105
+ send_control(GripControl.websocket_control_message('detach'))
106
+ end
107
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_grip
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Konstantin Bokarius
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gripcontrol
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ description: A Ruby on Rails convenience library for working with GRIP proxies.
28
+ email: bokarius@comcast.net
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/gripmiddleware.rb
34
+ - lib/nonwebsocketrequesterror.rb
35
+ - lib/rails_grip.rb
36
+ - lib/railssettings.rb
37
+ - lib/railtie.rb
38
+ - lib/websocketcontext.rb
39
+ homepage: https://github.com/fanout/rails-grip
40
+ licenses:
41
+ - MIT
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 1.9.0
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubyforge_project:
59
+ rubygems_version: 2.2.2
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: GRIP library for Ruby on Rails
63
+ test_files: []