本文档尚未完善,内容可能随时更改,可能无法准确描述 Apache™ Subversion® 软件的任何已发布版本。将此页面添加书签或将其推荐给其他人可能不是一个明智的选择。请访问 http://svnbooks.subversion.org.cn/ 获取本书的稳定版本。

httpd,Apache HTTP 服务器

Apache HTTP 服务器是一个 重型 网络服务器,Subversion 可以利用它。通过自定义模块,httpd 使 Subversion 仓库可以通过 WebDAV/DeltaV[64] 协议(HTTP 1.1 的扩展)提供给客户端。此协议采用作为万维网核心的普遍 HTTP 协议,并添加了写入功能,特别是版本化写入功能。其结果是一个标准化、健壮的系统,它方便地打包为 Apache 2.0 软件的一部分,受众多操作系统和第三方产品支持,并且不需要网络管理员打开另一个自定义端口。[65] 虽然 Apache-Subversion 服务器比 svnserve 具有更多功能,但它也更难设置。灵活性往往伴随着更多复杂性。

以下讨论中的大部分内容都包含对 Apache 配置指令的引用。虽然提供了一些关于使用这些指令的示例,但全面描述它们超出了本章的范围。Apache 团队维护着出色的文档,可在其网站上公开获取,网址为 http://httpd.apache.ac.cn。例如,配置指令的一般参考资料位于 http://httpd.apache.ac.cn/docs/current/mod/directives.html

此外,当您对 Apache 设置进行更改时,很有可能在某个步骤中会出错。如果您还不熟悉 Apache 的日志记录子系统,您应该了解它。在您的 httpd.conf 文件中,一些指令指定了 Apache 生成的访问日志和错误日志的磁盘位置(分别为 CustomLogErrorLog 指令)。Subversion 的 mod_dav_svn 也使用 Apache 的错误日志记录接口。您始终可以浏览这些文件的内容,以查找可能揭示问题的来源的信息,而这些信息在其他情况下并不明显。

先决条件

要通过 HTTP 网络化您的仓库,您基本上需要四个组件,这些组件包含在两个软件包中。您需要 Apache httpd 2.0 或更高版本,随附的 mod_dav DAV 模块,Subversion 以及与 Subversion 一起分发的 mod_dav_svn 文件系统提供程序模块。拥有所有这些组件后,网络化您的仓库的过程就变得非常简单,只需

  • 使用 mod_dav 模块启动和运行 httpd

  • mod_dav_svn 后端安装到 mod_dav,它使用 Subversion 的库来访问仓库

  • 配置您的 httpd.conf 文件以导出(或公开)仓库

您可以通过编译 httpd 和 Subversion 的源代码,或在系统上安装它们的预构建二进制软件包来完成前两项。有关如何编译 Subversion 以与 Apache HTTP 服务器一起使用以及如何为此目的编译和配置 Apache 本身的最新信息,请参阅 Subversion 源代码树顶层的 INSTALL 文件。

基本 Apache 配置

在系统上安装所有必需的组件后,剩下的就是通过其 httpd.conf 文件配置 Apache。使用 LoadModule 指令指示 Apache 加载 mod_dav_svn 模块。此指令必须位于任何其他与 Subversion 相关的配置项之前。如果您的 Apache 是使用默认布局安装的,那么您的 mod_dav_svn 模块应该已安装在 Apache 安装位置的 modules 子目录中(通常为 /usr/local/apache2)。LoadModule 指令语法简单,将命名模块映射到磁盘上共享库的位置

LoadModule dav_svn_module     modules/mod_dav_svn.so

Apache 将 LoadModule 配置项的库路径解释为相对于其自己的服务器根目录。如果按前面所示进行配置,Apache 将在其自己的 modules/ 子目录中查找 Subversion DAV 模块共享库。根据 Subversion 在系统上的安装方式,您可能需要为该库指定完全不同的路径,甚至可能是绝对路径,如以下示例所示

LoadModule dav_svn_module     C:/Subversion/libexec/mod_dav_svn.so

请注意,如果 mod_dav 作为共享对象编译(而不是直接静态链接到 httpd 二进制文件),您还需要为此提供类似的 LoadModule 语句。请确保它位于 mod_dav_svn 行之前

LoadModule dav_module         modules/mod_dav.so
LoadModule dav_svn_module     modules/mod_dav_svn.so

在配置文件的稍后位置,您现在需要告诉 Apache 您在哪里保存 Subversion 仓库(或仓库)。Location 指令采用类似 XML 的符号,以开始标签开头,以结束标签结尾,中间有各种其他配置指令。Location 指令的目的是指示 Apache 在处理指向给定 URL 或其子项的请求时执行一些特殊操作。对于 Subversion,您希望 Apache 只将对指向版本化资源的 URL 的支持传递给 DAV 层。您可以使用以下 httpd.conf 语法指示 Apache 将所有路径部分(URL 中跟随服务器名称和可选端口号的部分)以 /repos/ 开头的 URL 的处理委托给仓库位于 /var/svn/repository 的 DAV 提供程序

<Location /repos>
  DAV svn
  SVNPath /var/svn/repository
</Location>

如果您计划支持多个 Subversion 仓库,这些仓库将驻留在本地磁盘上的同一个父目录中,您可以使用另一个指令(SVNParentPath)来指示该公共父目录。例如,如果您知道您将在目录 /var/svn 中创建多个 Subversion 仓库,这些仓库将通过以下 URL 访问,如 http://my.server.com/svn/repos1http://my.server.com/svn/repos2 等,那么您可以使用以下示例中的 httpd.conf 配置语法

<Location /svn>
  DAV svn

  # Automatically map any "/svn/foo" URL to repository /var/svn/foo
  SVNParentPath /var/svn
</Location>

使用此语法,Apache 将所有路径部分以 /svn/ 开头的 URL 的处理委托给 Subversion DAV 提供程序,然后该提供程序将假设由 SVNParentPath 指令指定的目录中的任何项目实际上都是 Subversion 仓库。这是一种非常方便的语法,因为与使用 SVNPath 指令不同,您无需重新启动 Apache 即可添加或删除托管的仓库。

请确保在定义新的 Location 时,它不会与其他导出的位置重叠。例如,如果您的主 DocumentRoot 导出到 /www,请不要在 <Location /www/repos> 中导出 Subversion 仓库。如果收到对 URI /www/repos/foo.c 的请求,Apache 将不知道是查找 DocumentRoot 中的 repos/foo.c 文件,还是委托 mod_dav_svn 从 Subversion 仓库返回 foo.c。结果通常是服务器返回的错误,形式为 301 Moved Permanently

在此阶段,您应该认真考虑权限问题。如果您已经运行 Apache 一段时间作为您的常规 Web 服务器,那么您可能已经拥有一组内容,例如网页、脚本等。这些项目已经配置了一组权限,这些权限允许它们与 Apache 协同工作,或者更确切地说,允许 Apache 与这些文件协同工作。Apache 在用作 Subversion 服务器时,也需要正确的权限才能读取和写入 Subversion 仓库。

您需要确定一个权限系统设置,该设置既能满足 Subversion 的要求,又不会弄乱任何先前存在的网页或脚本安装。这可能意味着更改 Subversion 仓库的权限以匹配 Apache 为您提供服务的其他事物的权限,或者它可能意味着在 httpd.conf 中使用 UserGroup 指令来指定 Apache 应该以拥有 Subversion 仓库的用户和组的身份运行。没有单一的正确方法来设置您的权限,每个管理员都有不同的原因以某种方式做事。只是要注意,与权限相关的問題可能是配置 Subversion 仓库以与 Apache 一起使用时最常见的疏忽。

身份验证选项

在这一点上,如果您配置了 httpd.conf 以包含以下内容

<Location /svn>
  DAV svn
  SVNParentPath /var/svn
</Location>

那么您的仓库对全世界是 匿名 可访问的。在配置一些身份验证和授权策略之前,您通过 Location 指令提供的 Subversion 仓库通常对所有人都是可访问的。换句话说

  • 任何人都可以使用 Subversion 客户端签出仓库 URL(或其任何子目录)的工作副本。

  • 任何人都可以通过将 Web 浏览器指向仓库 URL 来交互式浏览仓库的最新修订版。

  • 任何人都可以提交到仓库。

