前言
这段时间做C#客户端项目时,在网上找到一个用Http请求实现文件上传的方法,实测有效。
在讲解如何实现Http上传文件之前,不妨先了解一下Http上传文件报文是什么样的。相信看了报文结构,有利于了解代码的实现过程。
下图是我自己的程序上传文件时时,用wireshark抓取的包内容。
可以看到,这个Http报文结构其实并不复杂。
需要注意的是,这个报文中有一个boundary,这是一个识别文件流的边界,用来标识文件开始和结尾的位置。
接下来,我将一步步说明如何封装Http上传文件报文。
C#中用Http请求实现文件上传
封装Http上传文件报文大概有五个步骤。
1. 初始化HttpWebRequest(需要引用System.Net)
2. 封装Http cookie
3. 生成时间戳,并封装Http报文头
4. 封装请求内容,并将封装好的请求报文用Stream类写入流(需要引用System.IO)
5. 接收应答报文
接下来,我们一步一步来讲解如何在C#中完成Http请求报文的封装。
初始化HttpWebRequest
使用请求地址作为参数,初始化一个HttpWebRequest实例。
// 初始化HttpWebRequest
HttpWebRequest httpRequest = (HttpWebRequest)HttpWebRequest.Create(strRequestUri);
封装Http cookie
首先,简单说明一下Cookie。有时也用其复数形式Cookies,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。本文为了示例,就以比较简单的形式展现。
在C#中,使用Cookie类来封装cookie,以键值对的形式保存。
然后,将这个封装好的cookie添加到CookieContainer容器中,最后填入HttpWebRequest。既然是容器,顾名思义,可以添加多个cookie。
// 封装Cookie
Uri uri = new Uri(strRequestUri);
Cookie cookie = new Cookie("Name", strCookie); // 设置key、value形式的Cookie
CookieContainer cookies = new CookieContainer();
cookies.Add(uri, cookie);
httpRequest.CookieContainer = cookies;
封装Http Header
Http上传文件报文,需要设置一个边界,这个边界可以是任意的。
这里,我们不妨用一个时间戳来作为边界。记得要将这个边界在报文头标识出来,这样服务器收到这个请求后就能识别边界了。
以下是一个简单范例:
// 生成时间戳
string strBoundary = "----------" + DateTime.Now.Ticks.ToString("x");
byte[] boundaryBytes = Encoding.ASCII.GetBytes(string.Format("\r\n--{0}--\r\n", strBoundary));
// 填报文类型
httpRequest.Method = "Post";
httpRequest.Timeout = 1000 * 120;
httpRequest.ContentType = "multipart/form-data; boundary=" + strBoundary;
写文件流
说是写文件流,其实这里我用的方法是将整个报文的头部、文件内容、尾部统统以流的形式发出去。
首先是封装报文头
封装Http报文时有几个小细节
(1)\"是用转义符确保输出双引号
(2)由于Http报文的格式要求,请求头部的结束部分需要有回车符和换行符
(3)在发送文件流内容时,可能会出现文件太大,而超过报文允许的最大字节数这种情况。可以采用分包发送的方式将文件传输。
// 生成时间戳
string strBoundary = "----------" + DateTime.Now.Ticks.ToString("x");
byte[] boundaryBytes = Encoding.ASCII.GetBytes(string.Format("\r\n--{0}--\r\n", strBoundary));
// 填报文类型
httpRequest.Method = "Post";
httpRequest.Timeout = 1000 * 120;
httpRequest.ContentType = "multipart/form-data; boundary=" + strBoundary;
// 封装HTTP报文头的流
StringBuilder sb = new StringBuilder();
sb.Append("--");
sb.Append(strBoundary);
sb.Append(Environment.NewLine);
sb.Append("Content-Disposition: form-data; name=\"");
sb.Append("file");
sb.Append("\"; filename=\"");
sb.Append(filename);
sb.Append("\"");
sb.Append(Environment.NewLine);
sb.Append("Content-Type: ");
sb.Append("multipart/form-data;");
sb.Append(Environment.NewLine);
sb.Append(Environment.NewLine);
byte[] postHeaderBytes = Encoding.UTF8.GetBytes(sb.ToString());
// 计算报文长度
FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
long length = postHeaderBytes.Length + fileStream.Length + boundaryBytes.Length;
httpRequest.ContentLength = length;
fileStream.Close();
// 将报文头写入流
Stream requestStream = httpRequest.GetRequestStream();
requestStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);
// 将上传文件内容写入流
byte[] buffer = new Byte[checked((uint)Math.Min(4096, (int)fileStream.Length))];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
requestStream.Write(buffer, 0, bytesRead);
// 将报文尾部写入流
requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
// 关闭流
requestStream.Close();
fileStream.Close();
如果按照以上代码封装Http头部,打印出来的内容大概如下:
------------8d2c2be6186514f
Content-Disposition: form-data; name="file"; filename="Desert.jpg"
Content-Type: multipart/form-data;
}
获得应答报文
通过流将请求报文发出去后,可以通过HttpWebRequest的GetResponse()方法来获取HttpWebResponse。
// 获得应答报文
HttpWebResponse httpResponse = (HttpWebResponse)httpRequest.GetResponse();
Stream responseStream = httpResponse.GetResponseStream();
StreamReader reader = new StreamReader(responseStream, System.Text.Encoding.UTF8);
string strResponse = reader.ReadToEnd();
reader.Close();
responseStream.Close();
附上完整方法,供大家参考。
C#中利用Http上传文件的方式应该有很多,我找到的方法可能不是最好的,欢迎大家不吝赐教。
private string UploadFile(string strRequestUri, string strCookie, string filename)
{
// 初始化HttpWebRequest
HttpWebRequest httpRequest = (HttpWebRequest)HttpWebRequest.Create(strRequestUri);
// 封装Cookie
Uri uri = new Uri(strRequestUri);
Cookie cookie = new Cookie("Name", strCookie); // 设置key、value形式的Cookie
CookieContainer cookies = new CookieContainer();
cookies.Add(uri, cookie);
httpRequest.CookieContainer = cookies;
// 生成时间戳
string strBoundary = "----------" + DateTime.Now.Ticks.ToString("x");
byte[] boundaryBytes = Encoding.ASCII.GetBytes(string.Format("\r\n--{0}--\r\n", strBoundary));
// 填报文类型
httpRequest.Method = "Post";
httpRequest.Timeout = 1000 * 120;
httpRequest.ContentType = "multipart/form-data; boundary=" + strBoundary;
// 封装HTTP报文头的流
StringBuilder sb = new StringBuilder();
sb.Append("--");
sb.Append(strBoundary);
sb.Append(Environment.NewLine);
sb.Append("Content-Disposition: form-data; name=\"");
sb.Append("file");
sb.Append("\"; filename=\"");
sb.Append(filename);
sb.Append("\"");
sb.Append(Environment.NewLine);
sb.Append("Content-Type: ");
sb.Append("multipart/form-data;");
sb.Append(Environment.NewLine);
sb.Append(Environment.NewLine);
byte[] postHeaderBytes = Encoding.UTF8.GetBytes(sb.ToString());
// 计算报文长度
FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
long length = postHeaderBytes.Length + fileStream.Length + boundaryBytes.Length;
httpRequest.ContentLength = length;
fileStream.Close();
// 将报文头写入流
Stream requestStream = httpRequest.GetRequestStream();
requestStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);
// 将上传文件内容写入流
byte[] buffer = new Byte[checked((uint)Math.Min(4096, (int)fileStream.Length))];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
requestStream.Write(buffer, 0, bytesRead);
// 将报文尾部写入流
requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
// 关闭流
requestStream.Close();
fileStream.Close();
// 获得应答报文
HttpWebResponse httpResponse = (HttpWebResponse)httpRequest.GetResponse();
Stream responseStream = httpResponse.GetResponseStream();
StreamReader reader = new StreamReader(responseStream, Encoding.UTF8);
string strResponse = reader.ReadToEnd();
reader.Close();
responseStream.Close();
return strResponse;
}