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 2200a718..dffc9575 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.20.0") + spec.add_dependency("protocol-http1", "~> 0.13.0") spec.add_dependency("protocol-http2", "~> 0.14.0") # spec.add_dependency("openssl") diff --git a/Gemfile b/gems.rb similarity index 100% rename from Gemfile rename to gems.rb 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 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/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/lib/async/http/version.rb b/lib/async/http/version.rb index a8a6f162..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.0" + VERSION = "0.52.3" end end 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 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 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