thrift_server 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +2 -0
  3. data/Makefile +27 -14
  4. data/README.md +142 -43
  5. data/circle.yml +14 -0
  6. data/echo_client.rb +22 -3
  7. data/echo_server.rb +23 -16
  8. data/echo_service.thrift +11 -0
  9. data/lib/thrift_server.rb +112 -36
  10. data/lib/thrift_server/instrumentation_middleware.rb +33 -0
  11. data/lib/thrift_server/log_subscriber.rb +60 -0
  12. data/lib/thrift_server/publisher.rb +25 -0
  13. data/lib/thrift_server/rpc_metrics_subscriber.rb +25 -0
  14. data/lib/thrift_server/server_metrics_subscriber.rb +32 -0
  15. data/lib/thrift_server/thread_pool_server.rb +115 -0
  16. data/lib/thrift_server/threaded_server.rb +87 -0
  17. data/lib/thrift_server/validation_middleware.rb +14 -0
  18. data/lib/thrift_server/version.rb +2 -2
  19. data/script/circleci/cache-image +10 -0
  20. data/test/log_subscriber_test.rb +86 -0
  21. data/test/processor_test.rb +189 -0
  22. data/test/rpc_metrics_subscriber_test.rb +44 -0
  23. data/test/server_metrics_subscriber_test.rb +51 -0
  24. data/test/support/log_yielder.rb +32 -0
  25. data/test/support/server_tests.rb +87 -0
  26. data/test/test_helper.rb +32 -23
  27. data/test/thread_pool_server_test.rb +101 -0
  28. data/test/threaded_server_test.rb +56 -0
  29. data/test/validation_middleware_test.rb +71 -0
  30. data/thrift_server.gemspec +2 -2
  31. metadata +34 -22
  32. data/lib/thrift_server/error_tracking_middleware.rb +0 -12
  33. data/lib/thrift_server/honeybadger_error_tracker.rb +0 -11
  34. data/lib/thrift_server/logging_middleware.rb +0 -13
  35. data/lib/thrift_server/metrics_middleware.rb +0 -16
  36. data/script/buildbox/ci +0 -5
  37. data/script/buildbox/step_failed +0 -5
  38. data/test/acceptance_test.rb +0 -225
  39. data/test/error_tracking_middleware_test.rb +0 -25
  40. data/test/honeybadger_error_tracking_test.rb +0 -12
  41. data/test/logging_middleware_test.rb +0 -42
  42. data/test/metrics_middleware_test.rb +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 41fa482d2834f442ecc5900d82d3042ac91512a7
4
- data.tar.gz: 65b5c1e6814cf096dbe6ab3492d859adad5cbc06
3
+ metadata.gz: 3c56d48273a394c80954def741cd2a44a285a019
4
+ data.tar.gz: 81f9248137da46630ed0bb415245db3121cb7087
5
5
  SHA512:
6
- metadata.gz: 1a51fac841e4c11faa84121d6b13cf20e068c62e323e7bcd4d3fca7d1541818df1417f5fe67951f7f7d22baa73b3d396b051bdf9e1d072abd5ceb584b1665416
7
- data.tar.gz: 3dee701b61ca406d448684664ed5ddec2d7e1e02b55b421622d54bbd38676885d1f65e20278b9a44a258fb7d0e1837472c50970911885d911ac0fbc0c8e48a97
6
+ metadata.gz: bfb2867a32d7603d8f511bd35afe3e1df1b8f707c42829a4d9790de1b02e350586d7c237be54bd0b75b5310b6d70a75558762379c2ed13b3c8b6168f95a9f9be
7
+ data.tar.gz: 7ff824cc62f161ac6d18779b035632a081eefd9cfdb2b66d8d1e9a2745c239f242ccce628b8874fe7214c90778ba4a10afc14051849f151832efbf38f51e00e6
data/Dockerfile CHANGED
@@ -9,4 +9,6 @@ ADD thrift_server.gemspec /usr/src/app/thrift_server.gemspec
9
9
 
10
10
  RUN bundle install
11
11
 
12
+ ADD . /usr/src/app
13
+
12
14
  CMD [ "bundle", "console" ]
