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 +5 -4
- data/lib/thrift/rack_middleware.rb +40 -37
- data/lib/thrift/rack_middleware/logger.rb +64 -0
- data/lib/thrift/rack_middleware/version.rb +1 -1
- data/spec/rack_middleware_spec.rb +44 -35
- data/thrift-rack-middleware.gemspec +2 -1
- metadata +26 -8
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
|
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 `'/
|
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.
|
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
|
-
|
62
|
-
if request.post? && request.path == hook_path
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
@@ -24,10 +24,11 @@ require 'thrift/rack_middleware'
|
|
24
24
|
|
25
25
|
describe RackMiddleware do
|
26
26
|
before(:each) do
|
27
|
-
@processor =
|
28
|
-
@factory =
|
29
|
-
@mock_app =
|
30
|
-
@
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
IOStreamTransport
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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 =
|
72
|
-
mock_proc =
|
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 =
|
80
|
-
mock_factory =
|
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.
|
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-
|
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: &
|
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: *
|
35
|
+
version_requirements: *70297905478220
|
25
36
|
- !ruby/object:Gem::Dependency
|
26
37
|
name: rack
|
27
|
-
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: *
|
46
|
+
version_requirements: *70297905477680
|
36
47
|
- !ruby/object:Gem::Dependency
|
37
48
|
name: thrift
|
38
|
-
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: *
|
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
|