ISAPI 与 ASP 在 Web 应用程序中的文件上传






4.71/5 (23投票s)
2002 年 6 月 30 日
11分钟阅读

296227

4947
本文介绍了在 Web 服务器上上传图片和文件的两种方法,以及两者的优缺点。
图。 ASP/ISAPI 上传时间图表。
描述
本文介绍了两种将图片和文件上传到 Web 服务器的方法,以及两者的优缺点。我的贡献基于对不同方法的比较。我没有发明任何微软上传技术:)
本文基于我同事 Doru Paraschiv 提供的一个 ASP 技术和我从 MSDN 上看到的一篇非常好的文章。特别感谢 Panos Kougiouris,他帮助我揭示了 ISAPI 二进制流技术。请查看 创建 DLL 以支持 IIS 基于 HTTP 的文件上传。
从浏览器上传文件通常是网站的一项要求。例如,假设您为一家房地产公司创建了一个网站。该网站包含房源列表以及每处房源的图片。房地产经纪人可以通过网站的管理部分添加新的房源。使用 HTML 表单添加新房源的文本很容易。然后有一天,一位房地产经纪人告诉您,她想将房源照片上传到您的网站。您将如何使用 ASP 和 ISAPI 来实现这一目标?
如何使用
这两种方法使用相同的函数结构。
首先,用户必须填写 PostFile.asp 表单中的字段。JavaScript 函数 ValidateForm
用于验证所需字段的内容。在 Upload.asp 文件中,我们调用 Upload
函数进行上传,然后调用 SaveFile
函数将文件写入磁盘。ASP 上传函数托管在 _incUpload.asp 文件中。
图。上传表单的图像。
其次,用户必须在 ReceiveRedirect.asp 文件中接收 Upload.asp(ASP 情况)或 UploadExt.dll(ISAPI 情况)的结果。也可以在 Upload.asp 或 UploadExt.dll 中完成执行,但在大多数情况下,需要重定向才能捕获表单的所有参数,以便将文件写入另一个服务器。
ISAPI 表单使用两个可选的隐藏参数来告诉 DLL 存储上传文件的目录(如果未指定,则脚本使用 c:\temp\)以及 ASP 重定向页面的名称(如果用户需要)。
上传目录的默认值(默认值相对于根站点目录 \Data\)在表单 sPathData
中以 ASP 变量的形式提供(在 _include.asp 中定义)。
图。接收 ASP 页面的图像。
您的 IIS 匿名用户帐户,通常是 IUSR_machinename
,必须对您要保存文件的目录具有写入访问权限。为了执行 ISAPI 扩展,UploadExt.dll 文件必须在 IIS 控制台管理器中具有“脚本和可执行文件”的执行权限。
ASP 上传详细信息
来自 MSDN:旧版本 Active Server Pages (ASP) 最明显的遗漏之一是处理 HTML 表单上传文件的能力。虽然 ASP
Request
对象一直允许轻松访问所有其他类型的表单字段,但纯 ASP 无法访问已上传的文件。通常的解决方案是使用 Posting Acceptor 或第三方组件来实现文件上传支持。在本文中,我们将向您展示如何轻松绕过此限制,并从发布到 ASP 文件的代码中检索一个或多个文件。
上传文件
此过程的第一个要求是拥有一个实际将文件上传(或“发布”)到您的 ASP 页面的 HTML 表单。
保存文件的 ASP 脚本开始时,会为服务器提供充足的时间来处理上传到它的任何文件。这是一个非常重要的考虑因素,因为处理大文件可能需要很长时间。如果脚本在进程完成之前超时,则上传的文件将丢失!接下来,脚本设置一些必要的常量并定义几个函数。我们稍后会回到这些函数,但现在,让我们继续关注脚本的主要部分,看看这个过程是如何工作的。
捕获 POST
脚本首先执行的操作是检索从 HTML 文件发布的所有数据,并将其放入名为 biData
的变量中。这是使用 Request
对象的 BinaryRead
方法完成的。顾名思义,此方法以原始二进制形式读取发布请求中的指定字节数。要读取的实际字节数由 Request
对象的 TotalBytes
属性提供。
您必须将所有这些二进制数据转换为更友好的格式。这是通过一个简单的循环完成的,该循环遍历提取的二进制数据,并使用一系列对某些二进制数据操作函数的调用,将数据转换为易于阅读的格式,并将其放入 PostData
变量中。
分割原始数据
一旦我们将发布的数据转换为 ASCII 格式,就可以相对简单地对其进行处理并从中提取每个表单字段。第一步是确定我们的数据是否已正确编码。之后,我们可以确定每个表单元素之间的边界。
令人惊讶的是,这两个信息都可以在一个方便的地方找到:HTTP_CONTENT_TYPE
头。 (是的,在调用 BinaryRead
之后您仍然可以访问标头。您只是无法访问 Form
或 QueryString
集合。) 如您在此处所见,此信息的结构非常简单。
-----------------------------7d22151d40264
鉴于此简单结构,提取我们需要的信息很容易。首先,我们使用 Split
函数围绕分号分割信息。这使我们可以通过检查调用 Split
创建的数组的第一个元素来轻松检查编码类型。假设编码类型是我们期望的(“multipart/form-data”),然后我们使用另一个 Split
调用提取边界。
每个表单数据块本身又分为两个块。第一个是信息块,它告诉有关表单字段的所有信息(即,其名称和任何相关信息)。第二个块是传输的实际表单数据。
Content-Disposition: form-data; name="RedirectPage"
ReceiveRedirect.asp
-----------------------------7d22151d40264
Content-Disposition: form-data; name="Name"
Adrian Bacaianu
-----------------------------7d22151d40264
Content-Disposition: form-data; name="Upload"
Submit Query
-----------------------------7d22151d40264--
正如您在此列表所示,边界信息用于分隔每个表单字段及其数据。不幸的是,表单字段的信息块与数据块之间没有明显的分隔符。嗯,实际上,它是明显的,但我们已经习惯了忽略这个特定的分隔符,以至于很难注意到。基本上,两个回车符和换行符对分隔信息块和数据块。这些块之间总共有四个字节。
拆分表单数据
现在,如果我们可以将所有这些表单数据提取到一个方便的集合中,就像 Request.Form
集合一样,那将是很好的。因此,考虑到这一点,我们将定义一个名为 myRequest
的集合和一个名为 myRequestFiles
的数组。虽然使用集合来存储所有上传的文件会很好,但集合无法轻松跟踪正确处理文件所需的所有信息。例如,对于单个文件,跟踪发布的字段名称、传输的实际文件内容、文件的文件/路径名(即它在客户端磁盘上的位置)以及文件的 MIME 类型非常方便。一个简单的二维数组是解决此问题的最简单方法。
此时,只需循环遍历在 postData
沿边界拆分时提取的所有表单字段即可。在检查每个字段时,使用简单的 Mid
函数调用来提取信息块和数据块。请注意,我们在这里不使用 Split
。这是因为数据本身可能包含双回车符和换行符对。
提取这些块后,(通过 InStr
函数)检查信息块以查看它是否包含字符串“filename=”。如果是这样,该字段将被标记为已上传文件。在这种情况下,我们调用几个函数来提取字段名称、文件名和文件的 MIME 类型。然后,这些信息与文件内容一起添加到已上传文件的数组中。
如果该字段不是文件,则使用 GetFieldName
函数提取其名称。然后将其添加到 myRequest
集合中。
就是这样!循环完成后,myRequest
集合将包含我们所有的“普通”表单字段,而 myRequestFiles
数组将包含最多 10 个已上传的文件。是的,这确实适用于多个上传的文件!如果您需要超过 10 个,只需在创建 myRequestFiles
数组时更改相应的维度。
保存文件
此时,剩下要做的就是实际将上传的文件(或文件)保存在服务器上。请注意,其他表单字段的内容(通过 myRequest
集合)被用于确定用于保存文件的文件名。需要注意的是,为了使保存操作正常工作,*您的 IIS 匿名用户帐户,通常是*IUSR_machinename
*,必须对您要保存文件的目录具有写入访问权限!*。
结论
嗯,这真的不难,不是吗?当然,这种方法存在一些缺点:大文件需要很长时间来处理,并且二进制文件可能难以正确处理。但是,一旦您掌握了基础知识(现在您已经掌握了!),就可以相对直接地适应此技术来处理更多棘手的文件。而且,最重要的是,您不必购买和安装第三方对象或使用备受诟病的发布接受器!
ISAPI 上传详细信息
在 MSDN 中,Panos 已经对此进行了很好的解释
“称为 multipart/form-data 的符合 MIME 的内容类型使得编写上传文件的 HTML 几乎微不足道。但在服务器端,ASP 无法访问 multipart/form-data 格式的数据。访问已上传文件的最灵活的方法是通过 C++ ISAPI 扩展 DLL。本文介绍了一个可重用的 ISAPI 扩展 DLL,它允许您在不编写 C++ 代码的情况下上传图片和文件。
但在服务器端,ASP 无法访问 multipart/form-data 格式的数据。有一个发布接受器组件,但其可编程性有限,并且它始终将已上传的文件存储在文件系统中。例如,如果您预先知道用户必须始终加载小文件并在内存中处理这些文件,您可能需要更灵活的解决方案。访问已上传文件的最灵活的方法是通过 C++ ISAPI 扩展 DLL。
表单元素的 ACTION
属性应指向一个目标组件(例如,CGI 或 ISAPI 扩展 DLL),该组件知道如何解析 multipart 编码并处理数据。用户按下浏览按钮,从文件系统中选择一个文件,然后按下上传按钮以发送文件。当然,您可能需要聘请一位设计师来用图形和其他酷炫的 JavaScript 技巧来点缀页面,但就 HTML 而言,它再简单不过了。
当浏览器向服务器发送请求时,它总是发送一个包含描述请求数据的 HTTP 数据包。数据包始终包含 URL 的虚拟路径。例如,如果您调用 http://myserver/default.asp,数据包将包含 /default.asp 路径。此外,如果请求是提交 HTML 表单的结果,则请求将包含表单中 INPUT
标签的内容。
下一个问题当然是如何对数据进行编码。事实证明,它是使用 MIME 编码完成的。默认(也是最简单)的编码是 application/x-www-form-urlencoded,它在 W3C 的 HTML 4.01 规范中有描述。application/xwww-form-urlencode 类型很简单,非常适合提交少量文本字段。
对于大型文本文件或图像,该规范定义了另一种编码:multipart/form-data。以下代码片段显示了使用 multipart/form-data 编码提交的表单的 HTTP 数据包的外观。
发布到 Web 服务器的原始数据格式如下
-----------------------------7d22151d40264
Content-Disposition: form-data; name="Filename"
UploadFileName.txt
-----------------------------7d22151d40264
Content-Disposition: form-data; name="Filedata";
filename="C:\boot.ini"
Content-Type: application/octet-stream
timeout=2
default=multi(0)disk(0)rdisk(0)partition(1)\WINNT
-----------------------------7d22151d40264
Content-Disposition: form-data; name="PathData"
C:\Projects\articles\Discover WEB. ISAPI versus ASP
web upload\Work\UploadISAPI\Data\
-----------------------------7d22151d40264
Content-Disposition: form-data; name="RedirectPage"
ReceiveRedirect.asp
-----------------------------7d22151d40264
Content-Disposition: form-data; name="Name"
Adrian Bacaianu
-----------------------------7d22151d40264
Content-Disposition: form-data; name="Upload"
Submit Query
-----------------------------7d22151d40264--
“ISAPI 扩展 DLL 解析输入流并收集所有名称/值对。然后它创建一个新的字典 COM 对象并将名称/值对存储在字典中。最后,它生成一个新 ID,并将新字典存储在字典的字典中使用该 ID 作为键”。
从该集合访问参数非常简单
//here create the "PathData" param MultipartEntry *pEntry = cParser["PathData"]; if(pEntry != NULL) sPath = pEntry->Data(); //here get data of //the "PathData" param sName = cParser[2]->Name(); //here get the name //("PathData") of the 2 param
如果响应直接从 ISAPI 扩展打印到 Web 浏览器,我们使用 SendToBrowser
方法
void SendToBrowser(LPEXTENSION_CONTROL_BLOCK pECB, const String & sMsg) { DWORD dwLen = sMsg.size(); if(dwLen > 0) //use HTTP WriteClient function pECB->WriteClient(pECB->ConnID, (LPVOID) sMsg.c_str(), &dwLen, 0); }
如果响应从 ISAPI 扩展重定向到另一个 ASP 页面,我们使用 RedirectBrowser
方法
void RedirectBrowser(LPEXTENSION_CONTROL_BLOCK pECB, const String & sMsg) { DWORD dwLen = sMsg.size(); if(dwLen > 0) // use HTTP ServerSupportFunction function pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_SEND_URL, (LPVOID)sMsg.c_str(), &dwLen, 0); }
结论
本文介绍了两种将图片和文件上传到 Web 服务器的方法,以及两者的优缺点。还有其他上传方法,将在另一篇文章中介绍!