Getting Started

This guide explains how to make HTTP requests and serve HTTP responses with Async::HTTP.

Installation

Add the gem to your project:

$ bundle add async-http

Core Concepts

Async::HTTP provides several interfaces for different kinds of HTTP applications:

Use Internet for general-purpose requests to different hosts. Use Client when your application repeatedly communicates with one endpoint or needs endpoint-specific configuration. See Choosing a Client for the ownership and configuration trade-offs.

Making a Request

The shared class Async::HTTP::Internet instance provides a convenient starting point. Run asynchronous HTTP operations inside Sync, which creates or reuses the event loop while returning the block result directly:

require "async/http/internet/instance"

Sync do
	Async::HTTP::Internet.get("https://httpbin.org/get") do |response|
		puts "Status: #{response.status}"
		puts response.read
	end
end

Passing a block automatically closes the response when the block exits, including when an exception is raised. Responses are streamed, so callers that do not use the block form must close the response explicitly.

require "async/http/internet/instance"

Sync do
	response = Async::HTTP::Internet.get("https://httpbin.org/get")
	puts response.read
ensure
	response&.close
end

Convenience methods are provided for GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH, and QUERY requests.

Connection Persistence

Internet creates a class Async::HTTP::Client for each remote endpoint and reuses its persistent connections. The underlying async pools are bound to the event loop and are closed when that event loop exits.

An explicitly created Internet can also be closed early when an application wants to release all cached clients before the event loop exits:

require "async/http/internet"

Sync do
	internet = Async::HTTP::Internet.new

	internet.get("https://example.com") do |response|
		puts response.status
	end
ensure
	internet&.close
end

Working with Responses

A response contains a status, headers, and a streaming body. Check the status before processing content, and use header names in lower case:

require "async/http/internet/instance"

Sync do
	Async::HTTP::Internet.get("https://httpbin.org/json") do |response|
		if response.success?
			puts response.headers["content-type"]
			puts response.read
		else
			warn "Request failed with status #{response.status}."
		end
	end
end

For larger responses, process the body incrementally rather than reading it into one string. See the protocol-http message body documentation for the complete body interface.

Downloading a File

Use response.save to stream a response directly to a file:

require "async/http/internet/instance"

Sync do
	Async::HTTP::Internet.get("https://example.com/archive.zip") do |response|
		raise "Download failed with status #{response.status}." unless response.success?
		
		response.save("archive.zip")
	end
end

Posting JSON

Pass headers and a body after the request target. The body may be a string or a compatible protocol-http body object.

require "async/http/internet/instance"
require "json"

data = {life: 42}
headers = [
	["accept", "application/json"],
	["content-type", "application/json"],
]

Sync do
	Async::HTTP::Internet.post("https://httpbin.org/anything", headers, JSON.dump(data)) do |response|
		raise "Request failed with status #{response.status}." unless response.success?
		
		puts JSON.pretty_generate(JSON.parse(response.read))
	end
end

For resource-oriented HTTP APIs, consider using async-rest, which builds on Async::HTTP.

Applying a Timeout

Networks can stall indefinitely, so impose a timeout around operations that must complete within a fixed duration:

require "async/http/internet/instance"

Sync do |task|
	task.with_timeout(2) do
		Async::HTTP::Internet.get("https://httpbin.org/delay/10") do |response|
			puts response.read
		end
	end
rescue Async::TimeoutError
	warn "The request timed out."
end

The response block still closes the response if the timeout interrupts the request while its body is being processed.

Making a Server

class Async::HTTP::Server accepts an application that maps each request to a Protocol::HTTP::Response. The following example starts a local server, makes one request, and then releases both client and server resources:

require "async/http"

endpoint = Async::HTTP::Endpoint.parse("http://localhost:9292")
server = Async::HTTP::Server.for(endpoint) do |request|
	Protocol::HTTP::Response[200, {"content-type" => "text/plain"}, ["Hello World"]]
end

Sync do
	server_task = server.run
	
	Async::HTTP::Client.open(endpoint) do |client|
		response = client.get("/")
		puts response.read
	ensure
		response&.close
	end
ensure
	server_task&.stop
end

Use Falcon when you need to host a Rack application or deploy an HTTP server in production. Use Async::HTTP::Server directly when building a protocol-level server or embedding HTTP handling into another asynchronous application.