原文地址:http://docode.top/Article/Detail/10002html
目錄:web
一、Http協議上傳文件(以圖片爲例)請求報文體內容格式c#
二、完整版HttpWebRequest模擬上傳文件請求報文內容封裝瀏覽器
三、asp.net(c#)使用HttpWebRequest攜帶請求參數模擬上傳文件封裝源碼下載服務器
首先,咱們來看下經過瀏覽器上傳文件的請求報文內容格式,這裏以本人本身寫的實例爲例,以下圖。除了能上傳圖片(即:頭像字段),還攜帶了用戶名、密碼兩個字段,很好的詮釋了http帶參數上傳文件的情形。點擊提交按鈕後,瀏覽器會將文件(即頭像文件)二進制數據和用戶名、密碼以post方式發送至服務器。這時咱們能夠經過抓包工具(如:fiddler)(或者瀏覽器自帶的開發者工具F12)查看請求報文內容。app
經過抓包工具獲取到攜帶參數上傳文件請求報文體內容格式以下:asp.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
POST /PostUploadHandler.ashx HTTP/1.1
Host: localhost:44187
Connection: keep-alive
Content-Length: 19839
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://localhost:44187
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryNSF3vGLxKBlk5kcB
Referer: http://localhost:44187/UploadDemo.aspx
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
------WebKitFormBoundaryNSF3vGLxKBlk5kcB
Content-Disposition: form-data; name="userName"
admin
------WebKitFormBoundaryNSF3vGLxKBlk5kcB
Content-Disposition: form-data; name="userPwd"
123456
------WebKitFormBoundaryNSF3vGLxKBlk5kcB
Content-Disposition: form-data; name="photo"; filename="1.png"
Content-Type: image/png
<!--這一行是文件二進制數據-->
------WebKitFormBoundaryNSF3vGLxKBlk5kcB--
|
一、請求頭中有一個Content-Type參數(默認值:application/x-www-form-urlencoded),其中multipart/form-data值表示向服務器發送二進制數據,boundary表示請求體的分界線,服務器就是依靠分界線分割請求體來讀取數據,此參數值可自定義。工具
二、請求體依靠boundary有規則的排列參數。每一行字符串後面包含一個換行符「\r\n」,有一個開始分界線(--boundary)和一個結束分界線(--boundary--),參數與參數之間經過--boundary分離,每個參數的鍵(key)和值(value)之間包含一個空行即:「\r\n"。post
經過上面介紹,咱們已經清楚瞭解了http協議上傳文件的POST請求報文內容格式,在.net中使用HttpWebRequest上傳文件,咱們只要按照此格式封裝請求報文,便可實現攜帶參數上傳功能了。編碼
爲了方便擴展和維護,把全部請求參數(如上傳地址url、攜帶參數、上傳文件流等)封裝到一個類中,代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
/// <summary>
/// 上傳文件 - 請求參數類
/// </summary>
public
class
UploadParameterType
{
public
UploadParameterType()
{
FileNameKey =
"fileName"
;
Encoding = Encoding.UTF8;
PostParameters =
new
Dictionary<
string
,
string
>();
}
/// <summary>
/// 上傳地址
/// </summary>
public
string
Url {
get
;
set
; }
/// <summary>
/// 文件名稱key
/// </summary>
public
string
FileNameKey {
get
;
set
; }
/// <summary>
/// 文件名稱value
/// </summary>
public
string
FileNameValue {
get
;
set
; }
/// <summary>
/// 編碼格式
/// </summary>
public
Encoding Encoding {
get
;
set
; }
/// <summary>
/// 上傳文件的流
/// </summary>
public
Stream UploadStream {
get
;
set
; }
/// <summary>
/// 上傳文件 攜帶的參數集合
/// </summary>
public
IDictionary<
string
,
string
> PostParameters {
get
;
set
; }
}
|
新建一個上傳文件工具類(命名爲:HttpUploadClient),在類中增長上傳方法(命名爲:Execute),以下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/// <summary>
/// Http上傳文件類 - HttpWebRequest封裝
/// </summary>
public
class
HttpUploadClient
{
/// <summary>
/// 上傳執行 方法
/// </summary>
/// <param name="parameter">上傳文件請求參數</param>
public
static
string
Execute(UploadParameterType parameter)
{
}
static
bool
CheckValidationResult(
object
sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return
true
;
}
}
|
Post上傳請求體參數是二進制格式的,咱們只須要將參數根據以上報文體內容格式拼接好數據,存放在內存流裏面,拼接完整後,將整個內存流轉換成二進制格式寫入到HttpWebRequest請求體中就行,下面咱們來一步一步的拼接報文體內容。
一、定義開始結束分界線boundary及拼接開始分界線:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
static
string
Execute(UploadParameterType parameter)
{
using
(MemoryStream memoryStream =
new
MemoryStream())
{
// 1.分界線
string
boundary =
string
.Format(
"----{0}"
, DateTime.Now.Ticks.ToString(
"x"
)),
// 分界線能夠自定義參數
beginBoundary =
string
.Format(
"--{0}\r\n"
, boundary),
endBoundary =
string
.Format(
"\r\n--{0}--\r\n"
, boundary);
byte
[] beginBoundaryBytes = parameter.Encoding.GetBytes(beginBoundary),
endBoundaryBytes = parameter.Encoding.GetBytes(endBoundary);
// 2.組裝開始分界線數據體 到內存流中
memoryStream.Write(beginBoundaryBytes, 0, beginBoundaryBytes.Length);
// ……
}
}
|
二、拼接附加攜帶參數:
1
2
3
4
5
6
7
8
9
10
11
|
// 3.組裝 上傳文件附加攜帶的參數 到內存流中
if
(parameter.PostParameters !=
null
&& parameter.PostParameters.Count > 0)
{
foreach
(KeyValuePair<
string
,
string
> keyValuePair
in
parameter.PostParameters)
{
string
parameterHeaderTemplate =
string
.Format(
"Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}\r\n{2}"
, keyValuePair.Key, keyValuePair.Value, beginBoundary);
byte
[] parameterHeaderBytes = parameter.Encoding.GetBytes(parameterHeaderTemplate);
memoryStream.Write(parameterHeaderBytes, 0, parameterHeaderBytes.Length);
}
}
|
三、拼接上傳文件體及結束分界線boundary(須要注意的是Content-Type的值是:application/octet-stream):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 4.組裝文件頭數據體 到內存流中
string
fileHeaderTemplate =
string
.Format(
"Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n"
, parameter.FileNameKey, parameter.FileNameValue);
byte
[] fileHeaderBytes = parameter.Encoding.GetBytes(fileHeaderTemplate);
memoryStream.Write(fileHeaderBytes, 0, fileHeaderBytes.Length);
// 5.組裝文件流 到內存流中
byte
[] buffer =
new
byte
[1024 * 1024 * 1];
int
size = parameter.UploadStream.Read(buffer, 0, buffer.Length);
while
(size > 0)
{
memoryStream.Write(buffer, 0, size);
size = parameter.UploadStream.Read(buffer, 0, buffer.Length);
}
// 6.組裝結束分界線數據體 到內存流中
memoryStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
|
四、經過以上步驟,上傳文件請求體內容數據已經拼接完成,接下來就是對HttpWebRequest對象的屬性設置(如:請求地址Url,請求方法Method,Content-Type等),把整個上傳文件請求體內存流寫入到HttpWebRequest對象的請求體中,而後發起上傳請求。以下源碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// 7.獲取二進制數據
byte
[] postBytes = memoryStream.ToArray();
// 8.HttpWebRequest 組裝
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(
new
Uri(parameter.Url, UriKind.RelativeOrAbsolute));
webRequest.Method =
"POST"
;
webRequest.Timeout = 10000;
webRequest.ContentType =
string
.Format(
"multipart/form-data; boundary={0}"
, boundary);
webRequest.ContentLength = postBytes.Length;
if
(Regex.IsMatch(parameter.Url,
"^https://"
))
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult;
}
// 9.寫入上傳請求數據
using
(Stream requestStream = webRequest.GetRequestStream())
{
requestStream.Write(postBytes, 0, postBytes.Length);
requestStream.Close();
}
// 10.獲取響應
using
(HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
{
using
(StreamReader reader =
new
StreamReader(webResponse.GetResponseStream(), parameter.Encoding))
{
string
body = reader.ReadToEnd();
reader.Close();
return
body;
}
}
|
完整版HttpWebRequest模擬上傳文件代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
/// <summary>
/// Http上傳文件類 - HttpWebRequest封裝
/// </summary>
public
class
HttpUploadClient
{
/// <summary>
/// 上傳執行 方法
/// </summary>
/// <param name="parameter">上傳文件請求參數</param>
public
static
string
Execute(UploadParameterType parameter)
{
using
(MemoryStream memoryStream =
new
MemoryStream())
{
// 1.分界線
string
boundary =
string
.Format(
"----{0}"
, DateTime.Now.Ticks.ToString(
"x"
)),
// 分界線能夠自定義參數
beginBoundary =
string
.Format(
"--{0}\r\n"
, boundary),
endBoundary =
string
.Format(
"\r\n--{0}--\r\n"
, boundary);
byte
[] beginBoundaryBytes = parameter.Encoding.GetBytes(beginBoundary),
endBoundaryBytes = parameter.Encoding.GetBytes(endBoundary);
// 2.組裝開始分界線數據體 到內存流中
memoryStream.Write(beginBoundaryBytes, 0, beginBoundaryBytes.Length);
// 3.組裝 上傳文件附加攜帶的參數 到內存流中
if
(parameter.PostParameters !=
null
&& parameter.PostParameters.Count > 0)
{
foreach
(KeyValuePair<
string
,
string
> keyValuePair
in
parameter.PostParameters)
{
string
parameterHeaderTemplate =
string
.Format(
"Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}\r\n{2}"
, keyValuePair.Key, keyValuePair.Value, beginBoundary);
byte
[] parameterHeaderBytes = parameter.Encoding.GetBytes(parameterHeaderTemplate);
memoryStream.Write(parameterHeaderBytes, 0, parameterHeaderBytes.Length);
}
}
// 4.組裝文件頭數據體 到內存流中
string
fileHeaderTemplate =
string
.Format(
"Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n"
, parameter.FileNameKey, parameter.FileNameValue);
byte
[] fileHeaderBytes = parameter.Encoding.GetBytes(fileHeaderTemplate);
memoryStream.Write(fileHeaderBytes, 0, fileHeaderBytes.Length);
// 5.組裝文件流 到內存流中
byte
[] buffer =
new
byte
[1024 * 1024 * 1];
int
size = parameter.UploadStream.Read(buffer, 0, buffer.Length);
while
(size > 0)
{
memoryStream.Write(buffer, 0, size);
size = parameter.UploadStream.Read(buffer, 0, buffer.Length);
}
// 6.組裝結束分界線數據體 到內存流中
memoryStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
// 7.獲取二進制數據
byte
[] postBytes = memoryStream.ToArray();
// 8.HttpWebRequest 組裝
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(
new
Uri(parameter.Url, UriKind.RelativeOrAbsolute));
webRequest.Method =
"POST"
;
webRequest.Timeout = 10000;
webRequest.ContentType =
string
.Format(
"multipart/form-data; boundary={0}"
, boundary);
webRequest.ContentLength = postBytes.Length;
if
(Regex.IsMatch(parameter.Url,
"^https://"
))
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult;
}
// 9.寫入上傳請求數據
using
(Stream requestStream = webRequest.GetRequestStream())
{
requestStream.Write(postBytes, 0, postBytes.Length);
requestStream.Close();
}
// 10.獲取響應
using
(HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
{
using
(StreamReader reader =
new
StreamReader(webResponse.GetResponseStream(), parameter.Encoding))
{
string
body = reader.ReadToEnd();
reader.Close();
return
body;
}
}
}
}
static
bool
CheckValidationResult(
object
sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return
true
;
}
}
|
爲了驗證封裝是否正確,能夠寫一個控制檯應用程序來模擬Http協議上傳文件(以圖片爲例),結果如圖: