thrift-rack-middleware 0.1.0 → 0.2.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.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A simple rack middleware that can intercept HTTP thrift requests.
4
4
 
5
- This is useful when used in cobination with a pooled application server like passenger.
5
+ This is useful when used in combination with a pooled application server like passenger.
6
6
  It is not an easy task to have thrift directly target worker processes that application servers like passenger manage.
7
7
 
8
8
  Accessing the handler applications via thrift this was may not be the fastest, but it is one of the easier ways to get started.
@@ -20,13 +20,14 @@ If you want to add this middleware to a Rails application, add the following to
20
20
 
21
21
  ### Defaults
22
22
 
23
- * `hook_path` defaults to `'/thrift_rpc'`
23
+ * `hook_path` defaults to `'/rpc_api'`
24
24
  * `protocol_factory` defaults to `Thrift::BinaryProtocolFactory.new`
25
25
  * `logger` see logging section below
26
26
 
27
27
  #### Logging
28
- You can optionally pass in a custom logger instance. If your application is a
29
- Rails application, Rails.logger will automatically be used. Otherwise, logging
28
+ You can optionally pass in a custom logger instance. If your application is a
29
+ Rails application, Rails.logger will automatically be used. If your application
30
+ is a Rack application, rack logger will automatically be used. Otherwise, logging
30
31
  will be directed to STDOUT
31
32
 
32
33
  ## Future features
@@ -21,18 +21,18 @@
21
21
  # access via Thrift. You have to insert it into the MiddlewareStack of Rails
22
22
  # within a custom initializer and not within the environment, because Thrift
23
23
  # is not fully loaded at that point.
24
- #
24
+ #
25
25
  # Here is a sample of to to use it:
26
- #
26
+ #
27
27
  # ActionController::Dispatcher.middleware.insert_before Rails::Rack::Metal, Thrift::RackMiddleware,
28
28
  # { :processor => YourCustomProcessor.new,
29
29
  # :hook_path => "/the_path_to_receive_api_calls",
30
30
  # :protocol_factory => Thrift::BinaryProtocolAcceleratedFactory.new }
31
- #
31
+ #
32
32
  # Some benchmarking showed this is much slower then any Thrift solution without
33
33
  # Rails, but it is still fast enough if you need to integrate your Rails app
34
34
  # into a Thrift-based infrastructure.
35
- #
35
+ #
36
36
  begin
37
37
  require 'rack'
38
38
  require 'rack/response'
@@ -42,6 +42,7 @@ rescue LoadError => e
42
42
  end
43
43
 
44
44
  require "thrift"
45
+ require "thrift/rack_middleware/logger"
45
46
 
46
47
  module Thrift
47
48
  class RackMiddleware
@@ -56,47 +57,49 @@ module Thrift
56
57
  end
57
58
 
58
59
  def call(env)
59
- set_logger(env)
60
60
  request = ::Rack::Request.new(env)
61
- # Need to add in more logging about the thrift object that was sent in if possible.
62
- if request.post? && request.path == hook_path
63
- # Try to parse the method called from the request body
64
- rpc_method = request.body.read.match(/(?<method_name>[a-z_0-9]+)/i).try { |match| match[:method_name] }
65
- request.body.rewind
66
- request.logger.info "[#{Time.now}] #{@hook_path} called with method: #{rpc_method}"
67
- start_time = Time.now
68
- output = StringIO.new
69
- transport = IOStreamTransport.new(request.body, output)
70
- protocol = @protocol_factory.get_protocol(transport)
61
+
62
+ if request.post? && request.path == @hook_path
63
+ process(request)
64
+ else
65
+ @app.call(env)
66
+ end
67
+ end
68
+
69
+ def process(request)
70
+ log = Thrift::Rack::Middleware::Logger.new(request.env).or(@logger).create!
71
+
72
+ rpc_method = parse_rpc_method(request)
73
+ log.method_name(@hook_path, rpc_method)
74
+
75
+ output = StringIO.new
76
+ transport = IOStreamTransport.new(request.body, output)
77
+ protocol = @protocol_factory.get_protocol(transport)
78
+
79
+ log.processing_time(@hook_path, rpc_method) do
71
80
  begin