data/Makefile CHANGED
@@ -2,16 +2,22 @@ APP:=thrift_server
2
2
 
3
3
  .DEFAULT_GOAL:=build
4
4
 
5
- APP_RUN:=docker run --rm -t -v $(CURDIR):/usr/src/app $(APP)
5
+ # CircleCI does not support --rm, so if the environment variable has
6
+ # value, then don't include --rm.
7
+ ifneq ($(shell echo $$CIRCLECI),)
8
+ DOCKER_RUN:=docker run -it
9
+ else
10
+ DOCKER_RUN:=docker run --rm -it
11
+ endif
6
12
 
7
13
  gen-rb/echo_service.rb: echo_service.thrift
8
- docker run --rm -t -v $(CURDIR):/data thrift:0.9.2 \
14
+ $(DOCKER_RUN) -v $(CURDIR):/data thrift:0.9.2 \
9
15
  thrift -o /data --gen rb /data/$<
10
16
 
11
17
  .PHONY: thrift
12
18
  thrift: gen-rb/echo_service.rb
13
19
 
14
- tmp/image: Dockerfile Gemfile lib/thrift_server/version.rb thrift_server.gemspec
20
+ tmp/image: Dockerfile lib/thrift_server/version.rb thrift_server.gemspec gen-rb/echo_service.rb
15
21
  docker build -t $(APP) .
16
22
  mkdir -p $(@D)
17
23
  docker inspect -f '{{.Id}}' $(APP) >> $@
@@ -19,24 +25,31 @@ tmp/image: Dockerfile Gemfile lib/thrift_server/version.rb thrift_server.gemspec
19
25
  .PHONY: build
20
26
  build: tmp/image
21
27
 
28
+ .PHONY: test
29
+ test: tmp/image
30
+ $(DOCKER_RUN) -v $(CURDIR):/usr/src/app $(APP) bundle exec rake
31
+
22
32
  .PHONY: test-unit
23
- test-unit: tmp/image
24
- $(APP_RUN) bundle exec rake
33
+ test-lib: tmp/image
34
+ $(DOCKER_RUN) $(APP) bundle exec rake
25
35
 
26
36
  .PHONY: test-network
27
- test-network: tmp/image gen-rb/echo_service.rb
28
- -@docker stop server > /dev/null 2>&1
29
- -@docker rm -v server > /dev/null 2>&1
30
- docker run -d --name server -v $(CURDIR):/usr/src/app $(APP) ruby echo_server.rb
31
- docker run --rm -t -v $(CURDIR):/usr/src/app --link server:server $(APP) ruby echo_client.rb server 9090
37
+ test-network: tmp/image gen-rb/echo_service.rb clean-containers
38
+ docker run -d --name threaded_server $(APP) ruby echo_server.rb --threaded
39
+ docker run -d --name thread_pool_server $(APP) ruby echo_server.rb --thread-pool
40
+ $(DOCKER_RUN) --link threaded_server:server $(APP) ruby echo_client.rb server 9090
41
+ $(DOCKER_RUN) --link thread_pool_server:server $(APP) ruby echo_client.rb server 9090
32
42
 
33
43
  .PHONY: test-ci
34
- test-ci: test-unit test-network
44
+ test-ci: test-lib test-network
45
+
46
+ .PHONY: clean-containers
47
+ clean-containers:
48
+ -@docker stop thread_pool_server threaded_server > /dev/null 2>&1
49
+ -@docker rm -v thread_pool_server threaded_server > /dev/null 2>&1
35
50
 
36
51
  .PHONY: clean
37
- clean:
38
- -docker stop server > /dev/null 2>&1
39
- -docker rm -v server > /dev/null 2>&1
52
+ clean: clean-containers
40
53
  @mkdir -p tmp
41
54
  @touch tmp/image
42
55
  -cat tmp/image | xargs --no-run-if-empty docker rmi
data/README.md CHANGED
@@ -3,13 +3,11 @@
3
3
  Encapsulate bolierplate code and functionality for running Thrift
4
4
  servers in Ruby. Bundled functionality:
5
5
 
