yahns 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Documentation/.gitignore +5 -0
  4. data/Documentation/GNUmakefile +50 -0
  5. data/Documentation/yahns-rackup.txt +152 -0
  6. data/Documentation/yahns.txt +68 -0
  7. data/Documentation/yahns_config.txt +563 -0
  8. data/GIT-VERSION-GEN +1 -1
  9. data/GNUmakefile +14 -7
  10. data/HACKING +56 -0
  11. data/INSTALL +8 -0
  12. data/README +15 -2
  13. data/Rakefile +2 -2
  14. data/bin/yahns +1 -2
  15. data/bin/yahns-rackup +9 -0
  16. data/examples/yahns_multi.conf.rb +14 -4
  17. data/examples/yahns_rack_basic.conf.rb +17 -1
  18. data/extras/README +16 -0
  19. data/extras/autoindex.rb +151 -0
  20. data/extras/exec_cgi.rb +108 -0
  21. data/extras/proxy_pass.rb +210 -0
  22. data/extras/try_gzip_static.rb +208 -0
  23. data/lib/yahns.rb +5 -2
  24. data/lib/yahns/acceptor.rb +64 -22
  25. data/lib/yahns/cap_input.rb +2 -2
  26. data/lib/yahns/{client_expire_portable.rb → client_expire_generic.rb} +12 -11
  27. data/lib/yahns/{client_expire.rb → client_expire_tcpi.rb} +7 -6
  28. data/lib/yahns/config.rb +107 -22
  29. data/lib/yahns/daemon.rb +2 -0
  30. data/lib/yahns/fdmap.rb +28 -9
  31. data/lib/yahns/http_client.rb +123 -37
  32. data/lib/yahns/http_context.rb +21 -3
  33. data/lib/yahns/http_response.rb +80 -19
  34. data/lib/yahns/log.rb +23 -4
  35. data/lib/yahns/queue_epoll.rb +20 -9
  36. data/lib/yahns/queue_quitter.rb +16 -0
  37. data/lib/yahns/queue_quitter_pipe.rb +24 -0
  38. data/lib/yahns/rack.rb +0 -1
  39. data/lib/yahns/rackup_handler.rb +57 -0
  40. data/lib/yahns/server.rb +189 -59
  41. data/lib/yahns/server_mp.rb +43 -35
  42. data/lib/yahns/sigevent_pipe.rb +1 -0
  43. data/lib/yahns/socket_helper.rb +37 -11
  44. data/lib/yahns/stream_file.rb +14 -4
  45. data/lib/yahns/stream_input.rb +13 -7
  46. data/lib/yahns/tcp_server.rb +7 -0
  47. data/lib/yahns/tmpio.rb +10 -3
  48. data/lib/yahns/unix_server.rb +7 -0
  49. data/lib/yahns/wbuf.rb +19 -2
  50. data/lib/yahns/wbuf_common.rb +10 -3
  51. data/lib/yahns/wbuf_str.rb +24 -0
  52. data/lib/yahns/worker.rb +5 -26
  53. data/test/helper.rb +15 -5
  54. data/test/server_helper.rb +37 -1
  55. data/test/test_bin.rb +17 -8
  56. data/test/test_buffer_tmpdir.rb +103 -0
  57. data/test/test_client_expire.rb +71 -35
  58. data/test/test_client_max_body_size.rb +5 -13
  59. data/test/test_config.rb +1 -1
  60. data/test/test_expect_100.rb +176 -0
  61. data/test/test_extras_autoindex.rb +53 -0
  62. data/test/test_extras_exec_cgi.rb +81 -0
  63. data/test/test_extras_exec_cgi.sh +35 -0
  64. data/test/test_extras_try_gzip_static.rb +177 -0
  65. data/test/test_input.rb +128 -0
  66. data/test/test_mt_accept.rb +48 -0
  67. data/test/test_output_buffering.rb +90 -63
  68. data/test/test_rack.rb +1 -1
  69. data/test/test_rack_hijack.rb +2 -6
  70. data/test/test_reopen_logs.rb +2 -8
  71. data/test/test_serve_static.rb +104 -8
  72. data/test/test_server.rb +448 -73
  73. data/test/test_stream_file.rb +1 -1
  74. data/test/test_unix_socket.rb +72 -0
  75. data/test/test_wbuf.rb +20 -17
  76. data/yahns.gemspec +3 -0
  77. metadata +57 -5
