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

使用 .NET 和 PHP 创建可扩展的 WebDAV 服务器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (15投票s)

2007 年 5 月 22 日

CPOL

32分钟阅读

viewsIcon

124440

演示了如何通过结合两个开源项目来创建基于 .NET 的 WebDAV 服务器。示例实现从文件系统中返回文件,但您可以将其扩展以从任何存储库返回资源。

概述

什么!? .NET? PHP? 在同一个句子里? 是的!我的需求是为 .NET 构建一个 WebDAV 服务器,而最成功的选择是将 .NET 和 PHP 结合起来。不,不是解释性的、缓慢的 PHP,而是以编译代码速度运行的 PHP,并且能够使用 .NET 程序集。

点击此链接可访问文章中引用的“操作方法”演示以及二进制和源代码文件

如果您下载了二进制文件,请告知我安装服务器实例时是否有任何困难。如果您创建了另一个存储库的接口,我很想听听您的经验。

引言

本文是关于实现 WebDAV 服务器的。如果 PHP 和/或 WebDAV 不在您的雷达范围内,那么本文就不适合您。同样,如果您只想通过 WebDAV 共享文件,那么请使用 IIS,因为此功能已内置。

本文的目的是展示如何使用 .NET 构建一个灵活的 WebDAV 服务器,该服务器可以扩展以从任何类型的存储库返回文档,或可以扩展以引入额外的文档属性或支持更细粒度的访问控制。

目录

文件

本文附带的链接提供的二进制文件是您安装、设置和运行本文讨论的 WebDAV 服务器所需的文件。要安装服务器,请解压缩二进制文件,然后使用通配符映射在 IIS 中创建一个虚拟目录。有关详细信息,请参阅下面的安装说明。可能遗漏的是,如果在 vanilla Windows 2003 或 Window 2000 上运行的 PC 上加载文件,则可能还需要 VS2005 C++ 运行时文件。此问题在此Phalanger 论坛帖子中进行了审查。如果您已安装 .NET 2.0 SDK,几乎肯定会安装 C++ 运行时文件安装程序。安装程序可以在此处找到。

源代码文件仅包含您需要应用于 Phalanger 源代码的更改和添加,这些内容在下面的源代码说明中进行了审查。有关完整的 Phalanger 源代码,请访问CodePlex上的 Phanalger 项目。PHP 源代码与二进制文件一起包含,因为这些文件实际上构成了 WebDAV 服务器的一部分。

注意:请勿将本文相关的二进制文件与 Phalanger 一起使用。您可能已经安装或正在计划安装 Phalanger 二进制文件和 Visual Studio 扩展。如果您这样做,并且为了防止与 Phalanger 程序集发生命名空间冲突,二进制文件中的程序集已从其原始 Phalanger 对应项重命名。扩展也已修改以使用重命名后的文件,因此它们将无法与 Phalanger 二进制文件一起使用。

冲突的可能性之所以存在,是因为 Phalanger 将其程序集安装到 GAC 中,而 .NET 可能会优先使用这些程序集。这是一个潜在的问题,因为为了实现 WebDAV 服务器作为处理程序(请参阅架构说明),必须修改 Phalanger 核心的一个源文件,使两个方法公开。

如果您不关心服务器的工作原理,只想尽快完成,请下载二进制文件并遵循下面的安装部分的说明。完成情况请告知我。

背景

在我的场景中,.NET 支持是必不可少的,因为服务器必须能够访问一套现有 .NET 程序集公开的属性。如果目标平台是 Java,则有多种选择,但反复的网络搜索显示 .NET 平台的选择非常少。似乎只有一个商业产品,还有一个名为Sphorium on SourceForge的 DAV 框架项目,但似乎仅此而已。商业产品可能是可行的;尽管如此,对于可重新分发的许可证而言,近 3K 美元的价格并不便宜。Sphorium 框架看起来很有前途,但它没有实现 DAV 规范中几个非常重要的元素。一些人已经写博客讨论为 .NET 创建 DAV 服务器,但没有人愿意或能够发布代码。

就在我准备打开钱罐购买商业产品的许可证时,我偶然发现了两个项目。一个是 PHP 对 WebDAV 服务器的实现。它是PEAR 项目的一部分。它是一个相当完整的 DAV 1 和 2 服务器实现,并带有一个示例实现,演示了如何允许对服务器上的文件进行受控访问。但是,它是 PHP,使用 PHP 并不容易满足访问 .NET 程序集的要求,而这正是第二个项目发挥作用的地方。

Phalanger

Phalanger 是一个用 .NET 实现的 PHP 编译器(在 .NET 2.0 中)。也就是说,它将获取 PHP 脚本并将其编译,以便它们可以从 ASP.NET 使用或作为独立应用程序使用。我不会详细介绍 Phalanger,除非解释 DAV 服务器的需要,因为Phalanger 网站CodePlex以及这里的CodeProject上有大量信息。微软已聘请了 Phalanger 项目的两位创始人,并且鉴于微软已创建动态语言运行时(开源)以促进动态语言的新闻,我希望在不久的将来,我们能看到 PherrousPHP 与 IronPython、IronRuby、JScript 和 VBScript 并驾齐驱。

