stark-rack 1.0.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.
@@ -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