logux-rack 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
module Process
|
5
|
+
class Batch
|
6
|
+
attr_reader :stream, :batch
|
7
|
+
|
8
|
+
def initialize(stream:, batch:)
|
9
|
+
@stream = stream
|
10
|
+
@batch = batch
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
last_chunk = batch.size - 1
|
15
|
+
preprocessed_batch.map.with_index do |chunk, index|
|
16
|
+
case chunk[:type]
|
17
|
+
when :action
|
18
|
+
process_action(chunk: chunk.slice(:action, :meta))
|
19
|
+
when :auth
|
20
|
+
process_auth(chunk: chunk[:auth])
|
21
|
+
end
|
22
|
+
stream.write(',') if index != last_chunk
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_action(chunk:)
|
27
|
+
Logux::Process::Action.new(stream: stream, chunk: chunk).call
|
28
|
+
end
|
29
|
+
|
30
|
+
def process_auth(chunk:)
|
31
|
+
Logux::Process::Auth.new(stream: stream, chunk: chunk).call
|
32
|
+
end
|
33
|
+
|
34
|
+
def preprocessed_batch
|
35
|
+
@preprocessed_batch ||= batch.map do |chunk|
|
36
|
+
case chunk[0]
|
37
|
+
when 'action'
|
38
|
+
preprocess_action(chunk)
|
39
|
+
when 'auth'
|
40
|
+
preprocess_auth(chunk)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def preprocess_action(chunk)
|
46
|
+
{ type: :action,
|
47
|
+
action: Logux::Action.new(chunk[1]),
|
48
|
+
meta: Logux::Meta.new(chunk[2]) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def preprocess_auth(chunk)
|
52
|
+
{ type: :auth,
|
53
|
+
auth: Logux::Auth.new(user_id: chunk[1],
|
54
|
+
credentials: chunk[2],
|
55
|
+
auth_id: chunk[3]) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/logux/rack.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'colorize'
|
4
|
+
require 'configurations'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'json'
|
7
|
+
require 'logger'
|
8
|
+
require 'nanoid'
|
9
|
+
require 'rest-client'
|
10
|
+
require 'sinatra/base'
|
11
|
+
require 'singleton'
|
12
|
+
|
13
|
+
module Logux
|
14
|
+
include Configurations
|
15
|
+
|
16
|
+
PROTOCOL_VERSION = 1
|
17
|
+
|
18
|
+
class WithMetaError < StandardError
|
19
|
+
attr_reader :meta
|
20
|
+
|
21
|
+
def initialize(msg, meta: nil)
|
22
|
+
@meta = meta
|
23
|
+
super(msg)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
UnknownActionError = Class.new(WithMetaError)
|
28
|
+
UnknownChannelError = Class.new(WithMetaError)
|
29
|
+
UnauthorizedError = Class.new(StandardError)
|
30
|
+
ParameterMissingError = Class.new(StandardError)
|
31
|
+
|
32
|
+
autoload :Client, 'logux/client'
|
33
|
+
autoload :Meta, 'logux/meta'
|
34
|
+
autoload :Action, 'logux/action'
|
35
|
+
autoload :Actions, 'logux/actions'
|
36
|
+
autoload :Auth, 'logux/auth'
|
37
|
+
autoload :BaseController, 'logux/base_controller'
|
38
|
+
autoload :ActionController, 'logux/action_controller'
|
39
|
+
autoload :ChannelController, 'logux/channel_controller'
|
40
|
+
autoload :ClassFinder, 'logux/class_finder'
|
41
|
+
autoload :ActionCaller, 'logux/action_caller'
|
42
|
+
autoload :PolicyCaller, 'logux/policy_caller'
|
43
|
+
autoload :Policy, 'logux/policy'
|
44
|
+
autoload :Add, 'logux/add'
|
45
|
+
autoload :Node, 'logux/node'
|
46
|
+
autoload :Response, 'logux/response'
|
47
|
+
autoload :Stream, 'logux/stream'
|
48
|
+
autoload :Process, 'logux/process'
|
49
|
+
autoload :Version, 'logux/version'
|
50
|
+
autoload :Test, 'logux/test'
|
51
|
+
autoload :ErrorRenderer, 'logux/error_renderer'
|
52
|
+
autoload :Utils, 'logux/utils'
|
53
|
+
autoload :ActionWatcher, 'logux/action_watcher'
|
54
|
+
|
55
|
+
configurable %i[
|
56
|
+
action_watcher
|
57
|
+
action_watcher_options
|
58
|
+
auth_rule
|
59
|
+
logger
|
60
|
+
logux_host
|
61
|
+
on_error
|
62
|
+
password
|
63
|
+
render_backtrace_on_error
|
64
|
+
verify_authorized
|
65
|
+
]
|
66
|
+
|
67
|
+
configuration_defaults do |config|
|
68
|
+
config.logux_host = 'localhost:1338'
|
69
|
+
config.verify_authorized = true
|
70
|
+
config.logger = ::Logger.new(STDOUT)
|
71
|
+
config.on_error = proc {}
|
72
|
+
config.auth_rule = proc { false }
|
73
|
+
config.render_backtrace_on_error = true
|
74
|
+
config.action_watcher = Logux::ActionWatcher
|
75
|
+
config.action_watcher_options = {}
|
76
|
+
end
|
77
|
+
|
78
|
+
module Rack
|
79
|
+
autoload :App, 'logux/rack/app'
|
80
|
+
end
|
81
|
+
|
82
|
+
class << self
|
83
|
+
def add(action, meta = Meta.new)
|
84
|
+
Logux::Add.new.call([[action, meta]])
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_batch(commands)
|
88
|
+
Logux::Add.new.call(commands)
|
89
|
+
end
|
90
|
+
|
91
|
+
def undo(meta, reason: nil, data: {})
|
92
|
+
add(
|
93
|
+
data.merge(type: 'logux/undo', id: meta.id, reason: reason),
|
94
|
+
Logux::Meta.new(clients: [meta.client_id])
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
def verify_request_meta_data(meta_params)
|
99
|
+
if configuration.password.nil?
|
100
|
+
logger.warn(%(Please, add password for logux server:
|
101
|
+
Logux.configure do |c|
|
102
|
+
c.password = 'your-password'
|
103
|
+
end))
|
104
|
+
end
|
105
|
+
auth = configuration.password == meta_params&.dig('password')
|
106
|
+
raise UnauthorizedError, 'Incorrect password' unless auth
|
107
|
+
end
|
108
|
+
|
109
|
+
def process_batch(stream:, batch:)
|
110
|
+
Logux::Process::Batch.new(stream: stream, batch: batch).call
|
111
|
+
end
|
112
|
+
|
113
|
+
def generate_action_id
|
114
|
+
Logux::Node.instance.generate_action_id
|
115
|
+
end
|
116
|
+
|
117
|
+
def logger
|
118
|
+
configuration.logger
|
119
|
+
end
|
120
|
+
|
121
|
+
def action_watcher
|
122
|
+
configuration.action_watcher.new(configuration.action_watcher_options)
|
123
|
+
end
|
124
|
+
|
125
|
+
def watch_action
|
126
|
+
action_watcher.call { yield }
|
127
|
+
end
|
128
|
+
|
129
|
+
def application
|
130
|
+
Logux::Rack::App
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
module Rack
|
5
|
+
LOGUX_ROOT_PATH = '/logux'
|
6
|
+
|
7
|
+
class App < Sinatra::Base
|
8
|
+
before do
|
9
|
+
request.body.rewind
|
10
|
+
content_type 'application/json'
|
11
|
+
end
|
12
|
+
|
13
|
+
post LOGUX_ROOT_PATH do
|
14
|
+
stream do |out|
|
15
|
+
begin
|
16
|
+
logux_stream = Logux::Stream.new(out)
|
17
|
+
logux_stream.write('[')
|
18
|
+
Logux.verify_request_meta_data(meta_params)
|
19
|
+
Logux.process_batch(stream: logux_stream, batch: command_params)
|
20
|
+
rescue => e
|
21
|
+
handle_processing_errors(logux_stream, e)
|
22
|
+
ensure
|
23
|
+
logux_stream.write(']')
|
24
|
+
logux_stream.close
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def logux_params
|
32
|
+
@logux_params ||= JSON.parse(request.body.read)
|
33
|
+
end
|
34
|
+
|
35
|
+
def command_params
|
36
|
+
logux_params.dig('commands') || []
|
37
|
+
end
|
38
|
+
|
39
|
+
def meta_params
|
40
|
+
logux_params&.slice('version', 'password')
|
41
|
+
end
|
42
|
+
|
43
|
+
def handle_processing_errors(logux_stream, exception)
|
44
|
+
Logux.configuration.on_error&.call(exception)
|
45
|
+
Logux.logger.error("#{exception}\n#{exception.backtrace.join("\n")}")
|
46
|
+
ensure
|
47
|
+
logux_stream.write(Logux::ErrorRenderer.new(exception).message)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.application
|
53
|
+
Logux::Rack::App
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/tasklib'
|
5
|
+
require_relative 'rack'
|
6
|
+
|
7
|
+
module Logux
|
8
|
+
class RakeTasks < ::Rake::TaskLib
|
9
|
+
attr_accessor :name
|
10
|
+
attr_accessor :verbose
|
11
|
+
attr_accessor :fail_on_error
|
12
|
+
attr_accessor :patterns
|
13
|
+
attr_accessor :formatters
|
14
|
+
attr_accessor :requires
|
15
|
+
attr_accessor :options
|
16
|
+
|
17
|
+
using Logux::Utils
|
18
|
+
|
19
|
+
ACTIONS_NAMESPACE = 'Actions'
|
20
|
+
CHANNELS_NAMESPACE = 'Channels'
|
21
|
+
|
22
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
23
|
+
def initialize(name = :logux)
|
24
|
+
setup(name)
|
25
|
+
namespace(name) do
|
26
|
+
desc 'Lists all Logux action types'
|
27
|
+
task(:actions, [:actions_path]) do |_, task_args|
|
28
|
+
task_args.with_defaults(actions_path: default_actions_path)
|
29
|
+
require_all(task_args.actions_path)
|
30
|
+
report(ACTIONS_HEAD, action_types)
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'Lists all Logux channel'
|
34
|
+
task(:channels, [:channels_path]) do |_, task_args|
|
35
|
+
task_args.with_defaults(channels_path: default_channels_path)
|
36
|
+
require_all(task_args.channels_path)
|
37
|
+
report(CHANNELS_HEAD, channels)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def default_actions_path
|
46
|
+
File.join(Dir.pwd, '**', 'actions')
|
47
|
+
end
|
48
|
+
|
49
|
+
def default_channels_path
|
50
|
+
File.join(Dir.pwd, '**', 'channels')
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def setup(name)
|
56
|
+
@name = name
|
57
|
+
@verbose = true
|
58
|
+
@fail_on_error = true
|
59
|
+
@patterns = []
|
60
|
+
@requires = []
|
61
|
+
@options = []
|
62
|
+
@formatters = []
|
63
|
+
end
|
64
|
+
|
65
|
+
ACTIONS_HEAD = [%w[action.type Class#method]].freeze
|
66
|
+
CHANNELS_HEAD = [%w[channel Class]].freeze
|
67
|
+
private_constant :ACTIONS_HEAD, :CHANNELS_HEAD
|
68
|
+
|
69
|
+
def require_all(path)
|
70
|
+
Dir[File.join(path, '**', '*.rb')].each do |file|
|
71
|
+
require file
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def action_types
|
76
|
+
classes = descendants_of(::Logux::ActionController).sort_by(&:name)
|
77
|
+
classes.flat_map { |klass| action_methods(klass) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def action_methods(klass)
|
81
|
+
prefix = action_type(klass)
|
82
|
+
klass.instance_methods(false).sort.map do |action|
|
83
|
+
[[prefix, action].join('/'), [klass.name, action].join('#')]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def action_type(klass)
|
88
|
+
strip_namespace(klass, ACTIONS_NAMESPACE)
|
89
|
+
end
|
90
|
+
|
91
|
+
def channels
|
92
|
+
classes = descendants_of(::Logux::ChannelController)
|
93
|
+
classes.select(&:name).sort_by(&:name).map do |klass|
|
94
|
+
[channel(klass), klass]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def channel(klass)
|
99
|
+
strip_namespace(klass, CHANNELS_NAMESPACE)
|
100
|
+
end
|
101
|
+
|
102
|
+
def strip_namespace(klass, namespace)
|
103
|
+
klass.name.gsub(/^#{namespace}::/, '').underscore
|
104
|
+
end
|
105
|
+
|
106
|
+
def descendants_of(parent)
|
107
|
+
ObjectSpace.each_object(Class).select { |klass| klass < parent }
|
108
|
+
end
|
109
|
+
|
110
|
+
def report(header, items)
|
111
|
+
output = header + items
|
112
|
+
first_column_length = output.map(&:first).max_by(&:length).length
|
113
|
+
output.each do |entity, klass_name|
|
114
|
+
puts "#{entity.rjust(first_column_length, ' ')} #{klass_name}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class Response
|
5
|
+
attr_reader :status, :action, :meta, :custom_data
|
6
|
+
|
7
|
+
def initialize(status, action:, meta:, custom_data: nil)
|
8
|
+
@status = status
|
9
|
+
@action = action
|
10
|
+
@meta = meta
|
11
|
+
@custom_data = custom_data
|
12
|
+
end
|
13
|
+
|
14
|
+
def format
|
15
|
+
[status, custom_data || meta.id]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/logux/stream.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class Stream
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :stream
|
8
|
+
|
9
|
+
def_delegators :stream, :close
|
10
|
+
|
11
|
+
def initialize(stream)
|
12
|
+
@stream = stream
|
13
|
+
end
|
14
|
+
|
15
|
+
def write(payload)
|
16
|
+
processed_payload = process(payload)
|
17
|
+
Logux.logger.debug("Write to Logux response: #{processed_payload}")
|
18
|
+
stream << processed_payload
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def process(payload)
|
24
|
+
payload.is_a?(::String) ? payload : payload.to_json
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/logux/test.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
module Test
|
5
|
+
class << self
|
6
|
+
attr_accessor :http_requests_enabled
|
7
|
+
|
8
|
+
def enable_http_requests!
|
9
|
+
raise ArgumentError unless block_given?
|
10
|
+
|
11
|
+
begin
|
12
|
+
self.http_requests_enabled = true
|
13
|
+
yield
|
14
|
+
ensure
|
15
|
+
self.http_requests_enabled = false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Client
|
21
|
+
def post(params)
|
22
|
+
if Logux::Test.http_requests_enabled
|
23
|
+
super(params)
|
24
|
+
else
|
25
|
+
Logux::Test::Store.instance.add(params.to_json)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
autoload :Helpers, 'logux/test/helpers'
|
31
|
+
autoload :Store, 'logux/test/store'
|
32
|
+
autoload :Matchers, 'logux/test/matchers'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
Logux::Client.prepend Logux::Test::Client
|