For的一些理解,用定制的PHP应用程序来获取Web服务器的状态信息
分类:微服架构

大多数网站托管公司都支持客户对Web站点统计数据的访问,但是你往往会觉得服务器所产生的状态信息不够全面。例如,配置不正确的Web服务器不能识别某些文件类型,这些类型的文件就不会出现在状态信息之中。幸好,你可以用PHP来定制状态信息收集程序,这样你就可以获取你所需要的信息了。

X-Forwarded-For 是一个 HTTP 扩展头部,主要是为了让 Web 服务器获取访问用户的真实 IP 地址(其实这个真实未必是真实的,后面会说到)。

在PHP获取客户端IP中常使用 $_SERVER["REMOTE_ADDR"] 。 但如果客户端是使用代理服务器来访问,那取到的是代理服务器的 IP 地址,而不是真正的客户端 IP 地址。要想透过代理服务器取得客户端的真实 IP 地址,就要使用 $_SERVER["HTTP_X_FORWARDED_FOR"] 来读取。 但只有客户端使用“透明代理”的情况下,$_SERVER["HTTP_X_FORWARDED_FOR"] 的值才是客户端真正的IP(如果是多层代理,该值可能是由客户端真正IP和多个代理服务器的IP组成,由逗号“,”分隔)。 而在“匿名代理”、“欺骗性代理”的情况下是代理服务器的IP值(如果是多层代理,该值可能由多个代理服务器的IP组成,由逗号“,”分隔)。 在“高匿名代理”的情况下是空值。

公共日志文件格式的结构

那为什么 Web 服务器只有通过 X-Forwarded-For 头才能获取真实的 IP?
这里用 PHP 语言来说明,不明白原理的开发者为了获取客户 IP,会使用 $_SERVER['REMOTE_ADDR'] 变量,这个服务器变量表示和 Web 服务器握手的 IP 是什么(这个不能伪造)。
但是很多用户都通过代理来访问服务器的,那么假如使用该全局变量,PHP获取到的 IP 就是代理服务器的 IP(不是用户的)。

关于HTTP头信息中的REMOTE_ADDR、HTTP_FORWARDED_FOR值,分析如下,假设客户端真实IP是221.5.252.160:

CLF最初是NCSA为HTTPd而设计的。CERN HTTPd是一个由万维网联盟维护的公共域Web服务器。W3C网站列出了该日志文件规范。基于微软和UNIX的Web服务器都可以生成CLF格式的日志文件。CLF格式如下: Host IdentAuthuserTime_Stamp "request" Status_codeFile_size

可能很多人看的晕乎乎的,那么看看一个请求可能经过的路径:

一、没有使用代理服务器的PHP获取客户端IP情况:复制代码 代码如下:REMOTE_ADDR = 221.5.252.160HTTP_VIA=没数值或者不显示HTTP_X_FORWARDED_FOR = 没数值或不显示

例如: 21.53.48.83 - - [22/Apr/2002:22:19:12 -0500] "GET /cnet.gif HTTP/1.0" 200 8237

客户端=>(正向代理=>透明代理=>服务器反向代理=>)Web服务器

二、使用透明代理服务器的情况:Transparent Proxies复制代码 代码如下:REMOTE_ADDR = 最后一个代理服务器 IPHTTP_VIA=代理服务器IPHTTP_X_FORWARDED_FOR = 客户端真实 IP (经过多个代理服务器时,这个值类似:221.5.252.160, 203.98.182.163, 203.129.72.215)

下面是日志条目的细目分类:

其中正向代理、透明代理、服务器反向代理这三个环节并不一定存在。

这类代理服务器还是将客户端真实的IP发送给了访问对象,无法达到隐藏真实身份的目的。

Host是网站访问者的IP地址或者DNS名;在上面的例子中,它是21.53.48.83。 Ident是该访客的远端身份。破折号表明“未指定”。 Authuser是用户ID。 Time_Stam是服务器以“日/月/年”这种格式返回的时间。 Request是网站访问者的HTTP请求,例如GET或者POST。 Status_Code是服务器所返回的状态代码,例如:200代表“正确――浏览器请求成功”。 File_Size是用户所请求文件的大小。在本例中,它为 8237字节。

  • 什么是正向代理呢,很多企业会在自己的出口网关上设置代理(主要是为了加速和节省流量)。
  • 透明代理可能是用户自己设置的代理(比如为了翻墙,这样也绕开了公司的正向代理)。
  • 服务器反向代理是部署在 Web 服务器前面的,主要原因是为了负载均衡和安全考虑。

