前面一篇文章寫了實現照相功能的一個例子,其實那個實現效果是個略縮圖。要查看全圖就要先指定照片的存放路徑。以后我會修改那個文章。今天先說下圖片,文件等上傳的實現。接着拿照片說事,光照完了不行還得往服務器上傳。
我們做web開發的時候幾乎都是通過一個表單來實現上傳。並且是post的方式。而且都必須要加個參數enctype = "multipart/form-data".然后再上傳后台用各種框架里的插件之類的就可以接收了,並沒有關心過這個文件具體是怎么傳的。現在用android開發 沒有那些框架了,所以不得不關心一下了。
其實我們這種前后台的交互是用的HTTP協議。而http協議默認是傳的字符串。所以我們上傳文件的話要加enctype = "multipart/form-data"這個參數來說明我們這傳的是文件不是字符串了。而我們做web開發的時候,瀏覽器是自動解析HTTP協議的。里面傳的哪些東西我們不用管。只要記住幾個參數就行。而我們要上傳的文件報文是保存在請求的頭文件里面的。下面就是上傳文件頭文件的格式:
POST/logsys/home/uploadIspeedLog!doDefault.html HTTP/1.1
Accept: text/plain, */*
Accept-Language: zh-cn
Host: 192.168.24.56
Content-Type:multipart/form-data;boundary=-----------------------------7db372eb000e2
User-Agent: WinHttpClient
Content-Length: 3693
Connection: Keep-Alive
-------------------------------7db372eb000e2
Content-Disposition: form-data; name="file"; filename="kn.jpg"
Content-Type: image/jpeg
(此處省略jpeg文件二進制數據...)
-------------------------------7db372eb000e2--
這就是Http上傳發送的文件格式。而我們要發送的時候必然要遵循這種格式來並且不能出一點差錯包括每行后面的回車,下面一段文字是網上找的感覺寫的比較精彩。(尊重原創:原文地址)
紅色字體部分就是協議的頭。給服務器上傳數據時,並非協議頭每個字段都得說明,其中,content-type是必須的,它包括一個類似標志性質的名為boundary的標志,它可以是隨便輸入的字符串。對后面的具體內容也是必須的。它用來分辨一段內容的開始。Content-Length: 3693 ,這里的3693是要上傳文件的總長度。綠色字體部分就是需要上傳的數據,可以是文本,也可以是圖片等。數據內容前面需要有Content-Disposition, Content-Type以及Content-Transfer-Encoding等說明字段。最后的紫色部分就是協議的結尾了。
注意這一行:
Content-Type: multipart/form-data; boundary=---------------------------7db372eb000e2
根據 rfc1867, multipart/form-data是必須的.
---------------------------7db372eb000e2 是分隔符,分隔多個文件、表單項。其中b372eb000e2 是即時生成的一個數字,用以確保整個分隔符不會在文件或表單項的內容中出現。Form每個部分用分隔符分割,分隔符之前必須加上"--"着兩個字符(即--{boundary})才能被http協議認為是Form的分隔符,表示結束的話用在正確的分隔符后面添加"--"表示結束。
前面的 ---------------------------7d 是 IE 特有的標志,Mozila 為---------------------------71.
每個分隔的數據的都可以用Content-Type來表示下面數據的類型,可以參考rfc1341 (http://www.ietf.org/rfc/rfc1341.txt)
以上對上傳文件的頭文件格式講的應該比較清楚了。其實很多參數我們也用不到,也不必刻意去記住,下面就看一個例子。應該能更好的理解:
- package com.example.photo;
- import java.io.BufferedReader;
- import java.io.DataOutputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.net.HttpURLConnection;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.util.UUID;
- public class HttpAssist {
- private static final String TAG = "uploadFile";
- private static final int TIME_OUT = 10 * 10000000; // 超時時間
- private static final String CHARSET = "utf-8"; // 設置編碼
- public static final String SUCCESS = "1";
- public static final String FAILURE = "0";
- public static String uploadFile(File file) {
- String BOUNDARY = UUID.randomUUID().toString(); // 邊界標識 隨機生成
- String PREFIX = "--", LINE_END = "\r\n";
- String CONTENT_TYPE = "multipart/form-data"; // 內容類型
- String RequestURL = "http://192.168.0.100:7080/YkyPhoneService/Uploadfile1";
- try {
- URL url = new URL(RequestURL);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setReadTimeout(TIME_OUT);
- conn.setConnectTimeout(TIME_OUT);
- conn.setDoInput(true); // 允許輸入流
- conn.setDoOutput(true); // 允許輸出流
- conn.setUseCaches(false); // 不允許使用緩存
- conn.setRequestMethod("POST"); // 請求方式
- conn.setRequestProperty("Charset", CHARSET); // 設置編碼
- conn.setRequestProperty("connection", "keep-alive");
- conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary="
- + BOUNDARY);
- if (file != null) {
- /**
- * 當文件不為空,把文件包裝並且上傳
- */
- OutputStream outputSteam = conn.getOutputStream();
- DataOutputStream dos = new DataOutputStream(outputSteam);
- StringBuffer sb = new StringBuffer();
- sb.append(PREFIX);
- sb.append(BOUNDARY);
- sb.append(LINE_END);
- /**
- * 這里重點注意: name里面的值為服務器端需要key 只有這個key 才可以得到對應的文件
- * filename是文件的名字,包含后綴名的 比如:abc.png
- */
- sb.append("Content-Disposition: form-data; name=\"img\"; filename=\""
- + file.getName() + "\"" + LINE_END);
- sb.append("Content-Type: application/octet-stream; charset="
- + CHARSET + LINE_END);
- sb.append(LINE_END);
- dos.write(sb.toString().getBytes());
- InputStream is = new FileInputStream(file);
- byte[] bytes = new byte[1024];
- int len = 0;
- while ((len = is.read(bytes)) != -1) {
- dos.write(bytes, 0, len);
- }
- is.close();
- dos.write(LINE_END.getBytes());
- byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINE_END)
- .getBytes();
- dos.write(end_data);
- dos.flush();
- /**
- * 獲取響應碼 200=成功 當響應成功,獲取響應的流
- */
- int res = conn.getResponseCode();
- if (res == 200) {
- return SUCCESS;
- }
- }
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return FAILURE;
- }
- }
這是文件上傳的客戶端代碼。 認真讀一下代碼,應該能夠理解上傳文件的格式了。同樣寫好這個頭文件之后采用HttpURLConnection向后台發送。也就是瀏覽器所用的http協議。我們只是把這個協議自己手動調用並且手動填寫頭文件內容。而不是通過瀏覽器幫我們寫了。看這段代碼的時候我發現都是把字符串轉化成字節流,然后利用DataOutputStream這個類來想后台傳輸。圖片文件也是利用這個類向后台傳。不知道大家有沒有跟我想法一樣的覺得到后台之后通過Request獲取字節流,然后把流寫到相應格式的文件或者圖片里就收到了。我覺得理論上應該是這樣的。而且網上也有一些相應的文件或者代碼。但是我拿來用的話卻都不成功,以下是一個例子(不能用。)
- public class UploadFile extends HttpServlet {
- static int i = 0;
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws IOException {
- ServletInputStream in = request.getInputStream();
- byte[] buffer = new byte[1024];
- File file = new File(request.getSession().getServletContext().getRealPath("/img/"),"img_"+i+".jpg");
- i++;
- FileOutputStream out = new FileOutputStream(file);
- int len = in.read(buffer, 0, 1024);
- while( len!=-1){
- out.write(buffer,0,len);
- len = in.read(buffer, 0, 1024);
- }
- out.close();
- in.close();
- }
- }
我覺得這幾行代碼跟我的想法很相似,但就是運行不成功。向后台上傳一個圖片之后也收到了,但就是打不開,提示文件損壞。而且我覺得這種思路是對的,於是就一直在找類似的實現方法或者,按自己的思路修改一下代碼,帶最終還是不行。所以只好用其他的方法。
服務器接收上傳的文件的方法最后是通過利用apache提供的兩個jar包來實現的。commons-fileupload.jar和commons-io.jar這倆jar包。在服務器端添加這倆包之后,寫一個Servlet來實現文件接收。直接上代碼:
- public class Uploadfile1 extends HttpServlet {
- @Override
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- request.setCharacterEncoding("utf-8");
- //獲得磁盤文件條目工廠。
- DiskFileItemFactory factory = new DiskFileItemFactory();
- //獲取文件上傳需要保存的路徑,upload文件夾需存在。
- String path = request.getSession().getServletContext().getRealPath("/upload");
- //設置暫時存放文件的存儲室,這個存儲室可以和最終存儲文件的文件夾不同。因為當文件很大的話會占用過多內存所以設置存儲室。
- factory.setRepository(new File(path));
- //設置緩存的大小,當上傳文件的容量超過緩存時,就放到暫時存儲室。
- factory.setSizeThreshold(1024*1024);
- //上傳處理工具類(高水平API上傳處理?)
- ServletFileUpload upload = new ServletFileUpload(factory);
- try{
- //調用 parseRequest(request)方法 獲得上傳文件 FileItem 的集合list 可實現多文件上傳。
- List<FileItem> list = (List<FileItem>)upload.parseRequest(request);
- for(FileItem item:list){
- //獲取表單屬性名字。
- String name = item.getFieldName();
- //如果獲取的表單信息是普通的文本信息。即通過頁面表單形式傳遞來的字符串。
- if(item.isFormField()){
- //獲取用戶具體輸入的字符串,
- String value = item.getString();
- request.setAttribute(name, value);
- }
- //如果傳入的是非簡單字符串,而是圖片,音頻,視頻等二進制文件。
- else{
- //獲取路徑名
- String value = item.getName();
- //取到最后一個反斜杠。
- int start = value.lastIndexOf("\\");
- //截取上傳文件的 字符串名字。+1是去掉反斜杠。
- String filename = value.substring(start+1);
- request.setAttribute(name, filename);
- /*第三方提供的方法直接寫到文件中。
- * item.write(new File(path,filename));*/
- //收到寫到接收的文件中。
- OutputStream out = new FileOutputStream(new File(path,filename));
- InputStream in = item.getInputStream();
- int length = 0;
- byte[] buf = new byte[1024];
- System.out.println("獲取文件總量的容量:"+ item.getSize());
- while((length = in.read(buf))!=-1){
- out.write(buf,0,length);
- }
- in.close();
- out.close();
- }
- }
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- }
代碼同樣轉自網絡,具體地址給忘了,這個真不是故意的。。。
代碼上有相應注釋,應該都能看懂。並且這個開源jar包提供的方法挺強大的,支持多文件上傳之類的。我猜它的源碼同樣是獲取客戶端傳過來的字節流。后面的代碼跟上面提供的思路一樣。只是不知道它的Item究竟是如何獲得這個字節流的。按着源碼看了看 也沒看太明白。以后再慢慢研究吧。有哪位明白的 還請指點。
這樣利用HTTP做到文件的上傳和接收都已經正確運行了。先留下個筆記,以后用的時候可以看看。
