PHP中的HTTP_HOST和SERVER_NAME之间的区别是什么?

$_SERVER['HTTP_HOST']$_SERVER['SERVER_NAME']在PHP中的区别是什么?

什么时候你会考虑使用其中一种而不是另一种,为什么?

455963 次浏览

看我想知道什么了。SERVER_NAME是服务器的主机名,而HTTP_HOST是客户端连接到的虚拟主机。

HTTP_HOST是从HTTP请求头中获得的,这是客户端实际用作请求的“目标主机”。SERVER_NAME在服务器配置中定义。使用哪一个取决于您需要它做什么。然而,您现在应该意识到,一个是客户端控制的值,因此在业务逻辑中使用可能不可靠,而另一个是服务器控制的值,它更可靠。但是,您需要确保所讨论的web服务器正确配置了SERVER_NAME。以Apache HTTPD为例,下面是它的文档的摘录:

如果没有指定ServerName,那么服务器将尝试通过对IP地址执行反向查找来推断主机名。如果ServerName中没有指定端口,那么服务器将使用传入请求的端口。为了获得最佳的可靠性和可预测性,应该使用ServerName指令指定显式主机名和端口。


更新:检查Pekka对你问题的回答后,其中包含一个链接到bobince的回答, PHP将总是返回HTTP_HOST的值为SERVER_NAME,这与我自己的PHP 4。Apache HTTPD 1.2。根据几年前的XAMPP经验,我从Windows XP (Apache HTTPD 2.2.1, PHP 5.2.8)上的当前XAMPP环境中吹了一些灰尘,启动它,创建了一个PHP页面,打印这两个值,使用URLConnection创建了一个Java测试应用程序来修改Host头,测试告诉我这确实是(错误的)情况。

在第一次怀疑PHP和挖掘一些PHP错误报告关于主题后,我了解到问题的根源在于使用的web服务器,当SERVER_NAME被请求时,它错误地返回了HTTP Host报头。所以我深入研究了Apache HTTPD错误报告使用不同的关键词关于主题,我终于找到了SERVER_NAME0。这种行为是在Apache HTTPD 1.3左右引入的。你需要将SERVER_NAME1指令设置为httpd.conf中的ServerName中的<VirtualHost>条目中的on(也检查SERVER_NAME2底部的警告!)

<VirtualHost *>
ServerName example.com
UseCanonicalName on
</VirtualHost>

这对我很管用。

总之,SERVER_NAME更可靠,但您在服务器配置上是依赖 !

HTTP_HOST是客户端发送的目标主机。用户可以自由操作。向您的站点发送请求,要求将HTTP_HOST值设置为www.stackoverflow.com是没有问题的。

SERVER_NAME来自服务器的VirtualHost定义,因此被认为更可靠。然而,它也可以在与您的web服务器设置相关的某些条件下从外部操纵:请参阅这个SO问题,它处理了这两个变体的安全方面。

你不应该依赖任何一个来保证安全。也就是说,用什么真的取决于你想做什么。如果您想确定脚本运行在哪个域上,只要来自恶意用户的无效值不能破坏任何东西,就可以安全地使用HTTP_HOST

如果你想检查server.php或其他什么,你想用下面的方法调用它:

<?php
phpinfo(INFO_VARIABLES);
?>

<?php
header("Content-type: text/plain");


print_r($_SERVER);
?>

然后使用您站点的所有有效url访问它,并检查差异。

我花了一段时间才明白人们所说的“SERVER_NAME更可靠”是什么意思。我使用共享服务器,不能访问虚拟主机指令。因此,我在.htaccess中使用mod_rewrite将不同的# eyz2映射到不同的目录。在这种情况下,HTTP_HOST是有意义的。

如果使用基于名称的虚拟主机,情况也类似:虚拟主机中的ServerName指令只是说明哪个主机名将映射到这个虚拟主机。底线是,在这两种情况下,客户端在请求期间提供的主机名(HTTP_HOST)必须与服务器中的名称匹配,该名称本身映射到一个目录。映射是用虚拟主机指令完成的还是htaccess mod_rewrite规则完成的在这里是次要的。在这些情况下,HTTP_HOST将与SERVER_NAME相同。我很高兴Apache是这样配置的。

的确,可能有一些特殊的配置很重要。

因此,从现在开始,我将使用SERVER_NAME,以防我的代码被移植到这些特殊配置中。

请注意,如果你想使用IPv6,你可能想使用HTTP_HOST而不是SERVER_NAME。如果你输入http://[::1]/,环境变量将如下:

HTTP_HOST = [::1]
SERVER_NAME = ::1

这意味着,如果你做一个mod_rewrite,你可能会得到一个糟糕的结果。SSL重定向示例:

# SERVER_NAME will NOT work - Redirection to https://::1/
RewriteRule .* https://%{SERVER_NAME}/


# HTTP_HOST will work - Redirection to https://[::1]/
RewriteRule .* https://%{HTTP_HOST}/

这只适用于在没有主机名的情况下访问服务器。

正如我在这个答案中提到的,如果服务器运行在不是80的端口上(在开发/内网机器上可能很常见),那么HTTP_HOST包含端口,而SERVER_NAME不包含端口。