什么是 WebDAV?

如果您正在阅读这篇文章,您可能已经对 WebDAV 有了很好的了解,因此没有必要进行详尽的解释。您可能已经在 Windows 的 Web 文件夹中使用了它。DAV 是分布式创作和版本控制的缩写。它是 Internet 工程任务组 (IETF) 的一项规范,它扩展了 HTTP,以便客户端和服务器可以通信要管理以及检索和存储这些文档的文档。该规范是 RFC 2518,可在以下位置找到:http://www.ietf.org/rfc/rfc2518.txt。使用该协议,客户端还可以请求有关文档的属性,以及请求或释放文档上的锁。服务器可能负责返回服务器文件系统管理的文件的内容,但同样,它也可能返回记录在数据库中的文档。

HTTP 定义了 GET 和 PUT 等动词。在 DAV 规范中,这些动词得以保留,因此,例如,PUT 在 DAV 中与在 HTTP 中一样是有效动词。语义基本相同,尽管存在重要且相当明显的差异。在 DAV 中,正在上传的资源(文档被称为资源)可能被另一个客户端锁定,因此服务器可能需要阻止上传发生并返回表示此条件的指定 HTTP 状态代码。然后,DAV 指定了新的状态代码和动词,例如 OPTIONS(允许客户端发现支持哪些 WebDAV 功能)、PROPFIND(查找可用文档及其属性)、LOCK、UNLOCK、MKCOL(目录被称为集合)、DELETE、COPY、MOVE 等。当然,这不仅仅是创建一个请求并指定一个动词那么简单。大多数动词将需要额外的标头信息或请求体中的 XML。服务器会响应大量信息,可能是标头,但很可能是响应体中的 XML。所有潜在的交互都描述在 94 页的规范中。

那么为什么是 DAV?

如果您只需要允许用户访问一组文件,那么 HTTP 将满足您的需求。如果配置您的 Web 服务器以允许目录浏览,那么用户就可以列出、下载和上传文档。但是,如果您想

  • 实施访问控制;
  • 允许客户端审查任意和应用程序特定的文档属性;
  • 从数据库或其他存储库提供文档,而不仅仅是从文件系统;
  • 通过安全的 IP 连接协作处理文档,

那么 DAV 是一个可以考虑的选项。

您可能会问的一个问题是:可以通过 WebDAV 提供哪些类型的存储库?

Microsoft Exchange 是一个 WebDAV 服务器,典型的存储库是用户的收件箱。Microsoft SharePoint 也是一个 WebDAV 服务器。有人创建了一个 WebDAV 服务器(用 Java 编写)来与 Amazon S3 功能进行接口。Sub-version,这个流行的源代码控制服务,支持 WebDAV。在www.webdav.org上有一个 WebDAV 服务应用程序列表,但任何可以表示为一组文档和文件夹的事物的存储库都可以由 WebDAV 服务提供。

WebDAV 客户端

已经有许多 WebDAV 客户端,包括 Windows 资源管理器和所有 Microsoft Office 程序。也就是说,Windows 资源管理器、Word 和其他 Office 程序会将 WebDAV 服务视为任何其他网络文件存储库。Windows 资源管理器允许您将 WebDAV 服务分配给驱动器号或将其保留为网络位置。这意味着,您可以实现一个 WebDAV 服务器,知道用户将能够使用日常使用的、并且 IT 部门已支持的软件来访问该服务。

在此处快速说明一下 Windows 中的 WebDAV 支持,因为如果我不指出来,就会有人指出来:Windows 客户端(资源管理器或 Office)并非 100% 兼容 - 这并不奇怪。所有 Microsoft 客户端(包括 Windows 资源管理器和 Office 套件)还优先支持 FrontPage 扩展。因为这些 Microsoft 客户端将默认使用 FrontPage,所以必须告诉它们服务器实际上是 WebDAV 服务器,而这正是规范差异出现的地方。要由 Windows 客户端使用的 WebDAV 服务在响应使用 OPTIONS 动词的任何请求时,都必须返回一个额外的 Windows 特定标头。本文下载文件中实现的服务器涵盖了此详细信息。

为什么是 PHP?

实际原因是,我发现的 WebDAV 服务器是用 PHP 编写的。此外,PHP 是我非常熟悉的语言。PHP 是一种备受推崇的脚本语言,被数百万个网站(包括我的)用于创建页面。现在 Ruby 和 Python 已获得 .NET 支持,我可以寻找用这些语言之一编写的服务器,但我对其中任何一种都不够熟悉,无法处理生成 WebDAV 服务的项目。WebDAV RFC (2518) 有 94 页的精确规范,因此任何人必须有使用现有项目来获得先发优势的动力。顺便说一句,如果您是 Python 程序员,有一个用 Python 编写的 WebDAV 服务器,可以在SourceForge上找到。

通过结合 PHP 和 .NET 的能力,我认为我获得了双赢。我可以选择处理一个编译后的应用程序,它将具有原生 .NET 应用程序的性能特征。另外,如果客户站点出现问题,我可以回退到脚本,从而能够调试问题,甚至在不需要安装 Visual Studio 并重新编译脚本以恢复性能的情况下更改代码。

