jubilee 0.1.2

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.
Files changed (54) hide show
  1. data/.rbenv-version +1 -0
  2. data/Gemfile +16 -0
  3. data/Gemfile.lock +54 -0
  4. data/Guardfile +24 -0
  5. data/README.md +61 -0
  6. data/Rakefile +94 -0
  7. data/VERSION +1 -0
  8. data/bin/jubilee +6 -0
  9. data/bin/jubilee_d +10 -0
  10. data/examples/jubilee/keystore.jks +0 -0
  11. data/examples/jubilee/server-keystore.jks +0 -0
  12. data/examples/ssl/ServerTest.java +19 -0
  13. data/examples/ssl/webroot/index.html +10 -0
  14. data/jars/netty-3.6.0.Beta1.jar +0 -0
  15. data/jars/vertx-core-1.3.0.final.jar +0 -0
  16. data/java/.idea/ant.xml +7 -0
  17. data/java/.idea/libraries/jruby.xml +9 -0
  18. data/java/.idea/libraries/netty_3_6_0_Beta1.xml +9 -0
  19. data/java/.idea/libraries/vertx_core_1_3_0_final.xml +9 -0
  20. data/java/src/jubilee/JubileeService.java +21 -0
  21. data/java/src/org/jruby/jubilee/Const.java +148 -0
  22. data/java/src/org/jruby/jubilee/RackApplication.java +78 -0
  23. data/java/src/org/jruby/jubilee/RackEnvironment.java +13 -0
  24. data/java/src/org/jruby/jubilee/RackErrors.java +44 -0
  25. data/java/src/org/jruby/jubilee/RackInput.java +62 -0
  26. data/java/src/org/jruby/jubilee/RackResponse.java +16 -0
  27. data/java/src/org/jruby/jubilee/Server.java +104 -0
  28. data/java/src/org/jruby/jubilee/deploy/Starter.java +26 -0
  29. data/java/src/org/jruby/jubilee/impl/DefaultRackEnvironment.java +98 -0
  30. data/java/src/org/jruby/jubilee/impl/NullIO.java +111 -0
  31. data/java/src/org/jruby/jubilee/impl/RubyIORackErrors.java +68 -0
  32. data/java/src/org/jruby/jubilee/impl/RubyIORackInput.java +164 -0
  33. data/lib/jubilee.rb +11 -0
  34. data/lib/jubilee/application.rb +13 -0
  35. data/lib/jubilee/cli.rb +74 -0
  36. data/lib/jubilee/configuration.rb +52 -0
  37. data/lib/jubilee/const.rb +39 -0
  38. data/lib/jubilee/jubilee.jar +0 -0
  39. data/lib/jubilee/response.rb +64 -0
  40. data/lib/jubilee/server.rb +16 -0
  41. data/lib/rack/handler/jubilee.rb +43 -0
  42. data/test/.rbenv-version +1 -0
  43. data/test/config/app.rb +5 -0
  44. data/test/jubilee/test_cli.rb +11 -0
  45. data/test/jubilee/test_config.rb +14 -0
  46. data/test/jubilee/test_persistent.rb +238 -0
  47. data/test/jubilee/test_rack_server.rb +116 -0
  48. data/test/jubilee/test_server.rb +68 -0
  49. data/test/sinatra_app/app.rb +31 -0
  50. data/test/sinatra_app/config.ru +6 -0
  51. data/test/sinatra_app/public/test.html +10 -0
  52. data/test/sinatra_app/unicorn.conf.rb +29 -0
  53. data/test/test_helper.rb +21 -0
  54. metadata +160 -0
