Skip to content

Commit fcf231a

Browse files
committed
Updated documentation + improved Internet interface.
1 parent e1e1f13 commit fcf231a

File tree

7 files changed

+188
-362
lines changed

7 files changed

+188
-362
lines changed

gems.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,4 @@
4444

4545
gem "localhost"
4646
gem "rack-test"
47-
48-
# Optional dependency:
49-
gem "thread-local"
5047
end

guides/getting-started/readme.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Getting Started
2+
3+
This guide explains how to get started with `Async::HTTP`.
4+
5+
## Installation
6+
7+
Add the gem to your project:
8+
9+
~~~ bash
10+
$ bundle add async-http
11+
~~~
12+
13+
## Core Concepts
14+
15+
- {ruby Async::HTTP::Client} is the main class for making HTTP requests.
16+
- {ruby Async::HTTP::Internet} provides a simple interface for making requests to any server "on the internet".
17+
- {ruby Async::HTTP::Server} is the main class for handling HTTP requests.
18+
- {ruby Async::HTTP::Endpoint} can parse HTTP URLs in order to create a client or server.
19+
- [`protocol-http`](https://github.com/socketry/protocol-http) provides the abstract HTTP protocol interfaces.
20+
21+
## Usage
22+
23+
### Making a Request
24+
25+
To make a request, create an instance of {ruby Async::HTTP::Internet} and call the appropriate method:
26+
27+
~~~ ruby
28+
require 'async/http/internet'
29+
30+
Sync do
31+
Async::HTTP::Internet.get("https://httpbin.org/get") do |response|
32+
puts response.read
33+
end
34+
end
35+
~~~
36+
37+
The following methods are supported:
38+
39+
~~~ ruby
40+
Async::HTTP::Internet.methods(false)
41+
# => [:patch, :options, :connect, :post, :get, :delete, :head, :trace, :put]
42+
~~~
43+
44+
Using a block will automatically close the response when the block completes. If you want to keep the response open, you can manage it manually:
45+
46+
~~~ ruby
47+
require 'async/http'
48+
49+
Sync do
50+
response = Async::HTTP::Internet.get("https://httpbin.org/get")
51+
puts response.read
52+
ensure
53+
response&.close
54+
end
55+
~~~
56+
57+
As responses are streamed, you must ensure it is closed when you are finished with it.
58+
59+
#### Persistence
60+
61+
By default, {ruby Async::HTTP::Internet} will create a {ruby Async::HTTP::Client} for each remote host you communicate with, and will keep those connections open for as long as possible. This is useful for reducing the latency of subsequent requests to the same host. When you exit the event loop, the connections will be closed automatically.
62+
63+
### Downloading a File
64+
65+
~~~ ruby
66+
require 'async/http'
67+
require 'async/http/internet/instance'
68+
69+
Sync do
70+
# Issue a GET request to Google:
71+
response = Async::HTTP::Internet.get("https://www.google.com/search?q=kittens")
72+
73+
# Save the response body to a local file:
74+
response.save("/tmp/search.html")
75+
ensure
76+
response&.close
77+
end
78+
~~~
79+
80+
### Posting Data
81+
82+
To post data, use the `post` method:
83+
84+
~~~ ruby
85+
require 'async/http'
86+
require 'async/http/internet/instance'
87+
88+
data = {'life' => 42}
89+
90+
Sync do
91+
# Prepare the request:
92+
headers = [['accept', 'application/json']]
93+
body = JSON.dump(data)
94+
95+
# Issues a POST request:
96+
response = Async::HTTP::Internet.post("https://httpbin.org/anything", headers, body)
97+
98+
# Save the response body to a local file:
99+
pp JSON.parse(response.read)
100+
ensure
101+
response&.close
102+
end
103+
~~~
104+
105+
For more complex scenarios, including HTTP APIs, consider using [async-rest](https://github.com/socketry/async-rest) instead.
106+
107+
### Timeouts
108+
109+
To set a timeout for a request, use the `Task#with_timeout` method:
110+
111+
~~~ ruby
112+
require 'async/http'
113+
require 'async/http/internet/instance'
114+
115+
Sync do |task|
116+
# Request will timeout after 2 seconds
117+
task.with_timeout(2) do
118+
response = Async::HTTP::Internet.get "https://httpbin.org/delay/10"
119+
ensure
120+
response&.close
121+
end
122+
rescue Async::TimeoutError
123+
puts "The request timed out"
124+
end
125+
~~~
126+
127+
### Making a Server
128+
129+
To create a server, use an instance of {ruby Async::HTTP::Server}:
130+
131+
~~~ ruby
132+
require 'async/http'
133+
134+
endpoint = Async::HTTP::Endpoint.parse('http://localhost:9292')
135+
136+
Sync do |task|
137+
Async(transient: true) do
138+
server = Async::HTTP::Server.for(endpoint) do |request|
139+
::Protocol::HTTP::Response[200, {}, ["Hello World"]]
140+
end
141+
142+
server.run
143+
end
144+
145+
client = Async::HTTP::Client.new(endpoint)
146+
response = client.get("/")
147+
puts response.read
148+
ensure
149+
response&.close
150+
end
151+
~~~

guides/links.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
mocking:
2-
order: 5
1+
getting-started:
2+
order: 0
3+
testing:
4+
order: 1

guides/mocking/readme.md renamed to guides/testing/readme.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# Mocking
1+
# Testing
22

3-
This guide explains how to modify `Async::HTTP::Client` for mocking responses in tests.
3+
This guide explains how to use `Async::HTTP` clients and servers in your tests.
4+
5+
In general, you should avoid making real HTTP requests in your tests. Instead, you should use a mock server or a fake client.
46

57
## Mocking HTTP Responses
68

lib/async/http/internet.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def client_for(endpoint)
3939
# @parameter url [String] The URL to request, e.g. `https://www.codeotaku.com`.
4040
# @parameter headers [Hash | Protocol::HTTP::Headers] The headers to send with the request.
4141
# @parameter body [String | Protocol::HTTP::Body] The body to send with the request.
42-
def call(method, url, headers = nil, body = nil)
42+
def call(method, url, headers = nil, body = nil, &block)
4343
endpoint = Endpoint[url]
4444
client = self.client_for(endpoint)
4545

@@ -48,7 +48,15 @@ def call(method, url, headers = nil, body = nil)
4848

4949
request = ::Protocol::HTTP::Request.new(endpoint.scheme, endpoint.authority, method, endpoint.path, nil, headers, body)
5050

51-
return client.call(request)
51+
response = client.call(request)
52+
53+
return response unless block_given?
54+
55+
begin
56+
yield response
57+
ensure
58+
response.close
59+
end
5260
end
5361

5462
def close
@@ -60,8 +68,8 @@ def close
6068
end
6169

6270
::Protocol::HTTP::Methods.each do |name, verb|
63-
define_method(verb.downcase) do |url, headers = nil, body = nil|
64-
self.call(verb, url, headers, body)
71+
define_method(verb.downcase) do |url, headers = nil, body = nil, &block|
72+
self.call(verb, url, headers, body, &block)
6573
end
6674
end
6775

lib/async/http/internet/instance.rb

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,24 @@
44
# Copyright, 2021-2023, by Samuel Williams.
55

66
require_relative '../internet'
7-
require 'thread/local'
7+
8+
::Thread.attr_accessor :async_http_internet_instance
89

910
module Async
1011
module HTTP
1112
class Internet
12-
# Provide access to a shared thread-local instance.
13-
extend ::Thread::Local
13+
# The global instance of the internet.
14+
def self.instance
15+
::Thread.current.async_http_internet_instance ||= self.new
16+
end
17+
18+
class << self
19+
::Protocol::HTTP::Methods.each do |name, verb|
20+
define_method(verb.downcase) do |url, headers = nil, body = nil, &block|
21+
self.instance.call(verb, url, headers, body, &block)
22+
end
23+
end
24+
end
1425
end
1526
end
1627
end

0 commit comments

Comments
 (0)