当然,您可能已经设置了预提交挂钩脚本以阻止提交(请参阅 名为“实现仓库挂钩”的部分)。但是,正如您在继续阅读时会看到的那样,也可以使用 Apache 的内置方法以特定方式限制访问。

[Tip] 提示

要求身份验证可以防止无效用户直接访问仓库,但不能保护有效用户的网络活动的隐私。请参阅 名为“使用 SSL 保护网络流量”的部分,了解如何配置您的服务器以支持 SSL 加密,这可以提供额外的保护层。

基本身份验证

验证客户端最简单的方法是通过 HTTP 基本身份验证机制,该机制只使用用户名和密码来验证用户的身份。Apache 提供了 htpasswd 实用程序[66] 来管理包含用户名和密码的文件。

[Warning] 警告

基本身份验证极其不安全,因为它以几乎纯文本的形式在网络上传输密码。有关使用更安全的摘要机制的详细信息,请参阅“摘要身份验证”部分

首先,创建一个密码文件,并授予 Harry 和 Sally 用户访问权限。

$ ### First time: use -c to create the file
$ ### Use -m to use MD5 encryption of the password, which is more secure
$ htpasswd -c -m /etc/svn-auth.htpasswd harry
New password: *****
Re-type new password: *****
Adding password for user harry
$ htpasswd -m /etc/svn-auth.htpasswd sally
New password: *******
Re-type new password: *******
Adding password for user sally
$

接下来,确保 Apache 能够访问提供基本身份验证和相关功能的模块:mod_auth_basicmod_authn_filemod_authz_user。在许多情况下,这些模块已编译到 httpd 本身中,但如果不是,则可能需要使用 LoadModule 指令显式加载其中一个或多个模块。

LoadModule auth_basic_module   modules/mod_auth_basic.so
LoadModule authn_file_module   modules/mod_authn_file.so
LoadModule authz_user_module   moduels/mod_authz_user.so

确保 Apache 能够访问所需功能后,您需要在 <Location> 块中添加更多指令,以告知 Apache 您希望使用哪种类型的身份验证,以及如何执行。

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Basic
  AuthName "Subversion repository"
  AuthType Basic
  AuthBasicProvider file
  AuthUserFile /etc/svn-auth.htpasswd
</Location>

这些指令的工作原理如下。

  • AuthName 是您为身份验证域选择的任意名称。大多数浏览器在提示用户名和密码时,会在对话框中显示此名称。

  • AuthType 指定要使用的身份验证类型。

  • AuthBasicProvider 指定要用于该位置的基本身份验证提供程序。在我们的示例中,我们希望查询本地密码文件。

  • AuthUserFile 指定要使用的密码文件的位置。

但是,此 <Location> 块还没有执行任何有用的操作。它仅仅告诉 Apache,如果 需要授权,它应该向 Subversion 客户端请求用户名和密码。(当需要授权时,Apache 也需要身份验证。)但是,这里缺少的是告知 Apache 哪些 类型的客户端请求需要授权的指令;目前还没有。最简单的方法是指定所有 请求都通过添加 Require valid-user 到该块来要求授权。

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Basic
  AuthName "Subversion repository"
  AuthType Basic
  AuthBasicProvider file
  AuthUserFile /etc/svn-auth.htpasswd

  # Authorization: Authenticated users only
  Require valid-user
</Location>

有关 Require 指令以及设置授权策略的其他方法的更多详细信息,请参阅“授权选项”部分

[Note] 注意

AuthBasicProvider 选项的默认值为 file,因此我们不会在以后的示例中包含它。只需知道,如果您在更广泛的上下文中将此值设置为其他值,则需要在您的 Subversion <Location> 块中将其显式重置为 file,才能获得该行为。

摘要身份验证

摘要身份验证是对基本身份验证的改进,它允许服务器在不通过网络发送未经保护的密码的情况下验证客户端的身份。客户端和服务器都会对用户名、密码、请求的 URI 以及服务器提供的 nonce(一次性数字)进行不可逆的 MD5 哈希运算,并且每次需要身份验证时都会更改。客户端将自己的哈希值发送到服务器,然后服务器验证哈希值是否匹配。

将 Apache 配置为使用摘要身份验证很简单。您需要确保 mod_auth_digest 模块可用(而不是 mod_auth_basic),然后对我们之前的示例进行一些小的修改。

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthDigestProvider file
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Authenticated users only
  Require valid-user
</Location>

请注意,AuthType 现在设置为 Digest,并且我们为 AuthUserFile 指定了不同的路径。摘要身份验证使用与基本身份验证不同的文件格式,该格式使用 Apache 的 htdigest 实用程序创建和管理[67],而不是 htpasswd。摘要身份验证还具有 的额外概念,该概念必须与 AuthName 指令的值匹配。

[Note] 注意

对于摘要身份验证,身份验证提供程序使用 AuthDigestProvider 进行选择,如前面的示例所示。与 AuthBasicProvider 指令一样,fileAuthDigestProvider 选项的默认值,因此,除非您需要覆盖从更广泛的配置上下文中继承的其他值,否则该行不是严格要求的。

可以按如下方式创建密码文件。

$ ### First time: use -c to create the file
$ htdigest -c /etc/svn-auth.htdigest "Subversion repository" harry
Adding password for harry in realm Subversion repository.
New password: *****
Re-type new password: *****
$ htdigest /etc/svn-auth.htdigest "Subversion repository" sally
Adding user sally in realm Subversion repository
New password: *******
Re-type new password: *******
$

授权选项

此时,您已配置了身份验证,但未配置授权。Apache 能够向客户端发出挑战并确认身份,但尚未告知如何允许或限制对拥有这些身份的客户端的访问。本节介绍两种控制对存储库访问的策略。

全面访问控制

最简单的访问控制形式是为某些用户授权对存储库进行只读访问,或对存储库进行读写访问。

您可以通过在 <Location> 块中直接添加 Require valid-user 来限制所有存储库操作的访问权限。来自“摘要身份验证”部分 的示例仅允许成功进行身份验证的客户端对 Subversion 存储库执行任何操作。

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Authenticated users only
  Require valid-user
</Location>

有时您不需要如此严格的控制。例如,托管 Subversion 自身源代码的服务器https://svn.apache.org/repos/asf/subversion/ 允许世界上任何人都执行只读存储库任务(例如签出工作副本和浏览存储库),但将写入操作限制为已验证的用户。可以使用 LimitLimitExcept 指令来实现这种类型的选择性限制。与 Location 指令一样,这些块也有起始和结束标记,您需要将它们嵌套在您的 <Location> 块中。

LimitLimitExcept 指令上存在的参数是受该块影响的 HTTP 请求类型。例如,要允许匿名只读操作,您将使用 LimitExcept 指令(传递 GETPROPFINDOPTIONSREPORT 请求类型参数),并将前面提到的 Require valid-user 指令放置在 <LimitExcept> 块中,而不是仅放置在 <Location> 块中。

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Authenticated users only for non-read-only
  #                (write) operations; allow anonymous reads
  <LimitExcept GET PROPFIND OPTIONS REPORT>
    Require valid-user
  </LimitExcept>
</Location>

这些只是一些简单的示例。有关 Apache 访问控制和 Require 指令的更深入的信息,请查看 Apache 文档的教程集中的 Security 部分,网址为http://httpd.apache.ac.cn/docs-2.0/misc/tutorials.html

每目录访问控制

可以使用 mod_authz_svn 设置更细粒度的权限。这个 Apache 模块会获取从客户端到服务器传递的各种不透明 URL,请求 mod_dav_svn 对其进行解码,然后可能会根据配置文件中定义的访问策略拒绝请求。

如果您是从源代码构建 Subversion 的,则 mod_authz_svn 会与 mod_dav_svn 一起自动构建和安装。许多二进制发行版也会自动安装它。要验证它是否已正确安装,请确保它在 httpd.confmod_dav_svnLoadModule 指令之后立即出现。

