PHP+Apache 作为正向/反向代理:如何在 PHP 中处理客户端请求和服务器响应?

PHP+Apache 作为正向/反向代理:如何在 PHP 中处理客户端请求和服务器响应?

我在正确配置 Apache mod_proxy.so 以使其按预期工作方面遇到了很多麻烦......

主要思想是在网络中的本地机器上创建一个代理,该代理将能够用 PHP 处理客户端请求(通过此 Apache 准备的代理连接的客户端)。此外,它还将能够用 PHP 处理服务器响应。

这是两个功能,它们彼此独立。

让我介绍一下我需要实现的目标:

替代文本

正如您在此处看到的,有两种方式:蓝色方式和红色方式。

对于蓝色的,我基本上在本地网络(家庭)上连接了一个客户端(机器 B - 手机),并将其配置为通过代理,即完全相同网络上的机器 A(个人计算机)。

因此我们假设(不是 DHCP):

机器 A:192.168.1.40 --> Apache 正在此机器上运行,并配置为监听端口 80。

机器B(手机):192.168.1.75 --> 配置为通过代理,其 IP 为 192.168.1.75,端口为 80(基本上是机器 A)。

正确配置 Apache 后,基本上就是从 httpd.conf 中删除 mod_proxy.so(主工作程序)、mod_proxy_connect.so(SSL、allowCONNECT 等)和 mod_proxy_http.so(处理 HTTP 请求/响应所需)行上的“#”,在我的例子中,有如下行:

# Implements a proxy/gateway for Apache.
Include "conf/extra/httpd-proxy.conf"

# Various default settings
Include "conf/extra/httpd-default.conf"

# Secure (SSL/TLS) connections
Include "conf/extra/httpd-ssl.conf"

这使我能够配置文件 httpd-proxy.conf 来准备正向代理或反向代理。

所以我不确定我需要的是正向代理还是反向代理。

对于正向代理我已经完成了以下操作:

<IfModule proxy_module>
<IfModule proxy_http_module>

#
# FORWARD Proxy
#

#ProxyRequests Off
ProxyRequests On
ProxyVia On

<Proxy *>
    Order deny,allow
#   Allow from all
    Deny from all
    Allow from 192.168.1
</Proxy>

</IfModule>
</IfModule>

它基本上将所有数据包正常地传递到服务器并返回到客户端。我可以通过查看 Apache 中的“access.log”完美地跟踪它(并且测试有效)。我用手机发出的任何请求都会出现在 Apache 日志中。所以它有效。

但问题来了:

  • 我需要处理这些客户端请求。而且我需要用 PHP 来做这件事。

我读了很多关于这个的资料。我详细阅读了 Apache 官方网站上关于 mod_proxy 的内容。我在论坛上搜索了很多,但都没有找到。

因此我想到了一个初步的近似值:

1) Apache 中的正向代理会传递所有数据包,但无法处理它们。这似乎是真的,那么,反向代理呢?

所以我设想了类似这样的事情:

ProxyRequests Off

<Proxy *>
Order deny,allow
Allow from all
</Proxy>

ProxyPass http://www.google.com http://www.yahoo.com
ProxyPassReverse http://www.google.com http://www.yahoo.com 

这只是一次测试,但当我尝试在手机上导航到 Google 时,这应该会导致我转到 Yahoo,不是吗?但事实并非如此。它不起作用。

因此,您确实看到,Apache 反向代理上的所有示例都如下:

ProxyPass /foo http://foo.example.com/bar
ProxyPassReverse /foo http://foo.example.com/bar

这意味着,本地环境中的任何类型的请求都将在远程位置得到解决。

但我需要的是相反的!当我在手机上请求远程站点时,我会在本地服务器(Apache 服务器)上解决此请求,并使用 PHP 模块对其进行处理。

因此,如果是正向代理,我需要先通过 PHP。如果是反向代理,我需要将“前进”方向更改为本地服务器,以便首先在 PHP 上进行处理。

然后想到了第二个选择:

2)我见过类似的事情:

<Proxy http://example.com/foo/*>
SetOutputFilter INCLUDES
</Proxy>

我开始寻找SetOutputFilter、SetInputFilter、AddOutputFilter 和 AddInputFilter

但我确实不知道如何使用它。

对我来说似乎不错,或者说是一个解决方案,因为有了这样的方法,我应该能够添加一个输入过滤器来处理 PHP 客户端请求并将我编程/想要的内容(而不是远程服务器响应)发送回客户端,即蓝色路径在架构上,我应该能够添加一个输出过滤器,这似乎使我能够在将远程服务器响应发送到客户端之前对其进行处理,这应该是红色路径在架构上。