为什么选择 PHP 服务器代码?

WebDAV 服务器必须响应规范中定义的动词、请求标头和 XML 的可能组合,并以规范定义的响应代码、标头和 XML 进行响应。

PHP 代码的作用是实现一个基类,该基类隐藏了动词、标头、响应代码和 XML 的所有管理,并允许派生类实现返回信息数组和成功/失败响应的函数。派生类实现不需要知道规范如何要求请求或响应 XML 的格式,或者规范要求或允许哪些响应代码和标头。

下面的图像显示了从派生类来看的基类。大写的方法是抽象的,可以被覆盖。它们对应于 webDAV 规范中指定的动词,并且(可选地)在派生类中实现以提供对该动词的支持。使用 PHP 基类,实现者可以专注于实现支持所选存储库所需的逻辑。

Screenshot - Server.png

最重要和最复杂的动词是 PROPFIND。PROPFIND 是客户端用于查询服务器有关可用资源和集合信息的动词。请求是标头和可选 XML 的混合体,它们共同作用,有点像 SQL Where 子句。请求可能询问

  • 列出根文件夹中的所有资源并列出支持的属性
  • 列出所有资源并返回属性 A、B 和 C 的详细信息

这看起来很简单,但就像以往一样,细节决定成败。PHP 基类负责处理细节,并且只需要派生类返回一个包含详细信息的结构化数组。

架构

WebDAV 服务器必须处理所有请求动词和所有查询路径。这一点很重要,因为用户请求的路径可能是目录或源代码文件。在处理 IIS 时,这是不熟悉的领域。IIS 根据与虚拟目录关联的文件扩展名映射来选择应用程序(ASP.NET、PHP、Perl 等)。即使 IIS 确定一个请求应该由 ASP.NET 处理,.NET 运行时还会根据machine.configweb.config中定义的与文件扩展名映射关联的处理程序来进一步精炼。

为确保 WebDAV 服务器接收所有请求,它被实现为一个处理程序;也就是说,“主”类派生自 IHttpHandler。此处理程序在web.config中指定,并且定义为响应所有动词和所有路径。还需要确保托管 WebDAV 服务器的网站或虚拟目录使用通配符映射(有关更多信息,请参阅安装部分)。

WebDAV 处理程序负责设置 AppDomain 的详细信息,并调用 Phalanger RequestContext 类,该类最终负责解析 PHP 脚本、预编译然后执行生成的代码以生成对客户端的响应。

ASP.NET IHttpHandler 接口指定一个属性 IsReusable() 和一个方法 ProcessRequest()。此方法由 ASP.NET 运行时调用,并传递当前请求的 HttpContext 实例。

这是二进制文件中使用此方法的实现

public void ProcessRequest(HttpContext context)
{
    if (context == null)
        throw new ArgumentNullException("context");

    // disables ASP.NET timeout if possible:
    try { context.Server.ScriptTimeout = Int32.MaxValue; } catch (HttpException) { }

    // ensure that Session ID is created
    RequestContext.EnsureSessionId();
            
    // default culture:
    Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
    RequestContext request_context = 
      RequestContext.Initialize(ApplicationContext.Default, context);

    PHP.Core.Debug.WriteLine("REQUEST", "Processing request");

    if (IsPHP(context.Request.Path))
    {
        Process(request_context, context);
    }
    else if (IsWebDAV(context.Request.Path))
    {
        Process(request_context, context, "Scripts/webdav.php");
    }
    else
    {
        context.Response.WriteFile(context.Request.PhysicalPath);
    }

    context.Response.End();

    if (request_context != null) request_context.Dispose();
}

实现的方法非常直接

  1. 检查 ASP.NET 提供的上下文;
  2. 调用静态 Phalanger 方法 RequestContext.EnsureSessionId() 来设置 PHP 会话信息(Phalanger 将 PHP 会话管理转换为 ASP.NET 会话等价物);
  3. 创建 Phalanger RequestContext 实例;和
  4. 将处理传递给两个私有类中的一个。

我的实现假设任何 PHP 文件 (.php) 都将作为 PHP 脚本执行,而不是被视为要访问或更新的资源,因为这有助于我进行调试。但没有内在原因说明这些不能被视为要传输到客户端的资源。

如果请求资源的扩展名不是 .PHP,则处理程序将硬编码为执行脚本“Scripts/webdav.php”,该脚本实现 WebDAV 逻辑。Phalanger 负责确定请求的脚本是否已预编译,并使用正确的程序集(如果已预编译),或者动态编译脚本。

void Process(RequestContext request_context, HttpContext context, string scriptFilename)
{
    PhpSourceFile requestFile = new PhpSourceFile(
            new FullPath(HttpRuntime.AppDomainAppPath),
            new FullPath(HttpRuntime.AppDomainAppPath + scriptFilename)
        );

    if (request_context.ScriptContext.Config.Session.AutoStart)
        request_context.StartSession();

    Type script = null;

    try
    {
        script = request_context.GetCompiledScript(requestFile);

        if (script != null)
        {
            request_context.IncludeScript(context.Request.PhysicalPath, script);
        }
    }
    catch (PHP.Core.ScriptDiedException)
    {
        Console.WriteLine("Died");
    }
    catch(Exception ex)
    {
        ReportStatus(ex.Message, 0);
        System.Console.WriteLine(ex.Message);
        // A user code or compiler have reported a fatal error.
        // We don't want to propagate the exception to web server.
    }
}