@@ -0,0 +1,128 @@
1
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require_relative 'server_helper'
4
+ require 'digest/md5'
5
+
6
+ class TestInput < Testcase
7
+ ENV["N"].to_i > 1 and parallelize_me!
8
+ include ServerHelper
9
+ alias setup server_helper_setup
10
+ alias teardown server_helper_teardown
11
+
12
+ MD5 = lambda do |e|
13
+ input = e["rack.input"]
14
+ buf = ""
15
+ md5 = Digest::MD5.new
16
+ while input.read(16384, buf)
17
+ md5 << buf
18
+ end
19
+ body = md5.hexdigest
20
+ h = { "Content-Length" => body.size.to_s, "Content-Type" => 'text/plain' }
21
+ [ 200, h, [body] ]
22
+ end
23
+
24
+ def test_input_timeout_lazybuffer
25
+ stream_input_timeout(:lazy)
26
+ end
27
+
28
+ def test_input_timeout_nobuffer
29
+ stream_input_timeout(false)
30
+ end
31
+
32
+ def stream_input_timeout(ibtype)
33
+ err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
34
+ cfg.instance_eval do
35
+ GTL.synchronize do
36
+ app(:rack, MD5) do
37
+ listen "#{host}:#{port}"
38
+ input_buffering ibtype
39
+ client_timeout 1
40
+ end
41
+ end
42
+ stderr_path err.path
43
+ end
44
+ pid = mkserver(cfg)
45
+ c = get_tcp_client(host, port)
46
+ c.write "PUT / HTTP/1.1\r\nContent-Length: 666\r\n\r\n"
47
+ assert_equal c, c.wait(6)
48
+ Timeout.timeout(30) { assert_match %r{HTTP/1\.1 408 }, c.read }
49
+ c.close
50
+ ensure
51
+ quit_wait(pid)
52
+ end
53
+
54
+ def input_server(ru, ibtype)
55
+ err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
56
+ cfg.instance_eval do
57
+ GTL.synchronize do
58
+ app(:rack, ru) { listen "#{host}:#{port}"; input_buffering ibtype }
59
+ end
60
+ stderr_path err.path
61
+ end
62
+ pid = mkserver(cfg)
63
+ [ host, port, pid ]
64
+ end
65
+
66
+ def test_read_negative_lazy; _read_neg(:lazy); end
67
+ def test_read_negative_nobuffer; _read_neg(false); end
68
+
69
+ def _read_neg(ibtype)
70
+ ru = lambda do |env|
71
+ rv = []
72
+ input = env["rack.input"]
73
+ begin
74
+ input.read(-1)
75
+ rescue => e
76
+ rv << e.class.to_s
77
+ end
78
+ rv << input.read
79
+ rv << input.read(1).nil?
80
+ rv = rv.join(",")
81
+ h = { "Content-Length" => rv.size.to_s }
82
+ [ 200, h, [ rv ] ]
83
+ end
84
+ host, port, pid = input_server(ru, ibtype)
85
+ c = get_tcp_client(host, port)
86
+ c.write "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nhello"
87
+ assert_equal c, c.wait(30)
88
+ head, body = c.read.split(/\r\n\r\n/)
89
+ assert_match %r{ 200 OK}, head
90
+ exc, full, final = body.split(/,/)
91
+ assert_equal "hello", full
92
+ assert_equal "ArgumentError", exc
93
+ assert_equal true.to_s, final
94
+ c.close
95
+ ensure
96
+ quit_wait(pid)
97
+ end
98
+
99
+ def test_gets_lazy; _gets(:lazy); end
100
+ def test_gets_nobuffer; _gets(false); end
101
+
102
+ def _gets(ibtype)
103
+ in_join = lambda do |input|
104
+ rv = []
105
+ while line = input.gets
106
+ rv << line
107
+ end
108
+ rv.join(",")
109
+ end
110
+ ru = lambda do |env|
111
+ rv = in_join.call(env["rack.input"])
112
+ h = { "Content-Length" => rv.size.to_s }
113
+ [ 200, h, [ rv ] ]
114
+ end
115
+ host, port, pid = input_server(ru, ibtype)
116
+ c = get_tcp_client(host, port)
117
+ buf = "a\nb\n\n"
118
+ c.write "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\n#{buf}"
119
+ assert_equal c, c.wait(30)
120
+ head, body = c.read.split(/\r\n\r\n/)
121
+ assert_match %r{ 200 OK}, head
122
+ expect = in_join.call(StringIO.new(buf))
123
+ assert_equal expect, body
124
+ c.close
125
+ ensure
126
+ quit_wait(pid)
127
+ end
128
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ require_relative 'server_helper'
4
+ require 'rack/lobster'
5
+
6
+ class TestMtAccept < Testcase
7
+ ENV["N"].to_i > 1 and parallelize_me!
8
+ include ServerHelper
9
+ alias setup server_helper_setup
10
+ alias teardown server_helper_teardown
11
+
12
+ def test_mt_accept
13
+ err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
14
+ opts = { threads: 1 }
15
+ cfg.instance_eval do
16
+ GTL.synchronize do
17
+ app(:rack, Rack::Lobster.new) { listen "#{host}:#{port}", threads: 1 }
18
+ end
19
+ stderr_path err.path
20
+ end
21
+ pid = mkserver(cfg)
22
+ Net::HTTP.start(host, port) do |http|
23
+ assert_equal 200, http.request(Net::HTTP::Get.new("/")).code.to_i
24
+ end
25
+ orig_count = Dir["/proc/#{pid}/task/*"].size
26
+ quit_wait(pid)
27
+
28
+ cfg = Yahns::Config.new
29
+ opts = { threads: 1 }
30
+ cfg.instance_eval do
31
+ GTL.synchronize do
32
+ app(:rack, Rack::Lobster.new) { listen "#{host}:#{port}", threads: 2 }
33
+ end
34
+ stderr_path err.path
35
+ end
36
+ pid = mkserver(cfg)
37
+ Net::HTTP.start(host, port) do |http|
38
+ assert_equal 200, http.request(Net::HTTP::Get.new("/")).code.to_i
39
+ end
40
+ Timeout.timeout(30) do
41
+ begin
42
+ new_count = Dir["/proc/#{pid}/task/*"].size
43
+ end until new_count == (orig_count + 1) && sleep(0.01)
44
+ end
45
+ ensure
46
+ quit_wait(pid)
47
+ end
48
+ end if RUBY_PLATFORM =~ /linux/ && File.directory?("/proc")
@@ -5,7 +5,7 @@ require 'digest/md5'
5
5
  require 'rack/file'
