用 PHP 模拟 Apache 的 ProxyPassReverse

Posted

技术标签:

【中文标题】用 PHP 模拟 Apache 的 ProxyPassReverse【英文标题】:Mimic Apache's ProxyPassReverse with PHP 【发布时间】:2018-07-08 10:13:14 【问题描述】:

问题总结

假设您运行一个服务器并希望仅为使用用户名和密码登录的用户(经过身份验证的用户)提供来自 localhost 应用程序的内容。

应用程序在您的服务器上运行,但不向公众公开。它只能通过您服务器上的http://localhost:3838 获得。

为简单起见,假设这是您运行 localhost 应用程序的方式:

cd my-private-folder
python3 -m http.server 3838

经过身份验证的用户应该可以前往http://example.com/app查看 由 localhost 应用提供的内容。

应拒绝未经身份验证的用户访问并重定向到登录页面。

问题

Apache 有一个指令 ProxyPass 在将请求发送到目标之前修改请求中的 HTTP 标头。它还有一个指令ProxyPassReverse,它会在响应返回给用户之前修改响应中的标头。

有没有办法在 php 中模仿 Apache 的行为?

有没有使用Laravel PHP 框架的简单方法?

作为参考,有一个名为 oxy 的包在 Go 中实现了这个目标。我不知道 PHP 的等效包。


尝试 1 失败:使用路由

这是我在 Laravel 5.5 中尝试过的:

# /var/www/example.com/routes/web.php
Route::get('/app', function() 
    return Response::make(
        file_get_contents("http://localhost:3838/")
    );
);
# This does not work, because the HTTP header is not modified.
Route::get('/app/any', function($any) 
    return Response::make(
        file_get_contents("http://localhost:3838/$any")
    );
)->where('any', '.*');

这成功地将内容从localhost:3838 传递到example.com/app,但它未能传递应用程序请求的任何资源,因为HTTP 标头没有被修改。

例如:

localhost 应用使用 javascript 请求 /resource.jpg

我希望资源从http://localhost:3838/resource.jpg 传递到http://example.com/app/resource.jpg

相反,该应用发出失败的请求以获取 http://example.com/resource.jpg

要了解失败的原因,请参阅下面正确运行的 Apache 解决方案。 Apache 有一个名为 ProxyPassReverse 的指令修改 HTTP 标头,因此 localhost 应用程序从正确的 URL 请求资源。


使用 Apache

Apache 完美运行!但是,添加新用户需要在服务器上运行htpasswd 命令。

使用 Laravel,新用户应该可以在网站上自行注册。

没有 Laravel,你可以像这样使用 Apache 来保护应用程序:

# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
  ServerName example.com
  Redirect /app /app/
  ProxyPass /app/ http://localhost:3838/
  ProxyPassReverse /app/ http://localhost:3838/
  ProxyPreserveHost On
  <Location /app/>
      AuthType Basic
      AuthName "Restricted Access - Please Authenticate"
      AuthUserFile /etc/httpd/htpasswd.users
      Require user myusername 
      # To add a new user: htpasswd /etc/httpd/htpasswd.users newuser 
  </Location>
</VirtualHost>

这个相关答案帮助我更好地理解 ProxyPassReverse 指令的工作原理:

https://***.com/a/8141490/330558

【问题讨论】:

您能澄清一下“Laravel 不会重写 URL...”的意思吗?如果在/app/any 路由中输出dd("http://localhost:3838/$any");,你会看到什么? /app/any 路由中我添加了dd("http://localhost:3838/$any");。当我访问http://example.com/app/shared 时,我看到了"http://localhost:3838/shared" 澄清一下,localhost:3838 已经开发好了,你只是想在 Laravel 中做一个代理? @Diogo 是的,完全正确。 @DiogoSgrillo 这只是问题的一个症状。应用程序 (localhost:3838) 认为请求路径是 localhost:3838,因此当它需要将资产返回为 localhost:3838/app/resource.jpg 时,它会返回该路径下的资产(例如:localhost:3838/resource.jpg),应用程序需要知道它在 @ 下运行987654353@. 【参考方案1】:

如果问题仅出在资产上,您也可以将它们路由到 localhost:3838 应用程序:

# /var/www/example.com/routes/web.php
Route::get('/app/any', function($any) 
    return Response::make(
        file_get_contents("http://localhost:3838/$any")
    );
)->where('any', '.*');

Route::get('/assets', function($assets) 
    return Response::make(
        file_get_contents("http://localhost:3838/$assets")
    );
)->where('assets', '.*(png|jpe?g|js)');

【讨论】:

抱歉,这样不行。经过更多研究,我意识到我正在尝试模仿 Apache 指令 ProxyPassProxyPassReverse 的行为。这些指令在 HTTP 标头到达 localhost 应用程序之前修改它。当localhost 应用程序响应时,将再次修改标头,然后将其传递回用户的浏览器。因此,PHP 还必须修改 HTTP 标头以获得与 Apache 相同的行为。 file_get_contents 函数只是一个 hack,但它不会修改标题。【参考方案2】:

我认为 Jens Segers 有一个包可以让 PHP 模仿 ProxyPass 指令:https://github.com/jenssegers/php-proxy

这是来自 GitHub README 的示例:

use Proxy\Proxy;
use Proxy\Adapter\Guzzle\GuzzleAdapter;
use Proxy\Filter\RemoveEncodingFilter;
use Zend\Diactoros\ServerRequestFactory;

// Create a PSR7 request based on the current browser request.
$request = ServerRequestFactory::fromGlobals();

// Create a guzzle client
$guzzle = new GuzzleHttp\Client();

// Create the proxy instance
$proxy = new Proxy(new GuzzleAdapter($guzzle));

// Add a response filter that removes the encoding headers.
$proxy->filter(new RemoveEncodingFilter());

// Forward the request and get the response.
$response = $proxy->forward($request)->to('http://example.com');

// Output response to the browser.
(new Zend\Diactoros\Response\SapiEmitter)->emit($response);

【讨论】:

以上是关于用 PHP 模拟 Apache 的 ProxyPassReverse的主要内容,如果未能解决你的问题,请参考以下文章

PHP Apache 共享内存存储(如 apc_store/apc_fetch)有啥好的 Flask/Python/WSGI 模拟?

ejabberd - BOSH 模块未启动

php apache有啥用

mock数据(模拟后台数据)

怎样用php中的curl模拟登陆

我用的php+sql+apache集成环境,我能直接用IP访问域名A解析但是发现有时候用域名不能访问为啥??