about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2013-10-24 23:25:47 +0000
committerEric Wong <e@80x24.org>2013-10-24 23:39:28 +0000
commitb73d91e6235f2c83cb768268a6661b88cdc96d4c (patch)
tree28c09ff832a580be28326bed376de923858baadb
parentc9ad7f3a7b83e990fd2fc731ec796eec4ed4130b (diff)
downloadyahns-b73d91e6235f2c83cb768268a6661b88cdc96d4c.tar.gz
This should speed up out-of-the-box performance with curl
as well as allow input_buffering :lazy/true users to reject
requests they don't like.
-rw-r--r--lib/yahns/http_client.rb13
-rw-r--r--lib/yahns/http_response.rb12
-rw-r--r--test/server_helper.rb8
-rw-r--r--test/test_client_max_body_size.rb8
-rw-r--r--test/test_expect_100.rb92
5 files changed, 123 insertions, 10 deletions
diff --git a/lib/yahns/http_client.rb b/lib/yahns/http_client.rb
index ce55525..641dc65 100644
--- a/lib/yahns/http_client.rb
+++ b/lib/yahns/http_client.rb
@@ -56,6 +56,10 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
     end
     @state = :body
     @input = k.tmpio_for(len)
+
+    # FIXME: we must buffer and not block here even if it's only a few bytes
+    http_100_response(@hs.env)
+
     rbuf = Thread.current[:yahns_rbuf]
     @hs.filter_body(rbuf, @hs.buf)
     @input.write(rbuf)
@@ -158,11 +162,16 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
     end
 
     # run the rack app
-    response = k.app.call(env.merge!(k.app_defaults))
+    status, headers, body = k.app.call(env.merge!(k.app_defaults))
     return :ignore if env.include?(RACK_HIJACK_IO)
+    if status.to_i == 100
+      # FIXME: we must buffer and not wait here even if it's only a few bytes
+      http_100_response(env)
+      status, headers, body = k.app.call(env)
+    end
 
     # this returns :wait_readable, :wait_writable, :ignore, or nil:
-    http_response_write(*response)
+    http_response_write(status, headers, body)
   end
 
   def hijack_proc(env)
diff --git a/lib/yahns/http_response.rb b/lib/yahns/http_response.rb
index 3ee4e21..a34832a 100644
--- a/lib/yahns/http_response.rb
+++ b/lib/yahns/http_response.rb
@@ -19,11 +19,23 @@ module Yahns::HttpResponse # :nodoc:
   CONN_CLOSE = "Connection: close\r\n\r\n"
   Z = ""
   RESPONSE_START = "HTTP/1.1 "
+  R100_RAW = "HTTP/1.1 100 Continue\r\n\r\n"
+  R100_CCC = "100 Continue\r\n\r\nHTTP/1.1 "
+  HTTP_EXPECT = "HTTP_EXPECT"
 
   def response_start
     @response_start_sent ? Z : RESPONSE_START
   end
 
+  # only used if input_buffering is true (not :lazy or false)
+  # :lazy/false gives control to the app
+  def http_100_response(env)
+    if env[HTTP_EXPECT] =~ /\A100-continue\z/i
+      kgio_write(@response_start_sent ? R100_CCC : R100_RAW)
+      env.delete(HTTP_EXPECT)
+    end
+  end
+
   def response_wait_write(rv)
     # call the kgio_wait_readable or kgio_wait_writable method
     ok = __send__("kgio_#{rv}") and return ok
diff --git a/test/server_helper.rb b/test/server_helper.rb
index 7a64618..aeb0dc9 100644
--- a/test/server_helper.rb
+++ b/test/server_helper.rb
@@ -70,4 +70,12 @@ module ServerHelper
     @err = tmpfile(%w(srv .err))
     @ru = nil
   end
+
+  def mkserver(cfg)
+    fork do
+      srv = Yahns::Server.new(cfg)
+      ENV["YAHNS_FD"] = @srv.fileno.to_s
+      srv.start.join
+    end
+  end
 end
diff --git a/test/test_client_max_body_size.rb b/test/test_client_max_body_size.rb
index cdf8ce9..5688f2b 100644
--- a/test/test_client_max_body_size.rb
+++ b/test/test_client_max_body_size.rb
@@ -27,14 +27,6 @@ class TestClientMaxBodySize < Testcase
     "Content-Length: #{bytes}\r\n\r\n#{'*' * body_bytes}"
   end
 