LoadModule dav_module         modules/mod_dav.so
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so

要激活此模块,您需要将 <Location> 块配置为使用 AuthzSVNAccessFile 指令,该指令指定一个包含存储库中路径的权限策略的单个文件。从 Subversion 1.7 开始,您还可以使用 AuthzSVNReposRelativeAccessFile 指令指定每个存储库访问文件。(稍后我们将讨论该文件的格式。)

Apache 具有灵活性,因此您可以选择以三种一般模式之一配置您的块。首先,选择以下基本配置模式之一。(以下示例非常简单;有关 Apache 身份验证和授权选项的更多详细信息,请查看 Apache 自身的文档。)

最开放的方法是允许所有人访问。这意味着 Apache 永远不会发送身份验证挑战,并且所有用户都将被视为 匿名。(请参阅示例 6.2,“匿名访问的示例配置”。)

示例 6.2. 匿名访问的示例配置

<Location /repos>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: None

  # Authorization: Path-based access control
  AuthzSVNAccessFile /path/to/access/file
</Location>

在偏执程度的另一端,您可以将 Apache 配置为对所有客户端进行身份验证。此块无条件地通过 Require valid-user 指令要求身份验证,并定义了对有效用户进行身份验证的方法。(请参阅示例 6.3,“已验证访问的示例配置”。)

示例 6.3. 已验证访问的示例配置

<Location /repos>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Path-based access control; authenticated users only
  AuthzSVNAccessFile /path/to/access/file
  Require valid-user
</Location>

第三种非常流行的模式是允许组合已验证和匿名访问。例如,许多管理员希望允许匿名用户读取某些存储库目录,但将对更敏感区域的访问限制为已验证的用户。在此设置中,所有用户最初都以匿名方式访问存储库。如果您的访问控制策略在任何时候都需要真实用户名,Apache 将要求客户端进行身份验证。要执行此操作,请同时使用 Satisfy AnyRequire valid-user 指令。(请参阅示例 6.4,“混合已验证/匿名访问的示例配置”。)

示例 6.4. 混合已验证/匿名访问的示例配置

<Location /repos>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Path-based access control; try anonymous access
  #                first, but authenticate if necessary
  AuthzSVNAccessFile /path/to/access/file
  Satisfy Any
  Require valid-user
</Location>

下一步是创建授权文件,其中包含存储库中特定路径的访问规则。我们将在本章后面的“基于路径的授权”部分 中介绍如何创建授权文件。

禁用基于路径的检查

mod_dav_svn 模块会进行大量工作,以确保您标记为 不可读 的数据不会意外泄露。这意味着它需要密切监控由 svn checkoutsvn update 等命令返回的所有路径和文件内容。如果这些命令遇到根据某个授权策略不可读的路径,则该路径通常会被完全省略。对于历史记录或重命名跟踪(例如,对很久以前重命名的文件运行 svn cat -r OLD foo.c 等命令),如果确定对象的某个旧名称是读限制的,则重命名跟踪将立即停止。

所有这些路径检查有时可能非常昂贵,尤其是在使用 svn log 的情况下。在检索修订版列表时,服务器会查看每个修订版中的每个已更改路径并检查其可读性。如果发现不可读路径,则会将其从修订版已更改路径列表中省略(通常使用 --verbose (-v) 选项查看),并且会抑制整个日志消息。不用说,对于影响大量文件的修订版来说,这可能非常耗时。这是安全性的代价:即使您根本没有配置 mod_authz_svn 等模块,mod_dav_svn 模块仍然要求 Apache httpd 对每个路径运行授权检查。 mod_dav_svn 模块不知道安装了哪些授权模块,因此它所能做的就是要求 Apache 调用可能存在的任何模块。

另一方面,也存在一种逃生舱口,它允许您以牺牲安全功能为代价来提高速度。如果您没有强制执行任何类型的目录级授权(即,不使用 mod_authz_svn 或类似模块),您可以禁用所有这些路径检查。在您的 httpd.conf 文件中,使用 SVNPathAuthz 指令,如 示例 6.5,“完全禁用路径检查” 所示。

示例 6.5. 完全禁用路径检查

<Location /repos>
  DAV svn
  SVNParentPath /var/svn

  SVNPathAuthz off
</Location>

SVNPathAuthz 指令默认情况下是 on。当设置为 off, 时,将禁用所有基于路径的授权检查;mod_dav_svn 停止对发现的每个路径调用授权检查。

版本控制的存储库访问文件

从 Subversion 1.8 开始,访问文件可以存储在 Subversion 存储库中。可以将访问文件存储在应用访问规则的同一存储库中,或者将其存储在完全不同的存储库中。这种方法可以为基于路径的授权配置启用 Subversion 的版本控制功能。

AuthzSVNAccessFileAuthzSVNReposRelativeAccessFile 两个配置指令都允许指定存储库内访问文件的位置。这些指令接受绝对 file:// URL 和存储库相对 URL(以 ^/ 开头的 URL)。

例如,可以指定存储库内访问文件的绝对 URL,如 示例 6.6,“使用单个版本控制的存储库内访问文件” 所示。

示例 6.6. 使用单个版本控制的存储库内访问文件

<Location /repos>
  DAV svn
  SVNParentPath /var/svn
  AuthzSVNAccessFile file:///var/svn/authzrepo/authz
</Location>

还可以指定存储库内访问文件的相对 URL,如 示例 6.7,“使用每个存储库的存储库内访问文件” 所示。

示例 6.7. 使用每个存储库的存储库内访问文件

<Location /repos>
  DAV svn
  SVNParentPath /var/svn
  AuthzSVNReposRelativeAccessFile ^/authz
</Location>

使用 SSL 保护网络流量

通过 http:// 连接到存储库意味着所有 Subversion 活动都是以明文方式发送到网络上的。这意味着诸如签出、提交和更新之类的操作可能被未经授权的方通过 嗅探 网络流量截获。使用 SSL 加密流量是保护网络上传输的潜在敏感信息的良好方法。

如果 Subversion 客户端被编译为使用 OpenSSL,它将获得通过 https:// URL 与 Apache 服务器通信的能力,因此所有流量都使用每个连接会话密钥加密。Subversion 客户端使用的 WebDAV 库不仅能够验证服务器证书,还能够在服务器要求时提供客户端证书。

Subversion 服务器 SSL 证书配置

