jubilee 0.4.0 → 0.4.1

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/jubilee.gemspec ADDED
@@ -0,0 +1,100 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "jubilee"
8
+ s.version = "0.4.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Isaiah Peng"]
12
+ s.date = "2013-09-16"
13
+ s.description = "Jubilee is a experimental webserver built for speed, it's based on Vertx."
14
+ s.email = "issaria@gmail.com"
15
+ s.executables = ["jubilee", "jubilee_d"]
16
+ s.extra_rdoc_files = [
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".ruby-version",
21
+ ".travis.yml",
22
+ "CHANGELOG",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "Guardfile",
26
+ "README.md",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bin/jubilee",
30
+ "bin/jubilee_d",
31
+ "examples/jubilee/keystore.jks",
32
+ "examples/jubilee/server-keystore.jks",
33
+ "examples/ssl/ServerTest.java",
34
+ "examples/ssl/webroot/index.html",
35
+ "jars/jackson-core-2.2.2.jar",
36
+ "jars/netty-all-4.0.4.Final.jar",
37
+ "jars/vertx-core-2.1.0-SNAPSHOT.jar",
38
+ "java/src/jubilee/JubileeService.java",
39
+ "java/src/org/jruby/jubilee/Const.java",
40
+ "java/src/org/jruby/jubilee/RackApplication.java",
41
+ "java/src/org/jruby/jubilee/RackEnvironment.java",
42
+ "java/src/org/jruby/jubilee/RackErrors.java",
43
+ "java/src/org/jruby/jubilee/RackInput.java",
44
+ "java/src/org/jruby/jubilee/RackResponse.java",
45
+ "java/src/org/jruby/jubilee/Server.java",
46
+ "java/src/org/jruby/jubilee/deploy/Starter.java",
47
+ "java/src/org/jruby/jubilee/impl/DefaultRackEnvironment.java",
48
+ "java/src/org/jruby/jubilee/impl/NullIO.java",
49
+ "java/src/org/jruby/jubilee/impl/RubyIORackErrors.java",
50
+ "java/src/org/jruby/jubilee/impl/RubyIORackInput.java",
51
+ "jubilee.gemspec",
52
+ "lib/jubilee.rb",
53
+ "lib/jubilee/application.rb",
54
+ "lib/jubilee/cli.rb",
55
+ "lib/jubilee/configuration.rb",
56
+ "lib/jubilee/const.rb",
57
+ "lib/jubilee/jubilee.jar",
58
+ "lib/jubilee/response.rb",
59
+ "lib/jubilee/server.rb",
60
+ "lib/jubilee/version.rb",
61
+ "lib/rack/handler/jubilee.rb",
62
+ "test/.ruby-version",
63
+ "test/config/app.rb",
64
+ "test/jubilee/test_cli.rb",
65
+ "test/jubilee/test_config.rb",
66
+ "test/jubilee/test_rack_server.rb",
67
+ "test/jubilee/test_response.rb",
68
+ "test/jubilee/test_server.rb",
69
+ "test/jubilee/test_upload.rb",
70
+ "test/sinatra_app/app.rb",
71
+ "test/sinatra_app/config.ru",
72
+ "test/sinatra_app/public/test.html",
73
+ "test/sinatra_app/unicorn.conf.rb",
74
+ "test/test_helper.rb"
75
+ ]
76
+ s.homepage = "http://github.com/isaiah/jubilee"
77
+ s.licenses = ["MIT"]
78
+ s.require_paths = ["lib"]
79
+ s.rubygems_version = "1.8.24"
80
+ s.summary = "JRuby webserver based on Vertx"
81
+
82
+ if s.respond_to? :specification_version then
83
+ s.specification_version = 3
84
+
85
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
86
+ s.add_runtime_dependency(%q<rack>, [">= 1.4.1"])
87
+ s.add_runtime_dependency(%q<spoon>, ["~> 0.0.4"])
88
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
89
+ else
90
+ s.add_dependency(%q<rack>, [">= 1.4.1"])
91
+ s.add_dependency(%q<spoon>, ["~> 0.0.4"])
92
+ s.add_dependency(%q<jeweler>, [">= 0"])
93
+ end
94
+ else
95
+ s.add_dependency(%q<rack>, [">= 1.4.1"])
96
+ s.add_dependency(%q<spoon>, ["~> 0.0.4"])
97
+ s.add_dependency(%q<jeweler>, [">= 0"])
98
+ end
99
+ end
100
+
@@ -1,5 +1,7 @@
1
1
  module Jubilee
