通過構(gòu)造基于 HTTP 協(xié)議的傳輸內(nèi)容實現(xiàn)圖片自動上傳到服務(wù)器功能 。如果自己編碼構(gòu)造 HTTP 協(xié)議,那么編寫的代碼質(zhì)量肯定不高,建議模仿 HttpClient.zip examples\mime\ClientMultipartFormPost.java 來實現(xiàn),并通過源碼來進(jìn)一步理解如何優(yōu)雅高效地構(gòu)造 HTTP 協(xié)議傳輸內(nèi)容。
自
己構(gòu)造 HTTP
協(xié)議傳輸內(nèi)容的想法,從何而來呢?靈感啟迪于《Android下的應(yīng)用編程——用HTTP協(xié)議實現(xiàn)文件上傳功能,超鏈接!》,以前從未想過通過抓取
HTTP 請求數(shù)據(jù)格式來實現(xiàn)數(shù)據(jù)提交。哎,Out 了。因為 Apache HttpClient
框架就是通過此方式來實現(xiàn)的,以前從未注意到,看來以后要多多向前人學(xué)習(xí)??!結(jié)果是:閱讀了此框架的源碼后,才知道自己編寫的代碼和人家相比真不是一個檔
次的?,F(xiàn)在已經(jīng)下定決心了,多讀開源框架代碼,不但可以熟悉相關(guān)業(yè)務(wù)流程,而且還可以學(xué)到設(shè)計模式在實際業(yè)務(wù)需求中的應(yīng)用,更重要的是領(lǐng)悟其中的思想。業(yè)
務(wù)流程、實踐能力、框架思想,一舉三得,何樂而不為呢。^-^
test.html 部分源碼:
<form action="Your_Action_Url" method="post" enctype="multipart/form-data" name="form1" id="form1">
<p>
<label for="upload_file"></label>
<input type="file" name="upload_file" id="upload_file" />
</p>
<p>
<input type="submit" name="action" id="action" value="upload" />
</p>
</form>
通過 HttpWatch 抓取到的包數(shù)據(jù):