此私有方法从 ProcessRequest() 调用以运行 WebDAV 代码。请求的文件被包装在 PhpSourceFile 实例中,然后调用 GetCompiledScript() 来检索脚本的预编译版本或编译请求的(和依赖的)脚本。

最后,使用对 IncludeScript() 的调用来执行脚本。万岁,就是这样。

配置文件

您可能已经注意到,在前面的脚本中,对 request_context.StartSession() 的调用取决于 request_context.ScriptContext.Config.Session.AutoStart 的值。此值是 Phalanger 从web.config检索的众多值之一。通常,PHP 使用一个名为PHP.ini的文件来存储其属性。在 Phalanger 中,此信息存储在web.config的自定义部分中。在Phalanger 网站上有一个关于 Phalanger 可以设置的属性的全面示例。

总之,Phalanger 使用一个名为“phpNet”的自定义配置部分。本文相关的 WebDAV 二进制文件包含一个web.config文件,该文件满足 WebDAV 服务器的需求,并且是与 CodePlex 网站上更全面的示例进行比较的样本。

您可以启用或禁用的重要功能包括运行时和编译时错误捕获;这些是除写入 ASP.NET 跟踪集合的信息之外还可以捕获的信息。服务器附带的Web.config文件启用了所有运行时和编译时错误的报告,并将它们定向到一个名为output.html的文件。

源代码

为了创建工作的服务器,有必要对 Phalanger 项目和 PHP WebDAV 脚本进行修改。

对 Phalanger 代码最重要的更改是对 PhpNetCore 项目的RequestContext.cs文件。此文件的更改是为了使 EnsureSessionIdGetCompiledScript()MultiScriptAssembly() 方法公开,以便可以从外部程序集调用它们。Phalanger 在 PhpNetCore 项目中实现了自己的处理程序,但这假设它只处理以.php扩展名结尾的文件,这对于 WebDAV 服务器来说是不够的。出于这个原因,已经创建了一个 WebDAV 处理程序,并且正是这个处理程序需要访问 PhpNetCore 项目的修改后的方法。

PhpNetCore 项目的HttpHeaders.cs也已更改,因为我相信引入了一个错误,该错误阻止了标头正确写入响应流。

这些更改后的文件包含在本文相关的源代码文件中,您可以将其应用于 Phalanger 源代码。所做的更改适用于构建 22713,因此您在将其应用于更高版本的 Phalanger 构建时可能需要小心。

WebDAV 服务器的 PHP 部分也包含在相关文件中,并包含多个脚本文件

文件 注释
FileSystemAccess.php 包含基类的实现,以及一个名为 HTTP_WebDAV_Server_Filesystem 的派生类,该类实现了 WebDAV 语义。此实现使用 JET(.mdb)来存储锁定信息。
FileSystemMySQL.php 包含基类的实现,以及一个名为 HTTP_WebDAV_Server_Filesystem 的派生类,该类实现了 WebDAV 语义。此实现使用 MySQL 数据库来存储锁定信息。
WebDAV.php 此脚本检查用户身份验证,如果成功,则实例化 FileSystem 类并调用 ServeRequest() 方法。
_parse_propfind.php 解析与 PROPFIND 请求相关的任何 XML。
_parse_proppatch.php 解析与 PROPPATCH(更改资源属性)请求相关的任何 XML。
_parse_lockinfo.php 解析与 LOCK 请求相关的任何 XML。
Auth.php 实现 Digest 身份验证的类。

其余文件提供杂项支持函数。

还有两个名为Server.phpFileSystem.php的文件。FileSystemAccess.phpFileSystemMySQL.php文件包含Server.phpFileSystem.php的内容。事实证明,Phalanger 为 Visual Studio 内的调试生成的行号信息对于派生类与基类位于不同文件中的情况不正确。这使得无法使用 Visual Studio 逐步调试实现派生类的文件中的 PHP 代码。当基类和派生类在同一个文件中时,不会出现此问题。因此,主要的实现位于FileSystemAccess.phpFileSystemMySQL.php中,因为它们结合了基类和派生类,而Server.php仅实现基类,FileSystem.php仅实现派生类(MySQL)。

PHP 文件已进行了大量修改,以在这些情况使用 .NET 类时调用它们,因为这更有意义,并且它们已更新以添加写入跟踪信息的语句,这些信息可以通过查看trace.axd进行审查。此外,PEAR PHP 实现假设 WebDAV 将从网站根目录运行,而我希望 WebDAV 服务器附加到网站根目录或特定虚拟目录。

PHP 代码量很大,因此在本篇文章中不可能全部介绍,所以我将分部分回顾WebDAV.php,因为它说明了 PHP 和 .NET 的集成

import namespace System;
import namespace System:::Configuration;

$appSettings = ConfigurationManager::$AppSettings;

