我有一个用 react 编写的网站,现在我想在网站上添加一个博客部分。该博客将基于 wordpress。
React 应用程序在 docker 容器中运行,我使用 wordpress docker 容器来运行 wordpress 博客。
为了访问该网站,我使用另一个运行 apache 并充当反向代理的容器。
在 apache 容器的文件中httpd.conf
,我有以下部分:
<VirtualHost *:80>
<Location "/">
ProxyPreserveHost On
ProxyPass "${REACT_SERVER}/"
ProxyPassReverse "${REACT_SERVER}/"
</Location>
<Location /blog>
ProxyPreserveHost On
ProxyPass "${BLOG_SERVER}/"
ProxyPassReverse "${BLOG_SERVER}/"
ProxyPassReverseCookiePath "/" "/blog"
</Location>
# more config for handling websockets
</VirtualHost>
变量REACT_SERVER
来自BLOG_SERVER
环境。
我遇到的问题是,当我尝试访问博客时,apache 成功将我的请求重定向到内部 wordpress 站点,但是当 wordpress 执行自己的重定向时,它使用与 apache 相同的主机,但路径不以 开头/blog
,因此我的 react 应用程序尝试处理该请求,但最终放弃并自行重定向到主页。
下面是一个使用的示例curl
:
➜ curl -v http://localhost:3005/blog/
* Trying 127.0.0.1:3005...
* Connected to localhost (127.0.0.1) port 3005 (#0)
> GET /blog/ HTTP/1.1
> Host: localhost:3005
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Date: Fri, 20 Aug 2021 16:27:32 GMT
< Server: Apache/2.4.48 (Debian)
< X-Powered-By: PHP/7.4.22
< Expires: Wed, 11 Jan 1984 05:00:00 GMT
< Cache-Control: no-cache, must-revalidate, max-age=0
< X-Redirect-By: WordPress
< Location: http://localhost:3005/wp-admin/install.php
< Content-Length: 0
< Content-Type: text/html; charset=UTF-8
<
* Connection #0 to host localhost left intact
如您所见,在X-Redirected-By
部分之后,Location
以 开头,/wp-admin
而不是/blog/wp-admin
。
来自文档在ProxyPassReverse
:
例如,假设本地服务器有地址http://example.com/; 然后
ProxyPass "/mirror/foo/" "http://backend.example.com/" ProxyPassReverse "/mirror/foo/" "http://backend.example.com/" ProxyPassReverseCookieDomain "backend.example.com" "public.example.com" ProxyPassReverseCookiePath "/" "/mirror/foo/"
不仅会引起本地请求 http://example.com/mirror/foo/bar在内部转换为代理请求http://backend.example.com/bar(ProxyPass 在此处提供的功能)。它还负责处理服务器 backend.example.com 在重定向时发送的重定向 http://backend.example.com/bar到http://backend.example.com/quux. Apache httpd 将其调整为http://example.com/mirror/foo/quux在将 HTTP 重定向响应转发给客户端之前。请注意,用于构建 URL 的主机名是根据 UseCanonicalName 指令的设置来选择的。
看起来这就是让它发挥作用所需要的全部条件,但它仍然不起作用。
如果你想知道,是的我已经尝试过普通的(没有Location
指令):
ProxyPass "/blog/" "${BLOG_SERVER}/"
ProxyPassReverse "/blog/" "${BLOG_SERVER}/"
ProxyPassReverseCookiePath "/" "/blog"
# etc...
我也得到了同样的结果。
我错过了什么?
答案1
这个问题看起来更像是 Wordpress 的问题,而不是配置错误。您需要告诉 wordpress 它位于子目录中,因为现在默认的 wordpress .htaccess 文件会将您重定向到 http://localhost:3005/wp-admin/install.php ,因为它不知道它位于名为的目录中博客。
选项1。 尝试解决此问题的一种方法是告诉 wordpress,它在 wp-config.php 文件中有一个新的基本 URL
define('WP_HOME','http://example.com/blog');
define('WP_SITEURL','http://example.com/blog');
选项 2。 尝试处理此问题的另一种方法是编辑 wordpress 的 htaccess 文件
你的 wordpress 中的 htaccess 文件应该看起来像这样
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /blog/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]
这将是 wordpress docker 容器内存在的 htaccess
答案2
好的,我想我已经解决问题了。
Wordpress 使用两个变量来确定:
- 哪个 URL 指向您的 wordpress 网站的位置(
WP_HOME
) - 哪个 URL 用于为您的 WordPress 网站加载资源(
WP_SITEURL
)
对于 wordpress 容器,我要做的第一件事就是确保这些 URL 与内部容器 URL 匹配,即$BLOG_SERVER
。因为我使用 docker-compose,所以很容易通过参数使用环境变量注入此 URL WORDPRESS_CONFIG_EXTRA
。
wordpress-blog:
image: wordpress:5
depends_on:
- blog-db
environment:
WORDPRESS_DB_HOST: blog-db
WORDPRESS_DB_NAME: blog
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_CONFIG_EXTRA: |
define('WP_HOME', 'http://wordpress-blog');
define('WP_SITEURL', 'http://wordpress-blog');
volumes:
- wordpress:/var/www/html
现在已经完成,我们现在可以关注代理了。
在我采用完全反向代理之前,我假设代理会以某种方式接管每个请求/blog/
,并从代理站点返回页面,这些页面看起来就像是直接从 wordpress 提供的一样。我没有考虑到的一件事是,这个假设还假设服务器端呈现的页面。
从新的配置开始VirtualHost
,它现在看起来像这样:
<VirtualHost *:80>
ProxyPass "/blog/" "${BLOG_SERVER}/"
ProxyPass "/" "${REACT_SERVER}/"
<Location "/">
ProxyPreserveHost On
ProxyErrorOverride On
ProxyPassReverse "${DEV_SERVER}/"
</Location>
<Location "/blog/">
ProxyPreserveHost Off
ProxyPassReverse "${BLOG_SERVER}/"
ProxyPassReverseCookiePath "/" "/blog/"
ProxyErrorOverride On
ProxyHTMLEnable On
ProxyHTMLExtended On
ProxyHTMLURLMap "${BLOG_SERVER}/"
SetOutputFilter INFLATE;proxy-html;DEFLATE
# ProxyPassReverseCookieDomain "%{HTTP_HOST:${BLOG_SERVER}}" %{HTTP_HOST}
</Location>
</VirtualHost>
要使此代理开始像代理一样运行,我必须做的下一件事是添加以下行:
ProxyPreserveHost Off
这确保了我们从 wordpress 获得的所有响应/请求看起来不像是来自我们(代理)。当我们开始处理代理 html 时,原因很快就会显而易见。
接下来,ProxyPass
指令被移出容器Location
,并直接移入VirtualHost
。
ProxyPass "/blog/" "${BLOG_SERVER}/"
ProxyPass "/" "${REACT_SERVER}/"
原因是Location
块在匹配请求时非常晚,有时路径/
会胜过/blog/
路径。我需要它更可靠,所以我决定自行指定代理(我看到了一个例子这里),然后修改容器内的路径Location
。
此时,反向代理现在在职的!但是页面中的 html 有指向 wordpress 网站内部 url 的链接。这里是mod_proxy_html进来。它可用于重写 html 中的所有链接以指向反向代理。它发现任何指向内部博客站点的链接,该链接将被替换为使用反向代理的链接。
ProxyHTMLEnable On
ProxyHTMLExtended On
ProxyHTMLURLMap "${BLOG_SERVER}/"
SetOutputFilter INFLATE;proxy-html;DEFLATE
最后一行可能会造成瓶颈,因为它本质上会解压来自博客站点的有效负载,重写所有 URL 以指向反向代理,然后再次压缩它们。如果您不想这样做,另一种方法是使用:
RequestHeader unset Accept-Encoding
即使有了所有这些,解决方案仍然不完美,因为页面上加载的任何向内部站点发出请求的 javascript 文件都不会将其请求路由到代理。
解决此问题的一个方法是采用当前答案关于这个问题,并改为WP_SITEURL
直接指向反向代理。
另一个解决方案是使用服务工作者到拦截网络请求。我喜欢这个解决方案,因为它不会将博客网站与反向代理紧密耦合。我可以想象,将服务工作者注入从代理请求的任何 html 页面,并让该服务工作者拦截所有与内部博客网站 url 匹配的请求,并将其替换为反向代理 url,这个想法并不太牵强(呵呵)。
我都没有选择这两个。经过深思熟虑,我认为在子域中托管 wordpress 更符合我的需求。我可能会选择 blog.example.com 之类的域名,但那是另一天的工作了。
总之,反向代理很难用 apache 正确实现。我不知道 nginx 方面的情况是否更乐观,但也许有一天我们会检查一下。我所采用的解决方案假设服务器端内容,这已被证明是代理的完美选择,但遗憾的是,动态加载的内容将需要更多工作。
来源
- 如何使用反向代理正确处理相对 URL
- https://stackoverflow.com/questions/21548631/how-can-locationmatch-and-proxypassmatch-be-combined
- http://apache.webthing.com/mod_proxy_html/config.html
- https://wordpress.org/support/article/editing-wp-config-php/
- https://stackoverflow.com/questions/25857114/difference-between-wp-siteurl-and-wp-home
- https://medium.com/swlh/apache-reverse-proxy-content-from-different-websites-3e82df87a34a
- http://www.apachetutor.org/apps/reverseproxies
- https://stackoverflow.com/questions/14431090/proxyhtml-to-rewrite-url
- https://community.bitnami.com/t/how-to-change-url-from-to-blog/45697/3
- https://wordpress.org/support/article/giving-wordpress-its-own-directory/#method-i-without-url-change
已启用 html 代理的 Apache 模块
LoadModule deflate_module modules/mod_deflate.so
LoadModule xml2enc_module modules/mod_xml2enc.so
LoadModule proxy_html_module modules/mod_proxy_html.so