cotton-tail 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b9caf210e7f688e34cc64e7e212d1290692a0559ffaa9f72a948c416dda3b7d
4
- data.tar.gz: a8657ad7cd3486e2ab3fb0fab7cd4a707b647fa9141f1184dda77afc2584601b
3
+ metadata.gz: 162f785a8c6f03379f26cc784a5c4a8e28a831d1f1b1145452addb16532f04c2
4
+ data.tar.gz: b5336ab860aafca2a2a15d9c6bc75aff74155bf43ee47948b2ea46688f805cdf
5
5
  SHA512:
6
- metadata.gz: 6818a57540fa93192d254fe1884907d6c91fa9c745642b9b3d5b66025ed682d485f5201580cecdf715d5017c47aea47f71d457e6604b5cac4bee9c9401699847
7
- data.tar.gz: 38f8f0afda612cd5045006cc5f03c164b70cac9b086f4a948c914e12d6e95c780e6b2ffdaf00cc150eac2956330ad7fd519d9a5f05bb462711163f4a635ddf50
6
+ metadata.gz: 1e95e2b691fbfe5ebdbc39b857fd26912015b7b8b74a0a7820540a2b5ab957ad437c06c235149cf7e8bbdadd4634007ce4ce83fc07c7e193c8996d0f71ea3e0f
7
+ data.tar.gz: 21e79d79373570d5d1371d29de75e0e918060f63c0877439bdf9da23ef030ccc85009d68d0f148c585e253a09a95d6dbd6f1a620b6cb67d94bbde1e0dd16df6e
checksums.yaml.gz.sig CHANGED
Binary file
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cotton-tail (0.2.1)
4
+ cotton-tail (0.3.0)
5
5
  bunny (~> 2.12)
6
+ ibsciss-middleware (~> 0.4.2)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
@@ -20,6 +21,7 @@ GEM
20
21
  faraday_middleware (0.12.2)
21
22
  faraday (>= 0.7.4, < 1.0)
22
23
  hashie (3.6.0)
24
+ ibsciss-middleware (0.4.2)
23
25
  jaro_winkler (1.5.1)
24
26
  multi_json (1.13.1)
25
27
  multipart-post (2.0.0)
data/cotton-tail.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.require_paths = ['lib']
30
30
 
31
31
  spec.add_dependency 'bunny', '~> 2.12'
32
+ spec.add_dependency 'ibsciss-middleware', '~> 0.4.2'
32
33
 
33
34
  spec.add_development_dependency 'bundler', '~> 1.16'
34
35
  spec.add_development_dependency 'rabbitmq_http_api_client', '~> 1.9'
data/examples/app.rb CHANGED
@@ -4,7 +4,9 @@
4
4
  require 'bundler/setup'
5
5
  require 'cotton_tail'
6
6
 
7
- app = CottonTail::App.new.define do
7
+ app = CottonTail::App.new
8
+
9
+ app.routes.draw do
8
10
  # Create the queue 'hello_world_queue' if it does not exists
9
11
  queue 'hello_world_queue', exclusive: true do
10
12
  # Create a binding from the default topic exchange ('amq.topic') to
@@ -18,26 +20,30 @@ app = CottonTail::App.new.define do
18
20
  puts 'Goodbye cruel world!'
19
21
  end
20
22
 
21
- handle 'inspect.message' do |delivery_info, properties, message|
23
+ handle 'inspect.message' do |env, routing_key, delivery_info, properties, payload|
24
+ puts env: env
25
+ puts routing_key: routing_key
22
26
  puts delivery_info: delivery_info
23
27
  puts properties: properties
24
- puts message: message
28
+ puts payload: payload
25
29
  end
26
30
  end
27
31
 
28
32
  queue 'require_ack_queue', exclusive: true, manual_ack: true do
29
- handle 'get.acked' do |delivery_info, _props, _msg, opts|
30
- conn = opts[:conn]
33
+ handle 'get.acked' do |_env, _routing_key, delivery_info, _properties, _message|
31
34
  delivery_tag = delivery_info[:delivery_tag]
32
35
  puts "acking with #{delivery_tag}"
33
- conn.ack(delivery_tag)
36
+
37
+ ch = delivery_info[:channel]
38
+ ch.ack(delivery_tag)
34
39
  end
35
40
 
36
- handle 'get.nacked' do |delivery_info, _props, _msg, opts|
37
- conn = opts[:conn]
41
+ handle 'get.nacked' do |_env, _routing_key, delivery_info, _properties, _message|
38
42
  delivery_tag = delivery_info[:delivery_tag]
39
43
  puts "nacking with #{delivery_tag}"
40
- conn.nack(delivery_tag)
44
+
45
+ ch = delivery_info[:channel]
46
+ ch.nack(delivery_tag)
41
47
  end
42
48
  end