6
- * Error tracking
7
- * Metrics on each RPC
8
- * Logging on each RPC
9
- * Middleware based approaching making it easy to extend
10
- * Binary protocol
11
- * Framed transport
12
- * Thread pool sever
6
+ * Metrics for server & RPCs
7
+ * Logging for server events & RPCs
8
+ * Middleware & pub-sub based approach making it easy to extend
9
+ * Deep validation on outgoing protocol messages
10
+ * `Thrift::ThreadPoolServer` & `Thrift::ThreadedServer` support
13
11
 
14
12
  ## Installation
15
13
 
@@ -27,31 +25,129 @@ Or install it yourself as:
27
25
 
28
26
  ## Usage
29
27
 
30
- The libary is defined to wrapping Thrift's processor classes in useful
31
- functionality for production servers. You must provide objects
32
- required to implement the behavior. Here's an example from
33
- [echo\_server.rb](echo_server.rb).
28
+ The library uses delegation to around a provided handler & Thrift
29
+ processor to implement use production server behavior. There are two
30
+ different ways to extend function: pub sub & middleware. Use pub sub
31
+ for events & middleware when you want to modify the request & response
32
+ before/after hitting the handler. Out of the box there is not extra
33
+ behavior. Here's the bare bones useful server.
34
34
 
35
- server = ThriftServer.build(EchoService::Processor, Handler.new, {
36
- logger: Logger.new($stdout),
37
- statsd: Statsd.new,
38
- error_tracker: ErrorTracker.new
39
- })
35
+ server = ThriftServer.threaded EchoService, Handler.new
40
36
 
41
- # Now call serve to start it
42
- server.serve
37
+ server.log Logger.new($stdout)
43
38
 
44
- The first arument is a generated `Thrift::Processor` subclass. The second
45
- is your implementation of the define protocol. The options hash
46
- defines all the misc objecs and settings. The following options are
47
- available:
39
+ server.start
48
40
 
