diff --git a/CHANGES.md b/CHANGES.md deleted file mode 100644 index d548766a4..000000000 --- a/CHANGES.md +++ /dev/null @@ -1,29 +0,0 @@ -## From 2.2 to 2.3 - -* New `isFilterInsecureCipherSuites` config to disable unsecure and weak ciphers filtering performed internally in Netty. - -## From 2.1 to 2.2 - -* New [Typesafe config](https://github.com/lightbend/config) extra module -* new `enableWebSocketCompression` config to enable per-message and per-frame WebSocket compression extension - -## From 2.0 to 2.1 - -* AHC 2.1 targets Netty 4.1. -* `org.asynchttpclient.HttpResponseHeaders` was [dropped](https://github.com/AsyncHttpClient/async-http-client/commit/f4786f3ac7699f8f8664e7c7db0b7097585a0786) in favor - of `io.netty.handler.codec.http.HttpHeaders`. -* `org.asynchttpclient.cookie.Cookie` was [dropped](https://github.com/AsyncHttpClient/async-http-client/commit/a6d659ea0cc11fa5131304d8a04a7ba89c7a66af) in favor - of `io.netty.handler.codec.http.cookie.Cookie` as AHC's cookie parsers were contributed to Netty. -* AHC now has a RFC6265 `CookieStore` that is enabled by default. Implementation can be changed in `AsyncHttpClientConfig`. -* `AsyncHttpClient` now exposes stats with `getClientStats`. -* `AsyncHandlerExtensions` was [dropped](https://github.com/AsyncHttpClient/async-http-client/commit/1972c9b9984d6d9f9faca6edd4f2159013205aea) in favor of default methods - in `AsyncHandler`. -* `WebSocket` and `WebSocketListener` methods were renamed to mention frames -* `AsyncHttpClientConfig` various changes: - * new `getCookieStore` now lets you configure a CookieStore (enabled by default) - * new `isAggregateWebSocketFrameFragments` now lets you disable WebSocket fragmented frames aggregation - * new `isUseLaxCookieEncoder` lets you loosen cookie chars validation - * `isAcceptAnyCertificate` was dropped, as it didn't do what its name stated - * new `isUseInsecureTrustManager` lets you use a permissive TrustManager, that would typically let you accept self-signed certificates - * new `isDisableHttpsEndpointIdentificationAlgorithm` disables setting `HTTPS` algorithm on the SSLEngines, typically disables SNI and HTTPS hostname verification - * new `isAggregateWebSocketFrameFragments` lets you disable fragmented WebSocket frames aggregation diff --git a/README.md b/README.md index ed3ef6765..303200eb5 100644 --- a/README.md +++ b/README.md @@ -1,263 +1,414 @@ -# Async Http Client +# Async Http Client + [![Build](https://github.com/AsyncHttpClient/async-http-client/actions/workflows/builds.yml/badge.svg)](https://github.com/AsyncHttpClient/async-http-client/actions/workflows/builds.yml) -![Maven Central](https://img.shields.io/maven-central/v/org.asynchttpclient/async-http-client) +[![Maven Central](https://img.shields.io/maven-central/v/org.asynchttpclient/async-http-client)](https://central.sonatype.com/artifact/org.asynchttpclient/async-http-client) +[![License](https://img.shields.io/github/license/AsyncHttpClient/async-http-client)](https://www.apache.org/licenses/LICENSE-2.0) + +AsyncHttpClient (AHC) is a high-performance, asynchronous HTTP client for Java +built on top of [Netty](https://github.com/netty/netty). +It supports HTTP/1.1, HTTP/2, and WebSocket protocols. + +## Table of Contents + +- [Features](#features) +- [Requirements](#requirements) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Configuration](#configuration) +- [HTTP Requests](#http-requests) +- [Handling Responses](#handling-responses) +- [HTTP/2](#http2) +- [WebSocket](#websocket) +- [Authentication](#authentication) +- [Proxy Support](#proxy-support) +- [Community](#community) +- [License](#license) + +## Features + +- **HTTP/2 with multiplexing** — enabled by default over TLS via ALPN, + with connection multiplexing and GOAWAY handling +- **HTTP/1.1 and HTTP/1.0** — connection pooling and keep-alive +- **WebSocket** — text, binary, and ping/pong frame support +- **Asynchronous API** — non-blocking I/O with `ListenableFuture` + and `CompletableFuture` +- **Compression** — automatic gzip, deflate, Brotli, and Zstd decompression +- **Authentication** — Basic, Digest, NTLM, and SPNEGO/Kerberos +- **Proxy** — HTTP, SOCKS4, and SOCKS5 with CONNECT tunneling +- **Native transports** — optional Epoll, KQueue, and io_uring +- **Request/response filters** — intercept and transform at each stage +- **Cookie management** — RFC 6265-compliant cookie store +- **Multipart uploads** — file, byte array, input stream, and string parts +- **Resumable downloads** — built-in `ResumableIOExceptionFilter` + +## Requirements + +Java 11+ -Follow [@AsyncHttpClient](https://twitter.com/AsyncHttpClient) on Twitter. +## Installation -The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. -The library also supports the WebSocket Protocol. +**Maven:** -It's built on top of [Netty](https://github.com/netty/netty). It's compiled with Java 11. +```xml + + org.asynchttpclient + async-http-client + 3.0.7 + +``` -## Installation +**Gradle:** + +```groovy +implementation 'org.asynchttpclient:async-http-client:3.0.7' +``` + +
+Optional: Native Transport -Binaries are deployed on Maven Central. -Add a dependency on the main AsyncHttpClient artifact: +For lower-latency I/O on Linux, add a native transport dependency: -Maven: ```xml - - - org.asynchttpclient - async-http-client - 3.0.7 - - + + + io.netty + netty-transport-native-epoll + linux-x86_64 + + + + + io.netty + netty-transport-native-io_uring + linux-x86_64 + ``` -Gradle: -```groovy -dependencies { - implementation 'org.asynchttpclient:async-http-client:3.0.7' -} +Then enable in config: + +```java +AsyncHttpClient client = asyncHttpClient(config().setUseNativeTransport(true)); +``` + +
+ +
+Optional: Brotli / Zstd Compression + +```xml + + com.aayushatharva.brotli4j + brotli4j + 1.18.0 + + + + com.github.luben + zstd-jni + 1.5.7-2 + ``` -### Dsl +
-Import the Dsl helpers to use convenient methods to bootstrap components: +## Quick Start + +Import the DSL helpers: ```java import static org.asynchttpclient.Dsl.*; ``` -### Client +Create a client, execute a request, and read the response: ```java -import static org.asynchttpclient.Dsl.*; +try (AsyncHttpClient client = asyncHttpClient()) { + // Asynchronous + client.prepareGet("https://www.example.com/") + .execute() + .toCompletableFuture() + .thenApply(Response::getResponseBody) + .thenAccept(System.out::println) + .join(); -AsyncHttpClient asyncHttpClient=asyncHttpClient(); + // Synchronous (blocking) + Response response = client.prepareGet("https://www.example.com/") + .execute() + .get(); +} ``` -AsyncHttpClient instances must be closed (call the `close` method) once you're done with them, typically when shutting down your application. -If you don't, you'll experience threads hanging and resource leaks. - -AsyncHttpClient instances are intended to be global resources that share the same lifecycle as the application. -Typically, AHC will usually underperform if you create a new client for each request, as it will create new threads and connection pools for each. -It's possible to create shared resources (EventLoop and Timer) beforehand and pass them to multiple client instances in the config. You'll then be responsible for closing -those shared resources. +> **Note:** `AsyncHttpClient` instances are long-lived, shared resources. +> Always close them when done. Creating a new client per request will degrade +> performance due to repeated thread pool and connection pool creation. ## Configuration -Finally, you can also configure the AsyncHttpClient instance via its AsyncHttpClientConfig object: +Use `config()` to build an `AsyncHttpClientConfig`: ```java -import static org.asynchttpclient.Dsl.*; - -AsyncHttpClient c=asyncHttpClient(config().setProxyServer(proxyServer("127.0.0.1",38080))); +AsyncHttpClient client = asyncHttpClient(config() + .setConnectTimeout(Duration.ofSeconds(5)) + .setRequestTimeout(Duration.ofSeconds(30)) + .setMaxConnections(500) + .setMaxConnectionsPerHost(100) + .setFollowRedirect(true) + .setMaxRedirects(5) + .setCompressionEnforced(true)); ``` -## HTTP +## HTTP Requests ### Sending Requests -### Basics - -AHC provides 2 APIs for defining requests: bound and unbound. -`AsyncHttpClient` and Dsl` provide methods for standard HTTP methods (POST, PUT, etc) but you can also pass a custom one. +**Bound** — build directly from the client: ```java -import org.asynchttpclient.*; +Response response = client + .prepareGet("https://api.example.com/users") + .addHeader("Accept", "application/json") + .addQueryParam("page", "1") + .execute() + .get(); +``` -// bound -Future whenResponse=asyncHttpClient.prepareGet("http://www.example.com/").execute(); +**Unbound** — build standalone via DSL, then execute: + +```java +Request request = get("https://api.example.com/users") + .addHeader("Accept", "application/json") + .addQueryParam("page", "1") + .build(); -// unbound - Request request=get("http://www.example.com/").build(); - Future whenResponse=asyncHttpClient.executeRequest(request); +Response response = client.executeRequest(request).get(); ``` -#### Setting Request Body +Methods: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `TRACE`. -Use the `setBody` method to add a body to the request. +### Request Bodies + +Use `setBody` to attach a body. Supported types: + +| Type | Description | +|---|---| +| `String` | Text content | +| `byte[]` | Raw bytes | +| `ByteBuffer` | NIO buffer | +| `InputStream` | Streaming input | +| `File` | File content | +| `Publisher` | Reactive stream | +| `BodyGenerator` | Custom body generation | + +```java +Response response = client + .preparePost("https://api.example.com/data") + .setHeader("Content-Type", "application/json") + .setBody("{\"name\": \"value\"}") + .execute() + .get(); +``` -This body can be of type: +For streaming bodies, see `FeedableBodyGenerator` which lets you push chunks +asynchronously. -* `java.io.File` -* `byte[]` -* `List` -* `String` -* `java.nio.ByteBuffer` -* `java.io.InputStream` -* `Publisher` -* `org.asynchttpclient.request.body.generator.BodyGenerator` +### Multipart Uploads -`BodyGenerator` is a generic abstraction that let you create request bodies on the fly. -Have a look at `FeedableBodyGenerator` if you're looking for a way to pass requests chunks on the fly. +```java +Response response = client + .preparePost("https://api.example.com/upload") + .addBodyPart(new FilePart("file", new File("report.pdf"), "application/pdf")) + .addBodyPart(new StringPart("description", "Monthly report")) + .execute() + .get(); +``` -#### Multipart +Part types: `FilePart`, `ByteArrayPart`, `InputStreamPart`, `StringPart`. -Use the `addBodyPart` method to add a multipart part to the request. +## Handling Responses -This part can be of type: +### Blocking -* `ByteArrayPart` -* `FilePart` -* `InputStreamPart` -* `StringPart` +```java +Response response = client.prepareGet("https://www.example.com/").execute().get(); +``` -### Dealing with Responses +> Useful for debugging, but defeats the purpose of an async client in production. -#### Blocking on the Future +### ListenableFuture -`execute` methods return a `java.util.concurrent.Future`. You can simply block the calling thread to get the response. +`execute()` returns a `ListenableFuture` that supports completion listeners: ```java -Future whenResponse=asyncHttpClient.prepareGet("http://www.example.com/").execute(); - Response response=whenResponse.get(); +ListenableFuture future = client + .prepareGet("https://www.example.com/") + .execute(); + +future.addListener(() -> { + Response response = future.get(); + System.out.println(response.getStatusCode()); +}, executor); ``` -This is useful for debugging but you'll most likely hurt performance or create bugs when running such code on production. -The point of using a non blocking client is to *NOT BLOCK* the calling thread! +> If `executor` is `null`, the callback runs on the Netty I/O thread. +> **Never block** inside I/O thread callbacks. + +### CompletableFuture + +```java +client.prepareGet("https://www.example.com/") + .execute() + .toCompletableFuture() + .thenApply(Response::getResponseBody) + .thenAccept(System.out::println) + .join(); +``` -### Setting callbacks on the ListenableFuture +### AsyncCompletionHandler -`execute` methods actually return a `org.asynchttpclient.ListenableFuture` similar to Guava's. -You can configure listeners to be notified of the Future's completion. +For most async use cases, extend `AsyncCompletionHandler` — it buffers the +full response and gives you a single `onCompleted(Response)` callback: ```java - ListenableFuture whenResponse = ???; - Runnable callback = () - > { - try { - Response response = whenResponse.get(); - System.out.println(response); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - }; +client.prepareGet("https://www.example.com/") + .execute(new AsyncCompletionHandler() { + @Override + public String onCompleted(Response response) { + return response.getResponseBody(); + } + }); +``` + +### AsyncHandler - java.util.concurrent.Executor executor = ???; - whenResponse.addListener(() - > ??? , executor); +For fine-grained control, implement `AsyncHandler` directly. This lets you +inspect status, headers, and body chunks as they arrive and abort early: + +```java +Future future = client + .prepareGet("https://www.example.com/") + .execute(new AsyncHandler<>() { + private int status; + + @Override + public State onStatusReceived(HttpResponseStatus s) { + status = s.getStatusCode(); + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders headers) { + return State.CONTINUE; + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart part) { + return State.ABORT; // stop early — we only needed the status + } + + @Override + public Integer onCompleted() { + return status; + } + + @Override + public void onThrowable(Throwable t) { + t.printStackTrace(); + } + }); ``` -If the `executor` parameter is null, callback will be executed in the IO thread. -You *MUST NEVER PERFORM BLOCKING* operations in there, typically sending another request and block on a future. +## HTTP/2 -#### Using custom AsyncHandlers +HTTP/2 is **enabled by default** for HTTPS connections via ALPN negotiation. +The client uses HTTP/2 when the server supports it and falls back to HTTP/1.1 +otherwise. No additional configuration is required. -`execute` methods can take an `org.asynchttpclient.AsyncHandler` to be notified on the different events, such as receiving the status, the headers and body chunks. -When you don't specify one, AHC will use a `org.asynchttpclient.AsyncCompletionHandler`; +- **Connection multiplexing** — concurrent streams over a single TCP connection +- **GOAWAY handling** — graceful connection draining on server shutdown +- **PING keepalive** — configurable ping frames to keep connections alive -`AsyncHandler` methods can let you abort processing early (return `AsyncHandler.State.ABORT`) and can let you return a computation result from `onCompleted` that will be used -as the Future's result. -See `AsyncCompletionHandler` implementation as an example. +### HTTP/2 Configuration -The below sample just capture the response status and skips processing the response body chunks. +```java +AsyncHttpClient client = asyncHttpClient(config() + .setHttp2MaxConcurrentStreams(100) + .setHttp2InitialWindowSize(65_535) + .setHttp2MaxFrameSize(16_384) + .setHttp2MaxHeaderListSize(8_192) + .setHttp2PingInterval(Duration.ofSeconds(30)) // keepalive pings + .setHttp2CleartextEnabled(true)); // h2c prior knowledge +``` -Note that returning `ABORT` closes the underlying connection. +To force HTTP/1.1, disable HTTP/2: ```java -import static org.asynchttpclient.Dsl.*; +AsyncHttpClient client = asyncHttpClient(config().setHttp2Enabled(false)); +``` -import org.asynchttpclient.*; -import io.netty.handler.codec.http.HttpHeaders; +## WebSocket -Future whenStatusCode = asyncHttpClient.prepareGet("http://www.example.com/") - .execute(new AsyncHandler () { - private Integer status; - - @Override - public State onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - status = responseStatus.getStatusCode(); - return State.ABORT; - } - - @Override - public State onHeadersReceived(HttpHeaders headers) throws Exception { - return State.ABORT; - } - +```java +WebSocket ws = client + .prepareGet("wss://echo.example.com/") + .execute(new WebSocketUpgradeHandler.Builder() + .addWebSocketListener(new WebSocketListener() { @Override - public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - return State.ABORT; + public void onOpen(WebSocket ws) { + ws.sendTextFrame("Hello!"); } - + @Override - public Integer onCompleted() throws Exception{ - return status; + public void onTextFrame(String payload, boolean finalFragment, int rsv) { + System.out.println(payload); } - + @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - } - }); + public void onClose(WebSocket ws, int code, String reason) {} - Integer statusCode = whenStatusCode.get(); + @Override + public void onError(Throwable t) { t.printStackTrace(); } + }) + .build()) + .get(); ``` -#### Using Continuations - -`ListenableFuture` has a `toCompletableFuture` method that returns a `CompletableFuture`. -Beware that canceling this `CompletableFuture` won't properly cancel the ongoing request. -There's a very good chance we'll return a `CompletionStage` instead in the next release. +## Authentication ```java -CompletableFuture whenResponse=asyncHttpClient - .prepareGet("http://www.example.com/") - .execute() - .toCompletableFuture() - .exceptionally(t->{ /* Something wrong happened... */ }) - .thenApply(response->{ /* Do something with the Response */ return resp;}); - whenResponse.join(); // wait for completion +// Client-wide Basic auth +AsyncHttpClient client = asyncHttpClient(config() + .setRealm(basicAuthRealm("user", "password"))); + +// Per-request Digest auth +Response response = client + .prepareGet("https://api.example.com/protected") + .setRealm(digestAuthRealm("user", "password").build()) + .execute() + .get(); ``` -You may get the complete maven project for this simple demo -from [org.asynchttpclient.example](https://github.com/AsyncHttpClient/async-http-client/tree/master/example/src/main/java/org/asynchttpclient/example) +Supported schemes: **Basic**, **Digest**, **NTLM**, **SPNEGO/Kerberos**. -## WebSocket - -Async Http Client also supports WebSocket. -You need to pass a `WebSocketUpgradeHandler` where you would register a `WebSocketListener`. +## Proxy Support ```java -WebSocket websocket = c.prepareGet("ws://demos.kaazing.com/echo") - .execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener( - new WebSocketListener() { - - @Override - public void onOpen(WebSocket websocket) { - websocket.sendTextFrame("...").sendTextFrame("..."); - } - - @Override - public void onClose(WebSocket websocket) { - // ... - } - - @Override - public void onTextFrame(String payload, boolean finalFragment, int rsv) { - System.out.println(payload); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - } - }).build()).get(); +// HTTP proxy +AsyncHttpClient client = asyncHttpClient(config() + .setProxyServer(proxyServer("proxy.example.com", 8080))); + +// Authenticated proxy +AsyncHttpClient client = asyncHttpClient(config() + .setProxyServer(proxyServer("proxy.example.com", 8080) + .setRealm(basicAuthRealm("proxyUser", "proxyPassword")))); ``` -## User Group +SOCKS4 and SOCKS5 proxies are also supported. + +## Community + +- [GitHub Discussions](https://github.com/AsyncHttpClient/async-http-client/discussions) — questions, ideas, and general discussion +- [Issue Tracker](https://github.com/AsyncHttpClient/async-http-client/issues) — bug reports and feature requests -Keep up to date on the library development by joining the Asynchronous HTTP Client discussion group +## License -[GitHub Discussions](https://github.com/AsyncHttpClient/async-http-client/discussions) +[Apache License 2.0](LICENSE.txt) diff --git a/pom.xml b/pom.xml index a089bd872..16a0e1c55 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ scm:git:git@github.com:AsyncHttpClient/async-http-client.git scm:git:git@github.com:AsyncHttpClient/async-http-client.git - https://github.com/AsyncHttpClient/async-http-client/tree/master + https://github.com/AsyncHttpClient/async-http-client/tree/main HEAD @@ -89,10 +89,7 @@ asynchttpclient - https://groups.google.com/group/asynchttpclient/topics - https://groups.google.com/group/asynchttpclient/subscribe - https://groups.google.com/group/asynchttpclient/subscribe - asynchttpclient@googlegroups.com + https://github.com/AsyncHttpClient/async-http-client/discussions