From a832d02cdcbdf4c64570cabd2008fc1a3d4c7daa Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 25 Apr 2020 13:36:20 +1200 Subject: [PATCH 1/9] Ensure the connection headers are written correctly. HTTP/1.0 persistent connections were not working correctly because `write_empty_body` does not write connection headers. Prefer to use `write_body` which implements the correct logic. --- lib/async/http/protocol/http1/client.rb | 2 +- spec/async/http/protocol/shared_examples.rb | 42 ++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/async/http/protocol/http1/client.rb b/lib/async/http/protocol/http1/client.rb index 87a422f4..782e9630 100644 --- a/lib/async/http/protocol/http1/client.rb +++ b/lib/async/http/protocol/http1/client.rb @@ -69,7 +69,7 @@ def call(request, task: Task.current) elsif protocol = request.protocol write_upgrade_body(protocol) else - write_empty_body(request.body) + write_body(@version, body, false, trailers) end return Response.read(self, request) diff --git a/spec/async/http/protocol/shared_examples.rb b/spec/async/http/protocol/shared_examples.rb index 6c7326dd..198a389c 100644 --- a/spec/async/http/protocol/shared_examples.rb +++ b/spec/async/http/protocol/shared_examples.rb @@ -189,6 +189,27 @@ context 'using GET method' do let(:expected) {"GET #{protocol::VERSION}"} + it "can handle many simultaneous requests", timeout: nil do |example| + duration = Async::Clock.measure do + 10.times do + tasks = 100.times.collect do + Async do + client.get("/") + end + end + + tasks.each do |task| + response = task.wait + expect(response).to be_success + expect(response.read).to eq expected + end + end + end + + example.reporter.message "Pool: #{client.pool}" + example.reporter.message "Duration = #{duration.round(2)}" + end + context 'with response' do let(:response) {client.get("/")} after {response.finish} @@ -223,27 +244,6 @@ expect(response.version).to_not be_nil end end - - it "can handle many simultaneous requests", timeout: 30 do |example| - duration = Async::Clock.measure do - 10.times do - tasks = 100.times.collect do - Async do - client.get("/") - end - end - - tasks.each do |task| - response = task.wait - expect(response).to be_success - expect(response.read).to eq expected - end - end - end - - example.reporter.message "Pool: #{client.pool}" - example.reporter.message "Duration = #{duration.round(2)}" - end end context 'HEAD' do From fbfd74c29b48e08c75bac0a6c3b21d79ef22da9b Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 5 May 2020 21:54:31 +1200 Subject: [PATCH 2/9] Prefer `gems.rb`. --- gems.locked | 103 +++++++++++++++++++++++++++++++++++++++++++++ Gemfile => gems.rb | 0 2 files changed, 103 insertions(+) create mode 100644 gems.locked rename Gemfile => gems.rb (100%) diff --git a/gems.locked b/gems.locked new file mode 100644 index 00000000..c8260334 --- /dev/null +++ b/gems.locked @@ -0,0 +1,103 @@ +PATH + remote: . + specs: + async-http (0.52.0) + async (~> 1.25) + async-io (~> 1.28) + async-pool (~> 0.2) + protocol-http (~> 0.18.0) + protocol-http1 (~> 0.12.0) + protocol-http2 (~> 0.14.0) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.0) + async (1.25.2) + console (~> 1.0) + nio4r (~> 2.3) + timers (~> 4.1) + async-container (0.14.1) + async (~> 1.0) + async-io (~> 1.4) + process-group + async-io (1.29.0) + async (~> 1.14) + async-pool (0.3.1) + async (~> 1.25) + async-rest (0.12.2) + async-http (~> 0.42) + protocol-http (~> 0.7) + async-rspec (1.14.0) + rspec (~> 3.0) + rspec-files (~> 1.0) + rspec-memory (~> 1.0) + bake (0.13.0) + samovar (~> 2.1) + bake-bundler (0.3.1) + bake (~> 0.9) + rspec + console (1.8.2) + covered (0.13.1) + async-rest + console (~> 1.0) + msgpack + parser + diff-lcs (1.3) + ffi (1.12.2) + mapping (1.1.1) + msgpack (1.3.3) + nio4r (2.5.2) + parser (2.7.1.2) + ast (~> 2.4.0) + process-group (1.2.1) + process-terminal (~> 0.2.0) + process-terminal (0.2.0) + ffi + protocol-hpack (1.4.2) + protocol-http (0.18.0) + protocol-http1 (0.12.0) + protocol-http (~> 0.18) + protocol-http2 (0.14.0) + protocol-hpack (~> 1.4) + protocol-http (~> 0.18) + rack (2.2.2) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.2) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-files (1.0.2) + rspec (~> 3.0) + rspec-memory (1.0.2) + rspec (~> 3.0) + rspec-mocks (3.9.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.3) + samovar (2.1.4) + console (~> 1.0) + mapping (~> 1.0) + timers (4.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + async-container (~> 0.14.0) + async-http! + async-rspec (~> 1.10) + bake-bundler + bundler + covered + rack-test + rspec (~> 3.6) + +BUNDLED WITH + 2.1.4 diff --git a/Gemfile b/gems.rb similarity index 100% rename from Gemfile rename to gems.rb From 5f63e9c0bd74ab37cc3281748c40a8aad3d19494 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 5 May 2020 21:54:49 +1200 Subject: [PATCH 3/9] Use the request version when writing the body. --- lib/async/http/protocol/http1/server.rb | 4 ++-- spec/async/http/performance_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/async/http/protocol/http1/server.rb b/lib/async/http/protocol/http1/server.rb index ada8484a..abc576ba 100644 --- a/lib/async/http/protocol/http1/server.rb +++ b/lib/async/http/protocol/http1/server.rb @@ -94,12 +94,12 @@ def each(task: Task.current) request = nil unless body response = nil - write_body(@version, body, head, trailers) + write_body(request.version, body, head, trailers) end else # If the request failed to generate a response, it was an internal server error: write_response(@version, 500, {}) - write_body(@version, nil) + write_body(request.version, nil) end # Gracefully finish reading the request body if it was not already done so. diff --git a/spec/async/http/performance_spec.rb b/spec/async/http/performance_spec.rb index 54d87c5f..36f30b04 100755 --- a/spec/async/http/performance_spec.rb +++ b/spec/async/http/performance_spec.rb @@ -30,7 +30,7 @@ require 'etc' require 'benchmark' -RSpec.shared_examples_for 'wrk benchmark' do +RSpec.shared_examples_for 'client benchmark' do let(:endpoint) {Async::HTTP::Endpoint.parse("http://127.0.0.1:9294")} let(:url) {endpoint.url.to_s} @@ -70,7 +70,7 @@ if ab = `which ab`.chomp! # puts [ab, "-n", (concurrency*repeats).to_s, "-c", concurrency.to_s, url].join(' ') - system(ab, "-n", (concurrency*repeats).to_s, "-c", concurrency.to_s, url) + system(ab, "-k", "-n", (concurrency*repeats).to_s, "-c", concurrency.to_s, url) end if wrk = `which wrk`.chomp! @@ -90,7 +90,7 @@ ) end - include_examples 'wrk benchmark' + include_examples 'client benchmark' end describe 'multiple chunks' do @@ -100,6 +100,6 @@ end end - include_examples 'wrk benchmark' + include_examples 'client benchmark' end end From e9faaa71b5b21ce9f08f85f1550e93d8dae92ce5 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 5 May 2020 21:55:31 +1200 Subject: [PATCH 4/9] Patch version bump. --- lib/async/http/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/http/version.rb b/lib/async/http/version.rb index a8a6f162..adcafa5f 100644 --- a/lib/async/http/version.rb +++ b/lib/async/http/version.rb @@ -22,6 +22,6 @@ module Async module HTTP - VERSION = "0.52.0" + VERSION = "0.52.1" end end From 935da72a3ca98ebedc2c3cf831c005c899e88b60 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 6 May 2020 00:38:55 +1200 Subject: [PATCH 5/9] Add support for pessimistic flushing. --- async-http.gemspec | 4 ++-- gems.locked | 14 +++++++------- lib/async/http/body/delayed.rb | 4 ++++ lib/async/http/body/hijack.rb | 6 ++++++ lib/async/http/body/writable.rb | 4 ++++ 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/async-http.gemspec b/async-http.gemspec index 2200a718..92ded1df 100644 --- a/async-http.gemspec +++ b/async-http.gemspec @@ -21,8 +21,8 @@ Gem::Specification.new do |spec| spec.add_dependency("async-io", "~> 1.28") spec.add_dependency("async-pool", "~> 0.2") - spec.add_dependency("protocol-http", "~> 0.18.0") - spec.add_dependency("protocol-http1", "~> 0.12.0") + spec.add_dependency("protocol-http", "~> 0.19.0") + spec.add_dependency("protocol-http1", "~> 0.13.0") spec.add_dependency("protocol-http2", "~> 0.14.0") # spec.add_dependency("openssl") diff --git a/gems.locked b/gems.locked index c8260334..700a9714 100644 --- a/gems.locked +++ b/gems.locked @@ -1,19 +1,19 @@ PATH remote: . specs: - async-http (0.52.0) + async-http (0.52.1) async (~> 1.25) async-io (~> 1.28) async-pool (~> 0.2) - protocol-http (~> 0.18.0) - protocol-http1 (~> 0.12.0) + protocol-http (~> 0.19.0) + protocol-http1 (~> 0.13.0) protocol-http2 (~> 0.14.0) GEM remote: https://rubygems.org/ specs: ast (2.4.0) - async (1.25.2) + async (1.26.0) console (~> 1.0) nio4r (~> 2.3) timers (~> 4.1) @@ -55,9 +55,9 @@ GEM process-terminal (0.2.0) ffi protocol-hpack (1.4.2) - protocol-http (0.18.0) - protocol-http1 (0.12.0) - protocol-http (~> 0.18) + protocol-http (0.19.0) + protocol-http1 (0.13.0) + protocol-http (~> 0.19) protocol-http2 (0.14.0) protocol-hpack (~> 1.4) protocol-http (~> 0.18) diff --git a/lib/async/http/body/delayed.rb b/lib/async/http/body/delayed.rb index f8f53cd0..33798bc9 100644 --- a/lib/async/http/body/delayed.rb +++ b/lib/async/http/body/delayed.rb @@ -32,6 +32,10 @@ def initialize(body, delay = 0.01) @delay = delay end + def ready? + false + end + def read Async::Task.current.sleep(@delay) diff --git a/lib/async/http/body/hijack.rb b/lib/async/http/body/hijack.rb index ffd18b33..204f9045 100644 --- a/lib/async/http/body/hijack.rb +++ b/lib/async/http/body/hijack.rb @@ -59,6 +59,12 @@ def empty? end end + def ready? + if @stream + @stream.output.ready? + end + end + # Read the next available chunk. def read unless @task diff --git a/lib/async/http/body/writable.rb b/lib/async/http/body/writable.rb index 4b06b3e6..b2eedcbf 100644 --- a/lib/async/http/body/writable.rb +++ b/lib/async/http/body/writable.rb @@ -68,6 +68,10 @@ def closed? @closed end + def ready? + !@queue.empty? + end + # Has the producer called #finish and has the reader consumed the nil token? def empty? @finished From 9810e84d09261091c98a22273846b6db0246c243 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 6 May 2020 00:40:50 +1200 Subject: [PATCH 6/9] Fix slowloris spec. --- spec/async/http/body/slowloris_spec.rb | 2 ++ spec/async/http/body/writable_examples.rb | 2 -- spec/async/http/body/writable_spec.rb | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/async/http/body/slowloris_spec.rb b/spec/async/http/body/slowloris_spec.rb index 7580302e..c98f47ac 100644 --- a/spec/async/http/body/slowloris_spec.rb +++ b/spec/async/http/body/slowloris_spec.rb @@ -23,6 +23,8 @@ require 'async/http/body/slowloris' RSpec.describe Async::HTTP::Body::Slowloris do + include_context Async::RSpec::Reactor + it_behaves_like Async::HTTP::Body::Writable it "closes body with error if throughput is not maintained" do diff --git a/spec/async/http/body/writable_examples.rb b/spec/async/http/body/writable_examples.rb index 572aaa24..6501e8f9 100644 --- a/spec/async/http/body/writable_examples.rb +++ b/spec/async/http/body/writable_examples.rb @@ -31,8 +31,6 @@ require 'async/rspec/ssl' RSpec.shared_examples_for Async::HTTP::Body::Writable do - include_context Async::RSpec::Reactor - it "can write and read data" do 3.times do |i| subject.write("Hello World #{i}") diff --git a/spec/async/http/body/writable_spec.rb b/spec/async/http/body/writable_spec.rb index 38bd4bd4..d51b6fae 100644 --- a/spec/async/http/body/writable_spec.rb +++ b/spec/async/http/body/writable_spec.rb @@ -21,5 +21,7 @@ require_relative 'writable_examples' RSpec.describe Async::HTTP::Body::Writable do + include_context Async::RSpec::Reactor + it_behaves_like Async::HTTP::Body::Writable end From 4e6ea03c86faa32ff95ace133ae9485a24f9debf Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 6 May 2020 00:41:20 +1200 Subject: [PATCH 7/9] Patch version bump. --- lib/async/http/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/http/version.rb b/lib/async/http/version.rb index adcafa5f..be1e21a6 100644 --- a/lib/async/http/version.rb +++ b/lib/async/http/version.rb @@ -22,6 +22,6 @@ module Async module HTTP - VERSION = "0.52.1" + VERSION = "0.52.2" end end From 5b3f425c08d3e49fecb3d92fc144a7d121f730cc Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 8 May 2020 14:55:20 +1200 Subject: [PATCH 8/9] Update dependencies. --- .gitignore | 2 +- async-http.gemspec | 2 +- gems.locked | 103 --------------------------------------------- 3 files changed, 2 insertions(+), 105 deletions(-) delete mode 100644 gems.locked diff --git a/.gitignore b/.gitignore index 2b257a42..6be3af54 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ /.bundle/ /.yardoc -/Gemfile.lock +/gems.locked /_yardoc/ /coverage/ /doc/ diff --git a/async-http.gemspec b/async-http.gemspec index 92ded1df..dffc9575 100644 --- a/async-http.gemspec +++ b/async-http.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.add_dependency("async-io", "~> 1.28") spec.add_dependency("async-pool", "~> 0.2") - spec.add_dependency("protocol-http", "~> 0.19.0") + spec.add_dependency("protocol-http", "~> 0.20.0") spec.add_dependency("protocol-http1", "~> 0.13.0") spec.add_dependency("protocol-http2", "~> 0.14.0") diff --git a/gems.locked b/gems.locked deleted file mode 100644 index 700a9714..00000000 --- a/gems.locked +++ /dev/null @@ -1,103 +0,0 @@ -PATH - remote: . - specs: - async-http (0.52.1) - async (~> 1.25) - async-io (~> 1.28) - async-pool (~> 0.2) - protocol-http (~> 0.19.0) - protocol-http1 (~> 0.13.0) - protocol-http2 (~> 0.14.0) - -GEM - remote: https://rubygems.org/ - specs: - ast (2.4.0) - async (1.26.0) - console (~> 1.0) - nio4r (~> 2.3) - timers (~> 4.1) - async-container (0.14.1) - async (~> 1.0) - async-io (~> 1.4) - process-group - async-io (1.29.0) - async (~> 1.14) - async-pool (0.3.1) - async (~> 1.25) - async-rest (0.12.2) - async-http (~> 0.42) - protocol-http (~> 0.7) - async-rspec (1.14.0) - rspec (~> 3.0) - rspec-files (~> 1.0) - rspec-memory (~> 1.0) - bake (0.13.0) - samovar (~> 2.1) - bake-bundler (0.3.1) - bake (~> 0.9) - rspec - console (1.8.2) - covered (0.13.1) - async-rest - console (~> 1.0) - msgpack - parser - diff-lcs (1.3) - ffi (1.12.2) - mapping (1.1.1) - msgpack (1.3.3) - nio4r (2.5.2) - parser (2.7.1.2) - ast (~> 2.4.0) - process-group (1.2.1) - process-terminal (~> 0.2.0) - process-terminal (0.2.0) - ffi - protocol-hpack (1.4.2) - protocol-http (0.19.0) - protocol-http1 (0.13.0) - protocol-http (~> 0.19) - protocol-http2 (0.14.0) - protocol-hpack (~> 1.4) - protocol-http (~> 0.18) - rack (2.2.2) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.2) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-files (1.0.2) - rspec (~> 3.0) - rspec-memory (1.0.2) - rspec (~> 3.0) - rspec-mocks (3.9.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.3) - samovar (2.1.4) - console (~> 1.0) - mapping (~> 1.0) - timers (4.3.0) - -PLATFORMS - ruby - -DEPENDENCIES - async-container (~> 0.14.0) - async-http! - async-rspec (~> 1.10) - bake-bundler - bundler - covered - rack-test - rspec (~> 3.6) - -BUNDLED WITH - 2.1.4 From f938774a0f91e1a48bef8b3aeacd5e9dc1de81af Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 8 May 2020 14:56:42 +1200 Subject: [PATCH 9/9] Patch version bump. --- lib/async/http/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/http/version.rb b/lib/async/http/version.rb index be1e21a6..2870ebde 100644 --- a/lib/async/http/version.rb +++ b/lib/async/http/version.rb @@ -22,6 +22,6 @@ module Async module HTTP - VERSION = "0.52.2" + VERSION = "0.52.3" end end