$realm = $appSettings->Get("Realm");
$DBHOST = $appSettings->Get("DBHOST");
$DB_WEBDAV = $appSettings->Get("DB_WEBDAV");
$DBUSER = $appSettings->Get("DBUSER");
$DBPWD = $appSettings->Get("DBPWD");
$SITEPATH = $appSettings->Get("SITEPATH");
$USERPASSWORDS = $appSettings->Get("Users");
$UseAuthentication = $appSettings->Get("UseAuthentication");
$ConnectionTimeout = $appSettings->Get("ConnectionTimeout");

System:::Web:::HttpContext::$Current->Trace->Write("REALM", $realm);

WebDAV.php文件开始导入 .NET 命名空间,方式与 C# 或 VB 类似。

在 C# 和 VB 中,点 (.) 用于表示“成员访问”,无论是类还是命名空间。在 PHP 中,点被保留为连接运算符。相反,Phalanger 设计者选择使用三个冒号来表示命名空间访问。设计者选择重用其他运算符,包括 $(表示字段,如变量或属性)、两个冒号 (::)(静态类或成员访问)和 ->(动态分配的成员访问)。

原则上,这很简单,但它确实意味着您必须知道类、方法或成员是如何声明的,以便您可以使用正确的语法。在 C# 中,无论您访问的是动态还是静态类成员或属性,都使用点。以该行为例

$appSettings = ConfigurationManager::$AppSettings;

这提供了对 appSettings 集合的访问,但由于 AppSettings 被定义为属性,因此必须使用 $ 前缀来访问它。另外,由于该属性是静态声明的,因此必须使用 :: 运算符而不是 -> 运算符来访问它。

返回的 $appSettings 是一个常规的 Hashtable,因此可以使用 -> 运算符访问其成员。您可以在该行看到此模式重复

System:::Web:::HttpContext::$Current->Trace->Write("REALM", $realm);

此行访问当前上下文的 Trace 类以将信息写入跟踪日志。

到目前为止,使用的代码都是 .NET。下面,混合使用 .NET 和 PHP 来根据web.config中指定的用户名凭据对当前用户进行身份验证。

if (isset($UseAuthentication) && strtolower($UseAuthentication) != "false")
{
    $users = array();

    if (strlen($USERPASSWORDS) > 0)
    {
        $userarray = explode(";", $USERPASSWORDS);
        foreach($userarray as $user)
        {
            if (strlen($user) == 0) continue;
            list($username, $password) = explode(":", $user);
            $md5 = md5($username.":".$realm.":".$password);

            $users[$username] = $md5;
            // System:::Web:::HttpContext::$Current->Trace->Warn("USER", 
            //       "$username:$realm:$password:$md5:" . $users[$username]);
        }

        if (count($users) > 0)
        {
            include_once("auth.php");

            $HTTPDigest =& new HTTPDigest($realm);

            if (!$authed = $HTTPDigest->authenticate($users)) {
                System:::Web:::HttpContext::$Current->Trace->Warn(
                                     "AUTHENTICATION", "Not logged in");
                $HTTPDigest->send();
            }
        }
    }

    if (count($users) == 0)
    {
        System:::Web:::HttpContext::$Current->Trace->Write("AUTHENTICATION", 
                       "No users have been defined");
        header('HTTP/1.0 401 No users');
        die('401 No users');
    }

    System:::Web:::HttpContext::$Current->Trace->Write("AUTHENTICATION", "Authenticated");
}

最后,实例化 WebDAV FileSystem 类,并调用 ServeRequest() 方法,传入 WebDAV 服务器将管理的文件的位置。

require_once "FilesystemNew.php";

$server = new HTTP_WebDAV_Server_Filesystem();
$server->db_host = $DBHOST;
$server->db_name = $DB_WEBDAV;
$server->db_user = $DBUSER;
$server->db_passwd = $DBPWD;

$server->ServeRequest($SITEPATH);

System:::Web:::HttpContext::$Current->Trace->Write("WEBDAV", "Completed");

调试

在客户端服务器应用程序中查找和修复问题始终是一个挑战。为了帮助解决这个问题,Phalanger 会生成一个日志文件。创建的文件名和写入文件的特定信息由Web.config中的设置控制。

我还在 PHP 中利用了通过调用 .NET 类来扩展 PHP 的能力,将信息添加到 ASP.NET 跟踪日志中,因为 PHP 代码被执行。您可以通过访问 ASP.NET 跟踪页面trace.axd来访问此附加信息。这假设您已在Web.config中启用了跟踪。当然,您也可以编辑 PHP 脚本并添加自己的其他跟踪信息。

一个完整的 WebDAV 查询可能涉及多个请求和响应,有时能够获得每个请求和响应的详细信息会很有帮助。根据我的经验,Microsoft 的 Eric Lawrence 开发的Fiddler2是实现此目的的绝佳工具。它可以记录所有请求及其响应的详细信息。它还允许您发出特定请求,并控制发送的动词、标头和正文。使用此功能,可以相对容易地聚焦和调试对特定动词的响应。Fiddler 易于安装。启动后,它会将自身注入为本地代理,以便记录和中继所有 HTTP 事务。

