1. 程式人生 > >輕鬆把玩HttpClient之封裝HttpClient工具類(九),新增多檔案上傳功能

輕鬆把玩HttpClient之封裝HttpClient工具類(九),新增多檔案上傳功能

       在Git上有人給我提Issue,說怎麼上傳檔案,其實我一開始就想上這個功能,不過這半年比較忙,所以一直耽擱了。這次正好沒什麼任務了,趕緊完成這個功能。畢竟作為一款工具類,有基本的請求和下載功能,就差上傳了,有點說不過去。好了,天不早了,咱乾點正事吧。

       如果你只想瞭解怎麼用HttpClient來上傳檔案,可以參考這篇文章:http://blog.csdn.net/fengyuzhengfan/article/details/39941851,裡面寫的很清楚了。這裡我主要介紹工具類中的修改。

       首先,上傳功能需要用到HttpMime這個,所以首先在pom中新增maven依賴(不會的自行百度)。

       其次,修改Utils中的map2List方法。閱讀過原始碼的都知道,我所有的請求引數,都是通過map來封裝的,原來都是返回list,上次升級以後,返回的都是HttpEntity物件(看來下次得修改一下方法名了map2HttpEntity尷尬),把所有的引數轉化為HttpEntity物件。

	//裝填引數
	HttpEntity entity = Utils.map2List(nvps, config.map(), config.inenc());
	//設定引數到請求物件中
	((HttpEntityEnclosingRequestBase)request).setEntity(entity);

       現在上傳用到的是MultipartEntityBuilder,所以新增一個特定型別,進行特殊處理。

	public static final String ENTITY_MULTIPART="$ENTITY_MULTIPART$";
	private static final List<String> SPECIAL_ENTITIY = Arrays.asList(ENTITY_STRING, ENTITY_BYTES, ENTITY_FILE, ENTITY_INPUTSTREAM, ENTITY_SERIALIZABLE, ENTITY_MULTIPART);

	/**
	 * 引數轉換,將map中的引數,轉到引數列表中
	 * 
	 * @param nvps				引數列表
	 * @param map				引數列表(map)
	 * @throws UnsupportedEncodingException 
	 */
	public static HttpEntity map2List(List<NameValuePair> nvps, Map<String, Object> map, String encoding) throws UnsupportedEncodingException {
		HttpEntity entity = null;
		if(map!=null && map.size()>0){
			boolean isSpecial = false;
			// 拼接引數
			for (Entry<String, Object> entry : map.entrySet()) {
				if(SPECIAL_ENTITIY.contains(entry.getKey())){//判斷是否在之中
					isSpecial = true;
					if(ENTITY_STRING.equals(entry.getKey())){//string
						entity = new StringEntity(String.valueOf(entry.getValue()), encoding);
						break;
					}else if(ENTITY_BYTES.equals(entry.getKey())){//file
						entity = new ByteArrayEntity((byte[])entry.getValue());
						break;
					}else if(ENTITY_FILE.equals(entry.getKey())){//file
						//....
						break;
					}else if(ENTITY_INPUTSTREAM.equals(entry.getKey())){//inputstream
//						entity = new InputStreamEntity();
						break;
					}else if(ENTITY_SERIALIZABLE.equals(entry.getKey())){//serializeable
//						entity = new SerializableEntity()
						break;
					}else if(ENTITY_MULTIPART.equals(entry.getKey())){//MultipartEntityBuilder
						File[] files  = null;
						if(File.class.isAssignableFrom(entry.getValue().getClass().getComponentType())){
							files=(File[])entry.getValue();
						}else if(entry.getValue().getClass().getComponentType()==String.class){
							String[] names = (String[]) entry.getValue();
							files = new File[names.length];
							for (int i = 0; i < names.length; i++) {
								files[i] = new File(names[i]);
							}
						}
						MultipartEntityBuilder builder = MultipartEntityBuilder.create();
						builder.setCharset(Charset.forName(encoding));// 設定請求的編碼格式
						builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);// 設定瀏覽器相容模式
						int count = 0;
						for (File file : files) {
							builder.addBinaryBody(String.valueOf(map.get(ENTITY_MULTIPART+".name")) + count++,file);
						}
						boolean forceRemoveContentTypeCharset = (Boolean)map.get(ENTITY_MULTIPART+".rmCharset");
						Map<String, Object> m = new HashMap<String, Object>();
						m.putAll(map);
						m.remove(ENTITY_MULTIPART);
						m.remove(ENTITY_MULTIPART+".name");
						m.remove(ENTITY_MULTIPART+".rmCharset");
						Iterator<Entry<String, Object>> iterator = m.entrySet().iterator();
						// 傳送的資料
				        while (iterator.hasNext()) {
							Entry<String, Object> e = iterator.next();
				            builder.addTextBody(e.getKey(), String.valueOf(e.getValue()), ContentType.create("text/plain", encoding));
				        }
						entity = builder.build();// 生成 HTTP POST 實體
						
						//強制去除contentType中的編碼設定,否則,在某些情況下會導致上傳失敗
						if(forceRemoveContentTypeCharset){
							removeContentTypeChraset(encoding, entity);
						}
						break;
					}else {
						nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
					}
				}else{
					nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
				}
			}
			if(!isSpecial) {
				entity = new UrlEncodedFormEntity(nvps, encoding);
			}
		}
		return entity;
	}

       這裡面有一個方法removeContentTypeChraset,主要是為了解決,如果呼叫了setCharset,中文檔名不會亂碼,但是在ContentType檔案頭中會多一個charset=xxx,而導致上傳失敗,解決辦法就是強制去掉這個資訊。而這個HttpEntity實際物件是MultipartFormEntity物件。這個類未宣告public,所以只能包內訪問。而且該類的contentType屬性是private final型別。就算可以通過物件拿到這個屬性,也無法修改。所以我只能通過反射來修改。

	private static void removeContentTypeChraset(String encoding, HttpEntity entity) {
		try {
			Class<?> clazz = entity.getClass();
			Field field = clazz.getDeclaredField("contentType");
			field.setAccessible(true); //將欄位的訪問許可權設為true:即去除private修飾符的影響
			if(Modifier.isFinal(field.getModifiers())){
				Field modifiersField = Field.class.getDeclaredField("modifiers"); //去除final修飾符的影響,將欄位設為可修改的  
				modifiersField.setAccessible(true);  
				modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);  
			}
			BasicHeader o = (BasicHeader) field.get(entity);
			field.set(entity, new BasicHeader(HTTP.CONTENT_TYPE, o.getValue().replace("; charset="+encoding,"")));
		} catch (NoSuchFieldException e) {
			Utils.exception(e);
		} catch (SecurityException e) {
			Utils.exception(e);
		} catch (IllegalArgumentException e) {
			Utils.exception(e);
		} catch (IllegalAccessException e) {
			Utils.exception(e);
		}
	}

       剩下的就是HttpConfig的修改了,所有引數都是通過這個物件封裝的。對於上傳,我沒想再建立一個file陣列物件來傳遞引數,因為在上傳時,可能還會有其他引數,都得需要通過map來傳遞,所以上傳也直接藉助map來完成。

/**
	 * 上傳檔案時用到
	 */
	public HttpConfig files(String[] filePaths) {
		return files(filePaths, "file");
	}
	/**
	 * 上傳檔案時用到
	 * @param filePaths		待上傳檔案所在路徑
	 */
	public HttpConfig files(String[] filePaths, String inputName) {
		return files(filePaths, inputName, false);
	}
	/**
	 * 上傳檔案時用到
	 * @param filePaths			待上傳檔案所在路徑
	 * @param inputName		即file input 標籤的name值,預設為file
	 * @param forceRemoveContentTypeChraset
	 * @return
	 */
	public HttpConfig files(String[] filePaths, String inputName, boolean forceRemoveContentTypeChraset) {
		synchronized (getClass()) {
			if(this.map==null){
				this.map= new HashMap<String, Object>();
			}
		}
		map.put(Utils.ENTITY_MULTIPART, filePaths);
		map.put(Utils.ENTITY_MULTIPART+".name", inputName);
		map.put(Utils.ENTITY_MULTIPART+".rmCharset", forceRemoveContentTypeChraset);
		return this;
	}
       map方法也做了相應的修改:
	/**
	 * 傳遞引數
	 */
	public HttpConfig map(Map<String, Object> map) {
		synchronized (getClass()) {
			if(this.map==null || map==null){
				this.map = map;
			}else {
				this.map.putAll(map);;
			}
		}
		return this;
	}

       最後,來一個測試類,

import java.util.HashMap;
import java.util.Map;

import com.tgb.ccl.http.common.HttpConfig;
import com.tgb.ccl.http.common.HttpCookies;
import com.tgb.ccl.http.common.Utils;
import com.tgb.ccl.http.exception.HttpProcessException;
import com.tgb.ccl.http.httpclient.HttpClientUtil;

/** 
 * 上傳功能測試
 * 
 * @author arron
 * @date 2016年11月2日 下午1:17:17 
 * @version 1.0 
 */
public class TestUpload {

	public static void main(String[] args) throws HttpProcessException {
		//登入後,為上傳做準備
		HttpConfig config = prepareUpload();
		
		String url= "http://test.free.800m.net:8080/up.php?action=upsave";//上傳地址
		String[] filePaths = {"D:\\中國.txt","D:\\111.txt","C:\\Users\\160049\\Desktop\\中國.png"};//待上傳的檔案路徑
		
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("path", "./tomcat/vhost/test/ROOT/");//指定其他引數
		config.url(url) //設定上傳地址
				 .encoding("GB2312") //設定編碼,否則可能會引起中文亂碼或導致上傳失敗
				 .files(filePaths,"myfile",true)//.files(filePaths),如果伺服器端有驗證input 的name值,則請傳遞第二個引數,如果上傳失敗,則嘗試第三個引數設定為true
				 .map(map);//其他需要提交的引數
		
		Utils.debug();//開啟列印日誌,呼叫 Utils.debug(false);關閉列印日誌
		String r = HttpClientUtil.upload(config);//上傳
		System.out.println(r);
		
	}

	/**
	 * 登入,並上傳檔案
	 * 
	 * @return
	 * @throws HttpProcessException
	 */
	private static HttpConfig prepareUpload() throws HttpProcessException {
		String url ="http://test.free.800m.net:8080/";
		String loginUrl = url+"login.php";
		String indexUrl = url+"index.php";
		HttpCookies cookies = HttpCookies.custom();
		//啟用cookie,用於登入後的操作
		HttpConfig config = HttpConfig.custom().context(cookies.getContext());
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("user_name", "test");
		map.put("user_pass", "800m.net");
		map.put("action", "login");
		String loginResult = HttpClientUtil.post(config.url(loginUrl).map(map));
		
		System.out.println("是否登入成功:"+loginResult.contains("成功"));
		//開啟主頁
		HttpClientUtil.get(config.map(null).url(indexUrl));
		
		return config;
	}
	
}

最新的完整程式碼請到GitHub上進行下載:https://github.com/Arronlong/httpclientUtil 。

       httpclientUtil (QQ交流群:548452686 httpclientUtil交流