将 Web 应用程序与 Apache 集成

作者:Andy Carlson

当您部署 Web 应用程序时,最终用户如何访问它?通常,Web 应用程序设置在网关设备之后,最终用户可以通过该网关设备访问它。Apache Web 服务器是 Linux 上常用作应用程序网关的产品之一。虽然它可以作为普通的 Web 服务器运行,但它也能够通过它连接到其他 Web 服务器。

在本文中,我将讨论将 Web 应用程序集成到 Apache 中需要做些什么。这包括集成 HTTP 协议功能、自定义内容以正确呈现以及重用配置片段。一旦您了解了这些基本的功能,您将拥有最大化 Web 应用程序可用性所需的工具。那么,让我们开始吧!

正则表达式速成课程

我在本文中通篇使用的一种机制是正则表达式(或 regex),可能需要简单介绍一下。Regex 用于定义要在 URL 中搜索的文本模式,或者在 HTML 或 JavaScript 等内容中查找和替换文本。文本处理命令 sed 使用 regex 进行搜索和替换。

对于下面的每个示例,将有三个部分:输入、regex 模式和输出。模式将应用于输入文本,并确定输出文本的值。

示例 1


Input:
  Name: Frank Sinatra
  Genre: Jazz
  Name: 2Pac
  Genre: Rap
  Name: Reel Big Fish
  Genre: Ska

Regex pattern: "^Name: "

Output:
  Name: Frank Sinatra
  Name: 2Pac
  Name: Reel Big Fish

此示例在输入文本中搜索与模式 "^Name: " 匹配的文本。此模式表示“查找每行开头为文本 'Name: ' 的行。”由于有两行以该文本开头,因此仅返回这两行。虽然“^”表示行的开头,“$”表示行的结尾。因此,如果您要应用模式“a$”,则会返回两行(Frank Sinatra 和 Ska)。让我们扩展该示例,并将示例 1 中的输入与新模式一起使用。

示例 2


Regex pattern: "^Name: [0-9]"

Output:
  Name: 2Pac

如您所见,我已将原始 regex 模式添加到末尾 [0-9]。这将搜索单个字符,该字符可以是 0 到 9 之间的任何数字,这就是为什么只返回“2Pac”这一行。您还可以使用字母字符指定范围([a-z][A-Z])。

除了模式选择之外,您还可以使用 regex 进行替换。regex 替换有两种格式:s|pattern|replace|modifier 或 s/pattern/replace/modifier。在 Apache 中,我发现使用管道样式替换更容易。示例 3 使用相同的输入和新模式。

示例 3


Regex pattern: "s|^(.*)Frank(.*)$|\1Dwezil\2|g"

Output:
  Name: Dwezil Sinatra
  Genre: Jazz
  Name: 2Pac
  Genre: Rap
  Name: Reel Big Fish
  Genre: Ska
  Name: Dwezil Zappa
  Genre: Unknown

此模式有很多需要剖析的地方。regex 的一个重要功能是能够匹配任何字符。点运算符将匹配任何一个字符。星号运算符将匹配 0 个或多个在其之前的任何字符或运算符。将这两个运算符放在一起可以匹配 0 个或多个任意字符。将它括在括号中允许匹配的文本在模式的替换部分中用变量表示。在本例中,\1 表示括号内的第一个文本块,\2 表示第二个。唯一显式匹配的字符是“Frank”。因此,包含“Frank”的行将被替换为“Frank”之前的所有内容(由 \1 表示)、“Dwezil”和“Frank”之后的所有内容(由 \2 表示)。如您所见,整个文本输入都已发送到输出,尽管已通过模式修改。

协议集成

当决定应用程序将受益于 Apache 集成时,它很可能位于与 Apache 分开的服务器上。要完全集成通过 HTTP 访问的应用程序,可以使用以下任何或所有模块:mod_rewritemod_proxymod_sslmod_headers。这些模块中的每一个都允许您自定义最终用户和 Web 服务器之间通信的方式,从修改 HTTP 标头数据到管理与其他服务器的代理连接。

首先,让我们看一下 mod_rewritemod_rewrite 模块中有很多指令,但我在这里只介绍一小部分:RewriteEngineRewriteCondRewriteRuleRewriteEngine 指令只是启用 URL 重写,其调用方式如下


RewriteEngine on