43
49
  end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'cotton_tail'
6
+
7
+ app = CottonTail::App.new
8
+
9
+ app.config.middleware do |_b|
10
+ # This is added to the end of the middleware stack
11
+ # 'message' is the return value of the handlers defined below
12
+ d.use ->(message) { puts message.upcase }
13
+ end
14
+
15
+ app.routes.draw do
16
+ queue 'hello_world_queue', exclusive: true do
17
+ handle 'say.hello' do
18
+ 'Hello world!'
19
+ end
20
+
21
+ handle 'say.goodbye' do
22
+ 'Goodbye cruel world!'
23
+ end
24
+ end
25
+ end
26
+
27
+ app.start
@@ -3,22 +3,17 @@
3
3
  module CottonTail
4
4
  # App is the main class for a CottonTail server
5
5
  class App
6
- def initialize(queue_strategy: Queue::Bunny, routing_strategy: Router)
7
- @dependencies = {
8
- queue_strategy: queue_strategy,
9
- routing_strategy: routing_strategy
10
- }
11
- end
6
+ attr_reader :env, :config
12
7
 
13
- # Define message routing
14
- def define(&block)
15
- @definition = DSL::App.new(**@dependencies)
16
- @definition.instance_eval(&block)
17
- self
8
+ def initialize(queue_strategy: Queue::Bunny, env: {}, connection: Bunny.new, config: Configuration.new)
9
+ @dependencies = { queue_strategy: queue_strategy, connection: connection }
10
+ @env = env
11
+ @config = config
12
+ @connection = connection.start
18
13
  end
19
14
 
20
15
  def queues
21
- @definition.queues
16
+ routes.queues
22
17
  end
23
18
 
24
19
  # Get a single message queue
@@ -38,11 +33,15 @@ module CottonTail
38
33
  sleep 0.01 while running?
39
34
  end
40
35
 
36
+ def routes
37
+ @routes ||= DSL::Routes.new(**@dependencies)
38
+ end
39
+
41
40
  private
42
41
 
43
42
  def supervisors
44
43
  @supervisors ||= queues.map do |_name, queue|
45
- Queue::Supervisor.new(queue, on_message: @definition.router)
44
+ Queue::Supervisor.new(queue, app: self)
46
45
  end
47
46
  end
48
47
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'middleware'
4
+
3
5
  module CottonTail
4
6
  # Configuration options
5
7
  class Configuration
@@ -7,11 +9,57 @@ module CottonTail
7
9
 
8
10
  def initialize
9
11
  @connection_args = nil
12
+ @middleware = Middleware::DEFAULT_STACK
13
+ @user_configs = {}
10
14
  end
11
15
 
16
+ # Sets the RabbitMQ connection params. Arguments are eventually passed
17
+ # to Bunny.new. Any valid params for Bunny.new are accepted.
18
+ #
19
+ # @see http://rubybunny.info/articles/connecting.html
12
20
  def connection_args=(*args, **kwargs)
13
21
  url, = args
14
22
  @connection_args = url ? Bunny::Session.parse_uri(url) : kwargs
15
23
  end
24
+
25
+ # Modify or retrieve the application middleware stack.
26
+ #
27
+ # @see https://github.com/Ibsciss/ruby-middleware
28
+ def middleware
29
+ return @middleware unless block_given?
30
+
31
+ @middleware = ::Middleware::Builder.new do |b|
32
+ b.use @middleware
33
+ yield b
34
+ end
35
+ end
36
+
37
+ def method_missing(method_id, *arguments, &block)
38
+ if user_config? method_id
39
+ @user_configs[method_id]
40
+ elsif setter?(method_id) && arguments.length == 1
41
+ @user_configs[getter_name(method_id)] = arguments.first
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ def respond_to_missing?(method_id, include_private = false)
48
+ user_config?(method_id) || super
49
+ end
50
+
51
+ private
52
+
53
+ def setter?(method_id)
54
+ method_id.to_s.end_with? '='
55
+ end
56
+
57
+ def user_config?(method_id)
58
+ @user_configs.key?(method_id)
59
+ end
60
+
61
+ def getter_name(setter)
62
+ setter.to_s.sub('=', '').to_sym
63
+ end
16
64
  end
17
65
  end
@@ -4,18 +4,22 @@ module CottonTail
4
4
  module DSL
5
5
  # This is the top level DSL for defining the bindings and message routing of
6
6
  # a cotton App
7
- class App
7
+ class Routes
8
8
  attr_reader :queues
9
9
 
10
- def initialize(queue_strategy:, routing_strategy:)
10
+ def initialize(queue_strategy:, connection:)
11
11
  @queue_strategy = queue_strategy
12
- @routing_strategy = routing_strategy
12
+ @connection = connection
13
13
  @queues = {}
14
14
  end
15
15
 
