在C#用戶端用HTTP上傳檔案到Java伺服器

來源:互聯網
上載者:User

標籤:val   tool   reads   accept   doctype   try   要求標頭   sharp   title   

最近在做C / S 開發,需要在C#用戶端上傳檔案到Java後台進行處理。

對於較大的檔案我們可以直接用FTP協議傳檔案,較小的檔案則可以向B / S 一樣用HTTP上傳。

首先,由於要傳檔案,我們需要用 POST 來發送資料。GET 有長度限制,而且資料跟在URL後面。

既然要發送POST請求,我們先來看看POST 請求的報文格式。

HTTP 報文介紹

先寫一個簡單的Html 頁面發送一個表單來觀察它發出的POST 報文,表單中包含一個上傳的檔案和檔案描述的文本。

<!DOCTYPE HTML><html><head>    <meta charset="UTF-8" />    <title>檔案上傳</title></head>    <body>        <form method="post" enctype="multipart/form-data"  action="http://www.baidu.com/form">            <input type="file" name="file">            <input type="text" name="description"            <br />            <input type="reset" value="reset">            <input type="submit" value="submit">        </form>    </body></html>

 

在Chrom 上的報文格式如下:

   POST /form HTTP/1.1    Host: www.baidu.com    Connection: keep-alive    Content-Length: 2417    Cache-Control: max-age=0    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8    Origin: null    Upgrade-Insecure-Requests: 1    User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryM4LGQcTCCIBilnPT    Accept-Encoding: gzip, deflate    Accept-Language: zh-CN,zh;q=0.8    Cookie: BAIDUID=9A110F7F907AEAC501CD156DDE0EA380:FG=1    ------WebKitFormBoundaryM4LGQcTCCIBilnPT    Content-Disposition: form-data; name="file"; filename="close.png"    Content-Type: image/png    這裡包括了圖片的位元據    ------WebKitFormBoundaryM4LGQcTCCIBilnPT    Content-Disposition: form-data; name="description"     This is a image    ------WebKitFormBoundaryM4LGQcTCCIBilnPT--

 

HTTP報文由三個部分組成:對報文進行描述的起始行(start line),包含屬性的首部(header)塊,以及可選的、包含資料的主體(body)部分。

請求報文的起始行格式為<method> <request-URL> <version>

POST /form HTTP/1.1

method :為用戶端希望伺服器對資源進行的動作,一般為GET、POST、HEAD等。

請求URL:為資源的絕對路徑,這裡是表單Action決定的。

版本:保報文所使用的Http 版本,如1.1 ,1.0。

HTTP 首部塊

可以有零個或多個首部,每個首部都包含一個名字,後面跟著一個冒號( : ),然後是一個可選的空格,接著是一個值,最後是一個CRLF( /r/n )。首部是由一個空行(CRLF)結束的。表示了首部列表的結束和實體主體部分的開始。在自己構造報文時一定要注意加換行和空行,以免造成格式錯誤。在HTTP 1.1 中,要求有效請求或響應中必須包含特定的首部。請求首部如下:

    Host: www.baidu.com                       Connection: keep-alive    Content-Length: 2417    Cache-Control: max-age=0    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8    User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryM4LGQcTCCIBilnPT    Accept-Encoding: gzip, deflate    Accept-Language: zh-CN,zh;q=0.8

簡單解釋一下幾個首部:

Host:接收請求的伺服器位址

Connection:允許用戶端和伺服器指定與請求/響應串連有關的選項,Keep-alive 表示持久串連

Content-Length:實體主體的大小,這個在構造報文的時候一定要設定

CacheControl:控制緩衝的行為

Accept:使用者代理程式可處理的媒體類型

User-Agent:HTTP用戶端程式的資訊,瀏覽器的資訊

Content-Type:實體主體的媒體類型,表單中有檔案上傳應設定為multipart/form-data。boundary 很重要,這是一個識別檔案流的邊界,用來標識檔案開始和結尾的位置。

Accept-Encoding:是瀏覽器發給伺服器,聲明瀏覽器支援的編碼類別型

Accept-Language聲明瀏覽器支援的語言

HTTP 資料主體

這部分為HTTP要傳輸的內容。

開始的boundary 就是在Content-Type中設定的值,boundary用於作為請求參數之間的界限標識,在多個參數之間要有一個明確的界限,這樣伺服器才能正確的解析到參數。它有格式要求,開頭必須是--,不同的瀏覽器產生的boundary也不同,但前面都要有-- 。

Content-Disposition就是當使用者想把請求所得的內容存為一個檔案的時候提供一個預設的檔案名稱。

中間的就是我們傳輸的資料了。

最後還要加上一個boundary--,不要忘記最後的--。

這樣報文就構造結束了。