6
6
 
7
7
  class TestOutputBuffering < Testcase
8
- parallelize_me!
8
+ ENV["N"].to_i > 1 and parallelize_me!
9
9
  include ServerHelper
10
10
  alias setup server_helper_setup
11
11
  alias teardown server_helper_teardown
@@ -41,7 +41,7 @@ class TestOutputBuffering < Testcase
41
41
  output_buffer(true, :http09)
42
42
  end
43
43
 
44
- def output_buffer(btype, check_type, delay = 4)
44
+ def output_buffer(btype, check_type)
45
45
  err = @err
46
46
  cfg = Yahns::Config.new
47
47
  host, port = @srv.addr[3], @srv.addr[1]
@@ -58,54 +58,47 @@ class TestOutputBuffering < Testcase
58
58
  end
59
59
  logger(Logger.new(err.path))
60
60
  end
61
- srv = Yahns::Server.new(cfg)
62
- pid = fork do
63
- ENV["YAHNS_FD"] = @srv.fileno.to_s
64
- srv.start.join
65
- end
61
+ pid = mkserver(cfg)
66
62
 
67
63
  case check_type
68
64
  when :curl
69
- # curl is faster for piping gigantic wads of data than Net::HTTP
70
- sh_sleep = delay ? "sleep #{delay} && " : ""
71
- md5 = `curl -sSf http://#{host}:#{port}/ | (#{sh_sleep} md5sum)`
72
- assert $?.success?, $?.inspect
73
- (md5 =~ /([a-f0-9]{32})/i) or raise "bad md5: #{md5.inspect}"
74
- md5 = $1
75
- assert_equal MD5.value, md5
65
+ # curl is faster for piping gigantic wads of data than Net::HTTP,
66
+ # and we need to be able to throttle a bit to force output buffering
67
+ c = IO.popen("curl -N -sSf http://#{host}:#{port}/")
68
+ wait_for_full(c)
69
+ dig, nr = md5sum(c)
70
+ c.close
71
+ assert_equal MD5.value, dig.hexdigest
76
72
  when :http09
77
73
  # HTTP/0.9
78
- c = TCPSocket.new(host, port)
74
+ c = get_tcp_client(host, port)
79
75
  c.write("GET /\r\n\r\n")
80
- md5in = IO.pipe
81
- md5out = IO.pipe
82
- sleep(delay) if delay
83
- md5pid = Process.spawn("md5sum", :in => md5in[0], :out => md5out[1])
84
- md5in[0].close
85
- md5out[1].close
86
- assert_equal(NR * RAND.size, IO.copy_stream(c, md5in[1]))
76
+ wait_for_full(c)
77
+ dig, nr = md5sum(c)
78
+ assert_equal(NR * RAND.size, nr)
87
79
  c.shutdown
