用 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
指令的工作原理:
【问题讨论】:
您能澄清一下“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 指令ProxyPass
和 ProxyPassReverse
的行为。这些指令在 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 模拟?