2
2
  class Configuration
3
+ attr_reader :app
4
+
3
5
  def initialize(options, &block)
4
6
  @options = options
5
7
  @block = block
@@ -9,15 +11,6 @@ module Jubilee
9
11
  @app = load_rack_adapter(@options, &@block)
10
12
  end
11
13
 
12
- def app
13
- if !@options[:quiet] and @options[:environment] == "development"
14
- logger = @options[:logger] || STDOUT
15
- Rack::CommonLogger.new(@app, logger)
16
- else
17
- @app
18
- end
19
- end
20
-
21
14
  def port
22
15
  @options[:Port]
23
16
  end
@@ -41,18 +34,36 @@ module Jubilee
41
34
  private
42
35
  def load_rack_adapter(options, &block)
43
36
  if block
44
- app = Rack::Builder.new(&block).to_app
37
+ inner_app = Rack::Builder.new(&block).to_app
45
38
  else
46
39
  if options[:rackup]
47
40
  Kernel.load(options[:rackup])
48
- app = Object.const_get(File.basename(options[:rackup], '.rb').capitalize.to_sym).new
41
+ inner_app = Object.const_get(File.basename(options[:rackup], '.rb').capitalize.to_sym).new
49
42
  else
50
43
  Dir.chdir options[:chdir] if options[:chdir]
51
- app, opts = Rack::Builder.parse_file "config.ru"
44
+ inner_app, opts = Rack::Builder.parse_file "config.ru"
52
45
  end
53
46
  end
54
- app
47
+ case ENV["RACK_ENV"]
48
+ when "development"
49
+ Rack::Builder.new do
50
+ use Rack::ContentLength
51
+ use Rack::Chunked
52
+ use Rack::CommonLogger, $stderr
53
+ use Rack::ShowExceptions
54
+ use Rack::Lint
55
+ run inner_app
56
+ end.to_app
57
+ when "deployment"
58
+ Rack::Builder.new do
59
+ use Rack::ContentLength
60
+ use Rack::Chunked
61
+ use Rack::CommonLogger, $stderr
62
+ run inner_app
63
+ end.to_app
64
+ else
65
+ inner_app
66
+ end
55
67
  end
56
-
57
68
  end
58
69
  end
Binary file
@@ -2,8 +2,7 @@ require 'rack/methodoverride'
2
2
  module Jubilee
3
3
  class Server < VertxServer
4
4
  def initialize(app, opts = {})
5
- app = Rack::MethodOverride.new(app)
6
- options = {Port: 3215, ssl: false}.merge(opts)
5
+ options = {Port: 3215, ssl: false, number_of_workers: 4}.merge(opts)
7
6
  if (options[:ssl]) && options[:keystore_path].nil?
8
7
  raise ArgumentError, "Please provide a keystore for ssl"
9
8
  end
@@ -1,7 +1,7 @@
1
1
  require 'test_helper'
2
2
  require 'timeout'
3
3
  require 'socket'
4
- class TestPersistent < MiniTest::Unit::TestCase
4
+ class TestResponse < MiniTest::Unit::TestCase
5
5
  def setup
6
6
  @valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
7
7
  @close_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: Close\r\n\r\n"
@@ -26,7 +26,6 @@ class TestPersistent < MiniTest::Unit::TestCase
26
26
 
27
27
  @server = Jubilee::Server.new @simple
28
28
  @server.start
29
-
30
29
  sleep 0.1
31
30
  @client = TCPSocket.new @host, @port
32
31
  end
@@ -243,5 +242,32 @@ class TestPersistent < MiniTest::Unit::TestCase
243
242
  assert_equal "HTTP/1.1 200 OK\r\ncontent-length: #{sz}\r\nx-header: Works\r\n\r\n", lines(4, c2)
244
243
  assert_equal "Hello", c2.read(5)
245
244
  end