有几点需要注意。Fiddler 不代理 localhost (127.0.0.1)。如果您在自己的 PC 上使用本地测试服务器实例,则需要使用您的机器名而不是 localhost 来引用您的 Web 服务器。其次,当 Fiddler 启动时,它始终运行,这通常没问题。但是,如果您在它运行时下载文件,作为代理,它将在后台缓存文件,并且只有在下载完成后才会看到运行/打开/保存/取消按钮。因为您通常期望在文件下载之前出现提示,所以您可能会认为文件传输已出错,特别是如果后台下载需要一段时间才能完成。

最后,别忘了您所做的更改可能会表现为 .NET 运行时错误。如果您在服务器上工作,您将在浏览器中看到这些错误,但如果您在另一台 PC 上工作,您将需要确保web.config中包含此元素:<customerrors mode="Off">。此外,您应该暂时将跟踪元素的 localonly 属性设置为 false,以便您可以在另一台 PC 上查看跟踪输出。

调试步骤摘要

  1. 将浏览器指向 WebDAV 站点或虚拟目录。如果看到 404,则表示 WebDAV 服务未响应。
  2. 如果看到空白页,则表示发生了运行时错误,原因可能是 C++ 运行时缺失或 IIS 进程无法写入.mdb
  3. 使用trace.axd页面查看任何可用的跟踪信息。这假设您已在Web.config中启用了跟踪。在跟踪中查找运行时错误报告。
  4. 启动 Fiddler2 并尝试从 Excel 访问 WebDAV 服务器。Fiddler2 记录发送到服务器的请求和来自服务器的响应,以便您可以检查双向发送的信息以查找问题的根源。

安装 WebDAV 服务器

webdavbinary.zip文件中的文件解压缩到一个文件夹。解压缩后的文件夹将具有以下结构

  • WebDAV
    • Scripts - 存储测试脚本和编译命令文件的位置
      • bin - 编译后的程序集将在此创建
      • Scripts - 存储 WebDAV 服务器脚本的位置
    • 服务器
      • bin - 运行 WebDAV 服务器所需的已编译程序集
      • db - 创建 MySQL 数据库的 SQL 脚本的位置(可选)
      • Dynamic - Phalanger 自动生成的“包装器”程序集
      • Extensions - WebDAV 服务器实现使用的 PHP 扩展
      • Files - 将由 WebDAV 服务器提供的文件和文件夹的默认位置
      • Scripts - 如果不使用预编译程序集,将使用 WebDAV 服务器脚本
      • TypeDefs - 用于创建静态包装器程序集的特定于扩展的 XML 文件
      • Wrappers - 用于“包装”原生 PHP 扩展的静态生成程序集
    • Source - 源代码更改

DynamicExtensionsTypeDefsWrappers是 Phalanger 所需的文件夹,并且在web.configphpNet自定义部分中引用。有关这些文件夹用途的更多信息,请参阅 Phalanger 文档。

锁数据库

支持规范的 WebDAV 服务器必须支持许多功能,包括保存有关任何给定资源的“锁定”状态的详细信息的能力。在此示例服务器中,锁定信息存储在 JET(Access/.mdb)数据库中。确保用于运行 IIS 的帐户有权更新.mdb文件。。如果 IIS 无法更新此文件,您将看到一条 ADO.NET 错误消息,指示必须使用可更新的查询。另外,如果您计划在 Windows 2000 Pro 或 Server 上使用本文相关的二进制文件,您需要确保安装了 Microsoft Data Access Components (MDAC) 的 2.6 版或更高版本。

使用 JET 有几个原因,主要原因是它易于安装,因为.mdb文件随二进制文件一起复制,而且没有数据库或脚本需要运行,也没有数据库权限需要设置。第二个原因是使用 JET 说明了如何从 PHP 脚本中使用 .NET Framework,在本例中是 System.Data 程序集中的类。以下是建立连接的示例

try
{
    $con = new System:::Data:::OleDb:::OleDbConnection();
    $con->ConnectionString = 
       "Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=.\\WebDAV.mdb;";
    $con->Open();
}
catch(System:::Exception $e)
{
    parent::TraceCategory("FILESYSTEM", "Locks database connection error: " . $e->Message);
    return;
}

其中大部分是您所期望的。需要注意的是,要捕获错误,必须捕获 .NET Exception 类型。您不能依赖 PHP 原生 Exception 类来捕获 .NET 异常,因为它们的继承关系不同。

下面的代码显示了已建立的连接,用于填充读取器,然后迭代返回的行。主要区别在于 Phalanger 尚不支持索引器。此遗漏的后果是,必须使用更原始的 IDataReader 方法 GetOrdinal()GetString() 来访问读取器的值。

try
{
    parent::TraceCategory("FILEINFO", $query);
    $cmd = new System:::Data:::OleDb:::OleDbCommand($query, $con);
    $reader = $cmd->ExecuteReader();
    $hasRows = $reader->HasRows;
}
catch(System:::Exception $e)
{
    parent::TraceCategory("FILEINFO", "QUERY ERROR: " . $e->Message);
}