@@ -0,0 +1,39 @@
1
+ module Jubilee
2
+ module Const
3
+ JUBILEE_VERSION = VERSION = "0.1.0".freeze
4
+ HTTP_11 = "HTTP/1.1".freeze
5
+ HTTP_10 = "HTTP/1.0".freeze
6
+
7
+ SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
8
+ SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
9
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
10
+ SERVER_NAME = "SERVER_NAME".freeze
11
+ SERVER_PORT = "SERVER_PORT".freeze
12
+
13
+ CGI_VER = "CGI/1.2".freeze
14
+
15
+ RACK_INPUT = "rack.input".freeze
16
+
17
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
18
+ GET = 'GET'.freeze
19
+ POST = "POST".freeze
20
+ REQUEST_PATH = "REQUEST_PATH".freeze
21
+ REQUEST_URI = "REQUEST_URI".freeze
22
+ PATH_INFO = "PATH_INFO".freeze
23
+ QUERY_STRING = "QUERY_STRING".freeze
24
+
25
+ CONTENT_LENGTH = "Content-Length".freeze
26
+ TRANSFER_ENCODING = "Transfer-Encoding".freeze
27
+
28
+ HTTP_VERSION = "HTTP_VERSION".freeze
29
+ HTTP_HOST = "HTTP_HOST".freeze
30
+ HTTP_USER_AGENT = "HTTP_USER_AGENT".freeze
31
+ HTTP_ACCEPT = "HTTP_ACCEPT".freeze
32
+ HTTP_COOKIE = "HTTP_COOKIE".freeze
33
+ HTTP_ACCEPT_LANGUAGE = "HTTP_ACCEPT_LANGUAGE".freeze
34
+ HTTP_ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING".freeze
35
+ HTTP_CONNECTION = "HTTP_CONNECTION".freeze
36
+
37
+ STATUS_WITH_NO_ENTITY_BODY = Hash[Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.map{|s| [s, true]}]
38
+ end
39
+ end
Binary file
@@ -0,0 +1,64 @@
1
+ require 'java'
2
+ module Jubilee
3
+ class Response
4
+ include Const
5
+ include org.jruby.jubilee.RackResponse
6
+
7
+ def initialize(array)
8
+ @status, @headers, @body = *array
9
+ @content_length = nil
10
+ if @body.kind_of? Array and @body.size == 1
11
+ @content_length = @body[0].bytesize
12
+ end
13
+ end
14
+
15
+ def respond(response)
16
+ no_body = @status < 200 || STATUS_WITH_NO_ENTITY_BODY[@status]
17
+ write_status(response)
18
+ write_headers(response)
19
+ if no_body
20
+ response.end
21
+ else
22
+ if @body.respond_to?(:to_path)
23
+ response.sendFile(@body.to_path)
24
+ else
25
+ write_body(response)
26
+ response.end
27
+ end
28
+ end
29
+ ensure
30
+ @body.close if @body.respond_to?(:close)
31
+ end
32
+
33
+ private
34
+ def write_status(response)
35
+ response.statusCode = @status
36
+ end
37
+
38
+ def write_headers(response)
39
+ @headers.each do |key, value|
40
+ case key
41
+ when CONTENT_LENGTH
42
+ @content_length = value
43
+ next
44
+ when TRANSFER_ENCODING
45
+ @allow_chunked = false
46
+ @content_length = nil
47
+ end
48
+ response.putHeader(key, value)
49
+ end
50
+ end
51
+
52
+ def write_body(response)
53
+ if @content_length
54
+ response.putHeader(CONTENT_LENGTH, @content_length.to_s)
55
+ else
56
+ response.setChunked(true)
57
+ end
58
+
59
+ @body.each do |part|
60
+ response.write(part)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,16 @@
1
+ module Jubilee
2
+ class Server < VertxServer
3
+ def initialize(app, opts = {})
4
+ options = {port: 3215, ssl: false}.merge(opts)
5
+ if (options[:ssl])
6
+ if options[:keystore].nil?
7
+ raise ArgumentError, "Please provide a keystore for ssl"
8
+ else
9
+ super(Application.new(app), options[:port], options[:ssl], options[:keystore], options[:keystore_password])
10
+ end
11
+ else
12
+ super(Application.new(app), options[:port])
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,43 @@
1
+ require 'rack/handler'
2
+ require 'jubilee'
3
+ require 'java'
4
+
5
+ module Rack
6
+ module Handler
7
+ module Jubilee
8
+ DEFAULT_OPTIONS = {
9
+ :host => '0.0.0.0',
10
+ :port => 3000,
11
+ :verbose => false
12
+ }
13
+ def self.run(app, options = {})
14
+ options = DEFAULT_OPTIONS.merge(options)
15
+
16
+ if options[:verbose]
17
+ app = Rack::CommonLogger.new(app, STDOUT)
18
+ end
19
+
20
+ if options[:environment]
21
+ ENV["RACK_ENV"] = options[:environment].to_s
22
+ end
23
+
24
+ @server = ::Jubilee::Server.new(app, options)
25
+
26
+ puts "Jubilee starting..."
27
+ puts "Environment: #{ENV['RACK_ENV']}"
28
+
29
+ yield @server if block_given?
30
+
31
+ @server.start
32
+ @starter = org.jruby.jubilee.deploy.Starter.new
33
+ @starter.block
34
+ end
35
+
36
+ def self.shutdown
37
+ @server.stop{ @starter.unblock }
38
+ exit
39
+ end
40
+ end
41
+ register :jubilee, Jubilee
42
+ end
43
+ end
@@ -0,0 +1 @@
1
+ jruby-1.7.0
@@ -0,0 +1,5 @@
1
+ class App
2
+ def call(env)
3
+ [200, {"Content-Type" => "text/plain"}, ["embeded app"]]
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ require 'test_helper'
2
+ require 'jubilee/cli'
3
+
4
+ class TestJubileeCLI < MiniTest::Unit::TestCase
5
+ def test_parse_options
6
+ cli = Jubilee::CLI.new(["app.rb"])
7
+ cli.parse_options
8
+ assert_equal "app.rb", cli.options[:rackup]
9
+ assert_equal 3215, cli.options[:port]
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+ require 'jubilee/configuration'
3
+
4
+ class TestConfig < MiniTest::Unit::TestCase
5
+ def setup
6
+ @config = Jubilee::Configuration.new({rackup: "config/app.rb"})
7
+ end
8
+ def test_load
9
+ @config.load
10
+ resp = [200, {"Content-Type" => "text/plain"}, ["embeded app"]]
11
+ skip "hard to test because of Rack::Lint"
12
+ #assert_equal resp, @config.app.call({})
13
+ end
14
+ end
@@ -0,0 +1,238 @@
1
+ require 'test_helper'
2
+ require 'timeout'
3
+ require 'socket'
4
+ class TestPersistent < MiniTest::Unit::TestCase
5
+ def setup
6
+ @valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
7
+ @close_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"
8
+ @http10_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
9
+ @keep_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: Keep-Alive\r\n\r\n"
10
+
11
+ @valid_post = "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nhello"
12
+ @valid_no_body = "GET / HTTP/1.1\r\nHost: test.com\r\nX-Status: 204\r\nContent-Type: text/plain\r\n\r\n"
13
+
14
+ @headers = { "X-Header" => "Works" }
15
+ @body = ["Hello"]
16
+ @inputs = []
17
+
18
+ @simple = lambda do |env|
19
+ @inputs << env['rack.input']
20
+ status = Integer(env['HTTP_X_STATUS'] || 200)
21
+ [status, @headers, @body]
22
+ end
23
+
24
+ @host = "127.0.0.1"
25
+ @port = 3215
26
+
27
+ @server = Jubilee::Server.new @simple
28
+ @server.start
29
+
30
+ @client = TCPSocket.new @host, @port
31
+ end
32
+
33
+ def teardown
34
+ @client.close
35
+ sleep 0.1 # in case server shutdown before request is submitted
36
+ @server.stop
37
+ end
38
+
39
+ def lines(count, s=@client)
40
+ str = ""
41
+ timeout(5) do
42
+ count.times { str << s.gets }
43
+ end
44
+ str
45
+ end
46
+
47
+ def test_one_with_content_length
48
+ @client << @valid_request
49
+ sz = @body[0].size.to_s
50
+
51
+ assert_match Regexp.new("HTTP/1.1 200 OK\r\nContent-Length: #{sz}\r\nX-Header: Works\r\n\r\n", true), lines(4)
52
+ assert_equal "Hello", @client.read(5)
53
+ end
54
+
55
+ def test_two_back_to_back
56
+ @client << @valid_request
57
+ sz = @body[0].size.to_s
58
+
59
+ assert_match Regexp.new("HTTP/1.1 200 OK\r\nContent-Length: #{sz}\r\nX-Header: Works\r\n\r\n", true), lines(4)
60
+ assert_equal "Hello", @client.read(5)
61
+
62
+ @client << @valid_request
63
+ sz = @body[0].size.to_s
64
+
65
+ assert_match Regexp.new("HTTP/1.1 200 OK\r\nContent-Length: #{sz}\r\nX-Header: Works\r\n\r\n", true), lines(4)
66
+ assert_equal "Hello", @client.read(5)
67
+ end
68
+
69
+ def test_post_then_get
70
+ @client << @valid_post
71
+ sz = @body[0].size.to_s
72
+
73
+ assert_match Regexp.new("HTTP/1.1 200 OK\r\nContent-Length: #{sz}\r\nX-Header: Works\r\n\r\n", true), lines(4)
74
+ assert_equal "Hello", @client.read(5)
75
+
76
+ @client << @valid_request
77
+ sz = @body[0].size.to_s
78
+
79
+ assert_match Regexp.new("HTTP/1.1 200 OK\r\nContent-Length: #{sz}\r\nX-Header: Works\r\n\r\n", true), lines(4)
80
+ assert_equal "Hello", @client.read(5)
81
+ end
82
+
83
+ #def test_no_body_then_get
84
+ # @client << @valid_no_body
85
+ # assert_equal "HTTP/1.1 204 No Content\r\nX-Header: Works\r\n\r\n", lines(3)
86
+
87
+ # @client << @valid_request
88
+ # sz = @body[0].size.to_s
89
+
90
+ # assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
91
+ # assert_equal "Hello", @client.read(5)
92
+ #end
93
+
94
+ def test_chunked
95
+ @body << "Chunked"
96
+
97
+ @client << @valid_request
98
+
99
+ assert_equal "HTTP/1.1 200 OK\r\nx-header: Works\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n7\r\nChunked\r\n0\r\n\r\n", lines(10)
100
+ end
101
+
102
+ def test_no_chunked_in_http10
103
+ @body << "Chunked"
104
+
105
+ @client << @http10_request
106
+
107
+ assert_equal "HTTP/1.0 200 OK\r\nConnection: close\r\nx-header: Works\r\n\r\n", lines(4)
108
+ assert_equal "HelloChunked", @client.read
109
+ end
110
+
111
+ def test_hex
112
+ str = "This is longer and will be in hex"
113
+ @body << str
114
+
115
+ @client << @valid_request
116
+
117
+ assert_equal "HTTP/1.1 200 OK\r\nx-header: Works\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n#{str.size.to_s(16)}\r\n#{str}\r\n0\r\n\r\n", lines(10)
118
+
119
+ end
120
+
121
+ def test_client11_close
122
+ @client << @close_request
123
+ sz = @body[0].size.to_s
124
+
125
+ assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(5)
126
+ assert_equal "Hello", @client.read(5)
127
+ end
128
+
129
+ def test_client10_close
130
+ @client << @http10_request
131
+ sz = @body[0].size.to_s
132
+
133
+ assert_equal "HTTP/1.0 200 OK\r\nConnection: close\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(5)
134
+ assert_equal "Hello", @client.read(5)
135
+ end
136
+
137
+ def test_one_with_keep_alive_header
138
+ @client << @keep_request
139
+ sz = @body[0].size.to_s
140
+
141
+ assert_equal "HTTP/1.0 200 OK\r\nConnection: keep-alive\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(5)
142
+ assert_equal "Hello", @client.read(5)
143
+ end
144
+
145
+ def test_persistent_timeout
146
+ @server.persistent_timeout = 2
147
+ @client << @valid_request
148
+ sz = @body[0].size.to_s
149
+
150
+ assert_equal "HTTP/1.1 200 OK\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(4)
151
+ assert_equal "Hello", @client.read(5)
152
+
153
+ sleep 3
154
+
155
+ assert_raises EOFError do
156
+ @client.read_nonblock(1)
157
+ end
158
+ end
159
+
160
+ def test_app_sets_content_length
161
+ @body = ["hello", " world"]
162
+ @headers['Content-Length'] = "11"
163
+
164
+ @client << @valid_request
165
+
166
+ assert_equal "HTTP/1.1 200 OK\r\ncontent-length: 11\r\nx-header: Works\r\n\r\n",
167
+ lines(4)
168
+ assert_equal "hello world", @client.read(11)
169
+ end
170
+
171
+ def test_allow_app_to_chunk_itself
172
+ skip "vertx doesn't support chunk self yet"
173
+ @headers = {'Transfer-Encoding' => "chunked" }
174
+
175
+ @body = ["5\r\nhello\r\n0\r\n\r\n"]
176
+
177
+ @client << @valid_request
178
+
179
+ assert_equal "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n", lines(7)
180
+ end
181
+
182
+
183
+ def test_two_requests_in_one_chunk
184
+ @server.persistent_timeout = 3
185
+
186
+ req = @valid_request.to_s
187
+ req << "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
188
+
189
+ @client << req
190
+
191
+ sz = @body[0].size.to_s
192
+
193
+ assert_equal "HTTP/1.1 200 OK\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(4)
194
+ assert_equal "Hello", @client.read(5)
195
+
196
+ assert_equal "HTTP/1.1 200 OK\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(4)
197
+ assert_equal "Hello", @client.read(5)
198
+ end
199
+
200
+ def test_second_request_not_in_first_req_body
201
+ @server.persistent_timeout = 3
202
+
203
+ req = @valid_request.to_s
204
+ req << "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
205
+
206
+ @client << req
207
+
208
+ sz = @body[0].size.to_s
209
+
210
+ assert_equal "HTTP/1.1 200 OK\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(4)
211
+ assert_equal "Hello", @client.read(5)
212
+
213
+ assert_equal "HTTP/1.1 200 OK\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(4)
214
+ assert_equal "Hello", @client.read(5)
215
+
216
+ # Since rack process request before the body is ready, we cannot
217
+ # utilize this optimization
218
+ # assert_kind_of Jubilee::NullIO, @inputs[0]
219
+ # assert_kind_of Jubilee::NullIO, @inputs[1]
220
+ end
221
+
222
+ def test_keepalive_doesnt_starve_clients
223
+ sz = @body[0].size.to_s
224
+
225
+ @client << @valid_request
226
+
227
+ c2 = TCPSocket.new @host, @port
228
+ c2 << @valid_request
229
+
230
+ out = IO.select([c2], nil, nil, 1)
231
+
232
+ assert out, "select returned nil"
233
+ assert_equal c2, out.first.first
234
+
235
+ assert_equal "HTTP/1.1 200 OK\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(4, c2)
236
+ assert_equal "Hello", c2.read(5)
237
+ end
238
+ end
@@ -0,0 +1,116 @@
1
+ require 'test_helper'
2
+ require 'rack/lint'
3
+ require 'rack/commonlogger'
4
+
5
+ class TestRackServer < MiniTest::Unit::TestCase
6
+
7
+ class ErrorChecker
8
+ def initialize(app)
9
+ @app = app
10
+ @exception = nil
11
+ @env = nil
12
+ end
13
+
14
+ attr_reader :exception, :env
15
+
16
+ def call(env)
17
+ begin
18
+ @env = env
19
+ return @app.call(env)
20
+ rescue Exception => e
21
+ @exception = e
22
+
23
+ [
24
+ 500,
25
+ { "X-Exception" => e.message, "X-Exception-Class" => e.class.to_s },
26
+ ["Error detected"]
27
+ ]
28
+ end
29
+ end
30
+ end
31
+
32
+ class ServerLint < Rack::Lint
33
+ def call(env)
34
+ assert("No env given") { env }
35
+ check_env env
36
+
37
+ @app.call(env)
38
+ end
39
+ end
40
+
41
+ def setup
42
+ @valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
43
+
44
+ @simple = lambda { |env| [200, { "X-Header" => "Works" }, ["Hello"]] }
45
+ @checker = ErrorChecker.new ServerLint.new(@simple)
46
+ end
47
+
48
+ def teardown
49
+ @server.stop
50
+ end
51
+
52
+ def test_lint
53
+ @server = Jubilee::Server.new @checker
54
+
55
+ @server.start
56
+
57
+ hit(['http://127.0.0.1:3215/test'])
58
+
59
+ if exc = @checker.exception
60
+ raise exc
61
+ end
62
+ end
63
+
64
+ def test_large_post_body
65
+ @checker = ErrorChecker.new ServerLint.new(@simple)
66
+ @server = Jubilee::Server.new @checker
67
+
68
+ @server.start
69
+
70
+ big = "x" * (1024 * 16)
71
+
72
+ Net::HTTP.post_form URI.parse('http://127.0.0.1:3215/test'),
73
+ { "big" => big }
74
+
75
+ if exc = @checker.exception
76
+ raise exc
77
+ end
78
+ end
79
+
80
+ def test_path_info
81
+ input = nil
82
+ @server = Jubilee::Server.new (lambda { |env| input = env; @simple.call(env) })
83
+ @server.start
84
+
85
+ hit(['http://127.0.0.1:3215/test/a/b/c'])
86
+
87
+ assert_equal "/test/a/b/c", input['PATH_INFO']
88
+ end
89
+
90
+ def test_query_string
91
+ input = nil
92
+ @server = Jubilee::Server.new (lambda { |env| input = env; @simple.call(env) })
93
+ @server.start
94
+
95
+ hit(['http://127.0.0.1:3215/test/a/b/c?foo=bar'])
96
+
97
+ assert_equal "foo=bar", input['QUERY_STRING']
98
+ end
99
+
100
+ def test_post_data
101
+ require 'rack/request'
102
+ input = nil
103
+ @server = Jubilee::Server.new (lambda { |env| input = env; @simple.call(env) })
104
+ @server.start
105
+
106
+ req = Net::HTTP::Post::Multipart.new("/", "foo" => "bar")
107
+ resp = Net::HTTP.start('localhost', 3215) do |http|
108
+ http.request req
109
+ end
110
+
111
+ #Net::HTTP.post_form URI.parse('http://127.0.0.1:3215/test'), { "foo" => "bar" }
112
+
113
+ request = Rack::Request.new input
114
+ assert_equal "bar", request.params["foo"]
115
+ end
116
+ end