描述如何生成客户端和服务器 SSL 证书以及如何配置 Apache 使用它们超出了本书的范围。许多其他参考资料(包括 Apache 自己的文档 (http://httpd.apache.ac.cn/docs/current/ssl/))描述了此过程。

[Tip] 提示

来自知名实体的 SSL 证书通常需要付费,但至少,您可以配置 Apache 使用使用 OpenSSL (https://www.openssl.org) 等工具生成的自签名证书。[68]

Subversion 客户端 SSL 证书管理

通过 https:// 连接到 Apache 时,Subversion 客户端可能会收到两种不同的响应类型

  • 服务器证书

  • 对客户端证书的挑战

服务器证书

当客户端收到服务器证书时,它需要验证服务器是否是它声称的那样。OpenSSL 通过检查服务器证书的签名者或 证书颁发机构 (CA) 来执行此操作。如果 OpenSSL 无法自动信任 CA,或者出现其他问题(例如证书过期或主机名不匹配),Subversion 命令行客户端将询问您是否要仍然信任服务器证书

$ svn list https://host.example.com/repos/project

Error validating server certificate for 'https://host.example.com:443':
 - The certificate is not issued by a trusted authority.  Use the
   fingerprint to validate the certificate manually!
Certificate information:
 - Hostname: host.example.com
 - Valid: from Jan 30 19:23:56 2004 GMT until Jan 30 19:23:56 2006 GMT
 - Issuer: CA, example.com, Sometown, California, US
 - Fingerprint: 7d:e1:a9:34:33:39:ba:6a:e9:a5:c4:22:98:7b:76:5c:92:a0:9c:7b

(R)eject, accept (t)emporarily or accept (p)ermanently?

这种对话本质上与您可能从您的网络浏览器(它只是像 Subversion 这样的另一个 HTTP 客户端)中看到的相同问题。如果您选择 (p)ermanent 选项,Subversion 将在您的私有运行时 auth/ 区域中缓存服务器证书,就像您的用户名和密码被缓存一样(请参阅 名为“缓存凭据”的部分),并且将在将来自动信任该证书。

您的运行时 servers 文件还使您能够使您的 Subversion 客户端自动信任特定的 CA,无论是全局还是按主机方式。只需将 ssl-authority-files 变量设置为以分号分隔的 PEM 编码 CA 证书列表即可

[global]
ssl-authority-files = /path/to/CAcert1.pem;/path/to/CAcert2.pem

许多 OpenSSL 安装也具有一组预定义的 默认 CA,它们几乎被普遍信任。要使 Subversion 客户端自动信任这些标准颁发机构,请将 ssl-trust-default-ca 变量设置为 true

客户端证书挑战

如果客户端收到对证书的挑战,则服务器要求客户端证明其身份。客户端必须发送回由服务器信任的 CA 签署的证书,以及 挑战响应,该响应证明客户端拥有与证书关联的私钥。私钥和证书通常以加密格式存储在磁盘上,由密码保护。当 Subversion 收到此挑战时,它会询问您加密文件的路径以及保护它的密码

$ svn list https://host.example.com/repos/project

Authentication realm: https://host.example.com:443
Client certificate filename: /path/to/my/cert.p12
Passphrase for '/path/to/my/cert.p12':  ********

请注意,客户端凭据存储在 .p12 文件中。要使用 Subversion 的客户端证书,它必须采用 PKCS#12 格式,这是一种可移植标准。大多数网络浏览器能够导入和导出该格式的证书。另一种选择是使用 OpenSSL 命令行工具将现有证书转换为 PKCS#12。

运行时 servers 文件还允许您按主机方式自动执行此挑战。如果您设置了 ssl-client-cert-filessl-client-cert-password 变量,Subversion 可以自动响应客户端证书挑战,而无需提示您

[groups]
examplehost = host.example.com

[examplehost]
ssl-client-cert-file = /path/to/my/cert.p12
ssl-client-cert-password = somepassword

更注重安全的个人可能希望排除 ssl-client-cert-password,以避免将密码明文存储在磁盘上。

性能调整

Apache HTTP Server 旨在实现高性能,但您可以改进其默认配置以从您的 Subversion 服务提供中获得更好的结果。在本节中,我们将推荐一些需要考虑的特定配置更改。但是,请注意,我们将在本文中讨论的某些 httpd.conf 配置选项会影响服务器的总体行为,而不仅仅是 Subversion 服务。因此,您需要考虑您的 HTTP 服务提供商的全部范围,以了解对 Subversion 而言的这些设置的修改如何影响您的其他服务。

KeepAlive

默认情况下,Apache HTTP Server 被配置为启用单个服务器连接的重复使用,用于多个请求。这对 Subversion 非常有利,因为与许多基于 HTTP 的应用程序不同,Subversion 可以非常快地针对单个操作对服务器生成数百或数千个请求,并且对服务器打开新连接的成本并不低。Subversion 希望在服务器终止连接之前从单个连接中挤出尽可能多的请求。 KeepAlive 指令是启用或禁用此连接重复使用功能的布尔标志,如前所述,默认情况下它的值为 On

但还有一个指令限制了客户端允许在单个连接上提交的请求数量:MaxKeepAliveRequests 指令。该选项的默认值为 100。这可能足以用于旧版本的 Subversion,但 Subversion 1.8 采用了一个不同的 HTTP 通信库(称为 Serf),它更喜欢将多个较小的特定信息位的请求进行管道传输,而不是要求服务器在单个响应中传输大量数据。我们建议您将 MaxKeepAliveRequests 选项的值增加到至少 1000

#
# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
#
KeepAlive On

#
# MaxKeepAliveRequests: The maximum number of requests to allow
# during a persistent connection. Set to 0 to allow an unlimited amount.
# We recommend you leave this number high, for maximum performance.
#
MaxKeepAliveRequests 1000

批量更新

Subversion 1.8 客户端与 1.8 之前客户端行为之间最大的区别在于更新类型操作(svn checkoutsvn updatesvn switch 等)的处理方式。使用 Neon HTTP 库进行通信的旧客户端更喜欢要求服务器在单个请求中提供来自服务器所需信息的整个有效负载。管理员会在他们的服务器日志中注意到,最初会有一些握手操作,然后是一个带有大量响应的 REPORT 请求。该响应是整个签出/更新数据集!

使用 Serf HTTP 库的 Subversion 客户端(包括所有基于 Subversion 1.8 构建的客户端)仍然会发送 REPORT 请求,但请求中的标志设置略有不同。这些标志指示服务器不要发送操作的所有数据,而是只发送客户端随后需要从服务器获取的更特定项目的清单,以完成该操作。在服务器的 access_log 中,该 REPORT 后面会跟着许多较小的请求(GET 和旧版本的 Subversion 中的 PROPFIND)。

每种方法都有其优缺点。正如我们提到的,所谓的批量更新会在服务器日志中生成明显更少的信息,但给定的 Apache HTTP Server 子进程在整个可能很长的操作过程中会被完全占用。非批量更新提供了设置内容缓存的机会(缓存本身可以提高性能),但生成的服务器日志流量比批量更新方法大几个数量级。因此,出于各种原因,管理员可能希望对客户端使用的哪种方法进行更多控制。Subversion 1.6 引入了 SVNAllowBulkUpdates mod_dav_svn 指令(一个简单的布尔标志)以允许管理员指定服务器是否允许处理批量更新请求。在 Subversion 1.8 中,此指令已扩展为除了已支持的 OnOff 值之外,还包括一个 Prefer 值。当 SVNAllowBulkUpdates 设置为 Prefer 时,支持的客户端(1.8 或更高版本)将尝试使用批量更新方法,除非另有配置。

额外的好处

我们已经介绍了 Apache 和 mod_dav_svn 的大多数身份验证和授权选项。但 Apache 还提供了一些其他不错的功能。

仓库浏览

对于 Subversion 仓库来说,Apache/WebDAV 配置最有用的一点是,您可以通过普通 Web 浏览器立即查看版本化的文件和目录。由于 Subversion 使用 URL 来标识版本化的资源,因此用于基于 HTTP 的仓库访问的这些 URL 可以直接在 Web 浏览器中输入。您的浏览器将对该 URL 发出 HTTP GET 请求;根据该 URL 代表的是版本化的目录还是文件,mod_dav_svn 将响应目录列表或文件内容。

URL 语法

如果 URL 不包含有关要查看的资源的哪个版本的信息,mod_dav_svn 将使用最新的版本进行响应。此功能具有一个很棒的副作用,您可以将 Subversion URL 传递给您的同行作为对文档的引用,并且这些 URL 将始终指向该文档的最新版本。当然,您甚至可以将这些 URL 用作来自其他网站的超链接。

从 Subversion 1.6 开始,mod_dav_svn 支持用于检查文件和目录的旧版本的公共 URI 语法。语法使用 URL 的查询字符串部分来指定一个或两个 peg 修订版本和操作修订版本,Subversion 将使用这些修订版本来确定要显示给您的 Web 浏览器的文件或目录的哪个版本。添加查询字符串名称/值对 p=PEGREV(其中 PEGREV 是修订版本号)以指定要应用于请求的 peg 修订版本。使用 r=REV(其中 REV 是修订版本号)以指定操作修订版本。

例如,如果您希望查看项目 /trunk 中的 README.txt 文件的最新版本,请将您的 Web 浏览器指向该文件的仓库 URL,该 URL 可能类似于以下内容

http://host.example.com/repos/project/trunk/README.txt

如果您现在希望查看该文件的某个旧版本,请将操作修订版本添加到 URL 的查询字符串中

http://host.example.com/repos/project/trunk/README.txt?r=1234

如果您尝试查看的项目在仓库的最新修订版本中不再存在怎么办?这就是 peg 修订版本派上用场的地方

http://host.example.com/repos/project/trunk/deleted-thing.txt?p=321

当然,您可以将 peg 修订版本和操作修订版本说明符结合起来,以微调要查看的确切项目

http://host.example.com/repos/project/trunk/renamed-thing.txt?p=123&r=21

前面的 URL 将显示在修订版本 123 中位于仓库 /trunk/renamed-thing.txt 的对象的修订版本 21。请参阅 名为“Peg 和操作修订版本”的部分,详细了解这些“peg 修订版本”和“操作修订版本”概念。这些概念可能有点难理解。

从 Subversion 1.8 开始,mod_dav_svn 能够替换关键字。当 mod_dav_svn 在文件的 URL 中找到查询参数 kw=1 时,它将在传送文件内容时扩展关键字。省略 kw 参数或使用除 1 之外的任何值作为该参数的值,将导致 Subversion 使用其默认行为,即传送文件内容而不扩展任何关键字。

由于关键字替换通常由 Subversion 客户端作为其工作副本管理的一部分执行,因此这是一种方便的方法,可以让服务器传送版本化文件的关键字扩展形式,而无需使用工作副本。

例如,如果您希望查看项目 /trunk 中的 README.txt 文件的最新版本,并扩展关键字,请将查询参数 kw=1 添加到 URL 中

http://host.example.com/repos/project/trunk/README.txt?kw=1

与客户端侧的关键字扩展一样,只有那些通过在文件本身设置的 svn:keywords 属性显式指定要扩展的关键字才会被扩展。请参阅 名为“关键字替换”的部分,详细了解关键字替换功能。

提醒一下,mod_dav_svn 只提供有限的仓库浏览体验。您可以查看目录列表和文件内容,但不能查看修订版本属性(例如提交日志消息)或文件/目录属性。对于需要更深入地浏览仓库及其历史记录的用户,有一些第三方软件包提供了此功能。一些示例包括 ViewVC (https://viewvc.org)、Trac (https://trac.edgewall.org) 和 WebSVN (https://websvnphp.github.io)。这些第三方工具不会影响 mod_dav_svn 的内置“可浏览性”,并且通常提供更广泛的功能集,包括显示上述属性集、显示文件修订版本之间的内容差异等等。

正确的 MIME 类型

在浏览 Subversion 仓库时,Web 浏览器会通过查看 Apache 响应 HTTP GET 请求中返回的 Content-Type: 标头,来获得有关如何呈现文件内容的线索。此标头的值是某种 MIME 类型。默认情况下,Apache 会告诉 Web 浏览器,所有仓库文件都是“默认”MIME 类型,通常是 text/plain。但是,如果用户希望仓库文件以更有意义的方式呈现,这可能会让人感到沮丧,例如,在仓库中,让 foo.html 文件在浏览时实际呈现为 HTML 可能会很不错。

要实现这一点,您只需要确保您的文件具有正确的 svn:mime-type 设置即可。我们在 名为“文件内容类型”的部分 中对此进行了更详细的讨论,您甚至可以将客户端配置为自动将正确的 svn:mime-type 属性附加到首次进入仓库的文件;请参阅 名为“自动属性设置”的部分

继续我们的示例,如果要将 svn:mime-type 属性设置为 text/html,并将其应用于 foo.html 文件,Apache 将正确地告诉您的 Web 浏览器将该文件呈现为 HTML。您也可以将正确的 image/* MIME 类型属性附加到图像文件,并最终让整个网站可以直接从仓库中查看!通常,这样做不会有任何问题,只要网站不包含任何动态生成的内容即可。

自定义外观

通常,您会更多地使用指向版本化文件的 URL,毕竟,有趣的内容往往都位于那里。但您可能会偶尔浏览 Subversion 目录列表,您会很快注意到,用于显示该列表的生成的 HTML 非常基础,肯定不打算美观(甚至有趣)。为了实现对这些目录显示的自定义,Subversion 提供了 XML 索引功能。在仓库的 httpd.confLocation 块中,单个 SVNIndexXSLT 指令将指示 mod_dav_svn 在显示目录列表时生成 XML 输出,并引用您选择的 XSLT 样式表

<Location /svn>
  DAV svn
  SVNParentPath /var/svn
  SVNIndexXSLT "/svnindex.xsl"
  …
</Location>

使用 SVNIndexXSLT 指令和创意 XSLT 样式表,您可以让您的目录列表与网站其他部分中使用的颜色方案和图像相匹配。或者,如果您愿意,可以使用 Subversion 源代码分发包的 tools/xslt/ 目录中提供的示例样式表。请记住,提供给 SVNIndexXSLT 指令的路径实际上是 URL 路径,浏览器需要能够读取您的样式表才能使用它们!

列出仓库

如果您通过 SVNParentPath 指令从单个 URL 为一组仓库提供服务,那么您也可以让 Apache 向 Web 浏览器显示所有可用的仓库。只需激活 SVNListParentPath 指令即可

<Location /svn>
  DAV svn
  SVNParentPath /var/svn
  SVNListParentPath on
  …
</Location>

如果用户现在将她的 Web 浏览器指向 URL http://host.example.com/svn/,她将看到一个位于 /var/svn 中的所有 Subversion 仓库的列表。显然,这可能存在安全问题,因此默认情况下此功能处于关闭状态。

Apache 日志记录

由于 Apache 核心是一个 HTTP 服务器,因此它包含非常灵活的日志记录功能。详细讨论所有日志记录配置方法超出了本书的范围,但我们应该指出,即使是最通用的 httpd.conf 文件也会导致 Apache 生成两个日志:error_logaccess_log。这些日志可能出现在不同的位置,但通常在 Apache 安装的日志记录区域创建。(在 Unix 上,它们通常位于 /usr/local/apache2/logs/ 中。)

The error_log 描述了 Apache 在运行过程中遇到的任何内部错误。The access_log 文件记录 Apache 收到的每个传入 HTTP 请求。这使得您可以轻松地查看,例如,Subversion 客户端来自哪些 IP 地址、客户端使用服务器的频率、哪些用户已成功进行身份验证以及哪些请求成功或失败。

不幸的是,由于 HTTP 是一个无状态协议,即使是最简单的 Subversion 客户端操作也会生成多个网络请求。查看 access_log 并推断出客户端正在做什么非常困难,大多数操作看起来像一系列神秘的 PROPPATCHGETPUTREPORT 请求。更糟糕的是,许多客户端操作会发送几乎相同的请求序列,因此更难将它们区分开来。

mod_dav_svn 然而,可以帮助您。通过激活一个 操作日志 功能,您可以要求 mod_dav_svn 创建一个单独的日志文件,描述您的客户端执行的哪些高级操作。

为此,您需要使用 Apache 的 CustomLog 指令(在 Apache 的文档中详细说明)。请确保在您的 Subversion Location之外 调用此指令。

<Location /svn>
  DAV svn
  …
</Location>

CustomLog logs/svn_logfile "%t %u %{SVN-ACTION}e" env=SVN-ACTION

在此示例中,我们要求 Apache 在标准 Apache logs 目录中创建一个特殊的日志文件 svn_logfile%t%u 变量分别被请求的时间和用户名替换。真正重要的部分是 SVN-ACTION 的两个实例。当 Apache 看到该变量时,它会替换 SVN-ACTION 环境变量的值,该变量由 mod_dav_svn 在检测到高级客户端操作时自动设置。

因此,您无需解释像这样的传统 access_log

[26/Jan/2007:22:25:29 -0600] "PROPFIND /svn/calc/!svn/vcc/default HTTP/1.1" 207 398
[26/Jan/2007:22:25:29 -0600] "PROPFIND /svn/calc/!svn/bln/59 HTTP/1.1" 207 449
[26/Jan/2007:22:25:29 -0600] "PROPFIND /svn/calc HTTP/1.1" 207 647
[26/Jan/2007:22:25:29 -0600] "REPORT /svn/calc/!svn/vcc/default HTTP/1.1" 200 607
[26/Jan/2007:22:25:31 -0600] "OPTIONS /svn/calc HTTP/1.1" 200 188
[26/Jan/2007:22:25:31 -0600] "MKACTIVITY /svn/calc/!svn/act/e6035ef7-5df0-4ac0-b811-4be7c823f998 HTTP/1.1" 201 227
…

您可以浏览像这样的更易懂的 svn_logfile

[26/Jan/2007:22:24:20 -0600] - get-dir /tags r1729 props
[26/Jan/2007:22:24:27 -0600] - update /trunk r1729 depth=infinity
[26/Jan/2007:22:25:29 -0600] - status /trunk/foo r1729 depth=infinity
[26/Jan/2007:22:25:31 -0600] sally commit r1730

除了 SVN-ACTION 环境变量外, mod_dav_svn 还填充了 SVN-REPOSSVN-REPOS-NAME 变量,它们分别包含到存储库的文件系统路径和基本名称。您可能希望在您的 CustomLog 格式字符串中包含对一个或两个变量的引用,尤其是在将来自多个存储库的使用信息合并到单个日志文件中时。有关记录的所有操作的详尽列表,请参见 名为“高级日志记录”的部分

显然,Apache 记录的有关您的 Subversion 活动的信息越多,这些日志消耗的服务器磁盘空间就越多。高流量 Subversion 服务器每天生成数 GB 的日志信息并不罕见。显然,日志只有在能够被有意义地处理时才有价值,而巨大的日志文件很快就会变得难以管理。Apache HTTP Server 日志管理有各种标准方法,这些方法超出了本书的范围。鼓励管理员使用最适合他们自己的日志轮换和归档方法。

但是,如果 Subversion 产生的日志信息过多而无用怎么办?例如,在 名为“批量更新”的部分中,我们提到 Subversion 客户端可能会采取某些方法来进行签出和其他更新样式的操作,这些操作会导致服务器日志快速增长,因为对更新数据集的各个部分的请求会被单独记录(而在 Subversion 的先前版本中,它们可能不会被记录)。在这种情况下,您可以考虑使用一些 Apache 配置技巧来有选择地消除部分日志活动。

Apache HTTP Server 的 mod_setenvif 模块提供了一个 SetEnvIf 指令,该指令便于有条件地设置环境变量。事实证明, CustomLog 指令可以根据环境变量的状态有条件地记录请求。以下是一个示例配置,该配置指示服务器 记录针对私有 Subversion URL 的 GETPROPFIND 请求。

# Matches everything, just to initialize the "in_repos" variable.
SetEnvIf Request_URI "^" in_repos=0

# Set "in_repos" if this is a request for a private Subversion URL.
SetEnvIf Request_URI "/!svn/" in_repos=1

# Set "do_not_log" for non-public request types we don't care to log.
SetEnvIf Request_Method "GET" do_not_log
SetEnvIf Request_Method "PROPFIND" do_not_log

# Unset "do_not_log" for URLs that aren't private Subversion URLs.
SetEnvIf in_repos 0 !do_not_log

# Log requests, but only if "do_not_log" isn't set.
CustomLog logs/access_log env=!do_not_log

使用此配置, httpd 仍将记录针对公共 Subversion URL 的 GET 请求。这些是当某人直接浏览存储库时,Web 浏览器生成的请求。但是,针对所谓的 私有 Subversion URL 的 GETPROPFIND 请求(这些请求正是用于在签出操作期间获取每个文件的请求)将不会被记录。

直写代理

使用 Apache 作为 Subversion 服务器的一个好处是,它可以设置为简单的复制。例如,假设您的团队分布在全球四个办事处。Subversion 存储库只能存在于其中一个办事处,这意味着另外三个办事处将无法访问它——他们在更新和提交代码时可能会遇到明显更慢的流量和响应时间。一个强大的解决方案是建立一个系统,该系统由一个 Apache 服务器和几个 Apache 服务器组成。如果您在每个办事处放置一个从服务器,用户可以从最靠近他们的任何一个从服务器签出一个工作副本。所有读取请求都转到它们本地的从服务器。写入请求会自动路由到单个主服务器。当提交完成后,主服务器会自动使用 svnsync 复制工具将新修订版 推送 到每个从服务器。

此配置为您的用户创造了巨大的感知速度提升,因为 Subversion 客户端流量通常为 80–90% 的读取请求。如果这些请求来自 本地 服务器,那将是一个巨大的胜利。

在本节中,我们将引导您完成此单主/多从系统标准设置的过程。但是,请记住,您的服务器必须至少运行 Apache 2.2.0(已加载 mod_proxy)和 Subversion 1.5(mod_dav_svn)。

[Note] 注意

这只是一个 Subversion 直写代理配置的示例。还有其他方法。例如,主服务器不必将更改推送到每个从服务器,从服务器可以定期从主服务器拉取这些更改。或者,主服务器可以将更改推送到单个从服务器,然后该从服务器将相同的更改推送到下一个从服务器,依此类推。鼓励管理员使用本节来基本了解 Subversion WebDAV 代理部署场景中发生的事情,然后实施最适合其组织的特定方法。

配置服务器

首先,以通常的方式配置主服务器的 httpd.conf 文件。使存储库在某个 URI 位置可用,并根据您的需要配置身份验证和授权。完成此操作后,以完全相同的方式配置每个 服务器,但将特殊的 SVNMasterURI 指令添加到块中

<Location /svn>
  DAV svn
  SVNPath /var/svn/repos
  SVNMasterURI http://master.example.com/svn
  …
</Location>

此新指令告诉从服务器将所有写入请求重定向到主服务器。(这是通过 Apache 的 mod_proxy 模块自动完成的。)但是,普通的读取请求仍由从服务器服务。请确保您的主服务器和从服务器都具有匹配的身份验证和授权配置;如果它们不同步,可能会导致很大麻烦。

接下来,我们需要处理无限递归问题。在当前配置中,想象一下,当 Subversion 客户端对主服务器执行提交时会发生什么。提交完成后,服务器使用 svnsync 将新修订版复制到每个从服务器。但是,由于 svnsync 似乎只是执行提交的另一个 Subversion 客户端,因此从服务器将立即尝试将传入的写入请求代理回主服务器!结果可想而知。

解决此问题的方案是让主服务器将修订版推送到从服务器上的不同 <Location>。此位置配置为 代理任何写入请求,但接受来自(仅来自)主服务器 IP 地址的正常提交

<Location /svn-proxy-sync>
  DAV svn
  SVNPath /var/svn/repos
  Order deny,allow
  Deny from all
  # Only let the server's IP address access this Location:
  Allow from 10.20.30.40
  …
</Location>
设置复制

现在您已在主服务器和从服务器上配置了 Location 块,您需要配置主服务器以复制到从服务器。我们的演练使用 svnsync,该工具在 名为“使用 svnsync 进行复制”的部分中有更详细的说明。

首先,确保每个从服务器存储库都具有一个 pre-revprop-change 钩子脚本,该脚本允许远程修订版属性更改。(这是在 svnsync 的接收端执行的标准程序。)然后登录到主服务器,并将每个从服务器存储库 URI 配置为从本地磁盘上的主存储库接收数据

$ svnsync init http://slave1.example.com/svn-proxy-sync \
               file:///var/svn/repos
Copied properties for revision 0.
$ svnsync init http://slave2.example.com/svn-proxy-sync \
               file:///var/svn/repos
Copied properties for revision 0.
$ svnsync init http://slave3.example.com/svn-proxy-sync \
               file:///var/svn/repos
Copied properties for revision 0.

# Perform the initial replication

$ svnsync sync http://slave1.example.com/svn-proxy-sync \
               file:///var/svn/repos
Transmitting file data ....
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .......
Committed revision 2.
Copied properties for revision 2.
…

$ svnsync sync http://slave2.example.com/svn-proxy-sync \
               file:///var/svn/repos
Transmitting file data ....
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .......
Committed revision 2.
Copied properties for revision 2.
…

$ svnsync sync http://slave3.example.com/svn-proxy-sync \
               file:///var/svn/repos
Transmitting file data ....
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .......
Committed revision 2.
Copied properties for revision 2.
…

完成此操作后,我们将主服务器的 post-commit 钩子脚本配置为在每个从服务器上调用 svnsync

#!/bin/sh
# Post-commit script to replicate newly committed revision to slaves

svnsync sync http://slave1.example.com/svn-proxy-sync \
             file:///var/svn/repos > /dev/null 2>&1 &
svnsync sync http://slave2.example.com/svn-proxy-sync \
             file:///var/svn/repos > /dev/null 2>&1 &
svnsync sync http://slave3.example.com/svn-proxy-sync \
             file:///var/svn/repos > /dev/null 2>&1 &

每行末尾的额外内容不是必需的,但它们是让同步命令在后台运行的巧妙方法,这样 Subversion 客户端就不会无限期地等待提交完成。除了此 post-commit 钩子外,您还需要一个 post-revprop-change 钩子,以便当用户(例如)修改日志消息时,从服务器也会获得该更改

#!/bin/sh
# Post-revprop-change script to replicate revprop-changes to slaves

REV=${2}
svnsync copy-revprops http://slave1.example.com/svn-proxy-sync \
                      file:///var/svn/repos \
                      -r ${REV} > /dev/null 2>&1 &
svnsync copy-revprops http://slave2.example.com/svn-proxy-sync \
                      file:///var/svn/repos \
                      -r ${REV} > /dev/null 2>&1 &
svnsync copy-revprops http://slave3.example.com/svn-proxy-sync \
                      file:///var/svn/repos \
                      -r ${REV} > /dev/null 2>&1 &

我们这里遗漏的唯一事情是用户级锁定(svn lock 类型的)的处理方法。锁定在提交操作期间由主服务器强制执行;但有关锁定的所有信息都通过从服务器在读取操作(如 svn updatesvn status)期间分发。因此,完全正常运行的代理设置需要将锁定信息从主服务器完美地复制到从服务器。不幸的是,大多数人可能用来实现此复制的机制在某种程度上都存在缺陷[69]。许多团队根本不使用 Subversion 的锁定功能,因此这可能对您来说不是问题。遗憾的是,对于确实使用锁定的团队,我们没有关于如何优雅地解决此问题的建议。

注意事项

您的主/从复制系统现在应该可以使用了。但是,需要提醒您两点。请记住,这种复制在面对计算机或网络崩溃时并不完全健壮。例如,如果某个自动 svnsync 命令由于某种原因未能完成,从服务器将开始落后。例如,您的远程用户会看到他们已提交修订版 100,但当他们运行 svn update 时,他们的本地服务器会告诉他们修订版 100 还不存在!当然,下次发生提交并随后执行 svnsync 成功时,问题将自动得到解决——同步将复制所有等待的修订版。但是,您可能仍然希望设置某种非带外监控,以注意同步故障并强制 svnsync 在出现问题时运行。

写通代理部署模型的另一个限制涉及版本不匹配——即安装的 Subversion 版本——在主服务器和从服务器之间。每次 Subversion 的新版本发布可能会(而且经常会)在客户端和服务器之间使用的网络协议中添加新功能。由于功能协商针对从服务器进行,因此使用的是从服务器的协议版本和功能集。但是写操作被逐字传递给主服务器。因此,存在从服务器以对从服务器而言真实但对主服务器而言不真实的方式响应客户端的功能协商请求的风险,如果主服务器运行的是旧版本的 Subversion。这可能导致客户端尝试使用主服务器不理解的新功能,并最终失败。

Subversion 1.8 通过引入一个新的 Apache 配置指令 SVNMasterVersion 来帮助缓解这个问题。通过在每个从服务器上配置 SVNMasterVersion 并将其设置为运行在主服务器上的 Subversion 实例的发布版本,从服务器可以更准确地与客户端协商功能支持。

不幸的是,Subversion 1.7 不提供 SVNMasterVersion 配置指令,并且已知在这方面存在一些特定问题。如果您在预 1.7 主服务器之前部署了 Subversion 1.7 从服务器,您需要使用 SVNAdvertiseV2Protocol Off 指令配置从服务器的 Subversion <Location> 块。

[Tip] 提示

为了获得最佳结果,请尝试在主服务器和从服务器上运行相同版本的 Subversion。

其他 Apache 功能

Apache 作为健壮的 Web 服务器提供的许多功能也可以用于增强 Subversion 的功能或安全性。Subversion 客户端能够使用 SSL(安全套接字层,之前已讨论过)。如果您的 Subversion 客户端构建为支持 SSL,它可以使用 https:// 访问您的 Apache 服务器并享受高质量的加密网络会话。

同样有用的是 Apache 和 Subversion 关系的其他功能,例如指定自定义端口(而不是默认的 HTTP 端口 80)或用于访问 Subversion 存储库的虚拟域名,或者通过 HTTP 代理访问存储库的能力。

最后,因为 mod_dav_svn 使用 WebDAV/DeltaV 协议的子集进行通信,因此可以通过第三方 DAV 客户端访问存储库。大多数现代操作系统(Win32、OS X 和 Linux)都具有将 DAV 服务器安装为标准网络 共享文件夹 的内置功能。这是一个复杂的话题,但在实施后也令人惊叹。有关详细信息,请阅读 附录 C,_WebDAV 和自动版本控制_

请注意,可以对 mod_dav_svn 进行许多其他小的调整,这些调整可能不值得详细介绍。但是,对于有兴趣的人来说,我们在 “mod_dav_svn 配置指令”部分 提供了所有 httpd.conf 指令的完整列表,这些指令得到了 mod_dav_svn 的响应。

Subversion Apache HTTP Server 配置参考

在前面的部分中,我们提到了管理员可以在他们的 httpd.conf 文件中使用的许多指令来启用和配置他们的 Subversion 服务器提供服务,并在介绍每个指令的同时介绍它切换的功能。在本节中,我们将快速总结 所有 由作为标准 Subversion 发行版一部分提供的两个 Apache HTTP Server 模块支持的配置指令。

mod_dav_svn 配置指令

以下配置指令由 Subversion 的 Apache HTTP Server 模块 mod_dav_svn 识别和支持。

DAV svn

必须包含在 Subversion 存储库的任何 DirectoryLocation 块中。它告诉 httpd 使用 Subversion 后端让 mod_dav 处理所有请求。

SVNActivitiesDB directory-path

指定活动数据库应存储在文件系统中的位置。默认情况下,mod_dav_svn 在名为 dav/activities.d 的存储库中创建一个目录并使用它。使用此选项指定的路径必须是绝对路径。

如果为 SVNParentPath 区域指定,mod_dav_svn 将存储库的基名附加到此处指定的路径。例如

<Location /svn>
  DAV svn

  # any "/svn/foo" URL will map to a repository in 
  # /net/svn.nfs/repositories/foo
  SVNParentPath         "/net/svn.nfs/repositories"

  # any "/svn/foo" URL will map to an activities db in
  #  /var/db/svn/activities/foo
  SVNActivitiesDB       "/var/db/svn/activities"
</Location>
SVNAdvertiseV2Protocol On|Off

Subversion 1.7 的新增功能,它切换 mod_dav_svn 是否宣布它支持该版本中也引入的 HTTP 协议的新版本。大多数管理员不希望使用此指令(默认情况下为 On),而是选择享受新协议带来的性能优势。但是,在将服务器配置为写通代理到不支持新协议的其他服务器时,将此指令的值设置为 Off

SVNAllowBulkUpdates On|Off|Prefer

切换对更新样式请求的包含所有内容的响应的支持。Subversion 客户端使用 REPORT 请求从 mod_dav_svn 获取有关目录树检出和更新的信息。他们可以要求服务器以两种方式之一发送该信息:将整个树的信息包含在一个大型(批量)响应中,或者使用 skelta(树增量的骨架表示)包含足够的信息,让客户端知道要使用后续请求从服务器获取哪些 额外 数据。当此指令包含 Off 值时,mod_dav_svn 将始终仅使用 skelta 响应来响应这些 REPORT 请求,而与客户端请求的响应类型无关。

此指令的默认值为 On,它允许服务器使用客户端请求的响应类型(批量或 skelta)来回复更新请求。从 Subversion 1.8 开始,此指令还接受 Prefer 值,它类似于 On,但它还使服务器向客户端宣布它 更喜欢 处理批量更新请求。

大多数人根本不需要使用此指令。它主要存在于希望(出于安全或审计原因)强制 Subversion 客户端单独获取更新和检出所需的所有文件和目录的管理员,从而在 Apache 的日志中留下 GETPROPFIND 请求的审计跟踪。

SVNAutoversioning On|Off

当它的值为 On 时,允许来自 WebDAV 客户端的写请求导致自动提交。自动生成一个通用的日志消息并将其附加到每个修订版。如果您启用自动版本控制,您可能需要设置 ModMimeUsePathInfo On,以便 mod_mime 可以自动将 svn:mime-type 设置为正确的 MIME 类型(当然,mod_mime 能够做到这一点)。有关更多信息,请参见 附录 C,_WebDAV 和自动版本控制_。此指令的默认值为 Off

SVNCacheFullTexts On|Off

当设置为 On 时,这告诉 Subversion 在有足够的内存缓存可用时缓存内容全文,这可以为服务器提供显着的性能优势。(另请参见 SVNInMemoryCacheSize 指令。)此指令的默认值为 Off

SVNCacheTextDeltas On|Off

当设置为 On 时,这告诉 Subversion 在有足够的内存缓存可用时缓存内容增量,这可以为服务器提供显着的性能优势。(另请参见 SVNInMemoryCacheSize 指令。)此指令的默认值为 Off

SVNCompressionLevel level

指定通过网络发送文件内容时使用的压缩级别。值为 0 表示完全禁用压缩,9 是最大值。 5 是默认值。

SVNHooksEnv file-path

指定 Subversion 存储库钩子脚本环境配置文件的路径。此文件用于描述执行存储库钩子脚本时的初始环境。有关此功能的更多信息,请参见 “钩子脚本环境配置”部分

SVNIndexXSLT directory-path

指定目录索引的 XSL 转换的 URI。此指令是可选的。

SVNInMemoryCacheSize size

指定每个 Subversion 内存对象缓存的进程的最大大小(以千字节为单位)。默认值为 16384;使用 0 值完全停用此缓存。

SVNListParentPath On|Off

当设置为 On 时,允许 GET SVNParentPath,这会导致列出该路径下的所有存储库。默认设置是 Off

SVNMasterURI url

指定指向主 Subversion 存储库的 URI(用于写通代理)。

SVNMasterVersion X.Y

指定提供主存储库的 Subversion 实例的发布版本号(用于写通代理)。

SVNParentPath directory-path

指定文件系统中父目录的位置,其子目录是 Subversion 存储库。在 Subversion 存储库的配置块中,必须存在此指令或 SVNPath,但不能同时存在。

SVNPath directory-path

指定 Subversion 存储库文件在文件系统中的位置。在 Subversion 存储库的配置块中,必须存在此指令或 SVNParentPath 指令,但不能同时存在。

SVNPathAuthz On|Off|short_circuit

通过启用子请求 (On)、禁用子请求 (Off;参见 名为“禁用基于路径的检查”的部分) 或直接查询 mod_authz_svn (short_circuit) 来控制基于路径的授权。此指令的默认值为 On

SVNReposName name

指定 Subversion 存储库的名称,用于 HTTP GET 响应。此值将被附加到所有目录列表的标题(当您使用 Web 浏览器导航到 Subversion 存储库时会显示这些目录列表)。此指令是可选的。

[Note] 注意

当 Subversion 尝试匹配访问控制文件中的规则时,它不会使用通过此指令配置的存储库名称。该文件语法中使用的存储库名称始终从存储库 URL 派生。参见 名为“基于路径的访问控制入门”的部分 以了解更多信息。

SVNSpecialURI component

指定特殊 Subversion 资源的 URI 组件(命名空间)。默认值为 !svn,大多数管理员都不会使用此指令。仅当迫切需要在存储库中包含名为 !svn 的文件时才设置此项。如果在已在使用的服务器上更改此项,它将破坏所有未完成的工作副本,您的用户将拿着干草叉和火把追捕您。

SVNUseUTF8 On|Off

当设置为 On 时,mod_dav_svn 将使用以 UTF-8 编码的存储库根路径与挂钩脚本进行通信,并将期望这些脚本同样以 UTF-8 编码生成输出(例如错误消息)。此选项的默认值为 Off,这意味着 mod_dav_svn 假设其挂钩脚本交互使用 7 位 ASCII 编码。此选项从 Subversion 1.8 开始可用。

[Note] 注意

管理员应确保挂钩脚本的字符集和编码期望值与其可能被调用的所有方式匹配。例如,如果一个存储库由 httpdsvnserve 共同提供服务,如果为 mod_dav_svn 启用了此选项,svnserve 也应该配置为使用 UTF-8(通过在其环境中设置适当的区域设置)。此外,那些脚本将访问的包含非 ASCII 字符的本地文件系统路径(例如存储库根路径)必须在文件系统中正确编码,以匹配脚本的期望值。

mod_authz_svn 配置指令

以下配置指令由 mod_authz_svn 提供,它是 Subversion 基于路径的授权 Apache HTTP Server 模块。有关在 Subversion 中使用基于路径的授权的深入描述,请参见 名为“基于路径的授权”的部分

AuthzForceUsernameCase Upper|Lower

设置为 UpperLower,以便在检查授权之前对经过身份验证的用户名执行指定类型的案例转换。虽然用户名以区分大小写的方式与授权规则文件中引用的用户名进行比较,但此指令至少可以将大小写不一致的用户名规范化为一致的形式。

AuthzSVNAccessFile file-path

查询 file-path 以获取访问规则,这些规则描述了 Subversion 存储库中路径的权限。在 Subversion 存储库或存储库集合的配置块中,只能存在此指令或 AuthzSVNReposRelativeAccessFile 指令,但不能同时存在。

从 Subversion 1.8 开始,AuthzSVNAccessFile 接受存储在 Subversion 存储库中的文件的 URL - 既可以是应用访问规则的同一存储库,也可以是完全不同的存储库。

AuthzSVNAnonymous On|Off

设置为 Off,以禁用此模块的两种特殊情况行为:与 Satisfy Any 指令的交互以及即使没有 Require 指令也强制执行授权策略。此指令的默认值为 On

AuthzSVNAuthoritative On|Off

设置为 Off,以允许将访问控制传递给较低级别的模块。此指令的默认值为 On

AuthzSVNNoAuthWhenAnonymousAllowed On|Off

设置为 On,以抑制对匿名用户允许执行的请求进行身份验证和授权。此指令的默认值为 On

AuthzSVNReposRelativeAccessFile file-path

查询 file-path 以获取访问规则,这些规则描述了 Subversion 存储库中路径的权限。与 AuthzSVNAccessFile 不同,为 AuthzSVNReposRelativeAccessFile 指定的路径相对于文件系统中存储库的 conf/ 目录。换句话说,file-path 指定了一个每个存储库的文件,该文件必须通过配置块中所有存储库的相对路径才能访问。在 Subversion 存储库或存储库集合的配置块中,只能存在此指令或 AuthzSVNAccessFile 指令,但不能同时存在。此选项从 Subversion 1.7 开始可用。

从 Subversion 1.8 开始,AuthzSVNReposRelativeAccessFile 接受存储在 Subversion 存储库中的文件的 URL - 既可以是应用访问规则的同一存储库,也可以是完全不同的存储库。



[65] 他们真的很讨厌这样做。

[68] 虽然自签名证书仍然容易受到 中间人 攻击(在客户端首次看到证书之前),但与嗅探未受保护的密码相比,这种攻击对于偶然观察者来说要难得多。

[69] http://subversion.org.cn/issue3457 跟踪了这些问题。

TortoiseSVN 官方中文版 1.14.7 发布