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

HttpClient 文件上傳下載完整實戰:PostMethod/HttpPost/GetMethod/HttpGet 四種風格對比

     編輯:Java 相關知識

Apache HttpClient 是 Java 發送 HTTP 請求的標準庫。本文針對文件上傳與文件下載兩大場景,系統對比 org.apache.commons.httpclient (舊 3.x) 與 org.apache.http.client (新 4.x/5.x) 兩代 API 的四種核心寫法,並提供服務端通用處理邏輯與最佳實踐建議。

版本區分速查

一、文件上傳:四種寫法對比

1. 舊版 PostMethod + FilePart (multipart/form-data)

// Commons HttpClient 3.x
public void uploadLegacy(String localFile) throws Exception {
  HttpClient client = new HttpClient();
  PostMethod post = new PostMethod(UPLOAD_URL);
  post.setParameter("userName", "scott");
  post.setParameter("passwd", "123456");
  
  Part[] parts = { new FilePart("file", new File(localFile)) };
  post.setRequestEntity(new MultipartRequestEntity(parts, post.getParams()));
  
  client.executeMethod(post);
  // 處理回應...
  post.releaseConnection();
}

2. 新版 HttpPost + FileBody (MultipartEntityBuilder)

// HttpClient 4.5+
public void uploadModern(String localFile) throws Exception {
  try (CloseableHttpClient client = HttpClients.createDefault()) {
    HttpPost post = new HttpPost(UPLOAD_URL);
    
    FileBody fileBody = new FileBody(new File(localFile));
    StringBody user = new StringBody("scott", ContentType.TEXT_PLAIN);
    StringBody pass = new StringBody("123456", ContentType.TEXT_PLAIN);
    
    HttpEntity entity = MultipartEntityBuilder.create()
        .addPart("file", fileBody)
        .addPart("userName", user)
        .addPart("pass", pass)
        .build();
    post.setEntity(entity);
    
    try (CloseableHttpResponse resp = client.execute(post)) {
      System.out.println(EntityUtils.toString(resp.getEntity()));
    }
  }
}

3. 服務端通用處理 (Servlet 3.0+)

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {
    
    req.setCharacterEncoding("UTF-8");
    String uploadPath = getServletContext().getRealPath("/") + "uploads/";
    File dir = new File(uploadPath); if (!dir.exists()) dir.mkdirs();

    DiskFileItemFactory factory = new DiskFileItemFactory();
    factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
    factory.setSizeThreshold(1024 * 1024); // 1MB 記憶體閾值

    ServletFileUpload upload = new ServletFileUpload(factory);
    upload.setFileSizeMax(50 * 1024 * 1024);
    upload.setSizeMax(50 * 1024 * 1024);
    upload.setHeaderEncoding("UTF-8");

    List<FileItem> items = upload.parseRequest(req);
    for (FileItem item : items) {
      if (item.isFormField()) {
        System.out.println("表單欄位: " + item.getFieldName() + "=" + item.getString("UTF-8"));
      } else {
        String fileName = new File(item.getName()).getName(); // 防路徑遍歷
        item.write(new File(uploadPath, fileName));
      }
    }
    resp.setHeader("token", "ok");
  }
}

二、文件下載:三種寫法對比

1. 舊版 GetMethod

public void downloadLegacy(String remoteName, String localFile) throws Exception {
  HttpClient client = new HttpClient();
  GetMethod get = new GetMethod(DOWNLOAD_URL);
  get.setRequestHeader("fileName", remoteName);
  get.setRequestHeader("userName", "scott");
  
  if (client.executeMethod(get) == HttpStatus.SC_OK) {
    try (FileOutputStream fos = new FileOutputStream(localFile)) {
      fos.write(get.getResponseBody());
    }
  }
  get.releaseConnection();
}

2. 新版 HttpGet (推薦)

public void downloadModern(String remoteName, String localFile) throws Exception {
  try (CloseableHttpClient client = HttpClients.createDefault();
       CloseableHttpResponse resp = client.execute(buildGet(remoteName));
       InputStream in = resp.getEntity().getContent();
       FileOutputStream fos = new FileOutputStream(localFile)) {
    
    HttpEntity entity = resp.getEntity();
    long len = entity.getContentLength();
    if (len <= 0) { System.out.println("檔案不存在"); return; }
    
    byte[] buf = new byte[8192];
    int read;
    while ((read = in.read(buf)) != -1) fos.write(buf, 0, read);
  }
}

private HttpGet buildGet(String fileName) {
  HttpGet get = new HttpGet(DOWNLOAD_URL);
  get.addHeader("fileName", fileName);
  get.addHeader("userName", "scott");
  return get;
}

3. 原生 HttpURLConnection (零依賴)

public void downloadNative(String remoteName, String localFile) throws Exception {
  URL url = new URL(DOWNLOAD_URL);
  HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  conn.setRequestMethod("POST");
  conn.setDoOutput(true);
  conn.setDoInput(true);
  conn.setRequestProperty("fileName", remoteName);
  conn.setRequestProperty("userName", "scott");
  conn.setConnectTimeout(60000);
  conn.setReadTimeout(60000);
  conn.connect();

  try (InputStream in = conn.getInputStream();
       FileOutputStream fos = new FileOutputStream(localFile)) {
    byte[] buf = new byte[8192];
    int read;
    while ((read = in.read(buf)) != -1) fos.write(buf, 0, read);
  }
}

4. 服務端下載處理 (支援 Range 斷點續傳基礎)

@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {
    String fileName = req.getHeader("fileName"); // 或 getParameter
    File file = new File(DOWNLOAD_DIR, fileName);
    if (!file.exists()) { resp.sendError(404); return; }

    resp.setContentType("application/octet-stream");
    resp.setHeader("Content-Disposition", "attachment; filename=" + fileName);
    resp.setContentLengthLong(file.length());
    resp.setHeader("Accept-Ranges", "bytes");

    try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
         BufferedOutputStream bos = new BufferedOutputStream(resp.getOutputStream())) {
      byte[] buf = new byte[8192];
      int len;
      while ((len = bis.read(buf)) != -1) bos.write(buf, 0, len);
    }
  }
}

三、核心最佳實踐總結

  1. 新專案強制用 4.5+/5.x:CloseableHttpClient 實作 AutoCloseable,配合 try-with-resources 無洩漏。
  2. 連線池複用:單例 PoolingHttpClientConnectionManager,避免頻繁建立 TCP 連線。
  3. 超時配置:RequestConfig.custom().setConnectTimeout(5000).setSocketTimeout(30000).build()。
  4. 大檔流式:上傳用 MultipartEntityBuilder、下載用 resp.getEntity().getContent() 邊讀邊寫,嚴禁 getResponseBody() 全讀入記憶體。
  5. 釋放連線:舊版 releaseConnection()、新版 resp.close() / EntityUtils.consume(entity) 必呼叫。
  6. 錯誤處理:統一捕獲 IOException、HttpException,記錄完整堆疊並拋出業務異常。

總結

HttpClient 文件上傳下載核心在於 MultipartEntityBuilder 組裝請求體 與 流式處理回應體。掌握新舊 API 差異、連線池複用、超時與資源釋放三大支柱,即能構建穩健的 HTTP 客戶端。現代專案建議直接使用 HttpClient 5.x + Reactive Streams (HttpClient Fluent API) 進一步簡化代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
套件核心類狀態建議
org.apache.commons.httpclientHttpClient, PostMethod, GetMethodLegacy 3.x維舊專案用
org.apache.http.clientCloseableHttpClient, HttpPost, HttpGetModern 4.5+/5.x新專案首選