我的时装店无限金币版
95.66MB · 2025-10-30
在前面的文章中,我们已经实现了注解系统和动态代理机制,现在到了最关键的部分 —— 实际的 HTTP 网络通信。这一层是框架与外部服务交互的桥梁,负责:
本文将详细介绍如何基于 JDK 的 HttpURLConnection 实现一个功能完整的 HTTP 客户端。
在开始实现之前,让我们快速回顾一下 HTTP 协议的基本结构:
GET /users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{"name": "John", "age": 30}
包含四个部分:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 45
{"id": 123, "name": "John", "age": 30}
包含四个部分:
首先,我们需要定义核心的接口:
package io.github.nemoob.httpclient;
import java.util.concurrent.CompletableFuture;
/**
* HTTP客户端接口
* 定义了同步和异步执行HTTP请求的方法
*/
public interface HttpClient extends AutoCloseable {
/**
* 同步执行HTTP请求
* @param request 请求对象
* @param handler 响应处理器
* @return 处理后的响应结果
* @throws HttpException HTTP异常
*/
<T> T execute(Request request, ResponseHandler<T> handler) throws HttpException;
/**
* 异步执行HTTP请求
* @param request 请求对象
* @param handler 响应处理器
* @return 异步结果
*/
<T> CompletableFuture<T> executeAsync(Request request, ResponseHandler<T> handler);
/**
* 关闭客户端,释放资源
*/
@Override
void close();
}
package io.github.nemoob.httpclient;
import java.util.Map;
/**
* HTTP请求接口
* 封装了HTTP请求的所有信息
*/
public interface Request {
/**
* 获取请求URL
*/
String getUrl();
/**
* 获取HTTP方法
*/
HttpMethod getMethod();
/**
* 获取请求头
*/
Map<String, String> getHeaders();
/**
* 获取请求体
*/
Object getBody();
/**
* 获取连接超时时间
*/
long getConnectTimeout();
/**
* 获取读取超时时间
*/
long getReadTimeout();
}
package io.github.nemoob.httpclient;
import java.io.InputStream;
import java.util.Map;
/**
* HTTP响应接口
* 封装了HTTP响应的所有信息
*/
public interface Response {
/**
* 获取状态码
*/
int getStatusCode();
/**
* 获取响应头
*/
Map<String, String> getHeaders();
/**
* 获取响应体字符串
*/
String getBodyAsString();
/**
* 获取响应体输入流
*/
InputStream getBodyAsStream();
/**
* 获取响应体字节数组
*/
byte[] getBody();
}
package io.github.nemoob.httpclient;
/**
* HTTP方法枚举
*/
public enum HttpMethod {
GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH, TRACE
}
package io.github.nemoob.httpclient;
import java.util.HashMap;
import java.util.Map;
/**
* HTTP请求实现类
*/
public class HttpRequest implements Request {
private String url;
private HttpMethod method;
private Map<String, String> headers = new HashMap<>();
private Object body;
private long connectTimeout = 5000;
private long readTimeout = 10000;
public HttpRequest() {}
public HttpRequest(String url, HttpMethod method) {
this.url = url;
this.method = method;
}
@Override
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public HttpMethod getMethod() {
return method;
}
public void setMethod(HttpMethod method) {
this.method = method;
}
@Override
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public void addHeader(String name, String value) {
this.headers.put(name, value);
}
@Override
public Object getBody() {
return body;
}
public void setBody(Object body) {
this.body = body;
}
@Override
public long getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(long connectTimeout) {
this.connectTimeout = connectTimeout;
}
@Override
public long getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(long readTimeout) {
this.readTimeout = readTimeout;
}
}
package io.github.nemoob.httpclient;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* HTTP响应实现类
*/
public class HttpResponse implements Response {
private int statusCode;
private Map<String, String> headers = new HashMap<>();
private byte[] body;
public HttpResponse() {}
public HttpResponse(int statusCode, Map<String, String> headers, byte[] body) {
this.statusCode = statusCode;
this.headers = headers;
this.body = body;
}
@Override
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
@Override
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public void addHeader(String name, String value) {
this.headers.put(name, value);
}
@Override
public String getBodyAsString() {
if (body == null) {
return null;
}
return new String(body, StandardCharsets.UTF_8);
}
@Override
public InputStream getBodyAsStream() {
if (body == null) {
return null;
}
return new ByteArrayInputStream(body);
}
@Override
public byte[] getBody() {
return body;
}
public void setBody(byte[] body) {
this.body = body;
}
}
现在我们来实现核心的 HTTP 客户端:
package io.github.nemoob.httpclient;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
/**
* 基于HttpURLConnection的HTTP客户端实现
*/
public class SimpleHttpClient implements HttpClient {
private String baseUrl = "";
private int connectTimeout = 5000;
private int readTimeout = 10000;
private ObjectMapper objectMapper = HttpClientFactory.getObjectMapper();
public SimpleHttpClient() {}
public SimpleHttpClient(String baseUrl) {
this.baseUrl = baseUrl;
}
// Setter 方法
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public <T> T execute(Request request, ResponseHandler<T> handler) throws HttpException {
try {
// 1. 构建完整URL
String fullUrl = buildFullUrl(request.getUrl());
// 2. 创建连接
HttpURLConnection connection = createConnection(fullUrl, request);
// 3. 设置请求属性
configureConnection(connection, request);
// 4. 发送请求体(如果有)
sendRequestBody(connection, request);
// 5. 获取响应
HttpResponse response = getResponse(connection);
// 6. 处理响应
return handler.handle(response);
} catch (Exception e) {
throw new HttpException("HTTP request failed", e);
}
}
@Override
public <T> CompletableFuture<T> executeAsync(Request request, ResponseHandler<T> handler) {
ExecutorService executor = HttpClientFactory.getDefaultExecutor();
return CompletableFuture.supplyAsync(() -> {
try {
return execute(request, handler);
} catch (HttpException e) {
throw new RuntimeException(e);
}
}, executor);
}
/**
* 构建完整URL
*/
private String buildFullUrl(String url) {
if (url.startsWith("http://") || url.startsWith("https://")) {
return url;
}
if (baseUrl.isEmpty()) {
throw new IllegalArgumentException("Base URL is required for relative URLs");
}
// 处理URL拼接
String base = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
String path = url.startsWith("/") ? url : "/" + url;
return base + path;
}
/**
* 创建HTTP连接
*/
private HttpURLConnection createConnection(String url, Request request) throws Exception {
URL urlObj = new URL(url);
HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
// 设置超时时间
connection.setConnectTimeout((int) request.getConnectTimeout());
connection.setReadTimeout((int) request.getReadTimeout());
return connection;
}
/**
* 配置连接属性
*/
private void configureConnection(HttpURLConnection connection, Request request) throws Exception {
// 设置HTTP方法
connection.setRequestMethod(request.getMethod().name());
// 设置请求头
for (Map.Entry<String, String> header : request.getHeaders().entrySet()) {
connection.setRequestProperty(header.getKey(), header.getValue());
}
// 设置默认请求头
setDefaultHeaders(connection);
// 如果有请求体,允许输出
if (request.getBody() != null && hasRequestBody(request.getMethod())) {
connection.setDoOutput(true);
}
// 总是允许输入
connection.setDoInput(true);
// 不使用缓存
connection.setUseCaches(false);
}
/**
* 设置默认请求头
*/
private void setDefaultHeaders(HttpURLConnection connection) {
// 设置User-Agent
if (connection.getRequestProperty("User-Agent") == null) {
connection.setRequestProperty("User-Agent", "Atlas-HttpClient/1.0");
}
// 设置Accept
if (connection.getRequestProperty("Accept") == null) {
connection.setRequestProperty("Accept", "application/json, text/plain, */*");
}
// 设置Accept-Encoding
if (connection.getRequestProperty("Accept-Encoding") == null) {
connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
}
}
/**
* 判断HTTP方法是否支持请求体
*/
private boolean hasRequestBody(HttpMethod method) {
return method == HttpMethod.POST ||
method == HttpMethod.PUT ||
method == HttpMethod.PATCH;
}
/**
* 发送请求体
*/
private void sendRequestBody(HttpURLConnection connection, Request request) throws Exception {
Object body = request.getBody();
if (body == null || !hasRequestBody(request.getMethod())) {
return;
}
byte[] bodyBytes;
if (body instanceof String) {
bodyBytes = ((String) body).getBytes(StandardCharsets.UTF_8);
} else if (body instanceof byte[]) {
bodyBytes = (byte[]) body;
} else {
// 序列化为JSON
String jsonBody = objectMapper.writeValueAsString(body);
bodyBytes = jsonBody.getBytes(StandardCharsets.UTF_8);
// 设置Content-Type(如果没有设置)
if (connection.getRequestProperty("Content-Type") == null) {
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
}
}
// 设置Content-Length
connection.setRequestProperty("Content-Length", String.valueOf(bodyBytes.length));
// 发送请求体
try (OutputStream outputStream = connection.getOutputStream()) {
outputStream.write(bodyBytes);
outputStream.flush();
}
}
/**
* 获取HTTP响应
*/
private HttpResponse getResponse(HttpURLConnection connection) throws Exception {
// 获取状态码
int statusCode = connection.getResponseCode();
// 获取响应头
Map<String, String> headers = getResponseHeaders(connection);
// 获取响应体
byte[] body = getResponseBody(connection, statusCode);
return new HttpResponse(statusCode, headers, body);
}
/**
* 获取响应头
*/
private Map<String, String> getResponseHeaders(HttpURLConnection connection) {
Map<String, String> headers = new HashMap<>();
for (Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
String key = entry.getKey();
List<String> values = entry.getValue();
if (key != null && values != null && !values.isEmpty()) {
// 如果有多个值,用逗号分隔
headers.put(key, String.join(", ", values));
}
}
return headers;
}
/**
* 获取响应体
*/
private byte[] getResponseBody(HttpURLConnection connection, int statusCode) throws Exception {
InputStream inputStream;
try {
// 根据状态码选择输入流
if (statusCode >= 200 && statusCode < 300) {
inputStream = connection.getInputStream();
} else {
inputStream = connection.getErrorStream();
if (inputStream == null) {
inputStream = connection.getInputStream();
}
}
if (inputStream == null) {
return new byte[0];
}
return readAllBytes(inputStream);
} catch (IOException e) {
// 如果无法读取响应体,返回空数组
return new byte[0];
}
}
/**
* 读取输入流的所有字节
*/
private byte[] readAllBytes(InputStream inputStream) throws IOException {
try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
byte[] data = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(data)) != -1) {
buffer.write(data, 0, bytesRead);
}
return buffer.toByteArray();
} finally {
try {
inputStream.close();
} catch (IOException e) {
// 忽略关闭异常
}
}
}
@Override
public void close() {
// HttpURLConnection 不需要显式关闭
// 这里可以添加其他清理逻辑
}
}
为了处理不同类型的响应,我们需要实现响应处理器:
package io.github.nemoob.httpclient;
/**
* 响应处理器接口
* 负责将HTTP响应转换为具体的Java对象
*/
public interface ResponseHandler<T> {
/**
* 处理HTTP响应
* @param response HTTP响应
* @return 处理后的结果
* @throws Exception 处理异常
*/
T handle(Response response) throws Exception;
}
package io.github.nemoob.httpclient;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Type;
/**
* JSON响应处理器
* 将JSON响应转换为Java对象
*/
public class JsonResponseHandler<T> implements ResponseHandler<T> {
private final Class<T> responseType;
private final Type genericType;
private final ObjectMapper objectMapper;
public JsonResponseHandler(Class<T> responseType, ObjectMapper objectMapper) {
this.responseType = responseType;
this.genericType = null;
this.objectMapper = objectMapper;
}
public JsonResponseHandler(Type genericType, ObjectMapper objectMapper) {
this.responseType = null;
this.genericType = genericType;
this.objectMapper = objectMapper;
}
@Override
@SuppressWarnings("unchecked")
public T handle(Response response) throws Exception {
// 检查状态码
if (response.getStatusCode() >= 400) {
throw new HttpException("HTTP error: " + response.getStatusCode() + ", body: " + response.getBodyAsString());
}
String body = response.getBodyAsString();
if (body == null || body.trim().isEmpty()) {
return null;
}
// 反序列化JSON
if (genericType != null) {
return objectMapper.readValue(body, objectMapper.getTypeFactory().constructType(genericType));
} else {
return objectMapper.readValue(body, responseType);
}
}
}
package io.github.nemoob.httpclient;
/**
* 空响应处理器
* 用于处理无返回值的请求
*/
public class VoidResponseHandler implements ResponseHandler<Void> {
@Override
public Void handle(Response response) throws Exception {
// 检查状态码
if (response.getStatusCode() >= 400) {
throw new HttpException("HTTP error: " + response.getStatusCode() + ", body: " + response.getBodyAsString());
}
return null;
}
}
定义专门的HTTP异常类:
package io.github.nemoob.httpclient;
/**
* HTTP异常类
* 封装HTTP请求过程中的各种异常
*/
public class HttpException extends Exception {
private int statusCode = -1;
private String responseBody;
public HttpException(String message) {
super(message);
}
public HttpException(String message, Throwable cause) {
super(message, cause);
}
public HttpException(String message, int statusCode, String responseBody) {
super(message);
this.statusCode = statusCode;
this.responseBody = responseBody;
}
public int getStatusCode() {
return statusCode;
}
public String getResponseBody() {
return responseBody;
}
}
让我们看看如何使用这个HTTP客户端:
// 创建HTTP客户端
SimpleHttpClient httpClient = new SimpleHttpClient("https://api.example.com");
// 创建GET请求
HttpRequest request = new HttpRequest("/users/123", HttpMethod.GET);
request.addHeader("Authorization", "Bearer token123");
// 执行请求
JsonResponseHandler<User> handler = new JsonResponseHandler<>(User.class, new ObjectMapper());
User user = httpClient.execute(request, handler);
System.out.println("User: " + user.getName());
// 创建POST请求
HttpRequest request = new HttpRequest("/users", HttpMethod.POST);
request.addHeader("Content-Type", "application/json");
// 设置请求体
User newUser = new User("John", 30);
request.setBody(newUser);
// 执行请求
JsonResponseHandler<User> handler = new JsonResponseHandler<>(User.class, new ObjectMapper());
User createdUser = httpClient.execute(request, handler);
// 异步执行请求
CompletableFuture<User> future = httpClient.executeAsync(request, handler);
// 处理异步结果
future.thenAccept(user -> {
System.out.println("Async result: " + user.getName());
}).exceptionally(throwable -> {
System.err.println("Request failed: " + throwable.getMessage());
return null;
});
虽然 HttpURLConnection 在某些情况下会自动复用连接,但我们可以通过系统属性来优化:
// 在应用启动时设置
System.setProperty("http.keepAlive", "true");
System.setProperty("http.maxConnections", "20");
合理设置超时时间,避免长时间等待:
public class SimpleHttpClient implements HttpClient {
// 默认连接超时:5秒
private int connectTimeout = 5000;
// 默认读取超时:10秒
private int readTimeout = 10000;
// 可以根据不同的接口设置不同的超时时间
public void setTimeouts(int connectTimeout, int readTimeout) {
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
}
}
优化读取缓冲区大小:
private byte[] readAllBytes(InputStream inputStream) throws IOException {
try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
// 使用较大的缓冲区提高读取效率
byte[] data = new byte[16384]; // 16KB
int bytesRead;
while ((bytesRead = inputStream.read(data)) != -1) {
buffer.write(data, 0, bytesRead);
}
return buffer.toByteArray();
}
}
private void validateResponse(HttpResponse response) throws HttpException {
int statusCode = response.getStatusCode();
if (statusCode >= 400) {
String errorMessage = String.format("HTTP %d: %s", statusCode, getStatusMessage(statusCode));
String responseBody = response.getBodyAsString();
throw new HttpException(errorMessage, statusCode, responseBody);
}
}
private String getStatusMessage(int statusCode) {
switch (statusCode) {
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 403: return "Forbidden";
case 404: return "Not Found";
case 500: return "Internal Server Error";
default: return "Unknown Error";
}
}
public <T> T execute(Request request, ResponseHandler<T> handler) throws HttpException {
try {
// ... 执行HTTP请求
} catch (java.net.SocketTimeoutException e) {
throw new HttpException("Request timeout", e);
} catch (java.net.ConnectException e) {
throw new HttpException("Connection failed", e);
} catch (java.net.UnknownHostException e) {
throw new HttpException("Unknown host", e);
} catch (IOException e) {
throw new HttpException("IO error", e);
} catch (Exception e) {
throw new HttpException("Unexpected error", e);
}
}
public class RetryableHttpClient implements HttpClient {
private final SimpleHttpClient delegate;
private final int maxRetries;
private final long retryDelay;
public <T> T execute(Request request, ResponseHandler<T> handler) throws HttpException {
HttpException lastException = null;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
return delegate.execute(request, handler);
} catch (HttpException e) {
lastException = e;
if (attempt < maxRetries && isRetryable(e)) {
try {
Thread.sleep(retryDelay * (attempt + 1));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw e;
}
} else {
throw e;
}
}
}
throw lastException;
}
private boolean isRetryable(HttpException e) {
int statusCode = e.getStatusCode();
return statusCode >= 500 || statusCode == 408 || statusCode == 429;
}
}
public class LoggingHttpClient implements HttpClient {
private final HttpClient delegate;
private final Logger logger = LoggerFactory.getLogger(LoggingHttpClient.class);
public <T> T execute(Request request, ResponseHandler<T> handler) throws HttpException {
long startTime = System.currentTimeMillis();
logger.info("HTTP {} {}", request.getMethod(), request.getUrl());
try {
T result = delegate.execute(request, handler);
long duration = System.currentTimeMillis() - startTime;
logger.info("HTTP {} {} completed in {}ms", request.getMethod(), request.getUrl(), duration);
return result;
} catch (HttpException e) {
long duration = System.currentTimeMillis() - startTime;
logger.error("HTTP {} {} failed in {}ms: {}", request.getMethod(), request.getUrl(), duration, e.getMessage());
throw e;
}
}
}
本文详细介绍了 Atlas HTTP Client 框架的底层 HTTP 客户端实现。关键要点包括: