$_SERVER — 服务器与执行环境信息
概述
$_SERVER 是 PHP 中最重要的超全局变量之一,它是一个包含了服务器和执行环境信息的数组。通过 $_SERVER,你可以获取 HTTP 请求的详细信息、服务器配置、客户端信息、文件路径等关键数据。几乎所有 Web 应用的路由、请求处理、安全控制都离不开 $_SERVER。
前置知识
在阅读本节之前,你需要了解:
- HTTP 协议基础(请求方法、请求头、URL 结构)
- CGI/FastCGI 规范(了解服务器如何向 PHP 传递信息)
- PHP 的不同运行模式(Apache/Nginx + PHP-FPM/内置服务器)
基础概念
$_SERVER 的数据来源
$_SERVER 中的数据来自多个渠道:
| 数据来源 | 示例键名 | 说明 |
|---|---|---|
| Web 服务器 | SERVER_SOFTWARE、DOCUMENT_ROOT | 由 Apache/Nginx 等设置 |
| HTTP 请求头 | HTTP_HOST、HTTP_USER_AGENT | 以 HTTP_ 前缀开头 |
| CGI 规范 | GATEWAY_INTERFACE、SERVER_PROTOCOL | FastCGI 标准变量 |
| PHP 自身 | PHP_SELF、argv、argc | PHP 引擎添加 |
| 客户端连接 | REMOTE_ADDR、REMOTE_PORT | TCP 连接信息 |
HTTP 头转换规则
HTTP 请求头中的横线 - 在 $_SERVER 中会被转换为下划线 _,并添加 HTTP_ 前缀。例如,请求头 X-Requested-With 对应 $_SERVER['HTTP_X_REQUESTED_WITH']。
常用键详解
请求相关
| 键名 | 示例值 | 说明 |
|---|---|---|
REQUEST_METHOD | GET / POST / PUT | HTTP 请求方法 |
REQUEST_URI | /index.php?id=1 | 请求的 URI(含查询字符串) |
REQUEST_SCHEME | http / https | 请求协议 |
QUERY_STRING | id=1&page=2 | 查询字符串(? 之后的部分) |
SCRIPT_NAME | /index.php | 当前脚本路径 |
PHP_SELF | /index.php | 当前执行脚本的文件名 |
PATH_INFO | /article/123 | URL 中跟在脚本名后面的路径信息 |
服务器相关
| 键名 | 示例值 | 说明 |
|---|---|---|
SERVER_NAME | example.com | 服务器主机名(来自配置) |
SERVER_PORT | 80 / 443 | 服务器端口 |
SERVER_SOFTWARE | Apache/2.4.52 | 服务器软件标识 |
SERVER_ADDR | 192.168.1.100 | 服务器 IP 地址 |
SERVER_PROTOCOL | HTTP/1.1 | 通信协议及版本 |
DOCUMENT_ROOT | /var/www/html | 网站根目录的文件系统路径 |
GATEWAY_INTERFACE | CGI/1.1 | CGI 规范版本 |
HTTPS 与安全
| 键名 | 示例值 | 说明 |
|---|---|---|
HTTPS | on / 1 | 是否为 HTTPS 连接 |
HTTP_X_FORWARDED_PROTO | https | 反向代理转发的原始协议 |
HTTP_X_FORWARDED_FOR | 1.2.3.4 | 反向代理转发的客户端 IP |
SSL_PROTOCOL | TLSv1.3 | SSL/TLS 协议版本 |
REMOTE_ADDR | 203.0.113.50 | 客户端 IP 地址 |
REMOTE_PORT | 54321 | 客户端端口号 |
客户端信息
| 键名 | 示例值 | 说明 |
|---|---|---|
HTTP_HOST | example.com | 请求的 Host 头 |
HTTP_USER_AGENT | Mozilla/5.0... | 客户端浏览器标识 |
HTTP_REFERER | https://google.com/ | 来源页面 URL |
HTTP_ACCEPT | text/html,... | 客户端可接受的内容类型 |
HTTP_ACCEPT_LANGUAGE | zh-CN,zh;q=0.9 | 客户端语言偏好 |
HTTP_ACCEPT_ENCODING | gzip, deflate | 客户端支持的编码 |
文件路径
| 键名 | 示例值 | 说明 |
|---|---|---|
SCRIPT_FILENAME | /var/www/html/index.php | 当前脚本的绝对路径 |
SCRIPT_NAME | /index.php | 当前脚本的 URL 路径 |
__FILE__ | /var/www/html/index.php | 魔术常量(不是 $_SERVER) |
语法与代码
获取请求基础信息
php
<?php
declare(strict_types=1);
/**
* 获取完整的请求 URL
*/
function getCurrentUrl(): string
{
$scheme = $_SERVER['REQUEST_SCHEME'] ?? 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$uri = $_SERVER['REQUEST_URI'] ?? '/';
return $scheme . '://' . $host . $uri;
}
/**
* 获取不带查询字符串的当前 URL
*/
function getCurrentUrlWithoutQuery(): string
{
$scheme = $_SERVER['REQUEST_SCHEME'] ?? 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$uri = $_SERVER['PHP_SELF'] ?? '/';
return $scheme . '://' . $host . $uri;
}
echo getCurrentUrl();
// 输出: https://example.com/index.php?id=1&page=2安全获取客户端真实 IP
php
<?php
declare(strict_types=1);
/**
* 获取客户端真实 IP 地址
* 支持反向代理场景(Nginx/Apache 反向代理、CDN)
*/
function getClientIp(): string
{
// 信任的代理服务器 IP 列表
$trustedProxies = ['127.0.0.1', '192.168.1.1'];
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
// 如果请求来自受信任的代理,检查转发头
if (in_array($remoteAddr, $trustedProxies, true)) {
$forwardedFor = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null;
if ($forwardedFor !== null) {
// X-Forwarded-For 可能包含多个 IP,取第一个
$ips = explode(',', $forwardedFor);
$clientIp = trim($ips[0]);
if (filter_var($clientIp, FILTER_VALIDATE_IP)) {
return $clientIp;
}
}
$realIp = $_SERVER['HTTP_X_REAL_IP'] ?? null;
if ($realIp !== null && filter_var($realIp, FILTER_VALIDATE_IP)) {
return $realIp;
}
}
return $remoteAddr;
}
echo getClientIp();判断 HTTPS 连接
php
<?php
declare(strict_types=1);
/**
* 判断当前请求是否为 HTTPS
* 兼容多种 Web 服务器和反向代理配置
*/
function isHttps(): bool
{
// 标准方式
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
return true;
}
// 反向代理转发的协议
if (
isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https'
) {
return true;
}
// Cloudflare
if (isset($_SERVER['HTTP_CF_VISITOR'])) {
$visitor = json_decode($_SERVER['HTTP_CF_VISITOR'], true);
if (isset($visitor['scheme']) && $visitor['scheme'] === 'https') {
return true;
}
}
// 标准端口判断(不够可靠,仅作补充)
$port = (int)($_SERVER['SERVER_PORT'] ?? 80);
return $port === 443;
}
var_dump(isHttps());路由解析基础
php
<?php
declare(strict_types=1);
/**
* 简单的路由解析器
* 从 $_SERVER 中提取路径信息进行路由匹配
*/
class SimpleRouter
{
private array $routes = [];
public function get(string $pattern, callable $handler): void
{
$this->routes['GET'][$pattern] = $handler;
}
public function post(string $pattern, callable $handler): void
{
$this->routes['POST'][$pattern] = $handler;
}
public function dispatch(): void
{
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
// 去除末尾的斜杠(根路径除外)
$uri = $uri !== '/' ? rtrim($uri, '/') : $uri;
if (isset($this->routes[$method][$uri])) {
($this->routes[$method][$uri])();
} else {
http_response_code(404);
echo '404 Not Found';
}
}
}
// 使用示例
$router = new SimpleRouter();
$router->get('/', fn() => print('首页'));
$router->get('/about', fn() => print('关于页面'));
$router->get('/api/users', fn() => print(json_encode(['Alice', 'Bob'])));
$router->dispatch();详细说明
$_SERVER 在不同环境下的差异
| 键名 | Apache | Nginx + PHP-FPM | 内置服务器 (php -S) | CLI |
|---|---|---|---|---|
SERVER_SOFTWARE | Apache/2.4.x | nginx/x.y.z | PHP x.y.z Development Server | CLI |
DOCUMENT_ROOT | 从 Apache 配置获取 | 从 nginx.conf 的 root 获取 | 启动时指定的目录 | 空 |
SCRIPT_FILENAME | 完整路径 | 完整路径 | 完整路径 | 当前脚本路径 |
SERVER_NAME | ServerName 指令 | server_name 指令 | localhost | - |
PATH_INFO | 需要 AcceptPathInfo | 需要额外配置 | 支持 | - |
环境差异注意
不同 Web 服务器和 PHP 运行模式下,$_SERVER 中的值可能不同。编写路由或请求处理代码时,不能假设某个键一定存在或具有特定格式。始终进行安全检查。
PHP_SELF 的安全隐患
$_SERVER['PHP_SELF'] 包含用户输入的路径信息,可能被用于 XSS 攻击:
php
<?php
declare(strict_types=1);
// 假设请求 URL 为: http://example.com/index.php/<script>alert('xss')</script>
// 危险!PHP_SELF 包含用户输入的路径
echo $_SERVER['PHP_SELF'];
// 输出: /index.php/<script>alert('xss')</script>
// 安全做法:使用 htmlspecialchars() 转义
echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'UTF-8');
// 输出: /index.php/<script>alert('xss')</script>
// 更安全的替代方案:使用 SCRIPT_NAME
echo $_SERVER['SCRIPT_NAME'];
// 输出: /index.php(不包含路径信息)实战示例
完整的请求信息收集类
php
<?php
declare(strict_types=1);
/**
* HTTP 请求信息收集器
* 统一封装 $_SERVER 的常用操作
*/
class HttpRequestInfo
{
public static function method(): string
{
return strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
}
public static function isMethod(string $method): bool
{
return self::method() === strtoupper($method);
}
public static function scheme(): string
{
return self::isHttps() ? 'https' : 'http';
}
public static function host(): string
{
return $_SERVER['HTTP_HOST'] ?? 'localhost';
}
public static function uri(): string
{
return $_SERVER['REQUEST_URI'] ?? '/';
}
public static function path(): string
{
$uri = $_SERVER['REQUEST_URI'] ?? '/';
return parse_url($uri, PHP_URL_PATH) ?: '/';
}
public static function query(): string
{
return $_SERVER['QUERY_STRING'] ?? '';
}
public static function baseUrl(): string
{
return self::scheme() . '://' . self::host();
}
public static function fullUrl(): string
{
return self::baseUrl() . self::uri();
}
public static function clientIp(): string
{
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
public static function userAgent(): string
{
return $_SERVER['HTTP_USER_AGENT'] ?? '';
}
public static function referer(): ?string
{
return $_SERVER['HTTP_REFERER'] ?? null;
}
public static function isHttps(): bool
{
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
return true;
}
return (
isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https'
);
}
public static function isAjax(): bool
{
return (
isset($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'
);
}
public static function acceptsJson(): bool
{
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
return str_contains($accept, 'application/json');
}
public static function contentType(): string
{
return $_SERVER['CONTENT_TYPE'] ?? '';
}
public static function contentLength(): int
{
return (int)($_SERVER['CONTENT_LENGTH'] ?? 0);
}
public static function documentRoot(): string
{
return $_SERVER['DOCUMENT_ROOT'] ?? '';
}
public static function scriptFilename(): string
{
return $_SERVER['SCRIPT_FILENAME'] ?? '';
}
public static function serverPort(): int
{
return (int)($_SERVER['SERVER_PORT'] ?? 80);
}
}
// 使用示例
if (HttpRequestInfo::isAjax()) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'path' => HttpRequestInfo::path(),
'method' => HttpRequestInfo::method(),
'ip' => HttpRequestInfo::clientIp(),
]);
} else {
echo '当前页面: ' . HttpRequestInfo::fullUrl();
echo '请求方法: ' . HttpRequestInfo::method();
}注意事项
- 不要信任客户端发送的头信息:
HTTP_REFERER、HTTP_USER_AGENT等都可以被客户端伪造 - 安全转义输出:输出
PHP_SELF等包含用户输入的值时,务必使用htmlspecialchars() - 检查键是否存在:使用
$_SERVER['KEY'] ?? ''或isset()避免 undefined index 警告 - 反向代理场景:在 Nginx/Apache 反向代理后,
REMOTE_ADDR是代理服务器 IP,需检查转发头 - 内置服务器差异:
php -S内置服务器的$_SERVER值可能与生产环境不同
最佳实践
- 封装统一接口:将
$_SERVER访问封装到请求类中,避免业务代码直接访问 - 防御性编程:使用 null 合并运算符(
??)防止未定义键异常 - 类型安全:对端口等数值型数据使用
(int)强制转换 - HTTPS 统一判断:封装
isHttps()方法,兼容多种部署环境 - 路径安全:输出路径信息时始终进行转义,防止 XSS
php
<?php
declare(strict_types=1);
// 最佳实践:安全的 $_SERVER 访问模式
function safeServer(string $key, string $default = ''): string
{
$value = $_SERVER[$key] ?? $default;
// 对可能包含用户输入的值进行转义
$userInputKeys = [
'PHP_SELF', 'PATH_INFO', 'REQUEST_URI',
'QUERY_STRING', 'HTTP_REFERER',
];
if (in_array($key, $userInputKeys, true)) {
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
return $value;
}下一节
下一节将详细介绍 $_GET 与 $_POST,了解 HTTP 请求参数的获取方式、区别以及安全处理方法。