用httpUrlConnection實現檔案上傳
1、事先了解
1.1 請求格式
我們使用http來上傳檔案,必須先了解http的請求格式,然後才好發報。主要分為以下四個部分:
(1)分界符:由兩個連字元“--”和任意字串組成;
(2)標準http報文格式,來形容上傳檔案的相關資訊,包括請求引數名,上傳檔名,檔案型別,接收語言等。
(3)上傳檔案的內容,通常是位元組流的形式;
(4)檔案全部上傳後的結束符:與分界符類似,只不過需要在分界符後面再加兩個連字元。
1.2 http報文格式
(1)http請求報文
一個http請求報文格式是由四個部分組成,分別是請求行,請求頭部,空行和請求資料。其中需要注意的是空行是必須的。
- 請求行
請求行是由請求方法、
請求方法有8個——
GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT。
一般常用的就是GET 和 POST 方法。
簡單說下兩者區別——
(1)GET
絕大部分的HTTP請求報文使用的是GET方法,如get這個單詞本身含義一樣,GET方法是用於獲取查詢資源資訊,當瀏覽器請求一個物件時,使用GET方法。
(2)POST方法
HTTP客戶機常常在使用者提交表單時使用POST方法,需要用到實體主體。POST表示可能修改變伺服器上的資源的請求。
get請求可以獲取靜態頁面,也可以把引數放在URL字串後面,傳遞給
post與get的不同之處在於post的引數不是放在URL字串裡面,而是放在http請求的正文內。
URL欄位就是"http://192.168.1.110:8080/abc"這種瀏覽器訪問地址。
HTTP協議版本就是1.0 或者1.1 這兩種其中之一。
- 請求頭部
2、Android端上傳程式碼
httpUrlConnection是 URLConnection的子類,我們先來看看 URLConnection這個抽象類的相關介紹。注:以下內容來自 JDK API 1.6
抽象類URLConnection 是所有類的超類,它代表應用程式和 URL 之間的通訊連結。此類的例項可用於讀取和寫入此
openConnection() |
connect() |
對影響到遠端資源連線的引數進行操作。 |
與資源互動;查詢頭欄位和內容。 |
---------------------------->
時間
- 通過在 URL 上呼叫 openConnection 方法建立連線物件。
- 處理設定引數和一般請求屬性。
- 使用 connect 方法建立到遠端物件的實際連線。
- 遠端物件變為可用。遠端物件的頭欄位和內容變為可訪問。
使用以下方法修改設定引數:
- setAllowUserInteraction
- setDoInput
- setDoOutput
- setIfModifiedSince
- setUseCaches
使用以下方法修改一般請求屬性:
- setRequestProperty
使用setDefaultAllowUserInteraction 和 setDefaultUseCaches 可設定 AllowUserInteraction 和 UseCaches引數的預設值。
上面每個set 方法都有一個用於獲取引數值或一般請求屬性值的對應 get 方法。適用的具體引數和一般請求屬性取決於協議。
在建立到遠端物件的連線後,以下方法用於訪問頭欄位和內容:
- getContent
- getHeaderField
- getInputStream
- getOutputStream
某些頭欄位需要經常訪問。以下方法:
- getContentEncoding
- getContentLength
- getContentType
- getDate
- getExpiration
- getLastModifed
提供對這些欄位的便捷訪問。getContent方法使用 getContentType 方法以確定遠端物件型別;子類重寫 getContentType 方法很容易。
一般情況下,所有的預連線引數和一般請求屬性都可忽略:預連線引數和一般請求屬性預設為敏感值。對於此介面的大多數客戶端而言,只有兩個需要的方法:getInputStream和getContent,它們通過便捷方法映象到 URL 類中。
而API上對 HttpURLConnection 的介紹是:
每個HttpURLConnection 例項都可用於生成單個請求,但是其他例項可以透明地共享連線到 HTTP 伺服器的基礎網路。請求後在HttpURLConnection 的 InputStream 或 OutputStream 上呼叫 close() 方法可以釋放與此例項關聯的網路資源,但對共享的持久連線沒有任何影響。如果在呼叫 disconnect() 時持久連線空閒,則可能關閉基礎套接字。
因此,由上可知,HttpURLConnection是java用於特定的HTTP通訊的類。通過使用HttpURLConnection物件的方法設定相關引數,我們就能進行http通訊。
(1)public void setDoInput(boolean doinput)方法
如果打算使用 URL 連線進行輸入,則將 DoInput 標誌設定為 true;如果不打算使用,則設定為 false。預設值為true。
(2)public void setDoOutput(boolean dooutput)
將此 URLConnection 的 doOutput 欄位的值設定為指定的值。
URL 連線可用於輸入和/或輸出。如果打算使用 URL 連線進行輸出,則將 DoOutput標誌設定為 true;如果不打算使用,則設定為 false。預設值為 false。
(3)public void setUseCaches(boolean usecaches)
將此 URLConnection 的 useCaches 欄位的值設定為指定的值。
有些協議用於文件快取。有時候能夠進行“直通”並忽略快取尤其重要,例如瀏覽器中的“重新載入”按鈕。如果連線中的UseCaches 標誌為 true,則允許連線使用任何可用的快取。如果為 false,則忽略快取。預設值來自 DefaultUseCaches,它預設為 true。
具體程式碼如下:
/* 上傳檔案至Server的方法 */
private void uploadFile() {
String end = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
String newName = "image.jpg";
String uploadFile = "storage/sdcard1/bagPictures/102.jpg";
;
String actionUrl = "http://192.168.1.123:8080/upload/servlet/UploadServlet";
try {
URL url = new URL(actionUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
/* 允許Input、Output,不使用Cache */
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
/* 設定傳送的method=POST */
con.setRequestMethod("POST");
/* setRequestProperty */
con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Charset", "UTF-8");
con.setRequestProperty("Content-Type",
"multipart/form-data;boundary=" + boundary);
/* 設定DataOutputStream */
DataOutputStream ds = new DataOutputStream(con.getOutputStream());
ds.writeBytes(twoHyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data; "
+ "name=\"file1\";filename=\"" + newName + "\"" + end);
ds.writeBytes(end);
/* 取得檔案的FileInputStream */
FileInputStream fStream = new FileInputStream(uploadFile);
/* 設定每次寫入1024bytes */
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
/* 從檔案讀取資料至緩衝區 */
while ((length = fStream.read(buffer)) != -1) {
/* 將資料寫入DataOutputStream中 */
ds.write(buffer, 0, length);
}
ds.writeBytes(end);
ds.writeBytes(twoHyphens + boundary + twoHyphens + end);
/* close streams */
fStream.close();
ds.flush();
/* 取得Response內容 */
InputStream is = con.getInputStream();
int ch;
StringBuffer b = new StringBuffer();
while ((ch = is.read()) != -1) {
b.append((char) ch);
}
/* 將Response顯示於Dialog */
showDialog("上傳成功" + b.toString().trim());
/* 關閉DataOutputStream */
ds.close();
} catch (Exception e) {
showDialog("上傳失敗" + e);
}
}
3、伺服器端的程式碼
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
System.out.println("IP:" + request.getRemoteAddr());
// 1、建立工廠類:DiskFileItemFactory
DiskFileItemFactory facotry = new DiskFileItemFactory();
String tempDir = getServletContext().getRealPath("/WEB-INF/temp");
facotry.setRepository(new File(tempDir));//設定臨時檔案存放目錄
// 2、建立核心解析類:ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(facotry);
upload.setHeaderEncoding("UTF-8");// 解決上傳的檔名亂碼
upload.setFileSizeMax(1024 * 1024 * 1024);// 單個檔案上傳最大值是1M
upload.setSizeMax(2048 * 1024 * 1024);//檔案上傳的總大小限制
// 3、判斷使用者的表單提交方式是不是multipart/form-data
boolean bb = upload.isMultipartContent(request);
if (!bb) {
return;
}
// 4、是:解析request物件的正文內容List<FileItem>
List<FileItem> items = upload.parseRequest(request);
String storePath = getServletContext().getRealPath(
"/WEB-INF/upload");// 上傳的檔案的存放目錄
for (FileItem item : items) {
if (item.isFormField()) {
// 5、判斷是否是普通表單:列印看看
String fieldName = item.getFieldName();// 請求引數名
String fieldValue = item.getString("UTF-8");// 請求引數值
System.out.println(fieldName + "=" + fieldValue);
} else {
// 6、上傳表單:得到輸入流,處理上傳:儲存到伺服器的某個目錄中,儲存時的檔名是啥?
String fileName = item.getName();// 得到上傳檔案的名稱 C:\Documents
// and
// Settings\shc\桌面\a.txt
// a.txt
//解決使用者沒有選擇檔案上傳的情況
if(fileName==null||fileName.trim().equals("")){
continue;
}
fileName = fileName
.substring(fileName.lastIndexOf("\\") + 1);
String newFileName = UUIDUtil.getUUID() + "_" + fileName;
System.out.println("上傳的檔名是:" + fileName);
InputStream in = item.getInputStream();
String savePath = makeDir(storePath, fileName) + "\\"
+ newFileName;
OutputStream out = new FileOutputStream(savePath);
byte b[] = new byte[1024];
int len = -1;
while ((len = in.read(b)) != -1) {
out.write(b, 0, len);
}
in.close();
out.close();
item.delete();//刪除臨時檔案
}
}
}catch(FileUploadBase.FileSizeLimitExceededException e){
request.setAttribute("message", "單個檔案大小不能超出5M");
request.getRequestDispatcher("/message.jsp").forward(request,
response);
}catch(FileUploadBase.SizeLimitExceededException e){
request.setAttribute("message", "總檔案大小不能超出7M");
request.getRequestDispatcher("/message.jsp").forward(request,
response);
}catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "上傳失敗");
request.getRequestDispatcher("/message.jsp").forward(request,
response);
}
}
// WEB-INF/upload/1/3 打散儲存目錄
private String makeDir(String storePath, String fileName) {
int hashCode = fileName.hashCode();// 得到檔名的hashcode碼
int dir1 = hashCode & 0xf;// 取hashCode的低4位 0~15
int dir2 = (hashCode & 0xf0) >> 4;// 取hashCode的高4位 0~15
String path = storePath + "\\" + dir1 + "\\" + dir2;
File file = new File(path);
if (!file.exists())
file.mkdirs();
System.out.println("儲存路徑是"+path);
return path;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
4、實驗結果