72
81
  @processor.process(protocol, protocol)
73
82
  rescue
74
- request.logger.error "There was an error processing the thrift request!"
75
- request.body.rewind
76
- request.logger.error " request body: #{request.body.read}"
77
- output.rewind
78
- request.logger.error " output: #{output.read}"
79
- raise $! # reraise the exception
83
+ log.error($!, request)
80
84
  end
81
-
82
- request.logger.info " Total time taken processing RPC request for #{rpc_method}: #{Time.now - start_time} seconds"
83
- output.rewind
84
- response = ::Rack::Response.new(output)
85
- response["Content-Type"] = "application/x-thrift"
86
- response.finish
87
- else
88
- @app.call(env)
89
85
  end
86
+
87
+ return_output(output)
88
+ end
89
+
90
+ def return_output(output)
91
+ output.rewind
92
+ response = ::Rack::Response.new(output)
93
+ response["Content-Type"] = "application/x-thrift"
94
+ response.finish
90
95
  end
91
96
 
92
- def set_logger(env)
93
- env['rack.logger'] = if @logger
94
- @logger
95
- elsif defined?(Rails) && Rails.logger
96
- Rails.logger
97
- else
98
- Logger.new(STDOUT)
99
- end
97
+ # Try to parse the method called from the request body
98
+ def parse_rpc_method(request)
99
+ rpc_method_match = request.body.read.match(/([a-z_0-9]+)/i)
100
+ rpc_method = rpc_method_match ? rpc_method_match[1] : 'UNKNOWN'
101
+ request.body.rewind
102
+ rpc_method
100
103
  end
101
104
  end
102
105
  end
@@ -0,0 +1,64 @@
1
+ require "logger"
2
+ require "benchmark"
3
+
4
+ module Thrift
5
+ module Rack
6
+ module Middleware
7
+ class Logger
8
+
9
+ def initialize(env)
10
+ @env = env
11
+ end
12
+
13
+ def or(logger)
14
+ @logger = logger if logger
15
+ self
16
+ end
17
+
18
+ def create!
19
+ return self if @logger
20
+
21
+ @logger = if defined?(Rails) && Rails.logger
22
+ Rails.logger
23
+ elsif env.key? "rack.logger"
24
+ env["rack.logger"]
25
+ else
26
+ Logger.new(STDOUT)
27
+ end
28
+
29
+ self
30
+ end
31
+
32
+ def processing_time(hook_path, rpc_method, &block)
33
+ time = Benchmark.realtime &block
34
+ @logger.info "Completed #{hook_path}##{rpc_method} in #{time_to_readable(time)}"
35
+ end
36
+
37
+ def error(error, request)
38
+ @logger.error "Error processing thrift request"
39
+ request.body.rewind
40
+ @logger.error " request body: '#{binary_to_readable(request.body.read)}'"
41
+
42
+ @logger.error error
43
+ @logger.error error.backtrace.join("\n\t")
44
+
45
+ raise error # reraise the error
46
+ end
47
+
48
+ def method_name(hook_path, rpc_method)
49
+ @logger.info "Called #{hook_path}##{rpc_method}"
50
+ end
51
+
52
+ private
53
+
54
+ def time_to_readable(time)
55
+ time >= 1 ? "#{time.round(3)}s" : "#{(time * 1000).round}ms"
56
+ end
57
+
58
+ def binary_to_readable(input)
59
+ input.gsub(/[^[:print:]]/) { |x| "\\#{x.ord}" }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,7 +1,7 @@
1
1
  module Thrift
2
2
  module Rack
3
3
  module Middleware
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
6
6
  end
7
7
  end
@@ -24,10 +24,11 @@ require 'thrift/rack_middleware'
24
24
 
25
25
  describe RackMiddleware do
26
26
  before(:each) do
