Skip to content

HTTP 客户端

This content is not available in your language yet.

HttpClient 支持 GET、POST、PUT、DELETE 等常用请求,以及文件上传、流式响应处理等高级功能。

使用 Feat 工厂方法创建客户端实例:

import tech.smartboot.feat.Feat;
import tech.smartboot.feat.core.client.HttpClient;
// 基础创建
HttpClient client = Feat.httpClient("https://api.example.com");
// 带配置选项
HttpClient client = Feat.httpClient("https://api.example.com", options -> {
options.debug(true)
.connectTimeout(5000)
.idleTimeout(30000);
});
HttpClient client = Feat.httpClient("https://api.example.com", options -> {
options.debug(true) // 开启调试日志
.connectTimeout(5000) // 连接超时(毫秒)
.idleTimeout(30000) // 空闲超时(毫秒)
.readBufferSize(8192) // 读缓冲区大小
.setWriteBufferSize(8192); // 写缓冲区大小
});
// 基础代理
HttpClient client = Feat.httpClient("https://api.example.com", options -> {
options.proxy("proxy.example.com", 8080);
});
// 带认证的代理
HttpClient client = Feat.httpClient("https://api.example.com", options -> {
options.proxy("proxy.example.com", 8080, "username", "password");
});

HttpClient 内部自动维护连接池,通过 keepalive 配置启用连接复用:

client.get("/users")
.header(header -> header.keepalive(true)) // 启用连接复用
.submit();

GET 请求用于获取资源,支持链式添加查询参数:

client.get("/users")
.addQueryParam("page", "1")
.addQueryParam("size", "20")
.onSuccess(response -> System.out.println(response.body()))
.submit();

POST 请求支持多种数据格式:

Map<String, Object> payload = new HashMap<>();
payload.put("name", "feat");
client.post("/users")
.postJson(payload) // 自动序列化并设置 Content-Type
.onSuccess(response -> System.out.println(response.body()))
.submit();

使用 rest() 方法发送 PUT、DELETE 等请求:

client.rest("PUT", "/users/1")
.postJson(userData)
.onSuccess(response -> System.out.println(response.body()))
.submit();
import tech.smartboot.feat.core.common.HeaderName;
client.get("/users")
.header(header -> header
.set(HeaderName.USER_AGENT, "Feat-Client/1.0")
.set(HeaderName.AUTHORIZATION, "Bearer token123")
.keepalive(true))
.submit();

set() vs add():

  • set() - 覆盖同名 header,适合设置唯一值(如 Content-Type)
  • add() - 追加 header,支持同名多值(如 Set-Cookie)
client.post("/upload")
.header(header -> {
header.set(HeaderName.CONTENT_TYPE, "application/json"); // 覆盖
header.add("X-Custom-Header", "value1"); // 追加
header.add("X-Custom-Header", "value2"); // 同名追加
})
.submit();

Feat 采用异步回调模型处理响应:

client.get("/users")
.onResponseHeader(response -> {
// 收到响应头时触发
System.out.println("Status: " + response.statusCode());
})
.onSuccess(response -> {
// 收到完整响应体时触发
System.out.println("Body: " + response.body());
})
.onFailure(error -> {
// 请求失败时触发
error.printStackTrace();
})
.onClose(() -> {
// 连接关闭时触发
System.out.println("Connection closed");
})
.submit();

以下泳道图展示了请求过程中各回调的触发时机:

sequenceDiagram
    autonumber
    participant App as 应用程序
    participant Client as HttpClient
    participant Server as 服务器

    App->>Client: submit() 发送请求
    Client->>Server: HTTP Request
    
    alt 请求成功
        Server-->>Client: HTTP Response Headers
        Client-->>App: onResponseHeader()
        
        Server-->>Client: Response Body (分块接收)
        Client-->>App: onResponseBody() (可选)
        
        Server-->>Client: 响应完成
        Client-->>App: onSuccess()
    else 请求失败
        Client-->>App: onFailure()
    end
    
    Client-->>App: onClose()

