Skip to content

$_SERVER — 服务器与执行环境信息

概述

$_SERVER 是 PHP 中最重要的超全局变量之一,它是一个包含了服务器和执行环境信息的数组。通过 $_SERVER,你可以获取 HTTP 请求的详细信息、服务器配置、客户端信息、文件路径等关键数据。几乎所有 Web 应用的路由、请求处理、安全控制都离不开 $_SERVER

前置知识

在阅读本节之前,你需要了解:

  • HTTP 协议基础(请求方法、请求头、URL 结构)
  • CGI/FastCGI 规范(了解服务器如何向 PHP 传递信息)
  • PHP 的不同运行模式(Apache/Nginx + PHP-FPM/内置服务器)

基础概念

$_SERVER 的数据来源

$_SERVER 中的数据来自多个渠道:

数据来源示例键名说明
Web 服务器SERVER_SOFTWAREDOCUMENT_ROOT由 Apache/Nginx 等设置
HTTP 请求头HTTP_HOSTHTTP_USER_AGENTHTTP_ 前缀开头
CGI 规范GATEWAY_INTERFACESERVER_PROTOCOLFastCGI 标准变量
PHP 自身PHP_SELFargvargcPHP 引擎添加
客户端连接REMOTE_ADDRREMOTE_PORTTCP 连接信息

HTTP 头转换规则

HTTP 请求头中的横线 -$_SERVER 中会被转换为下划线 _,并添加 HTTP_ 前缀。例如,请求头 X-Requested-With 对应 $_SERVER['HTTP_X_REQUESTED_WITH']

常用键详解

请求相关

键名示例值说明
REQUEST_METHODGET / POST / PUTHTTP 请求方法
REQUEST_URI/index.php?id=1请求的 URI(含查询字符串)
REQUEST_SCHEMEhttp / https请求协议
QUERY_STRINGid=1&page=2查询字符串(? 之后的部分)
SCRIPT_NAME/index.php当前脚本路径
PHP_SELF/index.php当前执行脚本的文件名
PATH_INFO/article/123URL 中跟在脚本名后面的路径信息

服务器相关

键名示例值说明
SERVER_NAMEexample.com服务器主机名(来自配置)
SERVER_PORT80 / 443服务器端口
SERVER_SOFTWAREApache/2.4.52服务器软件标识
SERVER_ADDR192.168.1.100服务器 IP 地址
SERVER_PROTOCOLHTTP/1.1通信协议及版本
DOCUMENT_ROOT/var/www/html网站根目录的文件系统路径
GATEWAY_INTERFACECGI/1.1CGI 规范版本

HTTPS 与安全

键名示例值说明
HTTPSon / 1是否为 HTTPS 连接
HTTP_X_FORWARDED_PROTOhttps反向代理转发的原始协议
HTTP_X_FORWARDED_FOR1.2.3.4反向代理转发的客户端 IP
SSL_PROTOCOLTLSv1.3SSL/TLS 协议版本
REMOTE_ADDR203.0.113.50客户端 IP 地址
REMOTE_PORT54321客户端端口号

客户端信息

键名示例值说明
HTTP_HOSTexample.com请求的 Host 头
HTTP_USER_AGENTMozilla/5.0...客户端浏览器标识
HTTP_REFERERhttps://google.com/来源页面 URL
HTTP_ACCEPTtext/html,...客户端可接受的内容类型
HTTP_ACCEPT_LANGUAGEzh-CN,zh;q=0.9客户端语言偏好
HTTP_ACCEPT_ENCODINGgzip, 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 在不同环境下的差异

键名ApacheNginx + PHP-FPM内置服务器 (php -S)CLI
SERVER_SOFTWAREApache/2.4.xnginx/x.y.zPHP x.y.z Development ServerCLI
DOCUMENT_ROOT从 Apache 配置获取nginx.confroot 获取启动时指定的目录
SCRIPT_FILENAME完整路径完整路径完整路径当前脚本路径
SERVER_NAMEServerName 指令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/&lt;script&gt;alert(&#039;xss&#039;)&lt;/script&gt;

// 更安全的替代方案:使用 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();
}

注意事项

  1. 不要信任客户端发送的头信息HTTP_REFERERHTTP_USER_AGENT 等都可以被客户端伪造
  2. 安全转义输出:输出 PHP_SELF 等包含用户输入的值时,务必使用 htmlspecialchars()
  3. 检查键是否存在:使用 $_SERVER['KEY'] ?? ''isset() 避免 undefined index 警告
  4. 反向代理场景:在 Nginx/Apache 反向代理后,REMOTE_ADDR 是代理服务器 IP,需检查转发头
  5. 内置服务器差异php -S 内置服务器的 $_SERVER 值可能与生产环境不同

最佳实践

  1. 封装统一接口:将 $_SERVER 访问封装到请求类中,避免业务代码直接访问
  2. 防御性编程:使用 null 合并运算符(??)防止未定义键异常
  3. 类型安全:对端口等数值型数据使用 (int) 强制转换
  4. HTTPS 统一判断:封装 isHttps() 方法,兼容多种部署环境
  5. 路径安全:输出路径信息时始终进行转义,防止 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 请求参数的获取方式、区别以及安全处理方法。

参考链接