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 +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
|