RewriteRule 允许服务器通过多种方式响应特定 URL 的 HTTP 请求,包括返回 HTTP 重定向(代码 301 或 302),这将最终用户重定向到指定的 URL 或将代理请求发送到后端服务器。以下是发出 HTTP 重定向的示例


RewriteRule /google http://www.google.com [R=301]

在本例中,当访问 /google 的 URL 时,服务器将响应 HTTP 301,将用户重定向到 http://www.google.com。此示例仅在请求 URL 完全等于“/google”时才有效。如果需要在任何以“/google”开头的 URL 上重定向,您可以使用 RewriteCond 定义条件重定向,如下所示


RewriteCond "%{REQUEST_URI}" "/google.*$"
RewriteRule "^.*$" http://www.google.com [R=301]

RewriteCond 指令有两部分:要检查的字符串值和要搜索的子字符串。在本例中,您正在 REQUEST_URI HTTP 会话变量中查找任何以“/google”开头的内容。如果满足该条件,则执行以下行中的 RewriteRule。因为您正在 RewriteCond 中确定目标 URL 的值,所以 RewriteRule 中目标 URL 的值定义为 "^.*$"

此处给出的示例都是面向用户的事件,例如 301 重定向。RewriteRule 指令也可用于将请求代理到服务器。这在后台完成,与 HTTP 重定向不同,因此请求在用户不知情的情况下转发。代理请求可以像下面的示例一样配置


RewriteRule "/home/(.*)$" http://back-end01.test:8080/$1 [P]

上面说明了虚拟根目录的示例。当用户访问 /home 下的任何内容时(注意“.*”表达式),请求将发送到端口 8080 上的 back-end01.test,位置设置为 /home 下的 URL 路径。例如,如果用户尝试访问 /home/test/image.jpg,则请求将发送到 back-end01.test:8080,位置为 /test/image.jpg。代理的 RewriteRule 也可以与 RewriteCond 结合使用以进行进一步的自定义。请注意,此语句仅代理 HTTP 请求。HTTP 响应的代理将需要 mod_proxy

通过 Apache 代理 HTTP 连接的另一个选项是 mod_proxy,它提供了 ProxyPassProxyPassReverseProxyPassMatch 以及许多其他指令,这些指令提供了更强大的代理选项。我在这里主要关注这三个指令。如前所述,RewriteRule 提供 HTTP 请求的代理。让我们比较一下已经给出的 RewriteRule 代理示例和 ProxyPass 的示例


ProxyPass /home http://back-end01.test:8080/

ProxyPass 语句提供了与 RewriteRule 语句大致相同的功能级别,但命令更简单。当请求进入任何以“/home”开头的 URL 时,请求标头将被重写,以便 http://back-end01.test:8080/ 正确接收请求。考虑以下 HTTP 请求的第一行


From user to server:    GET /home/test/image.jpg HTTP/1.1
From server to back-end:    GET /test/image.jpg HTTP/1.1

标头的第一行包含方法(在本例中为 GET)和正在请求的 URL。当服务器从客户端收到请求时,它会剥离 “/home”,如 ProxyPass 指令中所指定,并将请求转发到后端服务器。如果您希望代理响应数据包以及请求数据包,则可以将以下 ProxyPassReverse 语句与之前的 ProxyPass 语句配对


ProxyPassReverse /home http://back-end01.test:8080/

语法与 ProxyPass 完全相同,增加了 mod_proxy 配置的简易性。这将获取与 /home 的 HTTP 请求匹配的任何 HTTP 响应,并将响应转发回原始客户端。如果您需要添加一些程序化代理(类似于 RewriteCond),则可以使用 ProxyPassMatch。在实现正向/反向代理配置时,ProxyPassMatch 可以替换 ProxyPass。这是一个例子


ProxyPassMatch "^/home/([a-z0-9]*/docs)" http://docserver01.test:8080/$1
ProxyPassReverse /home http://docserver01.test:8080/

此示例表明在 /home 文件夹中,有许多子文件夹(假设是用户名),并且在每个子文件夹中都存在一个名为“docs”的文件夹。USERNAME/docs URL 存在于 docserver01.test:8080 的 Web 服务器根目录中,如服务器 URL 中的 $1 所示。ProxyPassReverse 的功能与上一个示例中相同。

