yahns 0.0.1 → 0.0.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 (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