245
+
246
+ def test_client_shutdown_writes
247
+ bs = 15609315 * rand
248
+ sock = TCPSocket.new('127.0.0.1', @port)
249
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
250
+ sock.syswrite("Host: example.com\r\n")
251
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
252
+ sock.syswrite("Trailer: X-Foo\r\n")
253
+ sock.syswrite("\r\n")
254
+ sock.syswrite("%x\r\n" % [ bs ])
255
+ sock.syswrite("F" * bs)
256
+ sock.syswrite("\r\n0\r\nX-")
257
+ "Foo: bar\r\n\r\n".each_byte do |x|
258
+ sock.syswrite x.chr
259
+ sleep 0.05
260
+ end
261
+ # we wrote the entire request before shutting down, server should
262
+ # continue to process our request and never hit EOFError on our sock
263
+ sock.shutdown(Socket::SHUT_WR)
264
+ buf = sock.read
265
+ assert_equal 'Hello', buf.split(/\r\n\r\n/).last
266
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
267
+ assert_equal 'Hello', next_client
268
+ lines = File.readlines("test_stderr.#$$.log")
269
+ assert lines.grep(/^Unicorn::ClientShutdown: /).empty?
270
+ assert_nil sock.close
271
+ end
246
272
  =end
247
273
  end
@@ -42,7 +42,6 @@ class TestJubileeServer < MiniTest::Unit::TestCase
42
42
  skip
43
43
  end
44
44
 
45
-
46
45
  def test_url_scheme_for_https
47
46
  app = lambda { |env| [200, {}, [env['rack.url_scheme']]] }