27
- @processor = mock("Processor")
28
- @factory = mock("ProtocolFactory")
29
- @mock_app = mock("AnotherRackApp")
30
- @middleware = RackMiddleware.new(@mock_app, :processor => @processor, :protocol_factory => @factory)
27
+ @processor = double("Processor", :process => nil)
28
+ @factory = double("ProtocolFactory", :get_protocol => double)
29
+ @mock_app = double("AnotherRackApp")
30
+ @logger = double("Logger").as_null_object
31
+ @middleware = RackMiddleware.new(@mock_app, :processor => @processor, :protocol_factory => @factory, :logger => @logger)
31
32
  end
32
33
 
33
34
  it "should call next rack application in the stack if request was not a post and not pointed to the hook_path" do
@@ -37,47 +38,55 @@ require 'thrift/rack_middleware'
37
38
  @middleware.call(env)
38
39
  end
39
40
 
40
- it "should serve using application/x-thrift" do
41
- env = {"REQUEST_METHOD" => "POST", "PATH_INFO" => "/rpc_api"}
42
- IOStreamTransport.stub!(:new)
43
- @factory.stub!(:get_protocol)
44
- @processor.stub!(:process)
45
- response = mock("RackResponse")
46
- response.should_receive(:[]=).with("Content-Type", "application/x-thrift")
47
- response.should_receive(:finish)
48
- Rack::Response.should_receive(:new).and_return(response)
49
- @middleware.call(env)
50
- end
41
+ context "thrift request" do
42
+ let(:request_body) { StringIO.new 'test_method' }
43
+ let(:env) { {"REQUEST_METHOD" => "POST", "PATH_INFO" => "/rpc_api", "rack.input" => request_body} }
51
44
 
52
- it "should use the IOStreamTransport" do
53
- body = mock("body")
54
- env = {"REQUEST_METHOD" => "POST", "PATH_INFO" => "/rpc_api", "rack.input" => body}
55
- output = mock("output")
56
- output.should_receive(:rewind)
57
- StringIO.should_receive(:new).and_return(output)
58
- protocol = mock("protocol")
59
- transport = mock("transport")
60
- IOStreamTransport.should_receive(:new).with(body, output).and_return(transport)
61
- @factory.should_receive(:get_protocol).with(transport).and_return(protocol)
62
- @processor.should_receive(:process).with(protocol, protocol)
63
- response = mock("RackResponse")
64
- response.stub!(:[]=)
65
- response.should_receive(:finish)
66
- Rack::Response.should_receive(:new).and_return(response)
67
- @middleware.call(env)
45
+ it "should serve using application/x-thrift" do
46
+ response = double("RackResponse")
47
+ response.should_receive(:[]=).with("Content-Type", "application/x-thrift")
48
+ response.should_receive(:finish)
49
+ Rack::Response.should_receive(:new).and_return(response)
50
+ @middleware.call(env)
51
+ end
52
+
53
+ it "should use the IOStreamTransport" do
54
+ protocol = double("protocol")
55
+ transport = double("transport")
56
+ IOStreamTransport.should_receive(:new).with(request_body, instance_of(StringIO)).and_return(transport)
57
+ @factory.should_receive(:get_protocol).with(transport).and_return(protocol)
58
+ @processor.should_receive(:process).with(protocol, protocol)
59
+ response = double("RackResponse")
60
+ response.stub(:[]=)
61
+ response.should_receive(:finish)
62
+ Rack::Response.should_receive(:new).and_return(response)
63
+ @middleware.call(env)
64
+ end
65
+
66
+ it "should log incoming method names" do
67
+ @logger.should_receive(:info).twice.with(/test_method/)
68
+ @middleware.call(env)
69
+ end
70
+
71
+ it "should log failures" do
72
+ error = RuntimeError.new('Fake Error')
73
+ @processor.stub(:process).and_raise(error)
74
+ @logger.should_receive(:error).with(error)
75
+ expect { @middleware.call(env) }.to raise_error(error)
76
+ end
68
77
  end
69
78
 
70
79
  it "should have appropriate defaults for hook_path and protocol_factory" do