49
- * `logger:` - a `Logger` instance
50
- * `statsd:` - a `Statsd` instance from the [statsd-ruby gem](https://github.com/reinh/statsd)
51
- * `error_tracker:` object that implement `#track(rpc, error)`. Use
52
- `ThriftServer::HoneybaderErrorTracker` if unsure
53
- * `threads:` - number of threads to run. Defaults to `4`
54
- * `port:` - port to run server on. Defaults to `9090`
41
+ The first arument is a module containing a generated
42
+ `Thrift::Processor` subclass or the module itself. `ThriftServer`
43
+ provides factories for building `Thrift::ThreadedServer` and
44
+ `Thrift::ThreadPoolServer` instances.
45
+
46
+ ### Threaded Servers
47
+
48
+ server = ThriftServer.threaded EchoService, Handler.new
49
+
50
+ server.log Logger.new($stdout)
51
+
52
+ server.start
53
+
54
+ `ThriftServer.threaded` accepts the following options:
55
+
56
+ * `port:` - port to run server on. Defaults to `9090`.
57
+
58
+ ### Thread Pool Servers
59
+
60
+ server = ThriftServer.thread_pool EchoService, Handler.new
61
+
62
+ server.log Logger.new($stdout)
63
+
64
+ server.start
65
+
66
+ `ThriftServer.threaded` accepts the following options:
67
+
68
+ * `threads:` - Pool size. Defaults to `25`.
69
+ * `port:` - port to run server on. Defaults to `9090`.
70
+
71
+ ## Pub Sub
72
+
73
+ Subscriber objects may be attached. The following events are
74
+ published:
75
+
76
+ * `rpc_incoming` - published everytime the server receives an RPC
77
+ * `rpc_ok` - Everything when according to plan
78
+ * `rpc_exception` - Handler raised an exception defined in the
79
+ protocol
80
+ * `rpc_error` - Handler raised an unexpected error (useful for error
81
+ tracking)
82
+ * `server_start` - Start started
83
+ * `server_connection_opened` - Client TCP connection
84
+ * `server_connection_closed` - Client TCP disconnect
85
+ * `thread_pool_server_pool_change` - Thread pool grow/shinks
86
+
87
+ The listener should implement a method. A listener will only receive
88
+ notifications if the appropriate method is implemented. Here's an
89
+ example:
90
+
91
+ class Counter
92
+ def initialize
93
+ @counter = 0
94
+ end
95
+
96
+ def rpc_incoming(rpc)
97
+ @counter += 1
98
+ end
99
+ end
100
+
101
+ server = ThriftServer.threaded EchoService, Handler.new
102
+ server.subscribe Counter.new
103
+
104
+ ### Built-in Subscribers
105
+
106
+ `ThriftServer` includes three subscribers: two for metrics and one for
107
+ logging. The `ThriftServer::LogSubscriber` uses a standard library
108
+ logger to print useful information when any of the previously
109
+ mentioned events happen. Attaching the subscriber is so important that
110
+ it has its own helper method.
111
+
112
+ server.log Logger.new($stdout)
113
+ # Same thing as above, just much longer
114
+ server.subscribe ThriftServer::LogSubscriber.new(Logger.new($stdout))
115
+
116
+ The remaining two middlware handle metrics. Each handles a different
117
+ metric granularity. `ThriftServer::ServerMetricsSubcriber` does stats
118
+ for all RPCs & server things. `ThriftServer::RpcMetricsSubscriber`
119
+ gives metrics for each individual RPC. Naturally there are important
120
+ subscribers and its highly recommend you add them. There is a shortcut
121
+ method for adding them both. They require a [statsd][] instance. You
122
+ can customize statsd prefix & postfix on that instance.
123
+
124
+ server.metrics Statsd.new
125
+ # Same thing as above, just much longer
126
+ server.subscribe ThriftServer::ServerMetricsSubscriber.new(Statsd.new)
127
+ server.subscribe ThriftServer::RpcMetricsSubscriber.new(Statsd.new)
128
+
129
+ `ThriftServer::ServerMetricsSubscriber` instruments the following
130
+ keys:
131
+
132
+ * `rpc.latency` - (timer)
133
+ * `rpc.incoming` - (counter)
134
+ * `rpc.success` - (counter) - Everything A-OK!
135
+ * `rpc.exception` - (counter) - Result was defined protocol exception
136
+ * `rpc.error` - (counter) - Uncaught errors
137
+ * `server.pool.size` - (guage) - Number of available threads
138
+ * `server.connection.active` - (guage) - Threads with active TCP connections
139
+
140
+ `ThriftServer::RpcMetricsSubscriber` produces the same metrics, but at
141
+ an individual RPC level. Assume the RPC is named `foo`.
142
+ keys:
143
+
144
+ * `rpc.foo.latency` (timer)
145
+ * `rpc.foo.incoming` (counter)
146
+ * `rpc.foo.success` (counter)
147
+ * `rpc.foo.exception` (counter)
148
+ * `rpc.foo.exception.xxx` (counter) - where `xxx` is listed in
149
+ `throws` in the Thrift IDL.
150
+ * `rpc.foo.error` (counter)
55
151
 
56
152
  ## Middleware
57
153
 
@@ -63,25 +159,26 @@ implement your own middleware easily. The middleware must respond to
63
159
  `args`. Here's an example that dumps the `args` to stdout.
64
160
 
65
161
  class ExampleMiddleware
66
- include Concord.new(:app)
162
+ include Concord.new(:app)
67
163
 
68
- def call(rpc)
69
- puts rpc.args.inspect
70
- app.call rpc
71
- end
164
+ def call(rpc)
165
+ puts rpc.args.inspect
166
+ app.call rpc
167
+ end
72
168
  end
73
169
 
74
170
  New middleware can be added at build time or afterwards.
75
171
 
76
- ThriftServer::build processor, handler, options do |stack|
77
- stack.use ExampleMiddlware
78
- end
172
+ ThriftServer.threaded processor, handler, options do |stack|
173
+ stack.use ExampleMiddlware
174
+ end
79
175
 
80
176
  Middleware can also be added after the server is built
81
177
 
82
- server = ThriftServer::build processor, handler, options
83
- server.use ExampleMiddleware
84
- serve.serve # start it!
178
+ server = ThriftServer.threaded processor, handler, options
179
+ server.use ExampleMiddleware
180
+
181
+ server.start # start it!
85
182
 
86
183
  ## Implementation
87
184
 
@@ -95,10 +192,10 @@ wrap the handler provided in `initialize` in the delegate class.
95
192
 
96
193
  ## Development
97
194
 
98
- $ vagrant up
99
- $ vagrant ssh
100
- $ cd /vagrant
101
- $ make test-ci
195
+ $ vagrant up
196
+ $ vagrant ssh
197
+ $ cd /vagrant
198
+ $ make test-ci
102
199
 
103
200
  ## Contributing
104
201
 
@@ -106,3 +203,5 @@ wrap the handler provided in `initialize` in the delegate class.
106
203
  2. Commit your changes (`git commit -am 'Add some feature'`)
107
204
  3. Push to the branch (`git push origin my-new-feature`)
108
205
  4. Create a new Pull Request
206
+
207
+ [statsd]: https://github.com/reinh/statsd
@@ -0,0 +1,14 @@
1
+ machine:
2
+ services:
3
+ - docker
4
+ dependencies:
5
+ cache_directories:
6
+ - "tmp/cache"
7
+ override:
8
+ - mkdir -p tmp/cache
9
+ - script/circleci/cache-image tmp/cache/ruby ruby:2.1
10
+ - script/circleci/cache-image tmp/cache/thrift thrift:0.9.2
11
+ test:
12
+ override:
13
+ - make test-ci
14
+ - make clean
@@ -23,13 +23,32 @@ transport.open
23
23
  protocol = Thrift::BinaryProtocol.new transport
24
24
  client = EchoService::Client.new protocol
25
25
 
26
- result = client.echo "testing"
26
+ response = client.echo "testing"
27
27
 
28
- transport.close
28
+ if response == 'testing'
29
+ $stdout.puts 'OK'
30
+ else
31
+ $stderr.puts 'Message not echoed'
32
+ abort
33
+ end
34
+
35
+ response = client.structEcho 'testing'
29
36
 
30
- if result == 'testing'
37
+ if response.message == 'testing'
31
38
  $stdout.puts 'OK'
32
39
  else
33
40
  $stderr.puts 'Message not echoed'
34
41
  abort
35
42
  end
43
+
44
+ begin
45
+ client.structEcho 'exception'
46
+ rescue EchoException
47
+ $stdout.puts 'OK'
48
+ else
49
+ abort 'Protocol exception lost'
50
+ end
51
+
52
+ client.ping 'hello'
53
+
54
+ transport.close
@@ -9,25 +9,32 @@ $LOAD_PATH << "#{__dir__}/gen-rb"
9
9
  require 'echo_service'
10
10
 
11
11
  class Handler
12
- def echo(msg)
13
- msg
12
+ def echo(message)
13
+ message
14
14
  end
15
- end
16
15
 
17
- class ErrorTracker
18
- def track(*)
16
+ def structEcho(message)
17
+ case message
18
+ when /exception/
19
+ fail EchoException
20
+ else
21
+ EchoResponse.new message: message
22
+ end
23
+ end
19
24
 
25
+ def ping(message)
26
+ # async, into the ether!
20
27
  end
21
28
  end
22
29
 
23
- logger = Logger.new $stdout
24
-
25
- server = ThriftServer.build(EchoService::Processor, Handler.new, {
26
- logger: logger,
27
- statsd: Statsd.new,
28
- error_tracker: ErrorTracker.new
29
- })
30
-
31
- logger.info 'Starting server'
32
-
33
- server.serve
30
+ if ARGV.include?('--threaded')
31
+ server = ThriftServer.threaded EchoService, Handler.new
32
+ server.log Logger.new($stdout)
33
+ server.start
34
+ elsif ARGV.include?('--thread-pool')
35
+ server = ThriftServer.thread_pool EchoService, Handler.new
36
+ server.log Logger.new($stdout)
37
+ server.start
38
+ else
39
+ abort 'No server option given'
40
+ end
@@ -1,3 +1,14 @@
1
+ exception EchoException { }
2
+
3
+ struct EchoResponse {
4
+ 1: required string message
5
+ }
6
+
1
7
  service EchoService {
2
8
  string echo(1: string message)
9
+
10
+ EchoResponse structEcho(1: string message)
11
+ throws (1: EchoException echo)
12
+
13
+ oneway void ping(1: string message)
3
14
  }
@@ -1,19 +1,43 @@
1
1
  require "thrift_server/version"
2
2
 
3
3
  require 'thrift'
4
+ require 'thrift-validator'
4
5
  require 'middleware'
5
6
  require 'concord'
6
7
  require 'forwardable'
7
- require 'honeybadger'
8
8
  require 'statsd-ruby'
9
9
 
10
- require_relative 'thrift_server/logging_middleware'
11
- require_relative 'thrift_server/metrics_middleware'
12
- require_relative 'thrift_server/error_tracking_middleware'
13
- require_relative 'thrift_server/honeybadger_error_tracker'
10
+ require_relative 'thrift_server/instrumentation_middleware'
11
+ require_relative 'thrift_server/validation_middleware'
14
12
 
15
- class ThriftServer
16
- RPC = Struct.new(:name, :args)
13
+ require_relative 'thrift_server/server_metrics_subscriber'
14
+ require_relative 'thrift_server/rpc_metrics_subscriber'
15
+ require_relative 'thrift_server/log_subscriber'
16
+
17
+ require_relative 'thrift_server/publisher'
18
+
19
+ require_relative 'thrift_server/thread_pool_server'
20
+ require_relative 'thrift_server/threaded_server'
21
+
22
+ module ThriftServer
23
+ RPC = Struct.new(:name, :args, :exceptions) do
24
+ def initialize(*)
25
+ super
26
+ self.exceptions ||= { }
27
+ end
28
+
29
+ def protocol_exception?(ex)
30
+ exceptions.values.include? ex.class
31
+ end
32
+
33
+ def exception_name(ex)
34
+ exceptions.invert.fetch ex.class
35
+ end
36
+
37
+ def to_s
38
+ name.to_s
39
+ end
40
+ end
17
41
 
18
42
  class MiddlewareStack < Middleware::Builder
19
43
  def finalize!
@@ -22,7 +46,7 @@ class ThriftServer
22
46
  end
23
47
  end
24
48
 
25
- class StackDelegate
49
+ class HandlerWrapper
26
50
  class Dispatcher
27
51
  include Concord.new(:app, :handler)
28
52
 
@@ -33,15 +57,14 @@ class ThriftServer
33
57
 
34
58
  extend Forwardable
35
59
 
36
- include Concord.new(:stack, :handler)
37
-
38
- def_delegator :stack, :use
60
+ include Concord::Public.new(:stack, :publisher, :handler)
39
61
 
40
62
  def call(rpc)
41
63
  app.call rpc
42
64
  end
43
65
 
44
66
  private
67
+
45
68
  def app
46
69
  @app ||= finalize_stack!
47
70
  end
@@ -53,54 +76,107 @@ class ThriftServer
53
76
  end
54
77
 
55
78
  class << self
56
- def build(processor, handler, options = { }, &block)
57
- stack = wrap(processor, options, &block).new handler
58
- transport = Thrift::ServerSocket.new options.fetch(:port, 9090)
79
+ def thread_pool(root, handler, options = { })
80
+ stack = wrap(root, options).new handler
81
+
82
+ threads, port = options.fetch(:threads, 25), options.fetch(:port, 9090)
83
+
84
+ transport = Thrift::ServerSocket.new port
59
85
  transport_factory = Thrift::FramedTransportFactory.new
60
86
 
61
- Thrift::ThreadPoolServer.new stack, transport, transport_factory, nil, options.fetch(:threads, 4)
87
+ ThreadPoolServer.new(stack, transport, transport_factory, nil, threads).tap do |server|
88
+ # Assign bookkeeping data that is spread across multiple objects
89
+ server.port = port
90
+
91
+ yield server if block_given?
92
+ end
62
93
  end
63
94
 
64
- def wrap(processor, options = { })
65
- rpcs = processor.instance_methods.select { |m| m =~ /^process_(.+)$/ }
95
+ def threaded(root, handler, options = { })
96
+ stack = wrap(root, options).new handler
97
+
98
+ port = options.fetch :port, 9090
99
+
100
+ transport = Thrift::ServerSocket.new port
101
+ transport_factory = Thrift::FramedTransportFactory.new
102
+
103
+ ThreadedServer.new(stack, transport, transport_factory).tap do |server|
104
+ # Assign bookkeeping data that is spread across multiple objects
105
+ server.port = port
66
106
 
67
- logger = options.fetch :logger do
68
- fail ArgumentError, ':logger required'
107
+ yield server if block_given?
69
108
  end
109
+ end
110
+
111
+ def wrap(root, options = { })
112
+ processor = root < ::Thrift::Processor ? root : root.const_get(:Processor)
113
+
114
+ processors = processor.ancestors.select do |ancestor|
115
+ ancestor < ::Thrift::Processor
116
+ end
117
+
118
+ processor_rpcs = processors.each_with_object({ }) do |ancestor, bucket|
119
+ rpc_methods = ancestor.
120
+ instance_methods(include_superclass = false).
121
+ select { |m| m =~ /^process_(.+)$/ }
70
122
 
71
- statsd = options.fetch :statsd do
72
- fail ArgumentError, ':statsd required'
123
+ rpc_names = rpc_methods.map do |rpc_method|
124
+ rpc_method.to_s.match(/^process_(.+)$/)[1]
125
+ end
126
+
127
+ bucket[ancestor] = rpc_names.map(&:to_sym)
73
128
  end
74
129
 
75
- error_tracker = options.fetch :error_tracker do
76
- fail ArgumentError, ':error_tracker required'
130
+ rpc_names = processor_rpcs.flat_map do |_, values|
131
+ values
77
132
  end
78
133
 
79
- stack = MiddlewareStack.new
80
- stack.use ErrorTrackingMiddleware, error_tracker
81
- stack.use MetricsMiddleware, statsd
82
- stack.use LoggingMiddleware, logger
134
+ rpc_protocol_exceptions = processor_rpcs.each_with_object({ }) do |(processor_klass, rpcs), bucket|
135
+ rpcs.each do |rpc|
136
+ result_class = rpc.to_s
137
+ result_class[0] = result_class[0].upcase
138
+ result_class_name = "#{result_class}_result"
139
+
140
+ service_namespace = processor_klass.name.match(/^(.+)::Processor$/)[1]
141
+
142
+ fields = Object.const_get "#{service_namespace}::#{result_class_name}::FIELDS"
83
143
 
84
- yield stack if block_given?
144
+ exception_fields = fields.values.select do |meta|
145
+ meta.key?(:class) && meta.fetch(:class) < ::Thrift::Exception
146
+ end
147
+
148
+ bucket[rpc] = exception_fields.each_with_object({ }) do |meta, exceptions|
149
+ exceptions[meta.fetch(:name).to_sym] = meta.fetch(:class)
150
+ end
151
+ end
152
+ end
153
+
154
+ publisher = Publisher.new
155
+
156
+ stack = MiddlewareStack.new
157
+ stack.use InstrumentationMiddleware, publisher
158
+ stack.use ValidationMiddleware
85
159
 
86
160
  wrapped = Class.new processor do
87
161
  extend Forwardable
88
162
 
89
- def_delegator :@handler, :use
163
+ def_delegators :@handler, :stack
164
+ def_delegators :@handler, :publisher
165
+
166
+ def_delegators :stack, :use
167
+ def_delegators :publisher, :publish, :subscribe
90
168
 
91
169
  define_method :initialize do |handler|
92
- stack_delegator = Class.new StackDelegate
170
+ stack_delegator = Class.new HandlerWrapper
93
171
  stack_delegator.module_eval do
94
- rpcs.each do |method|
95
- rpc_name = method.to_s.match(/^process_(.+)$/)[1]
96
-
97
- define_method rpc_name.to_sym do |*args|
98
- call RPC.new(rpc_name, args)
172
+ rpc_names.each do |rpc_name|
173
+ define_method rpc_name do |*args|
174
+ call RPC.new(rpc_name, args, rpc_protocol_exceptions.fetch(rpc_name, [ ]))
99
175
  end
100
176
  end
101
177
  end
102
178
 
103
- super stack_delegator.new(stack, handler)
179
+ super stack_delegator.new(stack, publisher, handler)
104
180
  end
105
181
  end
106
182