thrift-rack-middleware 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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