16
+ def draw(&block)
17
+ instance_eval(&block)
18
+ end
19
+
16
20
  # Define a new queue
17
21
  def queue(name, **opts, &block)
18
- @queue_strategy.call(name: name, **opts).tap do |queue_instance|
22
+ @queue_strategy.call(name: name, connection: @connection, **opts).tap do |queue_instance|
19
23
  @queues[name] = queue_instance
20
24
  queue_dsl = Queue.new(name, queue_instance, self)
21
25
  queue_dsl.instance_eval(&block) if block_given?
@@ -39,11 +43,11 @@ module CottonTail
39
43
 
40
44
  def handle(key, handler = nil, &block)
41
45
  handler ||= block
42
- router.route key, handler
46
+ handlers[key] = handler
43
47
  end
44
48
 
45
- def router
46
- @router ||= @routing_strategy.call
49
+ def handlers
50
+ @handlers ||= {}
47
51
  end
48
52
  end
49
53
  end
@@ -9,9 +9,9 @@ module CottonTail
9
9
  @context = context
10
10
  end
11
11
 
12
- def handle(routing_suffix, klass)
12
+ def handle(routing_suffix, handler = nil, &block)
13
13
  key = routing_key(routing_suffix)
14
- @context.instance_eval { handle key, klass }
14
+ @context.instance_eval { handle key, handler, &block }
15
15
  end
16
16
 
17
17
  private
@@ -3,7 +3,7 @@
3
3
  module CottonTail
4
4
  # DSL namespace
5
5
  module DSL
6
- autoload :App, 'cotton_tail/dsl/app'
6
+ autoload :Routes, 'cotton_tail/dsl/routes'
7
7
  autoload :Queue, 'cotton_tail/dsl/queue'
8
8
  autoload :Topic, 'cotton_tail/dsl/topic'
9
9
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CottonTail
4
+ module Middleware
5
+ # Router Middleware
6
+ class Router
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(request)
12
+ message = parse(request)
13
+ request.shift
14
+ request.unshift message.app.env
15
+ @app.call handler(message.app, message.routing_key).call(request)
16
+ end
17
+
18
+ private
19
+
20
+ def handler(app, route)
21
+ handlers(app).fetch(route) { raise UndefinedRouteError }
22
+ end
23
+
24
+ def parse(msg)
25
+ Message.new(*msg)
26
+ end
27
+
28
+ def handlers(app)
29
+ app.routes.handlers
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'middleware'
4
+ require 'logger'
5
+
6
+ module CottonTail
7
+ # Top level namespace for Middleware
8
+ module Middleware
9
+ autoload :Router, 'cotton_tail/middleware/router'
10
+
11
+ DEFAULT_STACK = ::Middleware::Builder.new do |b|
12
+ b.use Router
13
+ end
14
+ end
15
+ end
@@ -10,15 +10,16 @@ module CottonTail
10
10
  class Bunny < SimpleDelegator
11
11
  extend Forwardable
12
12
 
13
- def self.call(name:, **opts)
14
- new(name, **opts)
13
+ def self.call(**opts)
14
+ new(**opts)
15
15
  end
16
16
 
17
- def initialize(name, manual_ack: false, **opts)
17
+ def initialize(name:, connection:, manual_ack: false, **opts)
18
18
  super ::Queue.new
19
19
 
20
20
  @name = name
21
21
  @source_opts = opts
22
+ @connection = connection
22
23
 
23
24
  watch_source manual_ack
24
25
  end
@@ -40,16 +41,12 @@ module CottonTail
40
41
 
41
42
  private
42
43
 
43
- def_delegator :'CottonTail.configuration', :connection_args
44
+ attr_reader :connection
44
45
 
45
46
  def watch_source(manual_ack)
46
47
  source.subscribe(manual_ack: manual_ack) { |*args| self << args }
47
48
  end
48
49
 
49
- def connection
50
- @connection ||= ::Bunny.new(*connection_args).start
51
- end
52
-
53
50
  def source
54
51
  @source ||= channel.queue(@name, **@source_opts)
55
52
  end
@@ -10,9 +10,9 @@ module CottonTail
10
10
  Thread.new { new(queue, **kwargs).start }
11
11
  end
12
12
 
13
- def initialize(queue, on_message:)
13
+ def initialize(queue, app:)
14
14
  @queue = queue
15
- @on_message = on_message
15
+ @app = app
16
16
  end
17
17
 
18
18
  def fiber
@@ -22,10 +22,18 @@ module CottonTail
22
22
  end
23
23
 
24
24
  def start
25
- while fiber.alive?
26
- args = fiber.resume
27
- @on_message.call(*args) if args
28
- end
25
+ call_next while fiber.alive?
26
+ end
27
+
28
+ private
29
+
30
+ def call_next
31
+ args = fiber.resume
32
+ middleware.call([@app, *args]) if args
33
+ end
34
+
35
+ def middleware
36
+ @app.config.middleware
29
37
  end