88
80
  c.close
89
- md5in[1].close
90
- _, status = Timeout.timeout(10) { Process.waitpid2(md5pid) }
91
- assert status.success?, status.inspect
92
- md5 = md5out[0].read
93
- (md5 =~ /([a-f0-9]{32})/i) or raise "bad md5: #{md5.inspect}"
94
- md5 = $1
95
- assert_equal MD5.value, md5
96
- md5out[0].close
81
+ assert_equal MD5.value, dig.hexdigest
97
82
  else
98
83
  raise "TESTBUG"
99
84
  end
100
- rescue => e
101
- Yahns::Log.exception(Logger.new($stderr), "test", e)
102
- raise
103
85
  ensure
104
86
  quit_wait(pid)
105
87
  end
106
88
 
89
+ def md5sum(c)
90
+ dig = Digest::MD5.new
91
+ buf = ""
92
+ nr = 0
93
+ while c.read(8192, buf)
94
+ dig << buf
95
+ nr += buf.bytesize
96
+ end
97
+ [ dig, nr ]
98
+ end
99
+
107
100
  class BigHeader
108
- A = "A" * 65536
101
+ A = "A" * 8192
109
102
  def initialize(h)
110
103
  @h = h
111
104
  end
@@ -157,20 +150,14 @@ class TestOutputBuffering < Testcase
157
150
  end
158
151
  logger(Logger.new(err.path))
159
152
  end
160
- srv = Yahns::Server.new(cfg)
161
- pid = fork do
162
- ENV["YAHNS_FD"] = @srv.fileno.to_s
163
- srv.start.join
164
- end
153
+ pid = mkserver(cfg)
165
154
  threads = []
166
155
 
167
156
  # start with just a big header
168
157
  threads << Thread.new do
169
- c = TCPSocket.new(host, port)
158
+ c = get_tcp_client(host, port)
170
159
  c.write "GET / HTTP/1.0\r\n\r\n"
171
- begin
172
- sleep 1
173
- end while c.nread == 0
160
+ wait_for_full(c)
174
161
  nr = 0
175
162
  last = nil
176
163
  c.each_line do |line|
@@ -186,11 +173,9 @@ class TestOutputBuffering < Testcase
186
173
  end
187
174
 
188
175
  threads << Thread.new do
189
- c = TCPSocket.new(host, port)
176
+ c = get_tcp_client(host, port)
190
177
  c.write "GET /COPYING HTTP/1.0\r\n\r\n"
191
- begin
192
- sleep 1
193
- end while c.nread == 0
178
+ wait_for_full(c)
194
179
  nr = 0
195
180
  c.each_line do |line|
196
181
  case line
@@ -205,11 +190,9 @@ class TestOutputBuffering < Testcase
205
190
  end
206
191
 
207
192
  threads << Thread.new do
208
- c = TCPSocket.new(host, port)
193
+ c = get_tcp_client(host, port)
209
194
  c.write "GET /COPYING HTTP/1.0\r\nRange: bytes=5-46\r\n\r\n"
210
- begin
211
- sleep 1
212
- end while c.nread == 0
195
+ wait_for_full(c)
213
196
  nr = 0
214
197
  c.each_line do |line|
215
198
  case line
@@ -236,6 +219,7 @@ class TestOutputBuffering < Testcase
236
219
  cfg = Yahns::Config.new
237
220
  size = RAND.size * NR
238
221
  host, port = @srv.addr[3], @srv.addr[1]
222
+ re = /timeout on :wait_writable after 0\.1s$/
239
223
  cfg.instance_eval do
240
224
  ru = lambda do |e|
241
225
  if e["PATH_INFO"] == "/bh"
@@ -249,30 +233,34 @@ class TestOutputBuffering < Testcase
249
233
  app(:rack, ru) do
250
234
  listen "#{host}:#{port}"
251
235
  output_buffering false
252
- client_timeout 3
236
+ client_timeout 0.1
253
237
  logger(Logger.new(apperr.path))
254
238
  end
255
239
  end
256
240
  logger(Logger.new(err.path))
257
241
  end
258
- srv = Yahns::Server.new(cfg)
259
- pid = fork do
260
- ENV["YAHNS_FD"] = @srv.fileno.to_s
261
- srv.start.join
242
+ pid = mkserver(cfg)
243
+
244
+ wait_for_timeout_msg = lambda do
245
+ Timeout.timeout(6) do
246
+ sleep(0.01) until File.readlines(apperr.path).grep(re).size == 2
247
+ end
262
248
  end
