stark-rack 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ class Stark::Rack
2
+ # Slightly more verbose protocol so that we can get struct and field names
3
+ # included.
4
+ class VerboseProtocol < Thrift::BinaryProtocol
5
+ def write_struct_begin(name)
6
+ write_string name
7
+ end
8
+
9
+ def read_struct_begin
10
+ read_string
11
+ end
12
+
13
+ def write_field_begin(name, type, id)
14
+ write_string name
15
+ super
16
+ end
17
+
18
+ def write_field_stop
19
+ write_string ""
20
+ super
21
+ end
22
+
23
+ def read_field_begin
24
+ name = read_string
25
+ result = super
26
+ result[0] = name unless name.empty?
27
+ result
28
+ end
29
+ end
30
+
31
+ class VerboseProtocolFactory < Thrift::BinaryProtocolFactory
32
+ def get_protocol(trans)
33
+ return VerboseProtocol.new(trans)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "stark-rack"
5
+ s.version = "1.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Evan Phoenix"]
9
+ s.date = "2013-05-22"
10
+ s.description = "Provides middleware for mounting Stark/Thrift services as Rack endpoints."
11
+ s.email = ["evan@phx.io"]
12
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
13
+ s.files = [".autotest", ".gemtest", "History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib/stark/rack.rb", "lib/stark/rack/content_negotiation.rb", "lib/stark/rack/logging_processor.rb", "lib/stark/rack/metadata.rb", "lib/stark/rack/rest.rb", "stark-rack.gemspec", "test/calc-opt.rb", "test/calc.thrift", "test/config.ru", "test/gen-rb/calc.rb", "test/gen-rb/calc_constants.rb", "test/gen-rb/calc_types.rb", "test/helper.rb", "test/test_metadata.rb", "test/test_rack.rb", "test/test_rest.rb"]
14
+ s.homepage = "https://github.com/evanphx/stark-rack"
15
+ s.rdoc_options = ["--main", "README.txt"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = "stark-rack"
18
+ s.rubygems_version = "1.8.23"
19
+ s.summary = "Provides middleware for mounting Stark/Thrift services as Rack endpoints."
20
+ s.test_files = ["test/test_metadata.rb", "test/test_rack.rb", "test/test_rest.rb"]
21
+
22
+ if s.respond_to? :specification_version then
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<stark>, ["< 2.0.0"])
27
+ s.add_development_dependency(%q<rdoc>, ["~> 4.0"])
28
+ s.add_development_dependency(%q<rack>, [">= 1.5.0"])
29
+ s.add_development_dependency(%q<hoe>, ["~> 3.6"])
30
+ else
31
+ s.add_dependency(%q<stark>, ["< 2.0.0"])
32
+ s.add_dependency(%q<rdoc>, ["~> 4.0"])
33
+ s.add_dependency(%q<rack>, [">= 1.5.0"])
34
+ s.add_dependency(%q<hoe>, ["~> 3.6"])
35
+ end
36
+ else
37
+ s.add_dependency(%q<stark>, ["< 2.0.0"])
38
+ s.add_dependency(%q<rdoc>, ["~> 4.0"])
39
+ s.add_dependency(%q<rack>, [">= 1.5.0"])
40
+ s.add_dependency(%q<hoe>, ["~> 3.6"])
41
+ end
42
+ end
data/test/calc-opt.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Calc
2
+ class Client
3
+ def add(lhs, rhs)
4
+ op = @oprot
5
+ op.write_message_begin 'add', ::Thrift::MessageTypes::CALL, 0
6
+ op.write_struct_begin "add_args"
7
+ op.write_field_begin 'lhs', ::Thrift::Types::I32, 1
8
+ op.write_i32 lhs
9
+ op.write_field_end
10
+ op.write_field_begin 'rhs', ::Thrift::Types::I32, 2
11
+ op.write_i32 rhs
12
+ op.write_field_end
13
+ op.write_field_stop
14
+ op.write_struct_end
15
+ op.write_message_end
16
+ op.trans.flush
17
+ ip = @iprot
18
+ fname, mtype, rseqid = ip.read_message_begin
19
+ handle_exception mtype
20
+ ip.read_struct_begin
21
+ rname, rtype, rid = ip.read_field_begin
22
+ result = ip.read_i32
23
+ rname, rtype, rid = ip.read_field_begin
24
+ fail if rtype != ::Thrift::Types::STOP
25
+ ip.read_field_end
26
+ ip.read_struct_end
27
+ ip.read_message_end
28
+ return result
29
+ end
30
+ end
31
+ end
data/test/calc.thrift ADDED
@@ -0,0 +1,13 @@
1
+ struct State {
2
+ 1: i32 last_result
3
+ 2: map<string,i32> vars
4
+ }
5
+
6
+ service Calc {
7
+ i32 add(1: i32 lhs, 2: i32 rhs)
8
+ i32 last_result()
9
+ void store_vars(1: map<string,i32> vars)
10
+ i32 get_var(1: string name)
11
+ void set_state(1: State state)
12
+ State get_state()
13
+ }
data/test/config.ru ADDED
@@ -0,0 +1,19 @@
1
+ require 'thrift/rack'
2
+
3
+ $:.unshift File.expand_path("../gen-rb", __FILE__)
4
+
5
+ require 'calc'
6
+
7
+ class CalcImpl
8
+ def add(lhs, rhs)
9
+ lhs + rhs
10
+ end
11
+ end
12
+
13
+ impl = CalcImpl.new
14
+
15
+ processor = Calc::Processor.new impl
16
+
17
+ obj = Thrift::Rack.new processor
18
+
19
+ run obj
@@ -0,0 +1,80 @@
1
+ #
2
+ # Autogenerated by Thrift Compiler (0.8.0)
3
+ #
4
+ # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
+ #
6
+
7
+ require 'thrift'
8
+ require 'calc_types'
9
+
10
+ module Calc
11
+ class Client
12
+ include ::Thrift::Client
13
+
14
+ def add(lhs, rhs)
15
+ send_add(lhs, rhs)
16
+ return recv_add()
17
+ end
18
+
19
+ def send_add(lhs, rhs)
20
+ send_message('add', Add_args, :lhs => lhs, :rhs => rhs)
21
+ end
22
+
23
+ def recv_add()
24
+ result = receive_message(Add_result)
25
+ return result.success unless result.success.nil?
26
+ raise ::Thrift::ApplicationException.new(::Thrift::ApplicationException::MISSING_RESULT, 'add failed: unknown result')
27
+ end
28
+
29
+ end
30
+
31
+ class Processor
32
+ include ::Thrift::Processor
33
+
34
+ def process_add(seqid, iprot, oprot)
35
+ args = read_args(iprot, Add_args)
36
+ result = Add_result.new()
37
+ result.success = @handler.add(args.lhs, args.rhs)
38
+ write_result(result, oprot, 'add', seqid)
39
+ end
40
+
41
+ end
42
+
43
+ # HELPER FUNCTIONS AND STRUCTURES
44
+
45
+ class Add_args
46
+ include ::Thrift::Struct, ::Thrift::Struct_Union
47
+ LHS = 1
48
+ RHS = 2
49
+
50
+ FIELDS = {
51
+ LHS => {:type => ::Thrift::Types::I32, :name => 'lhs'},
52
+ RHS => {:type => ::Thrift::Types::I32, :name => 'rhs'}
53
+ }
54
+
55
+ def struct_fields; FIELDS; end
56
+
57
+ def validate
58
+ end
59
+
60
+ ::Thrift::Struct.generate_accessors self
61
+ end
62
+
63
+ class Add_result
64
+ include ::Thrift::Struct, ::Thrift::Struct_Union
65
+ SUCCESS = 0
66
+
67
+ FIELDS = {
68
+ SUCCESS => {:type => ::Thrift::Types::I32, :name => 'success'}
69
+ }
70
+
71
+ def struct_fields; FIELDS; end
72
+
73
+ def validate
74
+ end
75
+
76
+ ::Thrift::Struct.generate_accessors self
77
+ end
78
+
79
+ end
80
+
@@ -0,0 +1,8 @@
1
+ #
2
+ # Autogenerated by Thrift Compiler (0.8.0)
3
+ #
4
+ # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
+ #
6
+
7
+ require 'calc_types'
8
+
@@ -0,0 +1,7 @@
1
+ #
2
+ # Autogenerated by Thrift Compiler (0.8.0)
3
+ #
4
+ # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
+ #
6
+
7
+
data/test/helper.rb ADDED
@@ -0,0 +1,60 @@
1
+ module TestHelper
2
+ class Handler
3
+ def initialize(n)
4
+ @state = n::State.new
5
+ end
6
+ def add(a,b)
7
+ @state.last_result = a + b
8
+ end
9
+ def last_result
10
+ @state.last_result
11
+ end
12
+ def store_vars(hash)
13
+ @state.vars ||= {}
14
+ hash.each do |k,v|
15
+ @state.vars[k] = v.to_i
16
+ end
17
+ end
18
+ def get_var(name)
19
+ @state.vars[name]
20
+ end
21
+ def get_state
22
+ @state
23
+ end
24
+ def set_state(state)
25
+ @state = state
26
+ end
27
+ end
28
+
29
+ def setup
30
+ @n = Module.new
31
+
32
+ Stark.materialize File.expand_path("../calc.thrift", __FILE__), @n
33
+
34
+ @sr, @cw = IO.pipe
35
+ @cr, @sw = IO.pipe
36
+
37
+ @prev_logger = Stark.logger
38
+ @log_stream = StringIO.new
39
+ Stark.logger = Logger.new @log_stream
40
+
41
+ @client_t = Thrift::IOStreamTransport.new @cr, @cw
42
+ @client_p = Thrift::BinaryProtocol.new @client_t
43
+
44
+ @client = @n::Calc::Client.new @client_p, @client_p
45
+ @handler = Handler.new(@n)
46
+ end
47
+
48
+ def teardown
49
+ print @log_stream.string unless passed?
50
+ Stark.logger = @prev_logger
51
+
52
+ @client_t.close
53
+ @sr.close
54
+ @sw.close
55
+ end
56
+
57
+ def stark_rack
58
+ @stark_rack ||= Stark::Rack.new(@n::Calc::Processor.new(@handler), :log => false)
59
+ end
60
+ end
@@ -0,0 +1,43 @@
1
+ require "test/unit"
2
+ require "stark/rack"
3
+ require "stark/rack/metadata"
4
+ require "helper"
5
+
6
+ class TestMetadata < Test::Unit::TestCase
7
+ include TestHelper
8
+
9
+ def setup
10
+ super
11
+ @metadata = { 'version' => '1.0 baby', 'name' => "This is a sweet service" }
12
+ @rack = Stark::Rack::Metadata.new stark_rack, @metadata
13
+ end
14
+
15
+ def test_call_to_metadata
16
+ @client = Stark::Rack::Metadata::Client.new @client_p, @client_p
17
+ st = Thread.new do
18
+ env = { 'rack.input' => @sr, 'PATH_INFO' => '/metadata', 'REQUEST_METHOD' => 'POST' }
19
+ code, headers, out = @rack.call env
20
+
21
+ out.each do |s|
22
+ @sw << s
23
+ end
24
+ end
25
+
26
+ assert_equal @metadata, @client.metadata
27
+ end
28
+
29
+ def test_call_add_passes_through_metadata
30
+ st = Thread.new do
31
+ env = { 'rack.input' => @sr, 'PATH_INFO' => '/', 'REQUEST_METHOD' => 'POST' }
32
+ code, headers, out = @rack.call env
33
+
34
+ out.each do |s|
35
+ @sw << s
36
+ end
37
+ end
38
+
39
+ result = @client.add 1, 1
40
+ assert_equal 2, result
41
+ end
42
+
43
+ end
data/test/test_rack.rb ADDED
@@ -0,0 +1,65 @@
1
+ require "test/unit"
2
+ require "stark/rack"
3
+ require "helper"
4
+
5
+ class TestRack < Test::Unit::TestCase
6
+ include TestHelper
7
+
8
+
9
+ def test_call_to_thrift
10
+ st = Thread.new do
11
+ env = { 'rack.input' => @sr, 'REQUEST_METHOD' => 'POST' }
12
+ env['PATH_INFO'] = ''
13
+
14
+ code, headers, out = stark_rack.call env
15
+
16
+ out.each do |s|
17
+ @sw << s
18
+ end
19
+ end
20
+
21
+ out = @client.add 3, 4
22
+
23
+ assert_equal 7, out
24
+ end
25
+
26
+ def test_json_protocol
27
+ @client_p = Thrift::JsonProtocol.new @client_t
28
+ @client = @n::Calc::Client.new @client_p, @client_p
29
+
30
+ st = Thread.new do
31
+ env = { 'rack.input' => @sr, 'REQUEST_METHOD' => 'POST' }
32
+ env['PATH_INFO'] = ''
33
+ env['HTTP_CONTENT_TYPE'] = 'application/vnd.thrift+json'
34
+
35
+ code, headers, out = stark_rack.call env
36
+
37
+ out.each do |s|
38
+ @sw << s
39
+ end
40
+ end
41
+
42
+ out = @client.add 3, 4
43
+
44
+ assert_equal 7, out
45
+ end
46
+
47
+ def test_call_with_wrong_method
48
+ env = { 'REQUEST_METHOD' => 'GET' }
49
+ env['PATH_INFO'] = '/'
50
+
51
+ code, headers, out = stark_rack.call env
52
+
53
+ assert_equal 405, code
54
+ end
55
+
56
+ def test_call_to_undefined_url
57
+ env = { 'REQUEST_METHOD' => 'POST' }
58
+ env['PATH_INFO'] = '/blah'
59
+
60
+ code, headers, out = stark_rack.call env
61
+
62
+ assert_equal 404, code
63
+ end
64
+
65
+ end
data/test/test_rest.rb ADDED
@@ -0,0 +1,175 @@
1
+ require "test/unit"
2
+ require "stark/rack"
3
+ require "stark/rack/metadata"
4
+ require "stark/rack/rest"
5
+ require "helper"
6
+
7
+ class TestREST < Test::Unit::TestCase
8
+ include TestHelper
9
+
10
+ def test_get_last_result
11
+ rack = Stark::Rack::REST.new stark_rack
12
+ @handler.add 2, 2
13
+
14
+ out = ['']
15
+ Thread.new do
16
+ env = {'rack.input' => StringIO.new, 'REQUEST_METHOD' => 'GET',
17
+ 'PATH_INFO' => '/last_result', 'QUERY_STRING' => ''}
18
+
19
+ code, headers, out = rack.call env
20
+ end.join
21
+
22
+ json = '{"result":4}'
23
+ assert_equal json, out.join
24
+ end
25
+
26
+ def test_store_vars_with_single_arg_map
27
+ rack = Stark::Rack::REST.new stark_rack
28
+ Thread.new do
29
+ env = {'rack.input' => StringIO.new, 'REQUEST_METHOD' => 'GET',
30
+ 'PATH_INFO' => '/store_vars', 'QUERY_STRING' => 'a=1&b=2&c=3'}
31
+
32
+ code, headers, out = rack.call env
33
+ end.join
34
+
35
+ assert_equal 1, @handler.get_var('a')
36
+ assert_equal 2, @handler.get_var('b')
37
+ assert_equal 3, @handler.get_var('c')
38
+ end
39
+
40
+ def test_add_with_arg_map
41
+ rack = Stark::Rack::REST.new stark_rack
42
+ @handler.store_vars 'a' => 42
43
+ out = ['']
44
+ Thread.new do
45
+ env = {'rack.input' => StringIO.new, 'REQUEST_METHOD' => 'GET',
46
+ 'PATH_INFO' => '/add', 'QUERY_STRING' => 'arg[1]=1&arg[2]=1'}
47
+
48
+ code, headers, out = rack.call env
49
+ end.join
50
+
51
+ json = '{"result":2}'
52
+ assert_equal json, out.join
53
+ end
54
+
55
+ def test_add_with_arg_map_missing_an_argument
56
+ rack = Stark::Rack::REST.new stark_rack
57
+ @handler.store_vars 'a' => 42
58
+ out = ['']
59
+ Thread.new do
60
+ env = {'rack.input' => StringIO.new, 'REQUEST_METHOD' => 'GET',
61
+ 'PATH_INFO' => '/add', 'QUERY_STRING' => 'arg[2]=1'}
62
+
63
+ code, headers, out = rack.call env
64
+ end.join
65
+
66
+ json = '{"error":"undefined method `+\' for nil:NilClass (NoMethodError)"}'
67
+ assert_equal json, out.join
68
+ end
69
+
70
+ def test_get_var_with_arg_map
71
+ rack = Stark::Rack::REST.new stark_rack
72
+ @handler.store_vars 'a' => 42
73
+ out = ['']
74
+ Thread.new do
75
+ env = {'rack.input' => StringIO.new, 'REQUEST_METHOD' => 'GET',
76
+ 'PATH_INFO' => '/get_var', 'QUERY_STRING' => 'arg[1]=a'}
77
+
78
+ code, headers, out = rack.call env
79
+ end.join
80
+
81
+ json = '{"result":42}'
82
+ assert_equal json, out.join
83
+ end
84
+
85
+ def test_get_var_with_arg_array
86
+ rack = Stark::Rack::REST.new stark_rack
87
+ @handler.store_vars 'a' => 42
88
+ out = ['']
89
+ Thread.new do
90
+ env = {'rack.input' => StringIO.new, 'REQUEST_METHOD' => 'GET',
91
+ 'PATH_INFO' => '/get_var', 'QUERY_STRING' => 'args[]=a'}
92
+
93
+ code, headers, out = rack.call env
94
+ end.join
95
+
96
+ json = '{"result":42}'
97
+ assert_equal json, out.join
98
+ end
99
+
100
+ def test_get_state
101
+ rack = Stark::Rack::REST.new stark_rack
102
+ @handler.store_vars 'a' => 42
103
+ @handler.add 2, 2
104
+ out = ['']
105
+ Thread.new do
106
+ env = {'rack.input' => StringIO.new, 'REQUEST_METHOD' => 'GET',
107
+ 'PATH_INFO' => '/get_state', 'QUERY_STRING' => ''}
108
+
109
+ code, headers, out = rack.call env
110
+ end.join
111
+
112
+ json = '{"result":{"_struct_":"State","1:last_result":4,"2:vars":{"a":42}}}'
113
+ assert_equal json, out.join
114
+ end
115
+
116
+ def test_set_state_with_GET
117
+ rack = Stark::Rack::REST.new stark_rack
118
+ Thread.new do
119
+ env = {'rack.input' => StringIO.new, 'REQUEST_METHOD' => 'GET',
120
+ 'PATH_INFO' => '/set_state',
121
+ 'QUERY_STRING' => '_struct_=State&1-last_result=0&2-vars[a]=1&2-vars[b]=2'}
122
+
123
+ code, headers, out = rack.call env
124
+ end.join
125
+
126
+ assert_equal 0, @handler.last_result
127
+ assert_equal 1, @handler.get_var('a')
128
+ assert_equal 2, @handler.get_var('b')
129
+ end
130
+
131
+ def test_set_state_with_json_POST
132
+ rack = Stark::Rack::REST.new stark_rack
133
+ Thread.new do
134
+ env = {'rack.input' => StringIO.new('[{"_struct_":"State","1:last_result":0,"2:vars":{"a":1,"b":2}}]'),
135
+ 'REQUEST_METHOD' => 'POST', 'PATH_INFO' => '/set_state',
136
+ 'HTTP_CONTENT_TYPE' => 'application/json' }
137
+
138
+ code, headers, out = rack.call env
139
+ end.join
140
+
141
+ assert_equal 0, @handler.last_result
142
+ assert_equal 1, @handler.get_var('a')
143
+ assert_equal 2, @handler.get_var('b')
144
+ end
145
+
146
+ def test_set_state_json_bad_request
147
+ rack = Stark::Rack::REST.new stark_rack
148
+ code = headers = out = nil
149
+ Thread.new do
150
+ env = {'rack.input' => StringIO.new('42'),
151
+ 'REQUEST_METHOD' => 'POST', 'PATH_INFO' => '/set_state',
152
+ 'HTTP_CONTENT_TYPE' => 'application/json' }
153
+
154
+ code, headers, out = rack.call env
155
+ end.join
156
+
157
+ assert_equal 400, code
158
+ end
159
+
160
+ def test_get_metadata
161
+ metadata = { 'version' => '1.0 baby', 'name' => "This is a sweet service" }
162
+ rack = Stark::Rack::REST.new Stark::Rack::Metadata.new(stark_rack, metadata)
163
+
164
+ out = ['']
165
+ Thread.new do
166
+ env = {'rack.input' => StringIO.new, 'REQUEST_METHOD' => 'GET',
167
+ 'PATH_INFO' => '/metadata', 'QUERY_STRING' => ''}
168
+
169
+ code, headers, out = rack.call env
170
+ end.join
171
+
172
+ json = '{"result":{"version":"1.0 baby","name":"This is a sweet service"}}'
173
+ assert_equal json, out.join
174
+ end
175
+ end