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 new file mode 100644 index 00000000..700a9714 --- /dev/null +++ b/gems.locked @@ -0,0 +1,103 @@ +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 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..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.0" + VERSION = "0.52.2" 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