由于网站的升级, 为了解决大文件上传的问题。虽然网站上暂时没有用到,不过自己先记录下来,说不定日后有用呢。
一:原理介绍,NET大文件上传知识整理http://study.pay500.com/4/s43100.htm
二:上传数据分析。附一个上传的实例(注:文件内容为<pear:5894025>)。
"-----------------------------7d71a819230404/r/n
Content-Disposition: form-data; name=/"__VIEWSTATE/"/r/n/r/n
/wEPDwUKLTQwMjY2MDA0Mw9kFgICAw8WAh4HZW5jdHlwZQUTbXVsdGlwYXJ0L2Zvcm0tZGF0YWRkVfrcYy9Kk0z3vFO/UyMyiBzdnKs=
/r/n-----------------------------7d71a819230404/r/n
Content-Disposition: form-data; name=/"FiLeUpload1/";
filename=/"C://Documents and Settings//daihanzhang//桌面//pearPwd.txt/"/r/n
Content-Type: text/plain/r/n/r/n
pear:5894025
/r/n-----------------------------7d71a819230404/r/n
Content-Disposition: form-data; name=/"btnOK/"/r/n/r/n
上传
/r/n-----------------------------7d71a819230404/r/n
Content-Disposition: form-data; name=/"__EVENTVALIDATION/"/r/n/r/n
/wEWAgLthcn/AwLdkpmPAeXrK9GAkyaq5/M9HdwTChtx+2VJ
/r/n-----------------------------7d71a819230404--/r/n"
三:结合RTF1867协议的说明,可以知道实体分隔标识符串为:---------------------------7d71a819230404,和实体描述与取值的分隔符为:/r/n/r/n ,这样就可以知道从哪个地方可以提取要上传的文件的内容。
四:提取完毕以后,要模拟一个提交的内容,以实现替换。在提取的过程当中要注意一个问题就是:分隔符跨边界的问题,这是由于分段读取造成的。
五:下面是实现的源码。
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Reflection;
using System.Text;
using System.IO;
/// <summary>
/// Summary description for AspxFileUpLoadModule
/// </summary>
public class AspxFileUpLoadModule : IHttpModule
{
public AspxFileUpLoadModule()
{
//
// TODO: Add constructor logic here
//
}
public static int TransactData(byte[] readData, byte[] m_Boundary)
{
return TransactData(readData, m_Boundary, 0);
}
//返回 的是结束时的位置
public static int TransactData(byte[] readData, byte[] m_Boundary, int iStartIndex)
{
int iBodudaryIndex = 0;
int iBoundaryLen = m_Boundary.Length;
int iIndex = iStartIndex;
int iLen = readData.Length;
bool bEnd = false;
while (iIndex < iLen && (bEnd == false))
{
if (m_Boundary[iBodudaryIndex] == readData[iIndex])
{
iBodudaryIndex++;
}
else
{
iBodudaryIndex = 0;
}
if (iBodudaryIndex == iBoundaryLen)
{
bEnd = true;
}
iIndex++;
}
if (bEnd)
{
return iIndex;
}
else
{
return -1;
}
}
#region IHttpModule Members
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
#endregion
void context_BeginRequest(object sender, EventArgs e)
{
#region 初始化,决定是否要求处理
HttpApplication m_application = sender as HttpApplication;
IServiceProvider m_provider = HttpContext.Current;
HttpWorkerRequest m_workRequest = (HttpWorkerRequest)m_provider.GetService(typeof(HttpWorkerRequest));
string m_contentType = m_application.Request.ContentType;
if (!m_contentType.StartsWith("multipart/form-data", StringComparison.OrdinalIgnoreCase))
return;
if (!m_workRequest.HasEntityBody())
return;
#endregion
#region 获取分隔符
string m_boundaryStr = m_contentType.Substring(m_contentType.IndexOf("boundary=", 0, StringComparison.OrdinalIgnoreCase) + 9);
byte[] m_boundaryData = Encoding.ASCII.GetBytes("/r/n--" + m_boundaryStr);
#endregion
byte[] m_TmpData = Encoding.ASCII.GetBytes("name=/"FileUpload1/"");
byte[] m_Sepeatar = Encoding.ASCII.GetBytes("/r/n/r/n");
byte[] readData = m_workRequest.GetPreloadedEntityBody();
int m_PreLen = readData.Length;
MemoryStream cxp = new MemoryStream();//当前已读取内容的
cxp.Write(readData, 0, m_PreLen);
cn.daihanzhang.Service s = new cn.daihanzhang.Service();
int iTask = Guid.NewGuid().GetHashCode();
int iOrder = 1;
if (!m_workRequest.IsEntireEntityBodyIsPreloaded())
{
#region 初始化
bool fileContentReadOk = false;
byte[] buffer = new byte[655350];
int iSize = 655350;
int readSize = m_workRequest.ReadEntityBody(buffer, iSize);
#endregion
#region 处理数据节
//此时readSize=0的话与进入时的逻辑判断冲突,没加异常处理。
while (readSize != 0)
{
cxp.Write(buffer, 0, readSize);
readData = cxp.ToArray();
#region 继续读取
if (fileContentReadOk)
{
//name=FileUpload1的文件实体值已经加载完毕
readSize = m_workRequest.ReadEntityBody(buffer, iSize);
continue;
}
//name=FileUpload的文件头是否已经全部读取
int m_TmpStart = TransactData(readData, m_TmpData);
if (m_TmpStart == -1)
{
//继续读取
readSize = m_workRequest.ReadEntityBody(buffer, iSize);
continue;
}
//找到name=FileUpload的实体值的起始位置
int m_TmpEnd = TransactData(readData, m_Sepeatar, m_TmpStart);
if (m_TmpEnd == -1)
{
readSize = m_workRequest.ReadEntityBody(buffer, iSize);
continue;
}
#endregion
#region 读取文件内容并实体值中移除
int m_ValueLoadAll = TransactData(readData, m_boundaryData, m_TmpEnd);
if (m_ValueLoadAll == -1)
{
//为了检测“分隔符是否存在跨Entity的现象
readSize = m_workRequest.ReadEntityBody(buffer, iSize);
if (readSize == 0)
{
throw new System.Exception("非法的RFC数据实体定义!");
}
int len = m_boundaryData.Length;
int mBoundaryLen = readSize > len ? len : readSize;
int ks = readData.Length - m_TmpEnd + mBoundaryLen;
//在/r/n/r/n之后的内空
byte[] suffix = new byte[ks];
Array.Copy(readData, m_TmpEnd, suffix, 0, m_TmpEnd);
Array.Copy(buffer, 0, suffix, m_TmpEnd, mBoundaryLen);
int hasBoundary = TransactData(suffix, m_boundaryData);
if (hasBoundary == -1)
{
//不跨边界
byte[] fileContent = new byte[readData.Length - m_TmpEnd];
Array.Copy(readData, m_TmpEnd, fileContent, 0, fileContent.Length);
//上传文件片断
s.UpLoadFileChunkSize(iTask, iOrder, fileContent);
iOrder++;
//
cxp.Close();
cxp = new MemoryStream();
cxp.Write(readData, 0, m_TmpEnd);
}
}
else
{
fileContentReadOk = true;
int len = m_ValueLoadAll - m_TmpEnd - m_boundaryData.Length;
byte[] cn = new byte[len];
Array.Copy(readData, m_TmpEnd, cn, 0, len);
//上传最后一个文件段
s.UpLoadFileChunkSize(iTask, iOrder, cn);
MemoryStream ms2 = new MemoryStream();
//拷贝前缀符
ms2.Write(readData, 0, m_TmpEnd);
//拷贝后面的字符。
int startIndex = m_TmpEnd + len;
ms2.Write(readData, startIndex, readData.Length - startIndex);
cxp.Close();
byte[] tmp = ms2.ToArray();
cxp = new MemoryStream();
cxp.Write(tmp, 0, tmp.Length);
readSize = m_workRequest.ReadEntityBody(buffer, iSize);
}
#endregion
}
#endregion
}
else
{
//没加xxx==-1的判断,最好是加上。
//查找name="FileUpload1"的起始位置
int m_TmpStart = TransactData(readData, m_TmpData);
//实体值的起始位置
int m_TmpEnd = TransactData(readData, m_Sepeatar, m_TmpStart);
//实体值的结束位置
int m_ValueEnd = TransactData(readData, m_boundaryData, m_TmpEnd);
int len = m_ValueEnd - m_TmpEnd - m_boundaryData.Length;
byte[] cn = new byte[len];
Array.Copy(readData, m_TmpEnd, cn, 0, len);
//上传
s.UpLoadFileChunkSize(iTask, 1, cn);
MemoryStream ms2 = new MemoryStream();
//拷贝前缀符
ms2.Write(readData, 0, m_TmpEnd);
//拷贝后面的字符。
int startIndex = m_TmpEnd + len;
ms2.Write(readData, startIndex, readData.Length - startIndex);
cxp.Close();
byte[] tmp = ms2.ToArray();
cxp = new MemoryStream();
cxp.Write(tmp, 0, tmp.Length);
}
//为了查看移除文件的内容。
// string context = m_application.Request.ContentEncoding.GetString(cxp.ToArray());
this.AddTextPartToRequest(m_workRequest, cxp.ToArray ());
}
private byte[] AddTextPartToRequest(HttpWorkerRequest m_request, byte[] m_textData)
{
Type m_type;
BindingFlags m_flags = (BindingFlags.NonPublic | BindingFlags.Instance);
//Is there application host IIS6.0?
if (HttpContext.Current.Request.ServerVariables["SERVER_SOFTWARE"].Equals("Microsoft-IIS/6.0"))
{
m_type = m_request.GetType().BaseType.BaseType;
}
else
{
m_type = m_request.GetType().BaseType;
}
//Set values of working request
m_type.GetField("_contentAvailLength", m_flags).SetValue(m_request, m_textData.Length);
m_type.GetField("_contentTotalLength", m_flags).SetValue(m_request, m_textData.Length);
m_type.GetField("_preloadedContent", m_flags).SetValue(m_request, m_textData);
m_type.GetField("_preloadedContentRead", m_flags).SetValue(m_request, true);
return m_textData;
}
}
六:WebService 的服务接口的定义。
[WebMethod]
public string UpLoadFileChunkSize(int iTaskID,int order,byte[] data)
{
string cc = string.Format("~/CX/{0}/{0}.jpg", iTaskID,order);
string path = HttpContext.Current.Server.MapPath(cc);
if(Directory.Exists(Path.GetDirectoryName(path))==false )
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
FileStream fs=new FileStream(path ,FileMode.Append ,FileAccess.Write);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(data);
bw.Flush();
bw.Close();
fs.Close();
return cc;
}
七:该代码已经测试过,仅测试过JPG文件。TXT文件也测试过,不过好像与编码有关系。没仔细检查。
八:关于记录上传进度过的问题。
我想如果每次在上传一个文件片断后就将上传记录信息数据记录在一个特定的会话变量或是它的变量之中。客户端通过JS去访问当前上传的记录信息。就可以做到实时报告进度了。不过,具体的实现尚未完成。
附记:由于本代码参考了WebbUploadFile组件的实现,特此感谢!