263
249
  threads = []
264
250
  threads << Thread.new do
265
- c = TCPSocket.new(host, port)
251
+ c = get_tcp_client(host, port)
266
252
  c.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
267
- sleep(5) # wait for timeout
253
+ wait_for_full(c)
254
+ wait_for_timeout_msg.call
268
255
  assert_operator c.nread, :>, 0
269
256
  c
270
257
  end
271
258
 
272
259
  threads << Thread.new do
273
- c = TCPSocket.new(host, port)
260
+ c = get_tcp_client(host, port)
274
261
  c.write("GET /bh HTTP/1.1\r\nHost: example.com\r\n\r\n")
275
- sleep(5) # wait for timeout
262
+ wait_for_full(c)
263
+ wait_for_timeout_msg.call
276
264
  assert_operator c.nread, :>, 0
277
265
  c
278
266
  end
@@ -280,12 +268,51 @@ class TestOutputBuffering < Testcase
280
268
  assert_operator size, :>, threads[0].value.read.size
281
269
  assert_operator size, :>, threads[1].value.read.size
282
270
  msg = File.readlines(apperr.path)
283
- msg = msg.grep(/timeout on :wait_writable after 3s$/)
271
+ msg = msg.grep(re)
284
272
  assert_equal 2, msg.size
285
273
  threads.each { |t| t.value.close }
286
274
  ensure
287
275
  apperr.close! if apperr
288
276
  quit_wait(pid)
289
277
  end
290
- end if `which curl 2>/dev/null`.strip =~ /curl/ &&
291
- `which md5sum 2>/dev/null`.strip =~ /md5sum/
278
+
279
+ def test_hijacked
280
+ err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
281
+ cfg.instance_eval do
282
+ ru = lambda do |e|
283
+ h = {
284
+ "Content-Type" => "text/plain",
285
+ "Content-Length" => "4",
286
+ "rack.hijack" => proc { |x| x.write("HIHI"); x.close },
287
+ }
288
+ body = Object.new
289
+ def body.each; abort "body#each should not be used"; end
290
+ [ 200, BigHeader.new(h), body ]
291
+ end
292
+ GTL.synchronize do
293
+ app(:rack, ru) do
294
+ listen "#{host}:#{port}"
295
+ end
296
+ end
297
+ stderr_path err.path
298
+ end
299
+ pid = mkserver(cfg)
300
+ c = get_tcp_client(host, port)
301
+ c.write "GET / HTTP/1.0\r\n\r\n"
302
+ wait_for_full(c)
303
+ nr = 0
304
+ last = nil
305
+ c.each_line do |line|
306
+ case line
307
+ when %r{\AX-} then nr += 1
308
+ else
309
+ last = line
310
+ end
311
+ end
312
+ assert_equal NR, nr
313
+ assert_equal "HIHI", last
314
+ c.close
315
+ ensure
316
+ quit_wait(pid)
317
+ end
318
+ end if `which curl 2>/dev/null`.strip =~ /curl/
data/test/test_rack.rb CHANGED
@@ -4,7 +4,7 @@ require_relative 'helper'
4
4
  require 'rack/lobster'
5
5
  require 'yahns/rack'
6
6
  class TestRack < Testcase
7
- parallelize_me!
7
+ ENV["N"].to_i > 1 and parallelize_me!
8
8
 
9
9
  def test_rack
10
10
  tmp = tmpfile(%W(config .ru))
@@ -3,7 +3,7 @@
3
3
  require_relative 'server_helper'
4
4
 
5
5
  class TestRackHijack < Testcase
6
- parallelize_me!
6
+ ENV["N"].to_i > 1 and parallelize_me!
7
7
  include ServerHelper
8
8
  alias setup server_helper_setup
9
9
  alias teardown server_helper_teardown
@@ -53,11 +53,7 @@ class TestRackHijack < Testcase
53
53
  GTL.synchronize { app(:rack, HIJACK_APP) { listen "#{host}:#{port}" } }
54
54
  logger(Logger.new(err.path))
55
55
  end
56
- srv = Yahns::Server.new(cfg)
57
- pid = fork do
58
- ENV["YAHNS_FD"] = @srv.fileno.to_s
59
- srv.start.join
60
- end
56
+ pid = mkserver(cfg)
61
57
  res = Net::HTTP.start(host, port) { |h| h.get("/hijack_req") }
62
58
  assert_equal "request.hijacked", res.body
63
59
  assert_equal 200, res.code.to_i