2010年12月1日 星期三

[轉載] HttpClient 4.x 表單檔案上傳

最近手邊的專案漸漸由 HttpClient 3.x 轉移至改用 HttpClient 4.x 實作 Http 操作。
使用 Commons 專案的東西,捨棄原始的 HttpURLConnection 就是為了較輕鬆地處理 Http 操作的功能。

我們可以用下列方式透過網頁表單上傳檔案:

HttpClient client = new DefaultHttpClient();
HttpPost post =new HttpPost("http://example.com/upload.do");
MultipartEntity e = new MultipartEntity();
try{
        e.addPart("field1", new StringBody("some value"));
        e.addPart("file", new FileBody(new File("your_file_path")));
}catch (UnsupportedEncodingException ex) {
        ex.printStackTrace();
}

post.setEntity(e);
client.execute(post);

它的寫法是相當容易的!不過有個小缺點,那就是 HttpClient 4.x 某個版本將 Multipart Entity 實作獨立至 HttpMime 專案
http://hc.apache.org/httpcomponents-client/httpmime/index.html
所以,您要使用單純的表單上傳功能,就需要同時再引用 HttpMime 相關的 Library

C:\http-mime>dir
    磁碟區 C 中的磁碟沒有標籤。
    磁碟區序號: A8B9-DC3D

C:\http-mime 的目錄

2010/01/01 下午 01:20 <DIR> .
2010/01/01 下午 01:20 <DIR> ..
2009/12/31 下午 02:22 345,048 apache-mime4j-0.6.jar
2009/12/31 下午 02:22 25,993 httpmime-4.0.jar
2009/12/31 下午 02:22 2,255 jcip-annotations-1.0.jar
                    3 個檔案 373,296 位元組
                    2 個目錄 70,042,320,896 位元組可用

雖然,這些 Library 僅 300 多 kb,對一般的應用程式開發來說並不算什麼。
但總覺得是否能讓它單純一些呢?我們並不需要完整而強大的 Http Mime,
有時我們只需要足夠的功能罷了!隨著這個想法,試著實作一個極簡版本的 ThinMultipartEntity。
可以不需引用 HttpMime 就上傳檔案。

package com.google.jplurk.net;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;

public class ThinMultipartEntity implements HttpEntity {

  static Log logger = LogFactory.getLog(ThinMultipartEntity.class);

  private final static char[] MULTIPART_CHARS =
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
      .toCharArray();

  private String boundary = null;

  ByteArrayOutputStream out = new ByteArrayOutputStream();
  boolean isSetLast = false;
  boolean isSetFirst = false;

  public ThinMultipartEntity() {
    StringBuffer buf = new StringBuffer();
    Random rand = new Random();
    for (int i = 0; i < 30; i++) {
      buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
    }
    this.boundary = buf.toString();

  }

  public void writeFirstBoundary(){
    if(!isSetFirst){
      try {
        out.write(("--" + boundary + "\r\n").getBytes());
      } catch (IOException e) {
        logger.error(e.getMessage(), e);
      }
    }
    isSetFirst = true;
  }

  public void writeLastBoundary() {
    if(isSetLast){
      return ;
    }
    try {
      out.write(("\r\n--" + boundary + "--\r\n").getBytes());
    } catch (IOException e) {
      logger.error(e.getMessage(), e);
    }
    isSetLast = true;
  }

  public void addPart(String key, String value) {
    writeFirstBoundary();
    try {
      out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n").getBytes());
      out.write("Content-Type: text/plain; charset=UTF-8\r\n".getBytes());
      out.write("Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes());
      out.write(value.getBytes());
      out.write(("\r\n--" + boundary + "\r\n").getBytes());
    } catch (IOException e) {
      logger.error(e.getMessage(), e);
    }
  }

  public void addPart(String key, File value) {
    writeFirstBoundary();
    try {
      out.write(("Content-Disposition: form-data; name=\""
+ key+"\"; filename=\"" + value.getName()+ "\"\r\n").getBytes());
      out.write("Content-Type: application/octet-stream\r\n".getBytes());
      out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes());

      FileInputStream fin = new FileInputStream(value);
      int data = fin.read();
      while(data !=-1){
        out.write(data);
        data = fin.read();
      }

    } catch (IOException e) {
      logger.error(e.getMessage(), e);
    }
  }

  public long getContentLength() {
    writeLastBoundary();
    return out.toByteArray().length;
  }

  public Header getContentType() {
    return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
  }

  public boolean isChunked() {
    return false;
  }

  public boolean isRepeatable() {
    return false;
  }

  public boolean isStreaming() {
    return false;
  }

  public void writeTo(OutputStream outstream) throws IOException {
    outstream.write(out.toByteArray());
  }

  public Header getContentEncoding() {
    return null;
  }

  public void consumeContent() throws IOException,
      UnsupportedOperationException {
    if (isStreaming()) {
      throw new UnsupportedOperationException(
          "Streaming entity does not implement #consumeContent()");
    }
  }

  public InputStream getContent() throws IOException,
      UnsupportedOperationException {
    throw new UnsupportedOperationException(
        "Multipart form entity does not implement #getContent()");
  }

}

PS. 因為是極簡版的,有許多細節是被省略的
PS. 只試過上傳一個檔 XD 

沒有留言:

張貼留言