利用HttpWebRequest上传文件应注意的问题
最近,在做一个Online Video的公司内部系统时,遇到一个利用HttpWebRequest上传文件时的问题,现象如下:
1、上传30M以下的文件速度很快,大概2-3m。
2、上传90-100M的文件速度在3-5m。
3、上传30M-90M这个区间的任意大小的文件都失败,用时100-101m,错误描述:The operation is time out。
4、将同样的文件用相同的程序,放在另外一台机器上work一切正常。
这个错误是framwork底层抛出来的,若根据这个错误描述查找问题是无法真正解决问题的。
首先,我将代码show出来,大家看看有没有问题:
下面,我们来分析上述的现象,发现根本无法找到问题的根本,一头雾水。首先,可以肯定失败的原因是和文件的大小有关的,但是,发生错误的文件大小却是在一个区间段里的,这就无法解释了。然后,我们发现time out的时间都是刚刚超出100m,具体在100-101m之内;这个时间是在需要发送的Request里设的:
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uploadURL);
....
webRequest.Timeout = 1000;//默认为100m,单位为毫秒
那我们就决定将这个时间设的更长一些,但是,根本没用。其实,我们能基本肯定问题不是在这里,因为在上传90M这么大的文件都能很轻易成功,根据这点我们就有理由怀疑问题不是在这里。但是,这种莫名的问题让我们无法理解,接着,我们就像没头苍蝇一样,将所有可以设置time out的地方都设了,比如IIS,接受上传的WebSite里的config里,结果,大家可想而知。
在代码层面我们已经找不到任何办法了,然后,我们只能怀疑是这台服务器的环境有问题,就把问题抛给了公司的IT。可怜了这些IT,怎么可能找到问题!
后来,美国的同事与微软的工作人员一起联调,终于发现了问题的所在,是我们在发送Request的时候没有精确的指定这个Request的ContentLength,导致Framework底层无法知道你什么时候需要终止这个Request,我们只要在上面的代码中增加这段话:
...
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.ContentLength = postedStream.Length;
...
经过测试,发现果然是这个原因。但是,我们就有个问题,微软为什么不在底层处理好这些呢?我们认为微软在底层更能拿到我们的Request的长度,而且更容易包装,更利于使用。
1、上传30M以下的文件速度很快,大概2-3m。
2、上传90-100M的文件速度在3-5m。
3、上传30M-90M这个区间的任意大小的文件都失败,用时100-101m,错误描述:The operation is time out。
4、将同样的文件用相同的程序,放在另外一台机器上work一切正常。
这个错误是framwork底层抛出来的,若根据这个错误描述查找问题是无法真正解决问题的。
首先,我将代码show出来,大家看看有没有问题:
1private static void UploadFile(Stream postedStream, string fileName,
2 string uploadURL, string fileGroup, string fileType,
3 string specialPath, string userName, string uploadType)
4 {
5 if (string.IsNullOrEmpty(uploadURL))
6 throw new Exception("Upload Web URL Is Empty.");
7
8 HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uploadURL);
9
10 //Our method is post, otherwise the buffer (postvars) would be useless
11 webRequest.Method = WebRequestMethods.Http.Post;
12
13 // Proxy setting
14 WebProxy proxy = new WebProxy();
15 proxy.UseDefaultCredentials = true;
16 webRequest.Proxy = proxy;
17
18 //We use form contentType, for the postvars.
19 webRequest.ContentType = "application/x-www-form-urlencoded";
20
21 webRequest.Headers.Add("FileGroup", fileGroup);
22 webRequest.Headers.Add("FileType", fileType);
23 webRequest.Headers.Add("UploadType", uploadType);
24 webRequest.Headers.Add("FileName", fileName);
25 webRequest.Headers.Add("UserName", userName);
26 webRequest.Headers.Add("SpecialFolderPath", specialPath);
27
28 using (Stream requestStream = webRequest.GetRequestStream())
29 {
30 FileUtility.CopyStream(postedStream, requestStream);
31 }
32 try
33 {
34 webRequest.GetResponse().Close();
35 }
36 catch (WebException ex)
37 {
38 HttpWebResponse response = ex.Response as HttpWebResponse;
39 if (response != null)
40 {
41 throw new WebException(response.StatusDescription, ex);
42 }
43
44 throw ex;
45 }
46 catch (Exception exception)
47 {
48 throw exception;
49 }
50 }
2 string uploadURL, string fileGroup, string fileType,
3 string specialPath, string userName, string uploadType)
4 {
5 if (string.IsNullOrEmpty(uploadURL))
6 throw new Exception("Upload Web URL Is Empty.");
7
8 HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uploadURL);
9
10 //Our method is post, otherwise the buffer (postvars) would be useless
11 webRequest.Method = WebRequestMethods.Http.Post;
12
13 // Proxy setting
14 WebProxy proxy = new WebProxy();
15 proxy.UseDefaultCredentials = true;
16 webRequest.Proxy = proxy;
17
18 //We use form contentType, for the postvars.
19 webRequest.ContentType = "application/x-www-form-urlencoded";
20
21 webRequest.Headers.Add("FileGroup", fileGroup);
22 webRequest.Headers.Add("FileType", fileType);
23 webRequest.Headers.Add("UploadType", uploadType);
24 webRequest.Headers.Add("FileName", fileName);
25 webRequest.Headers.Add("UserName", userName);
26 webRequest.Headers.Add("SpecialFolderPath", specialPath);
27
28 using (Stream requestStream = webRequest.GetRequestStream())
29 {
30 FileUtility.CopyStream(postedStream, requestStream);
31 }
32 try
33 {
34 webRequest.GetResponse().Close();
35 }
36 catch (WebException ex)
37 {
38 HttpWebResponse response = ex.Response as HttpWebResponse;
39 if (response != null)
40 {
41 throw new WebException(response.StatusDescription, ex);
42 }
43
44 throw ex;
45 }
46 catch (Exception exception)
47 {
48 throw exception;
49 }
50 }
下面,我们来分析上述的现象,发现根本无法找到问题的根本,一头雾水。首先,可以肯定失败的原因是和文件的大小有关的,但是,发生错误的文件大小却是在一个区间段里的,这就无法解释了。然后,我们发现time out的时间都是刚刚超出100m,具体在100-101m之内;这个时间是在需要发送的Request里设的:
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uploadURL);
....
webRequest.Timeout = 1000;//默认为100m,单位为毫秒
那我们就决定将这个时间设的更长一些,但是,根本没用。其实,我们能基本肯定问题不是在这里,因为在上传90M这么大的文件都能很轻易成功,根据这点我们就有理由怀疑问题不是在这里。但是,这种莫名的问题让我们无法理解,接着,我们就像没头苍蝇一样,将所有可以设置time out的地方都设了,比如IIS,接受上传的WebSite里的config里,结果,大家可想而知。
在代码层面我们已经找不到任何办法了,然后,我们只能怀疑是这台服务器的环境有问题,就把问题抛给了公司的IT。可怜了这些IT,怎么可能找到问题!
后来,美国的同事与微软的工作人员一起联调,终于发现了问题的所在,是我们在发送Request的时候没有精确的指定这个Request的ContentLength,导致Framework底层无法知道你什么时候需要终止这个Request,我们只要在上面的代码中增加这段话:
...
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.ContentLength = postedStream.Length;
...
经过测试,发现果然是这个原因。但是,我们就有个问题,微软为什么不在底层处理好这些呢?我们认为微软在底层更能拿到我们的Request的长度,而且更容易包装,更利于使用。