下面將分別通過按照 HttpWatch 抓取下來的協(xié)議格式內(nèi)容構(gòu)造傳輸內(nèi)容實現(xiàn)文件上傳功能和基于 HttpClient 框架實現(xiàn)文件上傳功能。
項目配置目錄Your_Project/config,相關(guān)文件如下:
actionUrl.properties 文件內(nèi)容:
Your_Action_Url
formDataParams.properties 文件內(nèi)容(對應(yīng) HTML Form 屬性內(nèi)容):
action=upload
imageParams.properties 文件內(nèi)容(這里文件路徑已配置死了,不好!建議在程序中動態(tài)設(shè)置,即通過傳入相關(guān)參數(shù)實現(xiàn)。):
upload_file=images/roewe.jpg
MIMETypes.properties 文件內(nèi)容(參考自 Multimedia MIME Reference):
jpeg:image/jpeg
jpg:image/jpeg
png:image/png
gif:image/gif
1. 在《Android下的應(yīng)用編程——用HTTP協(xié)議實現(xiàn)文件上傳功能》代碼的基礎(chǔ)上,通過進(jìn)一步改進(jìn)得到如下代碼(Java、Android 都可以 run):
/**
* 文件名稱:UploadImage.java
*
* 版權(quán)信息:Apache License, Version 2.0
*
* 功能描述:實現(xiàn)圖片文件上傳。
*
* 創(chuàng)建日期:2011-5-10
*
* 作者:Huagang Li
*/
/*
* 修改歷史:
*/
public class UploadImage {
String multipart_form_data = "multipart/form-data";
String twoHyphens = "--";
String boundary = "****************fD4fH3gL0hK7aI6"; // 數(shù)據(jù)分隔符
String lineEnd = System.getProperty("line.separator"); // The value is "\r\n" in Windows.
/*
* 上傳圖片內(nèi)容,格式請參考HTTP 協(xié)議格式。
* 人人網(wǎng)Photos.upload中的”程序調(diào)用“http://wiki.dev.renren.com/wiki/Photos.upload#.E7.A8.8B.E5.BA.8F.E8.B0.83.E7.94.A8
* 對其格式解釋的非常清晰。
* 格式如下所示:
* --****************fD4fH3hK7aI6
* Content-Disposition: form-data; name="upload_file"; filename="apple.jpg"
* Content-Type: image/jpeg
*
* 這兒是文件的內(nèi)容,二進(jìn)制流的形式
*/
private void addImageContent(Image[] files, DataOutputStream output) {
for(Image file : files) {
StringBuilder split = new StringBuilder();
split.append(twoHyphens + boundary + lineEnd);
split.append("Content-Disposition: form-data; name=\"" +
file.getFormName() + "\"; filename=\"" + file.getFileName() + "\"" +
lineEnd);
split.append("Content-Type: " + file.getContentType() + lineEnd);
split.append(lineEnd);
try {
// 發(fā)送圖片數(shù)據(jù)
output.writeBytes(split.toString());
output.write(file.getData(), 0, file.getData().length);
output.writeBytes(lineEnd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/*
* 構(gòu)建表單字段內(nèi)容,格式請參考HTTP 協(xié)議格式(用FireBug可以抓取到相關(guān)數(shù)據(jù))。(以便上傳表單相對應(yīng)的參數(shù)值)
* 格式如下所示:
* --****************fD4fH3hK7aI6
* Content-Disposition: form-data; name="action"
* // 一空行,必須有
* upload
*/
private void addFormField(Set<Map.Entry<Object,Object>> params, DataOutputStream output) {
StringBuilder sb = new StringBuilder();
for(Map.Entry<Object, Object> param : params) {
sb.append(twoHyphens + boundary + lineEnd);
sb.append("Content-Disposition: form-data; name=\"" + param.getKey() + "\"" + lineEnd);
sb.append(lineEnd);
sb.append(param.getValue() + lineEnd);
}
try {
output.writeBytes(sb.toString());// 發(fā)送表單字段數(shù)據(jù)
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 直接通過 HTTP 協(xié)議提交數(shù)據(jù)到服務(wù)器,實現(xiàn)表單提交功能。
* @param actionUrl 上傳路徑
* @param params 請求參數(shù)key為參數(shù)名,value為參數(shù)值
* @param files 上傳文件信息
* @return 返回請求結(jié)果
*/
public String post(String actionUrl, Set<Map.Entry<Object,Object>> params, Image[] files) {
HttpURLConnection conn = null;
DataOutputStream output = null;
BufferedReader input = null;
try {
URL url = new URL(actionUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(120000);
conn.setDoInput(true); // 允許輸入
conn.setDoOutput(true); // 允許輸出
conn.setUseCaches(false); // 不使用Cache
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "keep-alive");
conn.setRequestProperty("Content-Type", multipart_form_data + "; boundary=" + boundary);
conn.connect();
output = new DataOutputStream(conn.getOutputStream());
addImageContent(files, output); // 添加圖片內(nèi)容
addFormField(params, output); // 添加表單字段內(nèi)容
output.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);// 數(shù)據(jù)結(jié)束標(biāo)志
output.flush();
int code = conn.getResponseCode();
if(code != 200) {
throw new RuntimeException("請求‘" + actionUrl +"’失?。?);
}
input = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String oneLine;
while((oneLine = input.readLine()) != null) {
response.append(oneLine + lineEnd);
}
return response.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 統(tǒng)一釋放資源
try {
if(output != null) {
output.close();
}
if(input != null) {
input.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
if(conn != null) {
conn.disconnect();
}
}
}
public static void main(String[] args) {
try {
String response = "";
BufferedReader in = new BufferedReader(new FileReader("config/actionUrl.properties"));
String actionUrl = in.readLine();
// 讀取表單對應(yīng)的字段名稱及其值
Properties formDataParams = new Properties();
formDataParams.load(new FileInputStream(new File("config/formDataParams.properties")));
Set<Map.Entry<Object,Object>> params = formDataParams.entrySet();
// 讀取圖片所對應(yīng)的表單字段名稱及圖片路徑
Properties imageParams = new Properties();
imageParams.load(new FileInputStream(new File("config/imageParams.properties")));
Set<Map.Entry<Object,Object>> images = imageParams.entrySet();
Image[] files = new Image[images.size()];
int i = 0;
for(Map.Entry<Object,Object> image : images) {
Image file = new Image(image.getValue().toString(), image.getKey().toString());
files[i++] = file;
}
// Image file = new Image("images/apple.jpg", "upload_file");
// Image[] files = new Image[0];
// files[0] = file;
response = new UploadImage().post(actionUrl, params, files);
System.out.println("返回結(jié)果:" + response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 基于 HttpClient 框架實現(xiàn)文件上傳,實例代碼如下:
/**
* 文件名稱:ClientMultipartFormPost.java
*
* 版權(quán)信息:Apache License, Version 2.0
*
* 功能描述:通過 HttpClient 4.1.1 實現(xiàn)文件上傳。
*
* 創(chuàng)建日期:2011-5-15
*
* 作者:Huagang Li
*/
/*
* 修改歷史:
*/
public class ClientMultipartFormPost {
/**
* 直接通過 HttpMime's MultipartEntity 提交數(shù)據(jù)到服務(wù)器,實現(xiàn)表單提交功能。
* @return Post 請求所返回的內(nèi)容
*/
public static String filePost() {
HttpClient httpclient = new DefaultHttpClient();
try {
BufferedReader in = new BufferedReader(new FileReader("config/actionUrl.properties"));
String actionUrl;
actionUrl = in.readLine();
HttpPost httppost = new HttpPost(actionUrl);
// 通過閱讀源碼可知,要想實現(xiàn)圖片上傳功能,必須將 MultipartEntity 的模式設(shè)置為 BROWSER_COMPATIBLE 。
MultipartEntity multiEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
// MultipartEntity multiEntity = new MultipartEntity();
// 讀取圖片的 MIME Type 類型集
Properties mimeTypes = new Properties();
mimeTypes.load(new FileInputStream(new File("config/MIMETypes.properties")));
// 構(gòu)造圖片數(shù)據(jù)
Properties imageParams = new Properties();
imageParams.load(new FileInputStream(new File("config/imageParams.properties")));
String fileType;
for(Map.Entry<Object,Object> image : imageParams.entrySet()) {
String path = image.getValue().toString();
fileType = path.substring(path.lastIndexOf(".") + 1);
FileBody binaryContent = new FileBody(new File(path), mimeTypes.get(fileType).toString());
// FileBody binaryContent = new FileBody(new File(path));
multiEntity.addPart(image.getKey().toString(), binaryContent);
}
// 構(gòu)造表單參數(shù)數(shù)據(jù)
Properties formDataParams = new Properties();
formDataParams.load(new FileInputStream(new File("config/formDataParams.properties")));
for(Entry<Object, Object> param : formDataParams.entrySet()) {
multiEntity.addPart(param.getKey().toString(), new StringBody(param.getValue().toString()));
}
httppost.setEntity(multiEntity);
// Out.println("executing request " + httppost.getRequestLine());
HttpResponse response = httpclient.execute(httppost);
HttpEntity resEntity = response.getEntity();
// Out.println("-------------------");
// Out.println(response.getStatusLine());
if(resEntity != null) {
String returnContent = EntityUtils.toString(resEntity);
EntityUtils.consume(resEntity);
return returnContent; // 返回頁面內(nèi)容
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 釋放資源
httpclient.getConnectionManager().shutdown();
}
return null;
}
// 測試
public static void main(String[] args) {
Out.println("Response content: " + ClientMultipartFormPost.filePost());
}
}
參考資料:
1. Android下的應(yīng)用編程——用HTTP協(xié)議實現(xiàn)文件上傳功能
2. httpcomponents-client-4.1.1\examples\org\apache\http\examples\entity\mime\ClientMultipartFormPost.java
3. How to upload a file using Java HttpClient library