48
47
  server = Jubilee::Server.new(app, {port:@port, ssl:true,
@@ -65,4 +64,5 @@ class TestJubileeServer < MiniTest::Unit::TestCase
65
64
  server.stop
66
65
  assert_equal "https", body
67
66
  end
67
+
68
68
  end
@@ -0,0 +1,298 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'test_helper'
4
+ require 'digest/md5'
5
+
6
+ class TestUpload < MiniTest::Unit::TestCase
7
+
8
+ def setup
9
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
10
+ @port = 3215
11
+ @hdr = {'Content-Type' => 'text/plain', 'Content-Length' => '0'}
12
+ @bs = 4096
13
+ @count = 256
14
+ @server = nil
15
+ @sha1 = Digest::SHA1.new
16
+
17
+ # we want random binary data to test 1.9 encoding-aware IO craziness
18
+ @random = File.open('/dev/urandom','rb')
19
+ @sha1_app = lambda do |env|
20
+ sha1 = Digest::SHA1.new
21
+ input = env['rack.input']
22
+ resp = {}
23
+
24
+ i = 0
25
+ while buf = input.read(@bs)
26
+ sha1.update(buf)
27
+ i += 1
28
+ end
29
+ resp[:sha1] = sha1.hexdigest
30
+
31
+ # rewind and read again
32
+ input.rewind
33
+ sha1.reset
34
+
35
+ while buf = input.read(@bs)
36
+ sha1.update(buf)
37
+ end
38
+
39
+ if resp[:sha1] == sha1.hexdigest
40
+ resp[:sysread_read_byte_match] = true
41
+ end
42
+ resp[:content_md5] = env['HTTP_CONTENT_MD5']
43
+
44
+ [ 200, @hdr.merge({'X-Resp' => resp.inspect}), [] ]
45
+ end
46
+ end
47
+
48
+ def teardown
49
+ @server.stop
50
+ @random.close
51
+ end
52
+
53
+ def test_put
54
+ start_server(@sha1_app)
55
+ sock = TCPSocket.new(@addr, @port)
56
+ sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
57
+ @count.times do |i|
58
+ buf = @random.sysread(@bs)
59
+ @sha1.update(buf)
60
+ sock.syswrite(buf)
61
+ end
62
+ read = sock.read.split(/\r\n/)
63
+ assert_equal "HTTP/1.0 200 OK", read[0]
64
+ resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
65
+ assert_equal @sha1.hexdigest, resp[:sha1]
66
+ end
67
+
68
+ def test_put_content_md5
69
+ start_server(@sha1_app)
70
+ md5 = Digest::MD5.new
71
+ sock = TCPSocket.new(@addr, @port)
72
+ sock.syswrite("PUT / HTTP/1.0\r\nTransfer-Encoding: chunked\r\n" \
73
+ "Trailer: Content-MD5\r\n\r\n")
74
+ @count.times do |i|
75
+ buf = @random.sysread(@bs)
76
+ @sha1.update(buf)
77
+ md5.update(buf)
78
+ sock.syswrite("#{'%x' % buf.size}\r\n")
79
+ sock.syswrite(buf << "\r\n")
80
+ end
81
+ sock.syswrite("0\r\n")
82
+
83
+ content_md5 = [ md5.digest! ].pack('m').strip.freeze
84
+ sock.syswrite("Content-MD5: #{content_md5}\r\n\r\n")
85
+ read = sock.read.split(/\r\n/)
86
+ assert_equal "HTTP/1.0 200 OK", read[0]
87
+ resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
88
+ assert_equal @sha1.hexdigest, resp[:sha1]
89
+ #assert_equal content_md5, resp[:content_md5]
90
+ end
91
+
92
+ def test_put_trickle_small
93
+ start_server(@sha1_app)
94
+ @count, @bs = 2, 128
95
+ assert_equal 256, length
96
+ sock = TCPSocket.new(@addr, @port)
97
+ hdr = "PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n"
98
+ @count.times do
99
+ buf = @random.sysread(@bs)
100
+ @sha1.update(buf)
101
+ hdr << buf
102
+ sock.syswrite(hdr)
103
+ hdr = ''
104
+ sleep 0.6
105
+ end
106
+ read = sock.read.split(/\r\n/)
107
+ assert_equal "HTTP/1.0 200 OK", read[0]
108
+ resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
109
+ assert_equal @sha1.hexdigest, resp[:sha1]
110
+ end
111
+
112
+ def test_put_keepalive_truncates_small_overwrite
113
+ start_server(@sha1_app)
114
+ sock = TCPSocket.new(@addr, @port)
115
+ to_upload = length + 1
116
+ sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{to_upload}\r\n\r\n")
117
+ @count.times do
118
+ buf = @random.sysread(@bs)
119
+ @sha1.update(buf)
120
+ sock.syswrite(buf)
121
+ end
122
+ sock.syswrite('12345') # write 4 bytes more than we expected
123
+ @sha1.update('1')
124
+
125
+ buf = sock.readpartial(4096)
126
+ while buf !~ /\r\n\r\n/
127
+ buf << sock.readpartial(4096)
128
+ end
129
+ read = buf.split(/\r\n/)
130
+ assert_equal "HTTP/1.0 200 OK", read[0]
131
+ resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
132
+ #assert_equal to_upload, resp[:size]
133
+ assert_equal @sha1.hexdigest, resp[:sha1]
134
+ end
135
+
136
+ def test_put_excessive_overwrite_closed
137
+ tmp = Tempfile.new('overwrite_check')
138
+ tmp.sync = true
139
+ start_server(lambda { |env|
140
+ nr = 0
141
+ while buf = env['rack.input'].read(65536)
142
+ nr += buf.size
143
+ end
144
+ tmp.write(nr.to_s)
145
+ [ 200, @hdr, [] ]
146
+ })
147
+ sock = TCPSocket.new(@addr, @port)
148
+ buf = ' ' * @bs
149
+ sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
150
+
151
+ @count.times { sock.syswrite(buf) }
152
+ assert_raises(Errno::ECONNRESET, Errno::EPIPE) do
153
+ 16384.times { sock.syswrite(buf) }
154
+ end
155
+ sock.gets
156
+ tmp.rewind
157
+ assert_equal length, tmp.read.to_i
158
+ end
159
+
160
+ # Despite reading numerous articles and inspecting the 1.9.1-p0 C
161
+ # source, Eric Wong will never trust that we're always handling
162
+ # encoding-aware IO objects correctly. Thus this test uses shell
163
+ # utilities that should always operate on files/sockets on a
164
+ # byte-level.
165
+ def test_uncomfortable_with_onenine_encodings
166
+ # POSIX doesn't require all of these to be present on a system
167
+ which('curl') or return
168
+ which('sha1sum') or return
169
+ which('dd') or return
170
+
171
+ start_server(@sha1_app)
172
+
173
+ tmp = Tempfile.new('dd_dest')
174
+ assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
175
+ "bs=#{@bs}", "count=#{@count}"),
176
+ "dd #@random to #{tmp}")
177
+ sha1_re = %r!\b([a-f0-9]{40})\b!
178
+ sha1_out = `sha1sum #{tmp.path}`
179
+
180
+ assert $?.success?, 'sha1sum ran OK'
181
+
182
+ assert_match(sha1_re, sha1_out)
183
+ sha1 = sha1_re.match(sha1_out)[1]
184
+ resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
185
+
186
+ assert $?.success?, 'curl ran OK'
187
+ assert_match(%r!\b#{sha1}\b!, resp)
188
+ assert_match(/sysread_read_byte_match/, resp)
189
+
190
+ # small StringIO path
191
+ assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
192
+ "bs=1024", "count=1"),
193
+ "dd #@random to #{tmp}")
194
+ sha1_re = %r!\b([a-f0-9]{40})\b!
195
+ sha1_out = `sha1sum #{tmp.path}`
196
+ assert $?.success?, 'sha1sum ran OK'
197
+
198
+ assert_match(sha1_re, sha1_out)
199
+ sha1 = sha1_re.match(sha1_out)[1]
200
+ resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
201
+ assert $?.success?, 'curl ran OK'
202
+ assert_match(%r!\b#{sha1}\b!, resp)
203
+ assert_match(/sysread_read_byte_match/, resp)
204
+ end
205
+
206
+ =begin
207
+ def test_chunked_upload_via_curl
208
+ # POSIX doesn't require all of these to be present on a system
209
+ which('curl') or return
210
+ which('sha1sum') or return
211
+ which('dd') or return
212
+
213
+ start_server(@sha1_app)
214
+
215
+ tmp = Tempfile.new('dd_dest')
216
+ assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
217
+ "bs=#{@bs}", "count=#{@count}"),
218
+ "dd #@random to #{tmp}")
219
+ sha1_re = %r!\b([a-f0-9]{40})\b!
220
+ sha1_out = `sha1sum #{tmp.path}`
221
+ assert $?.success?, 'sha1sum ran OK'
222
+
223
+ assert_match(sha1_re, sha1_out)
224
+ sha1 = sha1_re.match(sha1_out)[1]
225
+ cmd = "curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
226
+ -isSf --no-buffer -T- " \
227
+ "http://#@addr:#@port/"
228
+ resp = Tempfile.new('resp')
229
+ resp.sync = true
230
+
231
+ rd, wr = IO.pipe
232
+ wr.sync = rd.sync = true
233
+ pid = fork {
234
+ STDIN.reopen(rd)
235
+ rd.close
236
+ wr.close
237
+ STDOUT.reopen(resp)
238
+ exec cmd
239
+ }
240
+ rd.close
241
+
242
+ tmp.rewind
243
+ @count.times { |i|
244
+ wr.write(tmp.read(@bs))
245
+ sleep(rand / 10) if 0 == i % 8
246
+ }
247
+ wr.close
248
+ pid, status = Process.waitpid2(pid)
249
+
250
+ resp.rewind
251
+ resp = resp.read
252
+ assert status.success?, 'curl ran OK'
253
+ assert_match(%r!\b#{sha1}\b!, resp)
254
+ assert_match(/sysread_read_byte_match/, resp)
255
+ assert_match(/expect_size_match/, resp)
256
+ end
257
+ =end
258
+
259
+ def test_curl_chunked_small
260
+ # POSIX doesn't require all of these to be present on a system
261
+ which('curl') or return
262
+ which('sha1sum') or return
263
+ which('dd') or return
264
+
265
+ start_server(@sha1_app)
266
+
267
+ tmp = Tempfile.new('dd_dest')
268
+ # small StringIO path
269
+ assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
270
+ "bs=1024", "count=1"),
271
+ "dd #@random to #{tmp}")
272
+ sha1_re = %r!\b([a-f0-9]{40})\b!
273
+ sha1_out = `sha1sum #{tmp.path}`
274
+ assert $?.success?, 'sha1sum ran OK'
275
+
276
+ assert_match(sha1_re, sha1_out)
277
+ sha1 = sha1_re.match(sha1_out)[1]
278
+ resp = `curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
279
+ -isSf --no-buffer -T- http://#@addr:#@port/ < #{tmp.path}`
280
+ assert $?.success?, 'curl ran OK'
281
+ assert_match(%r!\b#{sha1}\b!, resp)
282
+ assert_match(/sysread_read_byte_match/, resp)
283
+ #assert_match(/expect_size_match/, resp)
284
+ end
285
+
286
+ private
287
+
288
+ def length
289
+ @bs * @count
290
+ end
291
+
292
+ def start_server(app)
293
+ @server = Jubilee::Server.new app
294
+ @server.start
295
+ sleep 0.1
296
+ end
297
+
298
+ end