回调触发顺序说明:

  1. 请求发送 - 调用 submit() 后,请求开始发送
  2. 收到响应头 - 服务器返回响应头时触发 onResponseHeader(),此时可以获取状态码和响应头
  3. 接收响应体 - 如果注册了 onResponseBody(),会分块接收响应体数据
  4. 请求成功 - 响应体完整接收后触发 onSuccess()
  5. 请求失败 - 网络异常或超时等情况下触发 onFailure()
  6. 连接关闭 - 无论成功或失败,连接关闭时都会触发 onClose()

响应体:

client.get("/users")
.onSuccess(response -> {
String body = response.body(); // 获取响应体字符串
})
.submit();

响应头:

client.get("/users")
.onResponseHeader(response -> {
// 获取单个头
String contentType = response.getHeader(HeaderName.CONTENT_TYPE);
// 获取所有头名称
Collection<String> headerNames = response.getHeaderNames();
// 获取指定头的所有值(支持多值)
Collection<String> values = response.getHeaders("Set-Cookie");
System.out.println("Status: " + response.statusCode());
System.out.println("Protocol: " + response.getProtocol());
})
.submit();

如需阻塞等待响应,使用 CompletableFuture.get()

HttpResponse response = client.get("/users")
.submit()
.get(); // 阻塞等待
System.out.println(response.body());

上传文件使用 multipart 表单:

import tech.smartboot.feat.core.client.Multipart;
import java.io.File;
import java.util.Arrays;
File avatar = new File("/path/to/avatar.jpg");
client.post("/upload")
.postBody(body -> body.multipart(Arrays.asList(
Multipart.newFormMultipart("name", "feat"),
Multipart.newFormMultipart("version", "1.0"),
Multipart.newFileMultipart("avatar", avatar)
)))
.onSuccess(response -> System.out.println(response.body()))
.submit();

如需处理二进制数据或大文件,使用 onResponseBody 流式处理:

client.get("/large-file.zip")
.onResponseBody((response, bytes, end) -> {
// 分块接收响应体数据
// bytes: 当前数据块
// end: 是否为最后一块
})
.submit();

使用流式 API 处理大文件,避免内存溢出:

FileOutputStream fos = new FileOutputStream("download.zip");
client.get("/large-file.zip")
.onResponseBody((response, bytes, end) -> {
fos.write(bytes);
if (end) {
fos.close();
}
})
.submit();

处理 Server-Sent Events 流式响应。详见 SSE 客户端 章节。

client.get("/events")
.onSSE(sse -> {
sse.onOpen(response -> System.out.println("SSE connection opened"));
sse.onData(event -> System.out.println("Received: " + event.getData()));
})
.submit();

使用 HttpClient(String host, int port) 构造方式,适合需要动态指定路径的场景:

HttpClient client = new HttpClient("api.example.com", 443);
client.get("/users").submit(); // 必须指定 path
HttpClientDemo.java
import tech.smartboot.feat.Feat;
import tech.smartboot.feat.core.client.HttpClient;
import tech.smartboot.feat.core.client.HttpResponse;
import tech.smartboot.feat.core.common.HeaderName;
public class HttpClientDemo {
public static void main(String[] args) throws Exception {
// 1. 创建客户端
HttpClient client = Feat.httpClient("https://api.github.com", options -> {
options.debug(true).connectTimeout(5000);
});
// 2. 发送请求
client.get("/users/smartboot")
.header(header -> header
.set(HeaderName.USER_AGENT, "Feat-Client")
.set(HeaderName.ACCEPT, "application/json"))
.onResponseHeader(response -> {
System.out.println("Status: " + response.statusCode());
})
.onSuccess(response -> {
System.out.println("Body: " + response.body());
})
.onFailure(Throwable::printStackTrace)
.submit()
.get();
}
}