$_SERVER['HTTP_HOST'] == 'localhost:8080'
$_SERVER['SERVER_NAME'] == 'localhost'

(至少这是我在Apache基于端口的虚拟主机中注意到的)

注意,在HTTPS上运行时HTTP_HOST 包含:443(除非您在非标准端口上运行,我还没有测试过)。

正如其他人所指出的,这两者在使用IPv6时也有所不同:

$_SERVER['HTTP_HOST'] == '[::1]'
$_SERVER['SERVER_NAME'] == '::1'

正如balusC所说,SERVER_NAME是不可靠的,可以在apache配置中更改,服务器的服务器名配置以及您和服务器之间的防火墙。

以下函数总是返回没有端口的真实主机(用户键入的主机),几乎是可靠的:

function getRealHost(){
list($realHost,)=explode(':',$_SERVER['HTTP_HOST']);
return $realHost;
}

假设你有一个简单的设置(CentOS 7, Apache 2.4。x和PHP 5.6.20),只有一个网站(不假设虚拟主机)…

在PHP的意义上,$_SERVER['SERVER_NAME']是PHP在httpd.conf中根据Apache配置(**ServerName**指令和UseCanonicalName On)在$_SERVER超全局中注册的元素(可能来自包含的虚拟主机配置文件,等等…)HTTP_HOST派生自HTTP的host报头。将其视为用户输入。使用前进行筛选和验证。

下面是一个示例,我使用$_SERVER['SERVER_NAME']作为比较的基础。下面的方法来自我创建的名为ServerValidator (Validator的子类)的具体子类。ServerValidator在使用$_SERVER中的6或7个元素之前检查它们。

在确定HTTP请求是否是POST时,我使用了这个方法。

public function isPOST()
{
return (($this->requestMethod === 'POST')    &&  // Ignore
$this->hasTokenTimeLeft()            &&  // Ignore
$this->hasSameGETandPOSTIdentities() &&  // Ingore
($this->httpHost === filter_input(INPUT_SERVER, 'SERVER_NAME')));
}

在调用此方法时,所有相关$_SERVER元素的过滤和验证(以及相关属性的设置)都已经完成。

台词……

($this->httpHost === filter_input(INPUT_SERVER, 'SERVER_NAME')

... 检查$_SERVER['HTTP_HOST']值(最终来自请求的host HTTP头)是否与$_SERVER['SERVER_NAME']匹配。

现在,我使用超全局语言来解释我的例子,但这只是因为有些人不熟悉INPUT_GETINPUT_POSTINPUT_SERVER

底线是,我不处理我的服务器上的POST请求,除非所有四个条件得到满足。因此,就POST请求而言,未能提供HTTP host报头(前面已经测试过了)将厄运标记为严格的HTTP 1.0浏览器。此外,在ServerName0中为ServerName请求的主机必须与值匹配,以及在$_SERVER超全局中为$_SERVER('SERVER_NAME')的值。同样,我将使用INPUT_SERVER和PHP过滤器函数,但您明白我的意思。

请记住,Apache经常在标准的重定向中使用ServerName(例如在URL后面留下斜杠:例如,http://www.example.com变成http://www.example.com/),即使您没有使用URL重写。

我使用$_SERVER['SERVER_NAME']作为标准,而不是$_SERVER['HTTP_HOST']。在这个问题上有很多争论。$_SERVER['HTTP_HOST']可以是空的,所以这不应该作为创建代码约定(如上面的公共方法)的基础。但是,仅仅因为它们都是集合并不能保证它们是相等的。测试是最好的方法(记住Apache版本和PHP版本)。

$ _SERVER(“SERVER_NAME”)是基于您的web服务器配置。 $_SERVER['HTTP_HOST']是基于客户端的请求。

我对所有答案都不满意。他们中的一些人是正确的,但没有讲述整个故事,没有把问题说清楚。

无论您使用哪个http服务器,HTTP_HOST都应该包含从客户端发送的http头Host中的原始值。因此,用户控制的数据不应该被信任。

变量SERVER_NAME是在服务器配置中配置的,它可能不会指向正确的URL。例如,在您的web服务器前面可能有一个反向代理,SERVER_NAMEserver1server2,但您不想将用户重定向到server1,而是重定向到用户友好的主机。

因此,HTTP_HOST是更可靠的变量,因为您可能希望客户端请求的主机访问您的PHP应用程序。您不需要比较它们来确保这是一个有效值(它们不一定相等)。有两种方法确保该值有效:

  1. 将该值与有效值列表进行比较(您需要知道有效值)
  2. 如果值不正确,请确保web服务器返回错误

第一个很容易理解,但在实际场景中可能会有问题(在您的开发环境中是这样,在登台环境中是这样,在生产环境中是这样……等等)。这意味着您需要知道在PHP中什么对这个环境有效。

第二个是关于服务器配置的:VirtualHost是一个概念,用于http服务器从同一台服务器传递多个网站。由于虚拟主机是由http报头Host(不区分大小写)选择的,客户端不能控制使用哪个虚拟主机,除非修改主机。当只配置了一个虚拟主机时,每个值都将使用这个虚拟主机。您需要配置第二个默认的虚拟主机(如果没有其他虚拟主机匹配),并且总是返回一个错误(例如"not found"或“forbidden")。