在 Apache 中使用 SSL 保护网站是通过 mod_ssl 完成的。虽然我不会讨论从头配置 SSL,但有一些指令与代理的 SSL 连接有关:SSLProxyCheckPeerExpireSSLProxyCheckPeerNameSSLProxyCheckPeerCN。在后端服务器上使用自签名证书是一种常见的做法(前提是在面向用户的服务器上安装了有效证书),这些指令解决了使用自签名证书时可能出现的一些常见问题。这些指令中的任何一个都可以提供两个参数之一:“on”或“off”。如果设置为“off”,SSLProxyCheckPeerExpire 将跳过检查后端服务器上使用的 SSL 证书的到期日期。要避免检查证书的公用名或备用名称是否与用于访问后端的服务器名称匹配,请将 SSLProxyCheckPeerName 设置为“off”。在旧版本的 Apache 中,您或许可以使用 SSLProxyCheckPeerCN(设置为“off”)代替 SSLProxyCheckPeerName

除了重写 URL 之外,可能还需要重写 HTTP 请求或响应标头字段。在 Apache 中,这是通过 mod_headers 完成的。此模块中只有两个指令:HeaderRequestHeader。这些指令分别用于修改响应和请求标头字段。许多操作可以与这些指令中的任何一个一起使用,但在这里,让我们看一下 setedit 操作——例如


Header set ReceiveTime "%t"

此示例将添加和替换 HTTP 响应中名为 ReceiveTime 的任何现有标头,并为其提供服务器收到请求时的 UNIX 时间戳值(由 "%t" 表示)。

如果您需要替换来自后端服务器的标头的值,您可以使用 edit 操作。考虑以下示例


Header edit Location "^http://back-end01.test:8080/(.*)$"
 ↪"http://public.test/$1"

此示例将替换 HTTP 响应中的 Location 属性,该属性将存在于 301/302 重定向中。如果在 Location 标头的开头找到 http://back-end01.test:8080,它会将该部分替换为 “http://public.test”(面向用户的 URL)。

内容集成

从协议的角度来看,一旦远程应用程序与 Apache 服务器集成,可能需要集成内容。这通常会表现为编码到 HTML 或 JavaScript 中的 URL,这些 URL 特定于后端服务器,而不是面向用户的服务器。基本的需求是能够搜索和替换 HTML 或 JavaScript 内容的片段,以便在通过 Apache 代理访问时可以正确呈现和执行。完成此操作的模块是 mod_substitute,特别是 Substitute 指令。Substitute 允许对 HTTP 响应的有效负载数据执行简单的 regex 替换。

在尝试替换文本之前要考虑的一件事是,后端 Web 服务器是否在通过网络发送数据之前压缩数据。如果它压缩数据,您的 Substitute 语句可能不起作用,因为它将在二进制压缩数据中搜索 ASCII 文本。为了解决这个问题,您可以指示 Apache 解压缩数据,操作响应,然后再重新压缩它。这是使用 SetOutputFilter 指令完成的,它是 Apache 核心功能的一部分。以下是它的工作原理


SetOutputFilter INFLATE;SUBSTITUTE;DEFLATE

从左到右读取参数,这告诉 Apache 从后端服务器 INFLATE(解压缩)数据,执行替换,然后在将其返回给最终用户之前 DEFLATE(压缩)数据。

Substitute 语句使用 regex 替换表达式。正如我之前提到的,我发现在 Apache 中使用管道样式替换表达式更容易。回顾一下,语法是 s|search|replace|options。我倾向于使用的两个常见选项:“i”,表示不区分大小写的搜索;以及“n”,允许将搜索和替换值作为 regex 处理。这是一个常见的用例示例


