前言

在前面的文章中,我们已经实现了注解系统和动态代理机制,现在到了最关键的部分 —— 实际的 HTTP 网络通信。这一层是框架与外部服务交互的桥梁,负责:

  • 建立网络连接
  • 发送 HTTP 请求
  • 接收 HTTP 响应
  • 处理各种网络异常
  • 管理连接生命周期

本文将详细介绍如何基于 JDK 的 HttpURLConnection 实现一个功能完整的 HTTP 客户端。

HTTP 协议基础回顾

在开始实现之前,让我们快速回顾一下 HTTP 协议的基本结构:

HTTP 请求格式

GET /users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123

{"name": "John", "age": 30}

包含四个部分:

  1. 请求行:方法 + 路径 + 协议版本
  2. 请求头:键值对形式的元数据
  3. 空行:分隔请求头和请求体
  4. 请求体:实际的数据内容(可选)

HTTP 响应格式

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 45

{"id": 123, "name": "John", "age": 30}

包含四个部分:

  1. 状态行:协议版本 + 状态码 + 状态描述
  2. 响应头:键值对形式的元数据
  3. 空行:分隔响应头和响应体
  4. 响应体:实际的数据内容

核心接口设计

首先,我们需要定义核心的接口:

HttpClient 接口

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();
}

Request 接口

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();
}

Response 接口

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();
}

实体类实现

HttpMethod 枚举

package io.github.nemoob.httpclient;

/**
 * HTTP方法枚举
 */
public enum HttpMethod {
    GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH, TRACE
}

HttpRequest 实现

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;
    }
}

HttpResponse 实现

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;
    }
}

SimpleHttpClient 核心实现

现在我们来实现核心的 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 不需要显式关闭
        // 这里可以添加其他清理逻辑
    }
}

响应处理器实现

为了处理不同类型的响应,我们需要实现响应处理器:

ResponseHandler 接口

package io.github.nemoob.httpclient;

/**
 * 响应处理器接口
 * 负责将HTTP响应转换为具体的Java对象
 */
public interface ResponseHandler<T> {
    
    /**
     * 处理HTTP响应
     * @param response HTTP响应
     * @return 处理后的结果
     * @throws Exception 处理异常
     */
    T handle(Response response) throws Exception;
}

JsonResponseHandler 实现

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);
        }
    }
}

VoidResponseHandler 实现

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请求示例

// 创建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;
});

性能优化考虑

1. 连接复用

虽然 HttpURLConnection 在某些情况下会自动复用连接,但我们可以通过系统属性来优化:

// 在应用启动时设置
System.setProperty("http.keepAlive", "true");
System.setProperty("http.maxConnections", "20");

2. 超时配置

合理设置超时时间,避免长时间等待:

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;
    }
}

3. 缓冲区优化

优化读取缓冲区大小:

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();
    }
}

错误处理最佳实践

1. 状态码处理

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";
    }
}

2. 网络异常处理

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);
    }
}

扩展功能

1. 请求重试机制

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;
    }
}

2. 请求日志记录

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 客户端实现。关键要点包括:

  1. 接口设计:清晰的接口定义,支持同步和异步执行
  2. 核心实现:基于 HttpURLConnection 的完整实现
  3. 响应处理:灵活的响应处理器机制
  4. 异常处理:完善的异常处理和错误信息
  5. 性能优化:连接复用、超时配置、缓冲区优化
  6. 扩展功能:重试机制、日志记录等
本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]