thrift_server 0.1.1 → 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.
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