try
{
    if ($hasRows)
    {
        while ($reader->Read())
        {
            $col_ns      = $reader->GetOrdinal("ns");
            $col_name    = $reader->GetOrdinal("name");
            $col_value   = $reader->GetOrdinal("ns");

            $info["props"][] = $this->mkprop($reader->GetString($col_ns), 
               $reader->GetString($col_name), $reader->GetString($col_value));
        }
    }
}
catch(System:::Exception $e)
{
    parent::TraceCategory("FILEINFO", "QUERY ERROR: " . $e->Message);
}
catch(Exception $e)
{
    parent::TraceCategory("FILEINFO", "FETCH ERROR: " . $e->message);
}

if ($reader != null) $reader->Close();

如果您确实更喜欢使用“数据库”,那么名为FileSystemMySQL.php的文件实现了相同的 WebDAV,但使用 MySQL 作为数据库。MySQL 实现使用原生 PHP 扩展与 MySQL 进行交互,因此说明了 Phalanger 如何在它们是用非托管代码编写的情况下,仍然保留对现有 PHP 扩展的支持。本文相关的二进制文件还包含一个.sql脚本来创建所需的 MySQL 数据库和表。创建数据库后,您需要编辑web.config文件以编辑描述数据库位置、数据库名称、用户名和密码的 appSetting 键。

Web 服务器 (IIS)

要在 IIS 中创建 WebDAV 服务器,请遵循以下说明。如果您喜欢观看而非仅仅阅读如何创建 WebDAV 应用程序,可以在此处找到演示如何使用 IIS 5.x 和 IIS 6.0 设置服务器的 Flash 演示。

  1. 创建虚拟文件夹(如果在使用 IIS 6 或 Windows 2000 Server 上的 IIS 5,您也可以创建新网站)。
  2. 选择解压缩二进制文件的文件夹的WebDAV子文件夹。
  3. 右键单击新的虚拟文件夹或网站,然后从上下文菜单中选择属性选项。
  4. 选择 ASP.NET 页面,并确保选择了 .NET 2.0。如果必须进行此更改,则必须首先进行此更改。
  5. 选择主页。
  6. 按配置页面。
  7. 删除 *所有* 现有映射。
  8. 在 IIS 6 上...
    1. 点击插入...按钮添加新的通配符映射。
    2. 输入 aspnet_isapi.dll 作为“可执行文件”;c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll
    3. 确保“验证文件是否存在”复选框未选中。
  9. 在 IIS 5 上...
    1. 点击添加按钮添加新的通配符映射。
    2. 输入 aspnet_isapi.dll 作为“可执行文件”;c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll。输入“.*”作为扩展名。
    3. 选择“所有动词”单选按钮。
    4. 确保“验证文件是否存在”复选框未选中。
  10. 按 OK 按钮保存对配置的更改。
  11. 选择文档选项卡,并取消选中启用默认文档复选框。
  12. 选择目录安全选项卡。按编辑...按钮。除匿名外的所有选项都取消选中。

编辑web.config

  1. 更改任何设置,如日志记录和跟踪。
  2. 设置 SITEPATH appSetting 值以引用您想用作 WebDAV 文件源的文件夹(此文件夹必须具有 IIS 进程可访问的安全权限)。
  3. 输入您希望能够访问 WebDAV 服务的用户的姓名和密码;或
  4. UseAuthentication appSetting 名称的值设置为 false
  5. 设置数据库位置和访问凭据的值。

要测试 WebDAV 服务器,首先将浏览器指向您创建的网站或虚拟目录。如果应用程序成功,您将看到您存储在 SITEPATH 位置的任何文件的列表。

Screenshot - WebDAV.png

输出的呈现由派生 FileSystem 类的 GetDir() 方法控制,因此您可以根据需要进行调整。如果您看不到列表,调试部分中有指向帮助您找出问题所在的文章。总之,这是要查看日志文件,其中会列出任何编译错误,查看跟踪信息,如果所有其他方法都失败,请使用Fiddler2查看客户端和服务器之间的事务。

当您在浏览器中看到文件/目录列表时,您就可以使用 WebDAV 客户端了。最简单的方法是打开一个 Office 产品(如 Word),然后点击文件打开菜单,并在文件名字段中输入您的 WebDAV 服务器站点或网站/虚拟目录的地址。

Office 产品将尝试像任何其他文件服务一样使用 WebDAV 服务,并显示文件和文件夹列表供您使用。同样,如果您看不到预期的文件和文件夹,或者 Office 产品报告错误,那么您可以按照调试部分中确定的步骤进行操作。尽管可以通过 Excel 访问服务,但因为它仍然通过您的 IIS 网站或虚拟目录,所以您可以使用浏览器显示跟踪页面。

使用 Windows 资源管理器

最后一步是创建一个 WebDAV 网络驱动器。这将使您能够从 Windows 资源管理器访问 WebDAV 服务。

  1. 点击我的网络位置。
  2. 右键单击以显示上下文菜单,然后选择映射网络驱动器。
  3. 点击“注册在线存储或连接到网络服务器”并按 OK 按钮。
  4. 选择“选择另一个网络位置”并再次按 Next 按钮。
  5. 输入您的 WebDAV 服务器站点或虚拟目录的地址,然后再次按 Next 按钮。注意:我发现在使用 Windows 资源管理器对本地服务器的 WebDAV 支持时,我不能使用 localhost,而必须使用机器的域名。
  6. 如果 Windows 能够连接到 WebDAV 服务器,系统会提示您输入快捷方式的名称。点击 Next 按钮,然后点击 Finish 按钮。

