rails_grip 1.0.0

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.
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: []