71
- mock_factory = mock("BinaryProtocolFactory")
72
- mock_proc = mock("Processor")
80
+ mock_factory = double("BinaryProtocolFactory")
81
+ mock_proc = double("Processor")
73
82
  BinaryProtocolFactory.should_receive(:new).and_return(mock_factory)
74
83
  rack_middleware = RackMiddleware.new(@mock_app, :processor => mock_proc)
75
84
  rack_middleware.hook_path.should == "/rpc_api"
76
85
  end
77
86
 
78
87
  it "should understand :hook_path, :processor and :protocol_factory" do
79
- mock_proc = mock("Processor")
80
- mock_factory = mock("ProtocolFactory")
88
+ mock_proc = double("Processor")
89
+ mock_factory = double("ProtocolFactory")
81
90
 
82
91
  rack_middleware = RackMiddleware.new(@mock_app, :processor => mock_proc,
83
92
  :protocol_factory => mock_factory,
@@ -19,7 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  # specify any dependencies here; for example:
22
- s.add_development_dependency "rspec"
22
+ s.add_development_dependency "rspec", "~> 2.14.0"
23
+ s.add_development_dependency "rake"
23
24
  s.add_runtime_dependency "rack", '>= 1.1.0'
24
25
  s.add_runtime_dependency "thrift", ">= 0.9.0"
25
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thrift-rack-middleware
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-08-31 00:00:00.000000000 Z
12
+ date: 2013-12-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70220388130540 !ruby/object:Gem::Requirement
16
+ requirement: &70297905478640 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.14.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70297905478640
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70297905478220 !ruby/object:Gem::Requirement
17
28
  none: false
18
29
  requirements:
19
30
  - - ! '>='
@@ -21,10 +32,10 @@ dependencies:
21
32
  version: '0'
22
33
  type: :development
23
34
  prerelease: false
24
- version_requirements: *70220388130540
35
+ version_requirements: *70297905478220
25
36
  - !ruby/object:Gem::Dependency
26
37
  name: rack
27
- requirement: &70220388130000 !ruby/object:Gem::Requirement
38
+ requirement: &70297905477680 !ruby/object:Gem::Requirement
28
39
  none: false
29
40
  requirements:
30
41
  - - ! '>='
@@ -32,10 +43,10 @@ dependencies:
32
43
  version: 1.1.0
33
44
  type: :runtime
34
45
  prerelease: false
35
- version_requirements: *70220388130000
46
+ version_requirements: *70297905477680
36
47
  - !ruby/object:Gem::Dependency
37
48
  name: thrift
38
- requirement: &70220388129400 !ruby/object:Gem::Requirement
49
+ requirement: &70297905477180 !ruby/object:Gem::Requirement
39
50
  none: false
40
51
  requirements:
41
52
  - - ! '>='
@@ -43,7 +54,7 @@ dependencies:
43
54
  version: 0.9.0
44
55
  type: :runtime
45
56
  prerelease: false
46
- version_requirements: *70220388129400
57
+ version_requirements: *70297905477180
47
58
  description: Rack middleware for thrift services
48
59
  email:
49
60
  - tomas.brazys@gmail.com
@@ -57,6 +68,7 @@ files:
57
68
  - README.md
58
69
  - Rakefile
59
70
  - lib/thrift/rack_middleware.rb
71
+ - lib/thrift/rack_middleware/logger.rb
60
72
  - lib/thrift/rack_middleware/version.rb
61
73
  - spec/rack_middleware_spec.rb
62
74
  - spec/spec_helper.rb
@@ -73,12 +85,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
85
  - - ! '>='
74
86
  - !ruby/object:Gem::Version
75
87
  version: '0'
88
+ segments:
89
+ - 0
90
+ hash: 40008673381423064
76
91
  required_rubygems_version: !ruby/object:Gem::Requirement
77
92
  none: false
78
93
  requirements:
79
94
  - - ! '>='
80
95
  - !ruby/object:Gem::Version
81
96
  version: '0'
97
+ segments:
98
+ - 0
99
+ hash: 40008673381423064
82
100
  requirements: []
83
101
  rubyforge_project: thrift-rack-middleware
84
102
  rubygems_version: 1.8.10