-  def mkserver(cfg)
-    fork do
-      srv = Yahns::Server.new(cfg)
-      ENV["YAHNS_FD"] = @srv.fileno.to_s
-      srv.start.join
-    end
-  end
-
   def test_0_lazy; cmbs_test_0(:lazy); end
   def test_0_true; cmbs_test_0(true); end
   def test_0_false; cmbs_test_0(false); end
diff --git a/test/test_expect_100.rb b/test/test_expect_100.rb
new file mode 100644
index 0000000..484f44d
--- /dev/null
+++ b/test/test_expect_100.rb
@@ -0,0 +1,92 @@
+# 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'
+
+class TestExpect100 < Testcase
+  ENV["N"].to_i > 1 and parallelize_me!
+  include ServerHelper
+  alias setup server_helper_setup
+  alias teardown server_helper_teardown
+
+  APP = lambda do |env|
+    h = [ %w(Content-Length 0), %w(Content-Type text/plain) ]
+    if env["HTTP_EXPECT"] =~ /100-continue/
+      code = env["HTTP_X_FORCE_RCODE"] || 100
+      [ code, h, [] ]
+    else
+      [ 201, h, [] ]
+    end
+  end
+
+  def test_buffer_noccc; _test_expect_100(true, false); end
+  def test_nobuffer_noccc; _test_expect_100(false, false); end
+  def test_lazybuffer_noccc; _test_expect_100(:lazy, false); end
+  def test_buffer_ccc; _test_expect_100(true, true); end
+  def test_nobuffer_ccc; _test_expect_100(false, true); end
+  def test_lazybuffer_ccc; _test_expect_100(:lazy, true); end
+
+  def _test_expect_100(btype, ccc)
+    err = @err
+    cfg = Yahns::Config.new
+    host, port = @srv.addr[3], @srv.addr[1]
+    cfg.instance_eval do
+      stderr_path err.path
+      GTL.synchronize {
+        app(:rack, APP) {
+          listen "#{host}:#{port}"
+          input_buffering btype
+          check_client_connection ccc
+        }
+      }
+    end
+    pid = mkserver(cfg)
+    c = get_tcp_client(host, port)
+    r = "PUT / HTTP/1.0\r\nExpect: 100-continue\r\n\r\n"
+    c.write(r)
+    assert c.wait(10), "timed out"
+    buf = c.read
+    assert_match(%r{\AHTTP/1\.1 100 Continue\r\n\r\nHTTP/1\.1 201}, buf)
+
+    rc = system("curl -sSf -T- http://#{host}:#{port}/", in: "/dev/null")
+    assert $?.success?, $?.inspect
+    assert rc
+  ensure
+    quit_wait(pid)
+  end
+
+  def _test_reject(btype, ccc)
+    err = @err
+    cfg = Yahns::Config.new
+    host, port = @srv.addr[3], @srv.addr[1]
+    cfg.instance_eval do
+      stderr_path err.path
+      GTL.synchronize {
+        app(:rack, APP) {
+          listen "#{host}:#{port}"
+          input_buffering btype
+          check_client_connection ccc
+        }
+      }
+    end
+    pid = mkserver(cfg)
+    c = get_tcp_client(host, port)
+    r = "PUT / HTTP/1.0\r\nExpect: 100-continue\r\nX-Force-RCODE: 666\r\n\r\n"
+    c.write(r)
+    assert c.wait(10), "timed out"
+    buf = c.read
+    assert_match(%r{\AHTTP/1\.1 666\r\n}, buf)
+
+    opts = { in: "/dev/null" }
+    url = "http://#{host}:#{port}/"
+    rc = system("curl -sf -T- -HX-Force-Rcode:666 #{url}", in: "/dev/null")
+    refute $?.success?, $?.inspect
+    refute rc
+  ensure
+    quit_wait(pid)
+  end
+
+  def test_reject_lazy_noccc; _test_reject(:lazy, false); end
+  def test_reject_true_noccc; _test_reject(false, false); end
+  def test_reject_lazy_ccc; _test_reject(:lazy, true); end
+  def test_reject_true_ccc; _test_reject(false, true); end
+end