红色路径,只是读取服务器响应并处理它们。仅此而已。蓝色路径是重要的。因为我将在处理请求后向客户端发送我想要的任何内容。

抱歉,这篇帖子太长了,但我需要尽我所能解释清楚。

我希望有人能理解我的问题并帮助我解决它!

答案1

@Sarek 是对的,但问题在于如何使用 PHP(来自 Apache 的 mod_proxy)处理代理请求,而不是 Apache 是否是合适的工具。

要使用 PHP 和 Apache 作为代理,我使用(它需要 mod_proxy 和 mod_rewrite)httpd.conf

# Forward proxy server
<VirtualHost *:8080>
    ProxyRequests On
    ProxyVia On
    
    <Proxy *>
        Order deny,allow
        Deny from all
        Allow from 192.168
        
        RewriteEngine On
        RewriteCond %{REQUEST_URI} !/pac.php
        RewriteRule ^ /endpoint.php [L]
    </Proxy>
</VirtualHost>

然后在/pac.php(定义聚合氯化铝文件内容):

<?php header('Content-Type: application/x-javascript-config') ?>
function FindProxyForURL(url, host)
{ 
    return "PROXY <?php echo $_SERVER['SERVER_ADDR'] ?>:<?php echo $_SERVER['SERVER_PORT'] ?>; DIRECT";
}
?>

它用于机器 B(代理客户端)上的代理配置。使用http://192.168.1.40:8080/pac.php。这样,客户端将始终对任何域/ip(如 127.0.0.1、localhost、*.local 等)使用代理。注意:IE 和 .Net Apps 仍然只需要写入localhost127.0.0.1FQDN 尾随点:http://localhost.http://127.0.0.1.

最后在/endpoint.php

<?php
// Don't handle domain existance
$url = $_SERVER['REQUEST_URI'];
$url_parts = parse_url($url);
// Some security checks (no local file...)
if(false === $url_parts || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], array('http', 'https'))){
    die();
}

$headers_raw = '';
foreach ($_SERVER as $name => $value)
{
    if (substr($name, 0, 5) == 'HTTP_')
    {
        $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
        $headers_raw .= $name . ': ' . $value . "\r\n";
    } else if ($name == "CONTENT_TYPE") {
        $headers_raw .= 'Content-Type: ' . $value . "\r\n";
    } else if ($name == "CONTENT_LENGTH") {
        $headers_raw .= 'Content-Length: ' . $value . "\r\n";
    }
}

// http://php.net/manual/en/context.http.php
$context = stream_context_create(array(
    'http' => array(
        'method' => $_SERVER['REQUEST_METHOD'],
        'header' => $headers_raw,
        'ignore_errors' => true,
        'content' => file_get_contents('php://input')
    )
));
$content = file_get_contents($url, false, $context);

$content_type = 'application/octet-stream';// "text/html"
$content_type_raw = $content_type;// "text/html; charset=UTF-8"
foreach($http_response_header as $response_header){
    if('Content-Type:' == substr($response_header, 0, 13)){
        $content_type_raw = substr($response_header, 14);
        $content_type = strstr($content_type_raw, ';', true);
        header($response_header);
    }
    elseif('Content-Encoding:' == substr($response_header, 0, 17) && 'gzip' == substr($response_header, 18, 4))
    {
        //Now lets uncompress the compressed data
        $content = gzinflate(substr($content, 10, -8));
    }
    elseif('Content-Length:' == substr($response_header, 0, 15))
    {
        //Skip it
    }
    else{
        header($response_header);
    }
}

// Content transforms
//var_dump($url_parts);exit();
if('text/html' == $content_type){
    echo str_replace('cat', 'dog', $content);
}else{
    echo $content;
}
?>

这是一个例子,不要在生产中使用它。它不处理任何无效/超时域/ip。

有了它,你可以重写请求的 URL(代理可以http://google.com/search?q=dog为 URL提供内容http://google.com/search?q=cat,一个有趣的笑话)并更新内容(如删除广告、插入 JS/CSS 等)

答案2

好的,首先:Apache 是错误的工具!

Apache 是一个 Web 服务器,而不是代理服务器。是的,它带有代理模块,但它首先是一个 Web 服务器。

相反,你应该研究像 squid 这样的真正的代理服务器。在 squid 中,你需要寻找一个名为“内容适配”的功能:

http://wiki.squid-cache.org/SquidFaq/ContentAdaptation

相关内容