65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (23投票s)

2002 年 6 月 30 日

11分钟阅读

viewsIcon

296227

downloadIcon

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.aspUploadExt.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 之后您仍然可以访问标头。您只是无法访问 FormQueryString 集合。) 如您在此处所见,此信息的结构非常简单。

-----------------------------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 服务器的方法,以及两者的优缺点。还有其他上传方法,将在另一篇文章中介绍!

© . All rights reserved.