C# 中發送POST請求
  private void UploadRequest(string url, string filePath)        {            // 時間戳記,用做boundary            string timeStamp = DateTime.Now.Ticks.ToString("x");            //根據uri建立HttpWebRequest對象            HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create(new Uri(url));            httpReq.Method = "POST";            httpReq.AllowWriteStreamBuffering = false; //對發送的資料不使用緩衝            httpReq.Timeout = 300000;  //設定獲得響應的逾時時間(300秒)            httpReq.ContentType = "multipart/form-data; boundary=" + timeStamp;            //檔案            FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);            BinaryReader binaryReader = new BinaryReader(fileStream);            //頭資訊            string boundary = "--" + timeStamp;            string dataFormat = boundary + "\r\nContent-Disposition: form-data; name=\"{0}\";filename=\"{1}\"\r\nContent-Type:application/octet-stream\r\n\r\n";            string header = string.Format(dataFormat, "file", Path.GetFileName(filePath));            byte[] postHeaderBytes = Encoding.UTF8.GetBytes(header);            //結束邊界            byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + timeStamp + "--\r\n");            long length = fileStream.Length + postHeaderBytes.Length + boundaryBytes.Length;            httpReq.ContentLength = length;//請求內容長度            try            {                //每次上傳4k                int bufferLength = 4096;                byte[] buffer = new byte[bufferLength];                //已上傳的位元組數                long offset = 0;                int size = binaryReader.Read(buffer, 0, bufferLength);                Stream postStream = httpReq.GetRequestStream();                //發送要求標頭部訊息                postStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);                while (size > 0)                {                    postStream.Write(buffer, 0, size);                    offset += size;                    size = binaryReader.Read(buffer, 0, bufferLength);                }                //添加尾部邊界                postStream.Write(boundaryBytes, 0, boundaryBytes.Length);                postStream.Close();                                //擷取伺服器端的響應                using (HttpWebResponse response = (HttpWebResponse)httpReq.GetResponse())                {                    Stream receiveStream = response.GetResponseStream();                    StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);                    string returnValue = readStream.ReadToEnd();                    MessageBox.Show(returnValue);                    response.Close();                    readStream.Close();                }            }            catch (Exception ex)            {                Debug.WriteLine("檔案傳輸異常: "+ ex.Message);            }            finally            {                fileStream.Close();                binaryReader.Close();            }        }
Java 端接收請求
public Map saveCapture(HttpServletRequest request, HttpServletResponse response, Map config) throws Exception {        response.setContentType("text/html;charset=UTF-8");        // 讀取請求Body        byte[] body = readBody(request);        // 取得所有Body內容的字串表示        String textBody = new String(body, "ISO-8859-1");        // 取得上傳的檔案名稱        String fileName = getFileName(textBody);        // 取得檔案開始與結束位置        String contentType = request.getContentType();        String boundaryText = contentType.substring(contentType.lastIndexOf("=") + 1, contentType.length());        // 取得實際上傳檔案的氣勢與結束位置        int pos = textBody.indexOf("filename=\"");        pos = textBody.indexOf("\n", pos) + 1;        pos = textBody.indexOf("\n", pos) + 1;        pos = textBody.indexOf("\n", pos) + 1;        int boundaryLoc = textBody.indexOf(boundaryText, pos) - 4;        int begin = ((textBody.substring(0, pos)).getBytes("ISO-8859-1")).length;        int end = ((textBody.substring(0, boundaryLoc)).getBytes("ISO-8859-1")).length;        //儲存到本地        writeToDir(fileName,body,begin,end);        response.getWriter().println("Success!");        return config;    }   private byte[] readBody(HttpServletRequest request) throws IOException {        // 擷取請求文本位元組長度        int formDataLength = request.getContentLength();        // 取得ServletInputStream輸入資料流對象        DataInputStream dataStream = new DataInputStream(request.getInputStream());        byte body[] = new byte[formDataLength];        int totalBytes = 0;        while (totalBytes < formDataLength) {            int bytes = dataStream.read(body, totalBytes, formDataLength);            totalBytes += bytes;        }        return body;    }        private String getFileName(String requestBody) {        String fileName = requestBody.substring(requestBody.indexOf("filename=\"") + 10);        fileName = fileName.substring(0, fileName.indexOf("\n"));        fileName = fileName.substring(fileName.indexOf("\n") + 1, fileName.indexOf("\""));        return fileName;    }        private void writeToDir(String fileName, byte[] body, int begin, int end) throws IOException {        FileOutputStream fileOutputStream = new FileOutputStream("d:/" + fileName);        fileOutputStream.write(body, begin, (end - begin));        fileOutputStream.flush();        fileOutputStream.close();    }


在用request.getParameter()取值的時候,要注意傳過來的資料的MIME類型。

GET 方式提交的話,表單項都儲存Header中,格式是http://localhost:8080/form?key1=value1&key2=value2 這樣的字串。server端通過request.getParameter("key1")是可以取到值的。
POST 方式,如果為 enctype application/x-www-form-urlencoded,表單資料都儲存在HTTP的資料主體,格式類似於下面這樣:用request.getParameter()是可以取到資料的。

但是如果enctype 為 multipart/form-data,就和上面的方式一樣,表單資料儲存在HTTP的資料主體,各個資料項目之間用boundary隔開。用request.getParameter()是取不到資料的,這時需要通過request.getInputStream來操作流取資料,需要自己對取到的流進行解析,才能得到表單項以及上傳的檔案內容等資訊。

這種需求屬於比較共通的功能,所以有很多開源的組件可以直接利用。比 如:apache的fileupload 組件,smartupload等。通過這些開源的upload組件提供的API,就可以直接從request中取 得指定的表單項了。

在傳回值時,只能返回位元組流或者字元流,不能同時擷取response.getWriter()、response.getOutputStream()。

參考:

《HTTP 權威指南》

http://www.cnblogs.com/txw1958/archive/2013/01/11/csharp-HttpWebRequest-HttpWebResponse.html

http://www.cnblogs.com/loujady/archive/2011/09/13/2174695.html
http://my.oschina.net/Barudisshu/blog/150026?fromerr=aaqkzmRK

 

在C#用戶端用HTTP上傳檔案到Java伺服器

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.