在PHP中获取完整URL:如何避免重复造轮子且不损失性能
\n\n每个PHP开发者迟早都会遇到这样一个问题:“如何获取当前页面的完整URL?”看起来很简单——取$_SERVER['REQUEST_URI'],加上域名就完成了。但魔鬼总是藏在细节中。关于“在PHP中获取完整URL”的问题在Stack Overflow上获得了数千次浏览和几十个回答。这并非偶然。
在本文中,我们将探讨如何在PHP中正确、快速且无错误地获取完整URL。您将了解HTTP_HOST与SERVER_NAME的区别,如何正确判断协议(HTTPS或HTTP),以及为什么不应盲目复制网上的代码。开始吧!
为什么简单的方法并不总是最好的?
\n\n最显而易见的写法如下:
\n\n<?php\n$fullUrl = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];\n?>\n\n这段代码能工作,但前提是您满足以下条件:
\n- \n
- 必须使用HTTP(而非HTTPS) \n
- 没有使用80或443以外的端口 \n
- 服务器正确传递了Host头 \n
在现实世界中,这些条件往往不成立。如果网站通过HTTPS运行,您将得到以http://开头的URL,这会导致浏览器中的混合内容错误。如果使用了非标准端口(例如8080),端口信息会丢失。而如果攻击者伪造了Host头,您可能得到指向其他域名的URL。
\n\n完整URL的组成部分:详细解析
\n\n在编写通用解决方案之前,让我们回顾一下完整URL的组成:
\n\nscheme://host:port/path?query#fragment\n\n在PHP中,我们可以从超全局数组$_SERVER中获取以下元素:
- \n
- scheme — 协议(HTTP或HTTPS)。没有直接的键,但可以通过
$_SERVER['HTTPS']或$_SERVER['REQUEST_SCHEME'](自PHP 7.4起可用)判断。 \n - host — 域名。有两个候选:
HTTP_HOST(来自Host头)和SERVER_NAME(来自服务器配置)。 \n - port — 端口。
$_SERVER['SERVER_PORT']。如果端口是标准端口(HTTP为80,HTTPS为443),可以省略。 \n - path — 路径。
$_SERVER['REQUEST_URI']包含路径和查询字符串。 \n - query — 查询字符串。可以通过
$_SERVER['QUERY_STRING']单独获取。 \n - fragment — 锚点。它不会发送到服务器,因此无法获取。 \n
正确的解决方案:getFullUrl()函数
\n\n以下是一种可靠且高性能的在PHP中获取完整URL的方法:
\n\n<?php\nfunction getFullUrl(): string {\n // 判断协议\n $scheme = 'http';\n if (\n (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] === '1'))\n || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')\n ) {\n $scheme = 'https';\n }\n\n // 主机:优先使用HTTP_HOST,但检查是否为空\n $host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost';\n\n // 端口:仅当非标准端口时添加\n $port = (int) ($_SERVER['SERVER_PORT'] ?? 80);\n $portStr = '';\n if (\n ($scheme === 'http' && $port !== 80) ||\n ($scheme === 'https' && $port !== 443)\n ) {\n $portStr = ':' . $port;\n }\n\n // URI\n $uri = $_SERVER['REQUEST_URI'] ?? '/';\n\n return $scheme . '://' . $host . $portStr . $uri;\n}\n?>\n\n这个函数考虑了:
\n- \n
- HTTPS协议(包括通过带有
X-Forwarded-Proto头的代理服务器)。 \n - 非标准端口。 \