async-http 0.52.0 → 0.52.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/async/http/body/delayed.rb +6 -2
- data/lib/async/http/body/hijack.rb +6 -0
- data/lib/async/http/body/slowloris.rb +2 -2
- data/lib/async/http/body/writable.rb +4 -0
- data/lib/async/http/endpoint.rb +2 -2
- data/lib/async/http/internet.rb +4 -0
- data/lib/async/http/protocol/http1/client.rb +1 -1
- data/lib/async/http/protocol/http1/server.rb +4 -3
- data/lib/async/http/version.rb +1 -1
- metadata +18 -74
- data/.editorconfig +0 -6
- data/.github/workflows/development.yml +0 -52
- data/.gitignore +0 -15
- data/.rspec +0 -3
- data/.travis.yml +0 -35
- data/Gemfile +0 -11
- data/README.md +0 -365
- data/async-http.gemspec +0 -39
- data/bake.rb +0 -0
- data/examples/compare/Gemfile +0 -9
- data/examples/compare/benchmark.rb +0 -78
- data/examples/download/chunked.rb +0 -86
- data/examples/fetch/Gemfile +0 -3
- data/examples/fetch/Gemfile.lock +0 -74
- data/examples/fetch/README.md +0 -3
- data/examples/fetch/config.ru +0 -28
- data/examples/fetch/public/index.html +0 -23
- data/examples/fetch/public/stream.js +0 -56
- data/examples/google/search.rb +0 -47
- data/examples/request.rb +0 -38
- data/examples/stream/stop.rb +0 -28
- data/examples/trenni/Gemfile +0 -5
- data/examples/trenni/streaming.rb +0 -35
- data/examples/upload/client.rb +0 -39
- data/examples/upload/data.txt +0 -41
- data/examples/upload/server.rb +0 -19
- data/examples/upload/upload.rb +0 -26
@@ -1,86 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'async'
|
4
|
-
require 'async/clock'
|
5
|
-
require 'async/barrier'
|
6
|
-
require 'async/semaphore'
|
7
|
-
require_relative '../../lib/async/http/endpoint'
|
8
|
-
require_relative '../../lib/async/http/client'
|
9
|
-
|
10
|
-
Async do
|
11
|
-
url = "https://static.openfoodfacts.org/data/en.openfoodfacts.org.products.csv"
|
12
|
-
|
13
|
-
endpoint = Async::HTTP::Endpoint.parse(url)
|
14
|
-
client = Async::HTTP::Client.new(endpoint)
|
15
|
-
|
16
|
-
headers = {'user-agent' => 'curl/7.69.1', 'accept' => '*/*'}
|
17
|
-
|
18
|
-
file = File.open("products.csv", "w")
|
19
|
-
Async.logger.info(self) {"Saving download to #{Dir.pwd}"}
|
20
|
-
|
21
|
-
begin
|
22
|
-
response = client.head(endpoint.path, headers)
|
23
|
-
content_length = nil
|
24
|
-
|
25
|
-
if response.success?
|
26
|
-
unless response.headers['accept-ranges'].include?('bytes')
|
27
|
-
raise "Does not advertise support for accept-ranges: bytes!"
|
28
|
-
end
|
29
|
-
|
30
|
-
unless content_length = response.body&.length
|
31
|
-
raise "Could not determine length of response!"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
ensure
|
35
|
-
response&.close
|
36
|
-
end
|
37
|
-
|
38
|
-
Async.logger.info(self) {"Content length: #{content_length/(1024**2)}MiB"}
|
39
|
-
|
40
|
-
parts = []
|
41
|
-
offset = 0
|
42
|
-
chunk_size = 1024*1024
|
43
|
-
|
44
|
-
start_time = Async::Clock.now
|
45
|
-
amount = 0
|
46
|
-
|
47
|
-
while offset < content_length
|
48
|
-
byte_range_start = offset
|
49
|
-
byte_range_end = [offset + chunk_size, content_length].min
|
50
|
-
parts << (byte_range_start...byte_range_end)
|
51
|
-
|
52
|
-
offset += chunk_size
|
53
|
-
end
|
54
|
-
|
55
|
-
Async.logger.info(self) {"Breaking download into #{parts.size} parts..."}
|
56
|
-
|
57
|
-
semaphore = Async::Semaphore.new(8)
|
58
|
-
barrier = Async::Barrier.new(parent: semaphore)
|
59
|
-
|
60
|
-
while !parts.empty?
|
61
|
-
barrier.async do
|
62
|
-
part = parts.shift
|
63
|
-
|
64
|
-
Async.logger.info(self) {"Issuing range request range: bytes=#{part.min}-#{part.max}"}
|
65
|
-
|
66
|
-
response = client.get(endpoint.path, [
|
67
|
-
["range", "bytes=#{part.min}-#{part.max-1}"],
|
68
|
-
*headers
|
69
|
-
])
|
70
|
-
|
71
|
-
if response.success?
|
72
|
-
Async.logger.info(self) {"Got response: #{response}... writing data for #{part}."}
|
73
|
-
written = file.pwrite(response.read, part.min)
|
74
|
-
|
75
|
-
amount += written
|
76
|
-
|
77
|
-
duration = Async::Clock.now - start_time
|
78
|
-
Async.logger.info(self) {"Rate: #{((amount.to_f/(1024**2))/duration).round(2)}MiB/s"}
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
barrier.wait
|
84
|
-
ensure
|
85
|
-
client&.close
|
86
|
-
end
|
data/examples/fetch/Gemfile
DELETED
data/examples/fetch/Gemfile.lock
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
GEM
|
2
|
-
specs:
|
3
|
-
async (1.24.2)
|
4
|
-
console (~> 1.0)
|
5
|
-
nio4r (~> 2.3)
|
6
|
-
timers (~> 4.1)
|
7
|
-
async-container (0.16.2)
|
8
|
-
async (~> 1.0)
|
9
|
-
async-io (~> 1.26)
|
10
|
-
process-group
|
11
|
-
async-http (0.50.5)
|
12
|
-
async (~> 1.23)
|
13
|
-
async-io (~> 1.27.0)
|
14
|
-
async-pool (~> 0.2)
|
15
|
-
protocol-http (~> 0.14.1)
|
16
|
-
protocol-http1 (~> 0.10.0)
|
17
|
-
protocol-http2 (~> 0.11.0)
|
18
|
-
async-http-cache (0.1.2)
|
19
|
-
async-http
|
20
|
-
protocol-http (~> 0.14.4)
|
21
|
-
async-io (1.27.4)
|
22
|
-
async (~> 1.14)
|
23
|
-
async-pool (0.2.0)
|
24
|
-
async (~> 1.8)
|
25
|
-
build-environment (1.13.0)
|
26
|
-
coderay (1.1.2)
|
27
|
-
console (1.8.2)
|
28
|
-
falcon (0.35.6)
|
29
|
-
async (~> 1.13)
|
30
|
-
async-container (~> 0.16.0)
|
31
|
-
async-http (~> 0.50.4)
|
32
|
-
async-http-cache (~> 0.1.0)
|
33
|
-
async-io (~> 1.22)
|
34
|
-
build-environment (~> 1.13)
|
35
|
-
localhost (~> 1.1)
|
36
|
-
process-metrics (~> 0.1.0)
|
37
|
-
rack (>= 1.0)
|
38
|
-
samovar (~> 2.1)
|
39
|
-
ffi (1.12.2)
|
40
|
-
localhost (1.1.6)
|
41
|
-
mapping (1.1.1)
|
42
|
-
method_source (0.9.2)
|
43
|
-
nio4r (2.5.2)
|
44
|
-
process-group (1.2.1)
|
45
|
-
process-terminal (~> 0.2.0)
|
46
|
-
process-metrics (0.1.1)
|
47
|
-
process-terminal (0.2.0)
|
48
|
-
ffi
|
49
|
-
protocol-hpack (1.4.2)
|
50
|
-
protocol-http (0.14.4)
|
51
|
-
protocol-http1 (0.10.2)
|
52
|
-
protocol-http (~> 0.13)
|
53
|
-
protocol-http2 (0.11.1)
|
54
|
-
protocol-hpack (~> 1.4)
|
55
|
-
protocol-http (~> 0.2)
|
56
|
-
pry (0.12.2)
|
57
|
-
coderay (~> 1.1.0)
|
58
|
-
method_source (~> 0.9.0)
|
59
|
-
rack (2.2.2)
|
60
|
-
samovar (2.1.4)
|
61
|
-
console (~> 1.0)
|
62
|
-
mapping (~> 1.0)
|
63
|
-
timers (4.3.0)
|
64
|
-
|
65
|
-
PLATFORMS
|
66
|
-
ruby
|
67
|
-
|
68
|
-
DEPENDENCIES
|
69
|
-
falcon
|
70
|
-
pry
|
71
|
-
rack
|
72
|
-
|
73
|
-
BUNDLED WITH
|
74
|
-
1.17.3
|
data/examples/fetch/README.md
DELETED
data/examples/fetch/config.ru
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rack'
|
3
|
-
|
4
|
-
class Echo
|
5
|
-
def initialize(app)
|
6
|
-
@app = app
|
7
|
-
end
|
8
|
-
|
9
|
-
def call(env)
|
10
|
-
request = Rack::Request.new(env)
|
11
|
-
|
12
|
-
if request.path_info == "/echo"
|
13
|
-
if output = request.body
|
14
|
-
return [200, {}, output.body]
|
15
|
-
else
|
16
|
-
return [200, {}, ["Hello World?"]]
|
17
|
-
end
|
18
|
-
else
|
19
|
-
return @app.call(env)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
use Echo
|
25
|
-
|
26
|
-
use Rack::Static, :urls => [''], :root => 'public', :index => 'index.html'
|
27
|
-
|
28
|
-
run lambda{|env| [404, {}, []]}
|
@@ -1,23 +0,0 @@
|
|
1
|
-
<!DOCTYPE html>
|
2
|
-
<html>
|
3
|
-
<head>
|
4
|
-
<title>Streaming Fetch</title>
|
5
|
-
</head>
|
6
|
-
<body>
|
7
|
-
<h1>Streams</h1>
|
8
|
-
|
9
|
-
<button id="stopButton">Start</button>
|
10
|
-
|
11
|
-
<h2>Sent</h2>
|
12
|
-
|
13
|
-
<ul id="sent">
|
14
|
-
</ul>
|
15
|
-
|
16
|
-
<h2>Received</h2>
|
17
|
-
|
18
|
-
<ul id="received">
|
19
|
-
</ul>
|
20
|
-
|
21
|
-
<script src="stream.js"></script>
|
22
|
-
</body>
|
23
|
-
</html>
|
@@ -1,56 +0,0 @@
|
|
1
|
-
const inputStream = new ReadableStream({
|
2
|
-
start(controller) {
|
3
|
-
interval = setInterval(() => {
|
4
|
-
let string = "Hello World!";
|
5
|
-
|
6
|
-
// Add the string to the stream
|
7
|
-
controller.enqueue(string);
|
8
|
-
|
9
|
-
// show it on the screen
|
10
|
-
let listItem = document.createElement('li');
|
11
|
-
listItem.textContent = string;
|
12
|
-
sent.appendChild(listItem);
|
13
|
-
}, 10000);
|
14
|
-
|
15
|
-
stopButton.addEventListener('click', function() {
|
16
|
-
clearInterval(interval);
|
17
|
-
controller.close();
|
18
|
-
})
|
19
|
-
},
|
20
|
-
pull(controller) {
|
21
|
-
// We don't really need a pull in this example
|
22
|
-
},
|
23
|
-
cancel() {
|
24
|
-
// This is called if the reader cancels,
|
25
|
-
// so we should stop generating strings
|
26
|
-
clearInterval(interval);
|
27
|
-
}
|
28
|
-
});
|
29
|
-
|
30
|
-
fetch("/echo", {method: 'POST', body: inputStream})
|
31
|
-
.then(response => {
|
32
|
-
const reader = response.body.getReader();
|
33
|
-
const decoder = new TextDecoder("utf-8");
|
34
|
-
|
35
|
-
function push() {
|
36
|
-
reader.read().then(({done, value}) => {
|
37
|
-
console.log("done:", done, "value:", value);
|
38
|
-
const string = decoder.decode(value);
|
39
|
-
|
40
|
-
// show it on the screen
|
41
|
-
let listItem = document.createElement('li');
|
42
|
-
|
43
|
-
if (done)
|
44
|
-
listItem.textContent = "<EOF>"
|
45
|
-
else
|
46
|
-
listItem.textContent = string;
|
47
|
-
|
48
|
-
received.appendChild(listItem);
|
49
|
-
|
50
|
-
if (done) return;
|
51
|
-
else push();
|
52
|
-
});
|
53
|
-
};
|
54
|
-
|
55
|
-
push();
|
56
|
-
});
|
data/examples/google/search.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "async"
|
4
|
-
require "async/clock"
|
5
|
-
require "protocol/http/middleware"
|
6
|
-
require_relative "../../lib/async/http"
|
7
|
-
|
8
|
-
URL = "https://www.google.com/search"
|
9
|
-
ENDPOINT = Async::HTTP::Endpoint.parse(URL)
|
10
|
-
|
11
|
-
# Console.logger.enable(Async::IO::Stream, Console::Logger::DEBUG)
|
12
|
-
|
13
|
-
class Google < Protocol::HTTP::Middleware
|
14
|
-
def search(term)
|
15
|
-
Async.logger.info(self) {"Searching for #{term}..."}
|
16
|
-
|
17
|
-
self.get("/search?q=#{term}", {"user-agent" => "Hi Google!"})
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
terms = %w{thoughtful fear size payment lethal modern recognise face morning sulky mountainous contain science snow uncle skirt truthful door travel snails closed rotten halting creator teeny-tiny beautiful cherries unruly level follow strip team things suggest pretty warm end cannon bad pig consider airport strengthen youthful fog three walk furry pickle moaning fax book ruddy sigh plate cakes shame stem faulty bushes dislike train sleet one colour behavior bitter suit count loutish squeak learn watery orange idiotic seat wholesale omniscient nostalgic arithmetic instruct committee puffy program cream cake whistle rely encourage war flagrant amusing fluffy prick utter wacky occur daily son check}
|
22
|
-
|
23
|
-
if count = ENV.fetch('COUNT', 20)&.to_i
|
24
|
-
terms = terms.first(count)
|
25
|
-
end
|
26
|
-
|
27
|
-
Async do |task|
|
28
|
-
client = Async::HTTP::Client.new(ENDPOINT)
|
29
|
-
google = Google.new(client)
|
30
|
-
|
31
|
-
google.search("null").finish
|
32
|
-
|
33
|
-
duration = Async::Clock.measure do
|
34
|
-
counts = terms.map do |term|
|
35
|
-
task.async do
|
36
|
-
response = google.search(term)
|
37
|
-
[term, response.read.scan(term).count]
|
38
|
-
end
|
39
|
-
end.map(&:wait).to_h
|
40
|
-
|
41
|
-
Async.logger.info(self, name: 'counts') {counts}
|
42
|
-
end
|
43
|
-
|
44
|
-
Async.logger.info(self, name: 'duration') {duration}
|
45
|
-
ensure
|
46
|
-
google.close
|
47
|
-
end
|
data/examples/request.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
#
|
3
|
-
# $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
4
|
-
# $LOAD_PATH.unshift(File.expand_path("../../http-protocol/lib", __dir__))
|
5
|
-
|
6
|
-
require 'async'
|
7
|
-
require 'async/logger'
|
8
|
-
require 'async/http/client'
|
9
|
-
require 'async/http/endpoint'
|
10
|
-
|
11
|
-
# Async.logger.level = Logger::DEBUG
|
12
|
-
|
13
|
-
Async do |task|
|
14
|
-
endpoint = Async::HTTP::Endpoint.parse("https://www.google.com")
|
15
|
-
|
16
|
-
client = Async::HTTP::Client.new(endpoint)
|
17
|
-
|
18
|
-
headers = {
|
19
|
-
'accept' => 'text/html',
|
20
|
-
}
|
21
|
-
|
22
|
-
request = Protocol::HTTP::Request.new(client.scheme, "www.google.com", "GET", "/search?q=cats", headers)
|
23
|
-
|
24
|
-
puts "Sending request..."
|
25
|
-
response = client.call(request)
|
26
|
-
|
27
|
-
puts "Reading response status=#{response.status}..."
|
28
|
-
|
29
|
-
if body = response.body
|
30
|
-
while chunk = body.read
|
31
|
-
puts chunk.size
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
response.close
|
36
|
-
|
37
|
-
puts "Finish reading response."
|
38
|
-
end
|
data/examples/stream/stop.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'async'
|
4
|
-
require 'async/http/internet'
|
5
|
-
|
6
|
-
Async do |parent|
|
7
|
-
internet = Async::HTTP::Internet.new
|
8
|
-
connection = nil
|
9
|
-
|
10
|
-
child = parent.async do
|
11
|
-
response = internet.get("https://utopia-falcon-heroku.herokuapp.com/beer/index")
|
12
|
-
connection = response.connection
|
13
|
-
|
14
|
-
response.each do |chunk|
|
15
|
-
Async.logger.info(response) {chunk}
|
16
|
-
end
|
17
|
-
ensure
|
18
|
-
Async.logger.info(response) {"Closing response..."}
|
19
|
-
response&.close
|
20
|
-
end
|
21
|
-
|
22
|
-
parent.sleep(5)
|
23
|
-
|
24
|
-
Async.logger.info(parent) {"Killing #{child}..."}
|
25
|
-
child.stop
|
26
|
-
ensure
|
27
|
-
internet&.close
|
28
|
-
end
|
data/examples/trenni/Gemfile
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'trenni/template'
|
3
|
-
|
4
|
-
require 'async'
|
5
|
-
require 'async/http/body/writable'
|
6
|
-
|
7
|
-
# The template, using inline text. The sleep could be anything - database query, HTTP request, redis, etc.
|
8
|
-
buffer = Trenni::Buffer.new(<<-EOF)
|
9
|
-
The "\#{self[:count]} bottles of \#{self[:drink]} on the wall" song!
|
10
|
-
|
11
|
-
<?r self[:count].downto(1) do |index| ?>
|
12
|
-
\#{index} bottles of \#{self[:drink]} on the wall,
|
13
|
-
\#{index} bottles of \#{self[:drink]},
|
14
|
-
take one down, and pass it around,
|
15
|
-
\#{index - 1} bottles of \#{self[:drink]} on the wall.
|
16
|
-
|
17
|
-
<?r Async::Task.current.sleep(1) ?>
|
18
|
-
<?r end ?>
|
19
|
-
EOF
|
20
|
-
|
21
|
-
template = Trenni::Template.new(buffer)
|
22
|
-
|
23
|
-
Async do
|
24
|
-
body = Async::HTTP::Body::Writable.new
|
25
|
-
|
26
|
-
generator = Async do
|
27
|
-
template.to_string({count: 100, drink: 'coffee'}, body)
|
28
|
-
end
|
29
|
-
|
30
|
-
while chunk = body.read
|
31
|
-
$stdout.write chunk
|
32
|
-
end
|
33
|
-
|
34
|
-
generator.wait
|
35
|
-
end.wait
|
data/examples/upload/client.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
4
|
-
|
5
|
-
require 'async'
|
6
|
-
require 'async/http/body/file'
|
7
|
-
require 'async/http/body/delayed'
|
8
|
-
require 'async/http/client'
|
9
|
-
require 'async/http/endpoint'
|
10
|
-
|
11
|
-
Async do
|
12
|
-
endpoint = Async::HTTP::Endpoint.parse("http://localhost:9222")
|
13
|
-
client = Async::HTTP::Client.new(endpoint, Async::HTTP::Protocol::HTTP2)
|
14
|
-
|
15
|
-
headers = [
|
16
|
-
['accept', 'text/plain'],
|
17
|
-
]
|
18
|
-
|
19
|
-
body = Async::HTTP::Body::Delayed.new(Async::HTTP::Body::File.open("data.txt", block_size: 32))
|
20
|
-
|
21
|
-
response = client.post(endpoint.path, headers, body)
|
22
|
-
|
23
|
-
puts response.status
|
24
|
-
|
25
|
-
# response.read -> string
|
26
|
-
# response.each {|chunk| ...}
|
27
|
-
# response.close (forcefully ignore data)
|
28
|
-
# body = response.finish (read and buffer response)
|
29
|
-
# response.save("echo.txt")
|
30
|
-
|
31
|
-
response.each do |chunk|
|
32
|
-
puts chunk.inspect
|
33
|
-
end
|
34
|
-
|
35
|
-
ensure
|
36
|
-
client.close if client
|
37
|
-
end
|
38
|
-
|
39
|
-
puts "Done."
|