30
38
  end
31
39
  end
@@ -6,9 +6,9 @@ module CottonTail
6
6
  module Queue
7
7
  # A supervisor for a single queue
8
8
  class Supervisor
9
- def initialize(queue, on_message:)
9
+ def initialize(queue, app:)
10
10
  @queue = queue
11
- @on_message = on_message
11
+ @app = app
12
12
  end
13
13
 
14
14
  def start
@@ -28,7 +28,7 @@ module CottonTail
28
28
  private
29
29
 
30
30
  def process
31
- @process ||= Reader.spawn(@queue, on_message: @on_message)
31
+ @process ||= Reader.spawn(@queue, app: @app)
32
32
  end
33
33
  end
34
34
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CottonTail
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/cotton_tail.rb CHANGED
@@ -7,19 +7,16 @@ module CottonTail
7
7
  autoload :App, 'cotton_tail/app'
8
8
  autoload :Configuration, 'cotton_tail/configuration'
9
9
  autoload :DSL, 'cotton_tail/dsl'
10
+ autoload :Middleware, 'cotton_tail/middleware'
10
11
  autoload :Queue, 'cotton_tail/queue'
11
12
  autoload :Router, 'cotton_tail/router'
12
13
  autoload :Version, 'cotton_tail/version'
13
14
 
14
- class << self
15
- def configure
16
- return configuration unless block_given?
15
+ # Message is a struct for working with the messages that are passed through
16
+ # the middleware stack.
17
+ Message = Struct.new(:app, :routing_key, :delivery_info, :properties,
18
+ :payload)
17
19
 
18
- yield configuration
19
- end
20
-
21
- def configuration
22
- @configuration ||= Configuration.new
23
- end
20
+ class UndefinedRouteError < StandardError
24
21
  end
25
22
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cotton-tail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Brennan
@@ -30,7 +30,7 @@ cert_chain:
30
30
  fXe/xr/Sc+2wCjHPVE2J+auN5hk3KCp1I4s2fKqyLIwyhTEF3shuYfCpC8rt/YdN
31
31
  cy9/lg5LCI3OvakzxL4Xt1Sq4h/xJZ06ydTVJ1wxfk6BXHrg
32
32
  -----END CERTIFICATE-----
33
- date: 2018-11-16 00:00:00.000000000 Z
33
+ date: 2018-11-21 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: bunny
@@ -46,6 +46,20 @@ dependencies:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
48
  version: '2.12'
49
+ - !ruby/object:Gem::Dependency
50
+ name: ibsciss-middleware
51
+ requirement: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 0.4.2
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.4.2
49
63
  - !ruby/object:Gem::Dependency
50
64
  name: bundler
51
65
  requirement: !ruby/object:Gem::Requirement
@@ -169,6 +183,7 @@ files:
169
183
  - cotton-tail.gemspec
170
184
  - docker-compose.yml
171
185
  - examples/app.rb
186
+ - examples/app_with_middleware.rb
172
187
  - examples/messages/get.acked
173
188
  - examples/messages/get.nacked
174
189
  - examples/messages/inspect.message
@@ -178,15 +193,16 @@ files:
178
193
  - lib/cotton_tail/app.rb
179
194
  - lib/cotton_tail/configuration.rb
180
195
  - lib/cotton_tail/dsl.rb
181
- - lib/cotton_tail/dsl/app.rb
182
196
  - lib/cotton_tail/dsl/queue.rb
197
+ - lib/cotton_tail/dsl/routes.rb
183
198
  - lib/cotton_tail/dsl/topic.rb
199
+ - lib/cotton_tail/middleware.rb
200
+ - lib/cotton_tail/middleware/router.rb
184
201
  - lib/cotton_tail/queue.rb
185
202
  - lib/cotton_tail/queue/bunny.rb
186
203
  - lib/cotton_tail/queue/memory.rb
187
204
  - lib/cotton_tail/queue/reader.rb
188
205
  - lib/cotton_tail/queue/supervisor.rb
189
- - lib/cotton_tail/router.rb
190
206
  - lib/cotton_tail/version.rb
191
207
  homepage: https://github.com/jamesBrennan/cotton-tail
192
208
  licenses:
metadata.gz.sig CHANGED
Binary file
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CottonTail
4
- # Register message handlers and dispatch messages to them
5
- class Router
6
- def self.call
7
- new
8
- end
9
-
10
- def initialize
11
- @handlers = {}
12
- end
13
-
14
- def route(key, handler)
15
- @handlers[key] = handler
16
- end
17
-
18
- def dispatch(key, *args)
19
- @handlers[key].call(*args)
20
- end
21
-
22
- alias call dispatch
23
- end
24
- end