logux-rack 0.1.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 +7 -0
- data/.rubocop_todo.yml +17 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +130 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config.ru +6 -0
- data/docker-compose.yml +29 -0
- data/lib/logux.rb +3 -0
- data/lib/logux/action.rb +68 -0
- data/lib/logux/action_caller.rb +46 -0
- data/lib/logux/action_controller.rb +6 -0
- data/lib/logux/action_watcher.rb +21 -0
- data/lib/logux/actions.rb +17 -0
- data/lib/logux/add.rb +37 -0
- data/lib/logux/auth.rb +13 -0
- data/lib/logux/base_controller.rb +37 -0
- data/lib/logux/channel_controller.rb +24 -0
- data/lib/logux/class_finder.rb +63 -0
- data/lib/logux/client.rb +21 -0
- data/lib/logux/error_renderer.rb +40 -0
- data/lib/logux/meta.rb +36 -0
- data/lib/logux/node.rb +37 -0
- data/lib/logux/policy.rb +14 -0
- data/lib/logux/policy_caller.rb +36 -0
- data/lib/logux/process.rb +9 -0
- data/lib/logux/process/action.rb +60 -0
- data/lib/logux/process/auth.rb +31 -0
- data/lib/logux/process/batch.rb +59 -0
- data/lib/logux/rack.rb +133 -0
- data/lib/logux/rack/app.rb +55 -0
- data/lib/logux/rack/version.rb +7 -0
- data/lib/logux/rake_tasks.rb +118 -0
- data/lib/logux/response.rb +18 -0
- data/lib/logux/stream.rb +27 -0
- data/lib/logux/test.rb +35 -0
- data/lib/logux/test/helpers.rb +73 -0
- data/lib/logux/test/matchers.rb +10 -0
- data/lib/logux/test/matchers/base.rb +25 -0
- data/lib/logux/test/matchers/response_chunks.rb +48 -0
- data/lib/logux/test/matchers/send_to_logux.rb +51 -0
- data/lib/logux/test/store.rb +21 -0
- data/lib/logux/utils.rb +19 -0
- data/lib/tasks/logux_tasks.rake +4 -0
- data/logux-rack.gemspec +68 -0
- metadata +386 -0
data/lib/logux/add.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
class Add
|
|
5
|
+
attr_reader :client, :version, :password
|
|
6
|
+
|
|
7
|
+
def initialize(client: Logux::Client.new,
|
|
8
|
+
version: Logux::PROTOCOL_VERSION,
|
|
9
|
+
password: Logux.configuration.password)
|
|
10
|
+
@client = client
|
|
11
|
+
@version = version
|
|
12
|
+
@password = password
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(commands)
|
|
16
|
+
return if commands.empty?
|
|
17
|
+
|
|
18
|
+
prepared_data = prepare_data(commands)
|
|
19
|
+
Logux.logger.debug("Logux add: #{prepared_data}")
|
|
20
|
+
client.post(prepared_data)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def prepare_data(commands)
|
|
26
|
+
{
|
|
27
|
+
version: PROTOCOL_VERSION,
|
|
28
|
+
password: password,
|
|
29
|
+
commands: commands.map do |command|
|
|
30
|
+
action = command.first
|
|
31
|
+
meta = command[1]
|
|
32
|
+
['action', action, meta || Meta.new]
|
|
33
|
+
end
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/logux/auth.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
class BaseController
|
|
5
|
+
class << self
|
|
6
|
+
def verify_authorized!
|
|
7
|
+
Logux.configuration.verify_authorized = true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def unverify_authorized!
|
|
11
|
+
Logux.configuration.verify_authorized = false
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :action, :meta
|
|
16
|
+
|
|
17
|
+
def initialize(action:, meta: {})
|
|
18
|
+
@action = action
|
|
19
|
+
@meta = meta
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def respond(status, action: @action, meta: @meta, custom_data: nil)
|
|
23
|
+
Logux::Response.new(status,
|
|
24
|
+
action: action,
|
|
25
|
+
meta: meta,
|
|
26
|
+
custom_data: custom_data)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def user_id
|
|
30
|
+
@user_id ||= meta.user_id
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def node_id
|
|
34
|
+
@node_id ||= meta.node_id
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
class ChannelController < BaseController
|
|
5
|
+
def subscribe
|
|
6
|
+
Logux.add_batch(initial_data.map { |d| [d, initial_meta] })
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initial_data
|
|
10
|
+
[]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initial_meta
|
|
14
|
+
{ clients: [meta.client_id] }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def since_time
|
|
18
|
+
@since_time ||= begin
|
|
19
|
+
since = action[:since]&.send(:[], :time)
|
|
20
|
+
Time.at(since).to_datetime if since
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
class ClassFinder
|
|
5
|
+
attr_reader :action, :meta
|
|
6
|
+
|
|
7
|
+
using Logux::Utils
|
|
8
|
+
|
|
9
|
+
def initialize(action:, meta:)
|
|
10
|
+
@action = action
|
|
11
|
+
@meta = meta
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def find_action_class
|
|
15
|
+
"#{class_namespace}::#{class_name}".constantize
|
|
16
|
+
rescue NameError
|
|
17
|
+
message =
|
|
18
|
+
"Unable to find action #{class_name.camelize}.\n" \
|
|
19
|
+
"Should be in app/logux/#{class_namespace.downcase}/#{class_path}.rb"
|
|
20
|
+
raise_error_for_failed_find(message)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def find_policy_class
|
|
24
|
+
"Policies::#{class_namespace}::#{class_name}".constantize
|
|
25
|
+
rescue NameError
|
|
26
|
+
message =
|
|
27
|
+
"Unable to find action policy #{class_name.camelize}.\n" \
|
|
28
|
+
"Should be in app/logux/#{class_namespace.downcase}/#{class_path}.rb"
|
|
29
|
+
raise_error_for_failed_find(message)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def class_name
|
|
33
|
+
if subscribe?
|
|
34
|
+
action.channel_name.camelize
|
|
35
|
+
else
|
|
36
|
+
action.type.split('/')[0..-2].map(&:camelize).join('::')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def class_namespace
|
|
43
|
+
subscribe? ? 'Channels' : 'Actions'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def subscribe?
|
|
47
|
+
action.type == 'logux/subscribe'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def action?
|
|
51
|
+
!subscribe?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def class_path
|
|
55
|
+
"#{class_namespace}::#{class_name}".underscore
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def raise_error_for_failed_find(message)
|
|
59
|
+
exception_class = action? ? UnknownActionError : UnknownChannelError
|
|
60
|
+
raise exception_class.new(message, meta: meta)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/logux/client.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
class Client
|
|
5
|
+
attr_reader :logux_host
|
|
6
|
+
|
|
7
|
+
def initialize(logux_host: Logux.configuration.logux_host)
|
|
8
|
+
@logux_host = logux_host
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def post(params)
|
|
12
|
+
client.post(params.to_json,
|
|
13
|
+
content_type: :json,
|
|
14
|
+
accept: :json)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def client
|
|
18
|
+
@client ||= RestClient::Resource.new(logux_host, verify_ssl: false)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
class ErrorRenderer
|
|
5
|
+
attr_reader :exception
|
|
6
|
+
|
|
7
|
+
def initialize(exception)
|
|
8
|
+
@exception = exception
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def message
|
|
12
|
+
case exception
|
|
13
|
+
when Logux::WithMetaError
|
|
14
|
+
build_message(exception, exception.meta.id)
|
|
15
|
+
when Logux::UnauthorizedError
|
|
16
|
+
build_message(exception, exception.message)
|
|
17
|
+
when StandardError
|
|
18
|
+
# some runtime error that should be fixed
|
|
19
|
+
render_stardard_error(exception)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def render_stardard_error(exception)
|
|
26
|
+
if Logux.configuration.render_backtrace_on_error
|
|
27
|
+
['error', exception.message + "\n" + exception.backtrace.join("\n")]
|
|
28
|
+
else
|
|
29
|
+
['error', 'Please check server logs for more information']
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def build_message(exception, additional_info)
|
|
34
|
+
[
|
|
35
|
+
exception.class.name.demodulize.camelize(:lower).gsub(/Error/, ''),
|
|
36
|
+
additional_info
|
|
37
|
+
]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/logux/meta.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
class Meta < Hash
|
|
5
|
+
def initialize(source_hash = {})
|
|
6
|
+
merge!(source_hash.stringify_keys)
|
|
7
|
+
|
|
8
|
+
self['id'] ||= Logux.generate_action_id
|
|
9
|
+
self['time'] ||= self['id'].split(' ')[0]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def node_id
|
|
13
|
+
id.split(' ')[1]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def user_id
|
|
17
|
+
node_id.split(':')[0]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def client_id
|
|
21
|
+
node_id.split(':')[0..1].join(':')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def logux_order
|
|
25
|
+
time + ' ' + id.split(' ')[1..-1].join(' ')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def time
|
|
29
|
+
fetch('time')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def id
|
|
33
|
+
fetch('id')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/logux/node.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
class Node
|
|
5
|
+
include ::Singleton
|
|
6
|
+
|
|
7
|
+
attr_accessor :last_time, :sequence
|
|
8
|
+
attr_writer :node_id
|
|
9
|
+
|
|
10
|
+
def generate_action_id
|
|
11
|
+
mutex.synchronize do
|
|
12
|
+
if last_time && now_time <= last_time
|
|
13
|
+
@sequence += 1
|
|
14
|
+
else
|
|
15
|
+
@sequence = 0
|
|
16
|
+
@last_time = now_time
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
"#{last_time} #{node_id} #{sequence}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def node_id
|
|
24
|
+
@node_id ||= "server:#{Nanoid.generate(size: 8)}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def now_time
|
|
30
|
+
Time.now.to_datetime.strftime('%Q')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def mutex
|
|
34
|
+
@mutex ||= Mutex.new
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/logux/policy.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
class PolicyCaller
|
|
5
|
+
extend Forwardable
|
|
6
|
+
|
|
7
|
+
attr_reader :action, :meta
|
|
8
|
+
|
|
9
|
+
def_delegators :Logux, :logger, :configuration
|
|
10
|
+
|
|
11
|
+
def initialize(action:, meta:)
|
|
12
|
+
@action = action
|
|
13
|
+
@meta = meta
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call!
|
|
17
|
+
logger.debug('Searching policy for Logux action:' \
|
|
18
|
+
" #{action}, meta: #{meta}")
|
|
19
|
+
policy.public_send("#{action.action_type}?")
|
|
20
|
+
rescue Logux::UnknownActionError, Logux::UnknownChannelError => e
|
|
21
|
+
raise e if configuration.verify_authorized
|
|
22
|
+
|
|
23
|
+
logger.warn(e)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def class_finder
|
|
29
|
+
@class_finder ||= Logux::ClassFinder.new(action: action, meta: meta)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def policy
|
|
33
|
+
class_finder.find_policy_class.new(action: action, meta: meta)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
module Process
|
|
5
|
+
class Action
|
|
6
|
+
attr_reader :stream, :chunk
|
|
7
|
+
attr_accessor :stop_process
|
|
8
|
+
|
|
9
|
+
def initialize(stream:, chunk:)
|
|
10
|
+
@stream = stream
|
|
11
|
+
@chunk = chunk
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
process_authorization!
|
|
16
|
+
process_action!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def action_from_chunk
|
|
20
|
+
@action_from_chunk ||= chunk[:action]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def meta_from_chunk
|
|
24
|
+
@meta_from_chunk ||= chunk[:meta]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def stop_process?
|
|
28
|
+
@stop_process ||= false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def stop_process!
|
|
32
|
+
@stop_process = true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def process_action!
|
|
38
|
+
return if stop_process?
|
|
39
|
+
|
|
40
|
+
action_caller = Logux::ActionCaller.new(
|
|
41
|
+
action: action_from_chunk,
|
|
42
|
+
meta: meta_from_chunk
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
stream.write(action_caller.call!.format)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def process_authorization!
|
|
49
|
+
policy_caller = Logux::PolicyCaller.new(action: action_from_chunk,
|
|
50
|
+
meta: meta_from_chunk)
|
|
51
|
+
policy_check = policy_caller.call!
|
|
52
|
+
status = policy_check ? :approved : :forbidden
|
|
53
|
+
stream.write([status, meta_from_chunk.id])
|
|
54
|
+
return stream.write(',') if policy_check
|
|
55
|
+
|
|
56
|
+
stop_process!
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logux
|
|
4
|
+
module Process
|
|
5
|
+
class Auth
|
|
6
|
+
attr_reader :stream, :chunk
|
|
7
|
+
|
|
8
|
+
def initialize(stream:, chunk:)
|
|
9
|
+
@stream = stream
|
|
10
|
+
@chunk = chunk
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
stream.write([auth_result, chunk.auth_id])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
AUTHENTICATED = 'authenticated'
|
|
18
|
+
DENIED = 'denied'
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def auth_result
|
|
23
|
+
auth_rule(chunk.user_id, chunk.credentials) ? AUTHENTICATED : DENIED
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def auth_rule(user_id, credentials)
|
|
27
|
+
Logux.configuration.auth_rule.call(user_id, credentials)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|