预编译 PHP 脚本

Phalanger 的一个潜在好处是它允许预编译 PHP 脚本。PHP 编译器(phpc.exe)包含在本文相关的二进制文件中(.\WebDAV\Server\phpc.exe),以及用于运行编译器的命令文件(.\WebDAV\Scripts\build.cmd)。

编译器、配置文件和脚本经过定制,用于编译.\WebDAV\Scripts中的脚本并将生成的程序集复制到.\WebDAV\Server\bin文件夹中。WebDAV 服务器无需预编译即可工作,因为它默认会使用.\WebDAV\Server\Scripts中的脚本副本。Phalanger 会优先使用已编译的脚本程序集,因此无需删除.\WebDAV\Server\Scripts子文件夹,尽管如果至少重命名该文件夹,会更令人信服地表明正在使用生成的程序集。

实现限制

当我仔细查看大多数 WebDAV 实现(无论是 Java、C# 还是 Python)时,WebDAV 规范的某些方面并未实现。本文的基础 PEAR WebDAV 服务器也不例外。原始 PHP 脚本的作者在代码中包含注释以突出潜在的不足之处。这些不足对我来说并不重要,但您可以查看代码,看看这些对您是否重要。这些遗漏包括

  1. 不支持对已锁定集合内资源的递归锁定。
  2. PUT 操作只支持一个资源。也就是说,单个 PUT 操作无法提交多个资源。
  3. 不支持跨不同 WebDAV 服务器(URL)复制资源。
  4. 不支持内容编码的 PUT 操作。

WebDAV 是一项规范,它描述了一个旨在促进客户端和服务器之间标准化通信的协议。然而,其中一个问题是两者是否以相同的方式实现了该协议。在此服务器中,客户端和服务器之间产生误解的可能性更大,因为服务器还实现了 Digest 身份验证。众所周知,IIS、Apache、Firefox 和 IE 有时在实现 Digest 规范的细节方面存在差异。下表显示了我已成功测试过的客户端和服务器的组合。它不详尽,也不包括非 Microsoft 客户端,但至少您可以查看我期望会起作用的组合。

客户端 服务器
Excel 2003 和 Windows XP (SP2) 上的 Web 文件夹 Windows XP2 (SP2) 上的 IIS 5.0
Windows 2003 上的 IIS 6.0
Windows 2000 Server (SP4) 上的 IIS 5.0
Windows 2000 (SP4) 上的 Excel 2003 和 Web 文件夹 Windows XP2 (SP2) 上的 IIS 5.0
Windows 2003 上的 IIS 6.0
Windows 2000 Server (SP4) 上的 IIS 5.0
Windows 2000 (SP4) 上的 Excel 2000 和 Web 文件夹 Windows 2003 上的 IIS 6.0
Windows 2003 上的 IIS 6.0

许可证

此软件的许可证受两个组成项目许可证的约束。

Phalanger 在 Microsoft 共享源许可 (SS-PL) 下发布,该许可允许以二进制形式重新分发。

PEAR PHP HTTP_WebDAV_Server 在 PHP 许可 2.02 版下发布,该许可允许在保留版权声明的前提下重新分发。

资源

Resource 链接
WebDAV 规范 RFC 2518
WebDAV 新闻网站 www.webdav.org
Phalanger http://php-compiler.net/
PEAR WebDAV pear.php.net
演示和文件 www.lyquidity.com/webdav/

历史

2007-05-28

  • 现在,当文件为只读且请求 LOCK 时,返回错误代码 403 Forbidden。这会导致 Office 将此类文件视为只读。
  • Phalanger PhpNetClassLibrary 项目的FileSystem.cs文件中存在错误。这些错误意味着 is_readableis_writable 始终返回 true。
  • Office 请求的布尔属性 ishiddenisreadonlyisstructureddocumentiscollection 现在已标记为具有数据类型。

2007-05-26

  • 从 MySQL 到支持 ADO.NET 的端口并未完全更改 checklock 函数,这在用户保存自己的文件时会导致问题。
  • 更新了webdav.php以支持 Windows 身份验证。使用 Windows 身份验证
    • IIS 必须设置为集成 Windows 身份验证,并且不支持任何其他类型的身份验证;
    • 将名为“UseAuthentication”的 appSettings 键设置为“windows”;
    • 设置 system.web 部分的身份验证元素
    • <authentication mode="Windows" />

    • system.web部分的授权部分必须设置为:<deny users="?"/>

2007-05-23

已修复错误 - 感谢那些发送电子邮件的人。

  • 修复了处理包含 URL 可编码字符(如空格)的文件时导致身份验证失败的错误。
  • 修复了在使用虚拟目录进行复制或移动时,目标路径计算中的错误。
  • 已将 HEAD 方法添加到派生类,专门用于处理此动词。HEAD 应类似于 GET,但不返回正文。
  • 更改了派生类的 GET 方法,以接受一个标志,指示该方法是否应包含正文。

2007-05-20

First version.

© . All rights reserved.