Substitute "s|(href="http)(://)back-end01.test:8080|$1s$2public.test|in"

对于此示例,我们假设面向用户的站点 (public.test) 运行 HTTPS,后端服务器 (back-end01.test) 在端口 8080 上运行 HTTP。如果后端 Web 服务器返回特定于自身的超链接,而不是面向用户的站点的超链接,这将是一个解决方案。在 regex 替换的搜索部分,这会将文本分成两组括号:(href=\"http)(://)。这些是您要在 regex 的替换部分中保留的文本块。在替换中,您在 http 之后插入一个 “s”,并将主机名/端口替换为面向用户的站点名称。处理后,生成的字符串将是 href="https://public.test。这将更新使用 “href” 属性的超链接(<a> 和 <link>)。对于 <img> 和 <script> 标记,您可以使用相同的 Substitute 语句并将 “href” 替换为 “src”。另一个需要考虑的问题是考虑使用双引号或单引号分隔属性值(href='href=")。

Substitute 的另一个应用是在不操作原始源代码的情况下扩展页面的功能。考虑以下示例


Substitute "s|(<body.*>)|\1<div style=\"font-size:14pt;
↪font-weight:bold;background-color:#ff0000;color:
↪#ffffff;display:block;text-align:center;\">This site
 ↪will be down for 24 hours beginning at 8 pm tonight</div>|in"

如果网站需要离线进行维护,这是一种简单的方法,可以在不修改应用程序本身的情况下提醒用户群体有关中断的信息。此示例只是在页面顶部(紧跟在 <body> 标记之后)插入一个红色条,其中显示有关中断的信息。根据页面的呈现方式,您可能需要选择另一个标记来作为起始点,而不是 <body>。

简化未来的集成

如果您只有少量语句,则此处介绍的所有主题都可以相对轻松地配置和维护。在现实世界中,通常会有许多站点使用类似的配置,并且必须为每个站点定义功能可能很耗时,并且可能导致错误。幸运的是,Apache 提供了一种机制,可以通过使用 mod_macro 在整个配置中重复功能。Apache 配置中的 <Macro> 指令的功能与函数或子例程非常相似。一旦定义了宏,就可以根据需要多次引用它,使您可以在配置中的一个位置维护您的详细功能。这是一个宏示例


<Macro RedirectSecure $host $path>
        RewriteCond "%{REQUEST_URI}" "^$path"
        RewriteRule "^/(.*)$" "https://$host/$1"
</Macro>

调用时,此宏将定义一个 RewriteCondRewriteRule,如果它们访问以 $path 参数的值开头的 URL,则会将用户重定向到 http://$host/$1,其中 $host 是指定为宏参数的主机名,$1 是整个 URL 路径。以下语法将用于调用此宏


Use RedirectSecure public.test /users

需要考虑的是调用宏的 Apache 配置中的位置。例如,RewriteRule 不能在 <VirtualHost> 块之外调用。因此,如果宏在 <VirtualHost> 块之外调用,Apache 将抛出错误并且不会启动。这是另一个例子


<Macro ReplaceContentURL $backendurl $publicurl>
        Substitute "s|(href=\")$backendurl|$1$publicurl|in"
        Substitute "s|(src=\")$backendurl|$1$publicurl|in"
</Macro>

此宏扩展了我之前介绍的 URL 替换。这将搜索 “href” 和 “src” 的标记属性,并将后端服务器的超链接替换为面向用户的服务器的超链接。以下是如何调用它的示例


Use ReplaceContentURL http://back-end01.test:8080 https://public.test

这将搜索 http://back-end01.test:8080,以 href="src=" 开头,并将 URL 替换为 https://public.test。宏可以用于 Apache 配置的任何部分。它们可以用于执行此处所示的小任务以及整个站点配置。尽管宏非常简单,但它们使大量难以维护的配置文件与简化的可重用配置之间有所不同。

至此,您已经掌握了在 Apache 中集成 HTTP、自定义内容和复制配置的一些基本知识。尽管此处未涵盖许多指令和模块,但这将是一个很好的起点,可以帮助您开始通过 Apache 访问您的应用程序。

资源

以下是我发现有用的一些文章以及我编写的一些示例 Apache 配置。

Apache 模块参考 (2.2): https://httpd.apache.ac.cn/docs/2.2/mod

Apache 模块参考 (2.4): https://httpd.apache.ac.cn/docs/2.4/mod

Git Instaweb 反向代理: http://git.andydoestech.com/git/scripts/.git/tree/config/gitreverseproxy.conf

Monit 反向代理: http://git.andydoestech.com/git/scripts/.git/tree/config/monit.conf

Adobe Experience Manager Apache 配置: http://git.andydoestech.com/git/aem-dispatcher-config/.git/tree

Andy Carlson 在 IT 行业工作了 15 年,从事网络和服务器管理以及偶尔的编码工作。他很庆幸选择了自己热爱、成长和学习的职业。他目前与妻子、三个女儿和儿子居住在俄亥俄州辛辛那提。他的家人目前正在进行国际收养两个孩子的程序。他喜欢弹吉他、编码以及与家人和朋友共度时光。

加载 Disqus 评论