三、使用普通匿名代理服务器的PHP获取客户端IP情况:Anonymous Proxies复制代码 代码如下:REMOTE_ADDR = 最后一个代理服务器 IPHTTP_VIA=代理服务器IPHTTP_X_FORWARDED_FOR = 代理服务器 IP (经过多个代理服务器时,这个值类似:203.98.182.163, 203.98.182.163, 203.129.72.215)

服务器状态代码

现在假设几种情况:

这种情况下隐藏了客户端的真实IP,但是向访问对象透露了客户端是使用代理服务器访问它们的。

你可以在HTTP标准中找到W3C所开发的服务器状态代码规范。这些由服务器所产生的状态代码表示了浏览器和服务器之间的数据传输成功与否。这些代码一般传递给浏览器或者添加到服务器日志中去。

  • 假如客户端直接连接 Web 服务器(假设 Web 服务器有公网地址),则 $_SERVER['REMOTE_ADDR'] 获取到的是客户端的真实 IP 。
  • 假设 Web 服务器前部署了反向代理(比如 Nginx),则 $_SERVER['REMOTE_ADDR'] 获取到的是反向代理设备的 IP(Nginx)。
  • 假设客户端通过正向代理直接连接 Web 服务器(假设 Web 服务器有公网地址),则 $_SERVER['REMOTE_ADDR'] 获取到的正向代理设备的 IP 。

四、使用欺骗性代理服务器的情况:Distorting Proxies复制代码 代码如下:REMOTE_ADDR = 代理服务器 IPHTTP_VIA=代理服务器IPHTTP_X_FORWARDED_FOR = 随机的 IP(经过多个代理服务器时,这个值类似:220.4.251.159, 203.98.182.163, 203.129.72.215)

收集数据

其实这里的知识点很多,记住一点就行了,$_SERVER['REMOTE_ADDR'] 获取到的 IP 是 Web 服务器 TCP 连接的 IP(这个不能伪造,一般 Web 服务器也不会修改这个头)。

这种情况下同样透露了客户端是使用了代理服务器,但编造了一个虚假的随机IP代替客户端的真实IP来欺骗它。

创建我们的自定义应用程序的第一步就是获取用户数据。每当用户选择网站的某个资源时,我们就希望创建一个对应的日志条目。幸好,服务器变量的存在使得我们能够查询用户浏览器并获取数据。

X-Forwarded-For

从上面大家也看出来了,因为有了各种代理,才会导致 REMOTE_ADDR 这个全局变量产生了一定的歧义,为了让 Web 服务器获取到真实的客户端 IP,X-Forwarded-For 出现了,这个协议头也是由 Squid 起草的(Squid 应该是最早的代理软件之一)。

这个协议头的格式:

X-Forwarded-For: client, proxy1, proxy2

client 表示用户的真实 IP,每经过一次代理服务器,代理服务器会在这个头增加用户的 IP(有点拗口)。
注意最后一个代理服务器请求 Web 服务器的时候是不会将自己的 IP 附加到 X-Forwarded-For 头上的,最后一个代理服务器的 IP 地址应该通过$_SERVER['REMOTE_ADDR']获取。

举个例子:
用户的 IP 为(A),分别经过两个代理服务器(B,C),最后到达 Web 服务器,那么Web 服务器接收到的 X-Forwarded-For 就是 A,B。

那么 PHP 如何获取真实客户端 IP 呢?

$ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? trim($_SERVER['HTTP_X_FORWARDED_FOR']) : '';
if (!$ip) {
    $ip = isset($_SERVER['REMOTE_ADDR']) ? trim($_SERVER['REMOTE_ADDR']) : '';
}
$a = explode('|', str_replace(',', '|', $ip));
$ip = trim($a[0]);

这里预先说明下,假设这两个代理服务器都是好的代理服务器,没有伪造 HTTP_X_FORWARDED_FOR。

五、使用高匿名代理服务器的PHP获取客户端IP情况:High Anonymity Proxies 复制代码 代码如下:REMOTE_ADDR = 代理服务器 IP

报头中的服务器变量携带了从浏览器传递到服务器的信息。REMOTE_ADDR就是一个服务器变量的例子。这个变量返回了用户的IP地址: 例子输出:27.234.125.222

配置反向代理

上面一直在说代理,大家可能觉得这到底有啥用?不同类型的代理有不同的目的,对于正向代理来说主要是为了加速并且让局域网的用户有一个真实的 IP 地址,而透明代理则主要是为了一些其他的目的(比如就是不想让别人知道我的 IP),而反向代理主要是企业内部安全和负载均衡考虑,这里主要说下如何配置反向代理。

现在只要是具备一定规模的网站(Web 服务器大于 1 台),为了安全和负载均衡考虑都会在 Web 服务器前面部署反向代理,反向代理有 HAproxy,Nginx,Apache 等等。

