首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

HttpClient 传输序列化对象实战:客户端与 Servlet 端完整交互指南

     編輯:Java 相關知識

架构概览

采用 Java 原生序列化 + HTTP POST 实现对象级请求/响应。注意:生产环境强烈建议改用 JSON/Protobuf 以规避反序列化漏洞(CVE-2015-4852 等),本文仅作技术原理演示。

核心 DTO 设计

public class RequestPayload implements Serializable {
    private static final long serialVersionUID = 1L;
    private String action;
    private Map params = new HashMap<>();
    // 全参构造器、getter/setter、toString()
}

public class ResponsePayload implements Serializable {
    private static final long serialVersionUID = 1L;
    private int code;
    private String message;
    private Object data;
    // 全参构造器、getter/setter
}

客户端实现

public class ObjectHttpClient {
    private final CloseableHttpClient httpClient;
    private final String baseUrl;

    public ObjectHttpClient(String baseUrl) {
        this.baseUrl = baseUrl;
        this.httpClient = HttpClients.custom()
            .setConnectionManager(new PoolingHttpClientConnectionManager())
            .evictIdleConnections(30, TimeUnit.SECONDS)
            .setDefaultRequestConfig(RequestConfig.custom()
                .setConnectTimeout(5000)
                .setSocketTimeout(15000)
                .build())
            .build();
    }

    public ResponsePayload send(RequestPayload request) throws IOException, ClassNotFoundException {
        HttpPost post = new HttpPost(baseUrl + "/api/object");
        post.setHeader("Content-Type", "application/x-java-serialized-object");
        post.setHeader("Accept", "application/x-java-serialized-object");

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(request);
            oos.flush();
            post.setEntity(new ByteArrayEntity(bos.toByteArray()));

            try (CloseableHttpResponse response = httpClient.execute(post)) {
                int status = response.getStatusLine().getStatusCode();
                if (status != HttpStatus.SC_OK) {
                    throw new IOException("HTTP " + status);
                }
                try (ObjectInputStream ois = new ObjectInputStream(response.getEntity().getContent())) {
                    return (ResponsePayload) ois.readObject();
                }
            }
        }
    }

    public void close() throws IOException {
        httpClient.close();
    }
}

服务端 Servlet 实现

@WebServlet("/api/object")
public class ObjectProcessServlet extends HttpServlet {
    private static final Logger log = LoggerFactory.getLogger(ObjectProcessServlet.class);

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 1. 校验 Content-Type
        String ct = req.getContentType();
        if (ct == null || !ct.contains("application/x-java-serialized-object")) {
            resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, 
                "Require application/x-java-serialized-object");
            return;
        }

        // 2. 反序列化请求
        RequestPayload request;
        try (ObjectInputStream ois = new ObjectInputStream(req.getInputStream())) {
            request = (RequestPayload) ois.readObject();
        } catch (ClassNotFoundException | IOException e) {
            log.warn("Deserialize request failed", e);
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid serialized object");
            return;
        }

        // 3. 业务处理(示例)
        ResponsePayload response;
        try {
            Object result = processBusiness(request);
            response = new ResponsePayload(200, "SUCCESS", result);
        } catch (BusinessException e) {
            response = new ResponsePayload(e.getCode(), e.getMessage(), null);
        } catch (Exception e) {
            log.error("Business process error", e);
            response = new ResponsePayload(500, "INTERNAL_ERROR", null);
        }

        // 4. 序列化响应
        resp.setContentType("application/x-java-serialized-object");
        try (ObjectOutputStream oos = new ObjectOutputStream(resp.getOutputStream())) {
            oos.writeObject(response);
            oos.flush();
        }
    }

    private Object processBusiness(RequestPayload req) {
        // 实际业务逻辑:路由到 Service 层
        return "Processed: " + req.getAction() + " with " + req.getParams().size() + " params";
    }
}

关键技术点与避坑指南

1. 连接池与超时配置

  • PoolingHttpClientConnectionManager:复用 TCP 连接,避免频繁握手开销
  • setConnectTimeout/setSocketTimeout:防止网络抖动导致线程无限阻塞
  • evictIdleConnections:定期清理空闲连接,防止服务端主动断开导致客户端复用失败

2. 流管理的黄金法则

// 正确模式:try-with-resources 保证关闭顺序
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
     ObjectOutputStream oos = new ObjectOutputStream(bos)) {
    oos.writeObject(obj);
    oos.flush();  // 必须 flush,确保数据写入底层字节数组
    byte[] data = bos.toByteArray();
} // 自动关闭 oos -> bos,释放内存

严禁手动 close() 后继续使用流对象,或在 finally 中重复关闭导致异常掩盖。

3. 序列化版本控制

显式声明 serialVersionUID,避免跨 JVM 版本/编译器差异导致 InvalidClassException。字段增删改需遵循兼容性规则:

  • 兼容:增加字段(transient 或有默认值)、修改方法、实现新接口
  • 不兼容:删除字段、修改字段类型、改变类层次结构

4. 安全加固(生产必做)

  1. 替代方案:改用 Jackson JSON、Protobuf、Avro,彻底消除反序列化攻击面
  2. 若必须用 Java 序列化:
    • 启用 -Djdk.serialFilter=... 白名单过滤(JDK 9+)
    • 使用 ValidatingObjectInputStream (Apache Commons IO) 限制可反序列化类
    • 网络层面:mTLS 双向认证、IP 白名单、请求签名验签
  3. 输入校验:反序列化前校验 Content-Length 上限(如 1MB),防止大对象 OOM 攻击

测试验证

public class TestMain {
    public static void main(String[] args) throws Exception {
        try (ObjectHttpClient client = new ObjectHttpClient("https://localhost:8080")) {
            RequestPayload req = new RequestPayload("QUERY", Map.of("id", 1001, "type", "user"));
            ResponsePayload resp = client.send(req);
            System.out.println("Code: " + resp.getCode());
            System.out.println("Msg:  " + resp.getMessage());
            System.out.println("Data: " + resp.getData());
        }
    }
}

启动 Servlet 容器(Tomcat/Jetty),运行测试类,控制台输出业务处理结果,验证对象双向传输成功。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved