about summary refs log tree commit homepage
path: root/test/test_output_buffering.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_output_buffering.rb')
-rw-r--r--test/test_output_buffering.rb288
1 files changed, 288 insertions, 0 deletions
diff --git a/test/test_output_buffering.rb b/test/test_output_buffering.rb
new file mode 100644
index 0000000..6fe22ba
--- /dev/null
+++ b/test/test_output_buffering.rb
@@ -0,0 +1,288 @@
+# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+require_relative 'server_helper'
+require 'digest/md5'
+require 'rack/file'
+
+class TestOutputBuffering < Testcase
+  parallelize_me!
+  include ServerHelper
+  alias setup server_helper_setup
+  alias teardown server_helper_teardown
+
+  GPLv3 = File.read("COPYING")
+  RAND = IO.binread("/dev/urandom", 666) * 119
+  dig = Digest::MD5.new
+  NR = 1337
+  MD5 = Thread.new do
+    NR.times { dig << RAND }
+    dig.hexdigest
+  end
+
+  class BigBody
+    def each
+      NR.times { yield RAND }
+    end
+  end
+
+  def test_output_buffer_false_curl
+    output_buffer(false, :curl)
+  end
+
+  def test_output_buffer_false_http09
+    output_buffer(false, :http09)
+  end
+
+  def test_output_buffer_true_curl
+    output_buffer(true, :curl)
+  end
+
+  def test_output_buffer_true_http09
+    output_buffer(true, :http09)
+  end
+
+  def output_buffer(btype, check_type, delay = 4)
+    err = @err
+    cfg = Yahns::Config.new
+    host, port = @srv.addr[3], @srv.addr[1]
+    len = (RAND.size * NR).to_s
+    cfg.instance_eval do
+      ru = lambda do |e|
+        [ 200, {'Content-Length'=>len}, BigBody.new ]
+      end
+      GTL.synchronize do
+        app(:rack, ru) do
+          listen "#{host}:#{port}"
+          output_buffering btype
+        end
+      end
+      logger(Logger.new(err.path))
+    end
+    srv = Yahns::Server.new(cfg)
+    pid = fork do
+      ENV["YAHNS_FD"] = @srv.fileno.to_s
+      srv.start.join
+    end
+
+    case check_type
+    when :curl
+      # curl is faster for piping gigantic wads of data than Net::HTTP
+      sh_sleep = delay ? "sleep #{delay} && " : ""
+      md5 = `curl -sSf http://#{host}:#{port}/ | (#{sh_sleep} md5sum)`
+      assert $?.success?, $?.inspect
+      (md5 =~ /([a-f0-9]{32})/i) or raise "bad md5: #{md5.inspect}"
+      md5 = $1
+      assert_equal MD5.value, md5
+    when :http09
+      # HTTP/0.9
+      c = TCPSocket.new(host, port)
+      c.write("GET /\r\n\r\n")
+      md5in = IO.pipe
+      md5out = IO.pipe
+      sleep(delay) if delay
+      md5pid = Process.spawn("md5sum", :in => md5in[0], :out => md5out[1])
+      md5in[0].close
+      md5out[1].close
+      assert_equal(NR * RAND.size, IO.copy_stream(c, md5in[1]))
+      c.close
+      md5in[1].close
+      _, status = Timeout.timeout(10) { Process.waitpid2(md5pid) }
+      assert status.success?, status.inspect
+      md5 = md5out[0].read
+      (md5 =~ /([a-f0-9]{32})/i) or raise "bad md5: #{md5.inspect}"
+      md5 = $1
+      assert_equal MD5.value, md5
+      md5out[0].close
+    else
+      raise "TESTBUG"
+    end
+  rescue => e
+    Yahns::Log.exception(Logger.new($stderr), "test", e)
+    raise
+  ensure
+    quit_wait(pid)
+  end
+
+  class BigHeader
+    A = "A" * 65536
+    def initialize(h)
+      @h = h
+    end
+    def each
+      NR.times do |n|
+        yield("X-#{n}", A)
+      end
+      @h.each { |k,v| yield(k,v) }
+    end
+  end
+
+  def test_big_header
+    err = @err
+    cfg = Yahns::Config.new
+    host, port = @srv.addr[3], @srv.addr[1]
+    cfg.instance_eval do
+      ru = lambda do |e|
+        case e["PATH_INFO"]
+        when "/COPYING"
+          Rack::File.new(Dir.pwd).call(e)
+          gplv3 = File.open("COPYING")
+          def gplv3.each
+            raise "SHOULD NOT BE CALLED"
+          end
+          size = gplv3.stat.size
+          len = size.to_s
+          ranges = Rack::Utils.byte_ranges(e, size)
+          status = 200
+          h = { "Content-Type" => "text/plain", "Content-Length" => len }
+          if ranges && ranges.size == 1
+            status = 206
+            range = ranges[0]
+            h["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
+            size = range.end - range.begin + 1
+            len.replace(size.to_s)
+          end
+          [ status , BigHeader.new(h), gplv3 ]
+        when "/"
+          h = { "Content-Type" => "text/plain", "Content-Length" => "4" }
+          [ 200, BigHeader.new(h), ["BIG\n"] ]
+        else
+          raise "WTF"
+        end
+      end
+      GTL.synchronize do
+        app(:rack, ru) do
+          listen "#{host}:#{port}"
+        end
+      end
+      logger(Logger.new(err.path))
+    end
+    srv = Yahns::Server.new(cfg)
+    pid = fork do
+      ENV["YAHNS_FD"] = @srv.fileno.to_s
+      srv.start.join
+    end
+    threads = []
+
+    # start with just a big header
+    threads << Thread.new do
+      c = TCPSocket.new(host, port)
+      c.write "GET / HTTP/1.0\r\n\r\n"
+      begin
+        sleep 1
+      end while c.nread == 0
+      nr = 0
+      last = nil
+      c.each_line do |line|
+        case line
+        when %r{\AX-} then nr += 1
+        else
+          last = line
+        end
+      end
+      assert_equal NR, nr
+      assert_equal "BIG\n", last
+      c.close
+    end
+
+    threads << Thread.new do
+      c = TCPSocket.new(host, port)
+      c.write "GET /COPYING HTTP/1.0\r\n\r\n"
+      begin
+        sleep 1
+      end while c.nread == 0
+      nr = 0
+      c.each_line do |line|
+        case line
+        when %r{\AX-} then nr += 1
+        else
+          break if line == "\r\n"
+        end
+      end
+      assert_equal NR, nr
+      assert_equal GPLv3, c.read
+      c.close
+    end
+
+    threads << Thread.new do
+      c = TCPSocket.new(host, port)
+      c.write "GET /COPYING HTTP/1.0\r\nRange: bytes=5-46\r\n\r\n"
+      begin
+        sleep 1
+      end while c.nread == 0
+      nr = 0
+      c.each_line do |line|
+        case line
+        when %r{\AX-} then nr += 1
+        else
+          break if line == "\r\n"
+        end
+      end
+      assert_equal NR, nr
+      assert_equal GPLv3[5..46], c.read
+      c.close
+    end
+    threads.each do |t|
+      assert_equal t, t.join(30)
+      assert_nil t.value
+    end
+  ensure
+    quit_wait(pid)
+  end
+
+  def test_client_timeout
+    err = @err
+    apperr = tmpfile(%w(app .err))
+    cfg = Yahns::Config.new
+    size = RAND.size * NR
+    host, port = @srv.addr[3], @srv.addr[1]
+    cfg.instance_eval do
+      ru = lambda do |e|
+        if e["PATH_INFO"] == "/bh"
+          h = { "Content-Type" => "text/plain", "Content-Length" => "4" }
+          [ 200, BigHeader.new(h), ["BIG\n"] ]
+        else
+          [ 200, {'Content-Length' => size.to_s }, BigBody.new ]
+        end
+      end
+      GTL.synchronize do
+        app(:rack, ru) do
+          listen "#{host}:#{port}"
+          output_buffering false
+          client_timeout 3
+          logger(Logger.new(apperr.path))
+        end
+      end
+      logger(Logger.new(err.path))
+    end
+    srv = Yahns::Server.new(cfg)
+    pid = fork do
+      ENV["YAHNS_FD"] = @srv.fileno.to_s
+      srv.start.join
+    end
+    threads = []
+    threads << Thread.new do
+      c = get_tcp_client(host, port)
+      c.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
+      sleep(5) # wait for timeout
+      assert_operator c.nread, :>, 0
+      c
+    end
+
+    threads << Thread.new do
+      c = get_tcp_client(host, port)
+      c.write("GET /bh HTTP/1.1\r\nHost: example.com\r\n\r\n")
+      sleep(5) # wait for timeout
+      assert_operator c.nread, :>, 0
+      c
+    end
+    threads.each { |t| t.join(10) }
+    assert_operator size, :>, threads[0].value.read.size
+    assert_operator size, :>, threads[1].value.read.size
+    msg = File.readlines(apperr.path)
+    msg = msg.grep(/timeout on :wait_writable after 3s$/)
+    assert_equal 2, msg.size
+  ensure
+    quit_wait(pid)
+  end
+end if `which curl 2>/dev/null`.strip =~ /curl/ &&
+       `which md5sum 2>/dev/null`.strip =~ /md5sum/