这里通过 Nginx 来部署反向代理:

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

简单的解释下:

  • X-Forwarded-For 表示 Nginx 接收到的头,原样的转发过来(假如不转发,Web 服务器就不能获取这个头)。
  • X-Real-IP,这是一个内部协议头(就是反向代理服务器和 Web 服务器约定的),这个头表示连接反向代理服务器的 IP 地址(这个地址不能伪造),其实个人觉得为了让 PHP 代码保持无二义性,不应该这样设置,可以修改为 proxy_set_header REMOTE_ADDR $remote_addr;

HTTP_VIA=没数值或者不显示HTTP_X_FORWARDED_FOR = 没数值或不显示。

下面的PHP代码将显示出当前用户的IP地址: ?php echo $_SERVER[REMOTE_ADDR]; ?

Apache WEB 服务器的 Access 日志如何获取 X-Forwarded-For 头

其实写这篇文章主要是因为自己在 Apache Web 服务器上获取不到 X-Forwarded-For(上层的负载均衡设备确定传递了),搜索了下(在 Apache 官方文档并没有找到解决方案),解决如下:

LogFormat "%{X-Forwarded-For}i %a %h %A %l %u %t "%r" %>s %b "%{Referer}i"
 "%{User-Agent}i"" combined

无论是REMOTE_ADDR还是HTTP_FORWARDED_FOR,这些头消息未必能够取得到,因为不同的浏览器不同的网络设备可能发送不同的IP 头消息。因此PHP使用$_SERVER["REMOTE_ADDR"] 、$_SERVER["HTTP_X_FORWARDED_FOR"] 获取的值可能是空值也可能是“unknown”值。

让我们看看我们的PHP应用程序的代码。首先,我们需要定义我们想跟踪的网站资源并指定文件大小: //获取我们想记录的文件名称 $fileName="cnet-banner.gif"; $fileSize="92292";

X-Forwarded-For 安全性

那么很多同学会说,通过 X-Forwarded-For 就能获取到用户的真实 IP,是不是万事大吉了,对于 Web 服务器来说,安全有两个纬度,第一个纬度是 REMOTE_ADDR 这个头,这个头不能伪造。第二个纬度就是 X-Forwarded-For,但是这个头是可以伪造的。

那么谁在伪造呢?,我们分别看下:

正向代理一般是公司加速使用的,假如没有特殊的目的,不应该传递 X-Forwarded-For 头,因为它的上层连接是内部 IP,不应该暴露出去,当然它也可以透明的传递这个头的值(而这个值用户可以伪造)。

透明代理,这个可能是用户自己搭建的(比如翻墙),而且在一个用户的请求中,可能有多个透明代理,这时候透明代理就抓瞎了,为了让自己尽量的正确,也会透明的传递这个头的值(而这个值用户可以伪造),当然一些不法企业或者人员,为了一些目的,会改下这个头的值(比如来自世界各地的 IP 地址)。

反向代理,Web 服务器前的反向代理服务器是不会伪造的(同一个公司的),一般会原样传递这个头的值。

那么对应用程序来说,既然这个值不能完全相信,该怎么办呢?这取决于应用的性质:

假如提供的服务可能就是一些非机密服务,也不需要知道用户的真实 IP,那么建议应用程序或者 Web 服务器对 REMOTE_ADDR 做一些限制,比如进行限速等等,也可以放行一些白名单的代理 IP,但是这些白名单 IP 就太难衡量了。

假设你的服务很重要,比如抽奖(一个 IP 只能一次抽奖),这时候你可能想通过 X-Forwarded-For 来获取用户的真实 IP(假如使用 REMOTE_ADDR 则会误杀一片),但是由于 X-Forwarded-For 可能会伪造,所以其实并没有什么好的办法,只能在应用层进行处理了。

PHP获取客户端IP时另外一点需注意,使用函数getenv('HTTP_X_FORWARDED_FOR')或getenv 也可以如上代码一样取得同样的效果。但getenv()不支持在IIS的isapi方式下运行的PHP。

你无需把这些值保存到静态变量中去。如果你要跟踪许多条目,那么你可以把它们保存到数组或者数据库中去。在这种情况下,你可能会希望通过一个外部链接来找到每个条目,如下所示: a href="weblogger.php?bannerid=123"imgsrc="cnet-banner.gif" border="0"/a

REMOTE_ADDR 是你的客户端跟你的服务器“握手”时候的IP。如果使用了“匿名代理”,REMOTE_ADDR将显示代理服务器的IP。

其中“123”表示“cnet-banner.gif”所对应的记录。然后,我们通过服务器变量来查询用户浏览器。这样我们就得到在我们的日志文件中添加新条目所需的数据: //得到网站浏览者的CLF信息 $host=$_SERVER[REMOTE_ADDR]; $ident=$_SERVER[REMOTE_IDENT]; $auth=$_SERVER[REMOTE_USER]; $timeStamp=date("d/M/Y:H:i:s O"); $reqType=$_SERVER[REQUEST_METHOD]; $servProtocol=$_SERVER[SERVER_PROTOCOL]; $statusCode="200";

HTTP_CLIENT_IP 是代理服务器发送的HTTP头。如果是“超级匿名代理”,则返回none值。同样,REMOTE_ADDR也会被替换为这个代理服务器的IP。

然后,我们检查服务器是否返回了空值。根据CLF规范,空值应该用破折号来代替。这样,下一个代码块的任务就是寻找空值并用破折号来取代它: //给空值添加破折号 if ($host==""){ $host="-"; } if ($ident==""){ $ident="-"; } if ($auth==""){ $auth="-"; } if ($reqType==""){ $reqType="-"; } if ($servProtocol==""){ $servProtocol="-"; }

$_SERVER['REMOTE_ADDR']; //访问端IP

一旦我们获取了必要的信息,这些值将被组织成一种符合CLF规范的格式: //创建CLF格式的字符串 $clfString=$host." ".$ident." ".$auth." [".$timeStamp."] "".$reqType." /".$fileName." ".$servProtocol."" ".$statusCode." ".$fileSize."";

$_SERVER['HTTP_CLIENT_IP']; //代理端的

创建自定义日志文件 现在,格式化之后的数据可以存放到我们的自定义日志文件中去。首先,我们将创建一种文件命名协定,并编写每日产生一个新日志文件的方法。在本文所举的例子中,每个文件都以“weblog-”开头,然后是按月/日/年表示的日期,文件扩展名为.log。.log扩展名一般表示服务器日志文件。 // 用当前日期来命名日志文件 $logPath="./log/"; $logFile=$logPath."weblog-".date("mdy").".log";

$_SERVER['HTTP_X_FORWARDED_FOR']; //用户是在哪个IP使用的代理

现在,我们需要判断当前日志文件是否存在。如果存在,我们就向它添加条目;否则,应用程序就创建新的日志文件。 //检查日志文件是否已经存在 if (file_exists($logFile)){ //如果存在,则打开已存在的日志文件 $fileWrite = fopen($logFile,"a");} else { //否则,创建新的日志文件 $fileWrite = fopen($logFile,"w"); }

根据以上几种情况写出的PHP代码:复制代码 代码如下:

如果你在写或者追加文件时,收到“权限不足”错误信息,请更改目标日志文件夹的权限来允许写操作。绝大多数Web服务器的默认权限为“可读可执行”。你可以用CHMOD命令或者使用FTP客户端来改变文件夹的权限。

然后,我们创建文件锁定机制,这样当两个或者更多用户同时访问日志文件时,只有其中的一个用户可以对该文件进行写操作: //创建文件写操作的锁定机制 flock($fileWrite, LOCK_SH);

最后,我们写入条目的内容: //写CLF条目 fwrite($fileWrite,$clfString); //解除文件锁定状态 flock($fileWrite, LOCK_UN); //关闭日志文件 fclose($fileWrite);

处理日志数据

在该系统产品化之后,客户希望得到对所收集到的访问者数据的详细统计分析。由于所有的定制日志文件都是按照一个标准的格式组织的,因此任何一个日志分析器都可以处理它们。日志分析器是一个工具,它分析大的日志文件并产生饼图、直方图以及其它统计图形。日志分析器也用来收集数据,并综合出提供哪些用户访问你的网站、点击数等方面的信息。

下面列出了几个比较流行的日志分析器:

WebTrends是一个非常不错的日志分析器,它适用于大规模网站以及企业级的网络。 Analog是一个颇受欢迎的免费日志分析器。 Webalizer是一个免费的分析程序。它可以产生HTML报告,这样大多数网络浏览器都可以查看它的报告。

遵守标准

我们可以轻松的扩展该应用程序来让它支持其它类型的日志记录。这样你就可以捕获到更多的数据,如浏览器类型以及referrer。这里的经验就是:在你编程的时候遵循标准或者惯例终究会简化工作。

本文由10bet手机官网发布于微服架构,转载请注明出处:For的一些理解,用定制的PHP应用程序来获取Web服务器的状态信息

上一篇:保证Oracle数据库安全性的策略和方法,建立用户组保证Oracle数据库安全性 下一篇:PostgreSQL教程(二):模式Schema详解
猜你喜欢
热门排行
精彩图文