Skip to content

HTTP 协议概览

HTTP(HyperText Transfer Protocol,超文本传输协议)是 Web 应用通信的基础协议。PHP 作为服务端语言,理解 HTTP 协议的工作原理对于构建高效、安全的 Web 应用至关重要。本节将深入讲解 HTTP 协议的核心概念及其在 PHP 中的体现。

前置知识

阅读本节前,建议先了解:PHP Web 运行模式PHP 内置服务器

基础概念

HTTP 工作模型

HTTP 采用 请求-响应(Request-Response) 模型:客户端发起请求,服务端返回响应。整个过程经历以下几个步骤:

客户端(浏览器)          服务端(PHP + Web 服务器)
    |                          |
    |--- 1. TCP 连接 ---------->|
    |                          |
    |--- 2. HTTP 请求 --------->|
    |                          |--- 3. 处理请求 (PHP)
    |                          |
    |<-- 4. HTTP 响应 ----------|
    |                          |
    |--- 5. 渲染页面           |
    |                          |
    |--- 6. 连接关闭/保持 -----|

HTTP 版本演进

版本发布年份特性PHP 支持
HTTP/1.01996短连接、无 Host 头全版本
HTTP/1.11997长连接、管线化、分块传输全版本
HTTP/22015多路复用、头部压缩、服务器推送7.4+(需扩展)
HTTP/32022基于 QUIC、0-RTT 连接8.3+(需扩展)

HTTP/2 与 PHP

PHP-FPM 本身不直接处理 HTTP/2,通常由 Nginx/Apache 终止 TLS 并代理到 PHP-FPM。PHP 8.3+ 的 cURL 扩展支持 HTTP/3。

HTTP 请求结构

一个完整的 HTTP 请求由以下三部分组成:

请求行(Request Line)
请求头(Headers)
空行
请求体(Body,可选)

示例:

GET /index.php?id=1 HTTP/1.1
Host: www.example.com
Accept: text/html,application/xhtml+xml
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Cookie: session_id=abc123
Connection: keep-alive

HTTP 响应结构

状态行(Status Line)
响应头(Headers)
空行
响应体(Body)

示例:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1234
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure
Cache-Control: max-age=3600
X-Powered-By: PHP/8.3

<!DOCTYPE html>
<html>...</html>

PHP 中的 HTTP 超全局变量

PHP 通过超全局变量自动解析 HTTP 请求,开发者无需手动解析原始 HTTP 报文。

$_SERVER 中的 HTTP 相关键

php
<?php

declare(strict_types=1);

// 请求方法和协议
echo $_SERVER['REQUEST_METHOD']    // GET, POST, PUT, DELETE...
echo $_SERVER['SERVER_PROTOCOL']  // HTTP/1.1, HTTP/2
echo $_SERVER['REQUEST_URI']      // /index.php?id=1
echo $_SERVER['QUERY_STRING']     // id=1
echo $_SERVER['HTTP_HOST']        // www.example.com
echo $_SERVER['SERVER_NAME']      // www.example.com(配置值)
echo $_SERVER['SERVER_PORT']      // 80 或 443
echo $_SERVER['HTTPS']            // on(HTTPS 时)
echo $_SERVER['REQUEST_SCHEME']   // http 或 https(PHP 7.0+)
echo $_SERVER['REMOTE_ADDR']      // 客户端 IP
echo $_SERVER['REMOTE_PORT']      // 客户端端口
echo $_SERVER['DOCUMENT_ROOT']    // /var/www/html

// HTTP 请求头(以 HTTP_ 为前缀)
echo $_SERVER['HTTP_ACCEPT'];           // Accept 头
echo $_SERVER['HTTP_ACCEPT_LANGUAGE'];  // Accept-Language 头
echo $_SERVER['HTTP_USER_AGENT'];       // User-Agent 头
echo $_SERVER['HTTP_REFERER'];         // 来源页面
echo $_SERVER['HTTP_COOKIE'];          // Cookie 头
echo $_SERVER['HTTP_AUTHORIZATION'];    // Authorization 头

// 文件路径
echo $_SERVER['SCRIPT_FILENAME'];  // /var/www/html/index.php
echo $_SERVER['SCRIPT_NAME'];      // /index.php
echo $_SERVER['PHP_SELF'];         // /index.php

// 时间
echo $_SERVER['REQUEST_TIME'];           // 请求开始时间戳
echo $_SERVER['REQUEST_TIME_FLOAT'];     // 请求开始时间(微秒精度,PHP 5.4+)

判断 HTTPS 连接

php
<?php

declare(strict_types=1);

/**
 * 安全地判断当前是否为 HTTPS 连接
 */
function isHttps(): bool
{
    // 标准 HTTPS 检测
    if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
        return true;
    }

    // 反向代理场景(Nginx -> Apache/PHP-FPM)
    if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
        return true;
    }

    // 非标准 HTTPS 端口
    if (!empty($_SERVER['SERVER_PORT']) && (int) $_SERVER['SERVER_PORT'] === 443) {
        return true;
    }

    return false;
}

if (isHttps()) {
    echo "当前为安全连接";
} else {
    echo "当前为非安全连接";
}

HTTP 状态码

状态码分类

范围类别含义
1xxInformational信息性状态码,表示请求已接收
2xxSuccess请求成功
3xxRedirection重定向
4xxClient Error客户端错误
5xxServer Error服务端错误

常用状态码详解

php
<?php

declare(strict_types=1);

// === 2xx 成功 ===

// 200 OK - 请求成功(默认状态码)
http_response_code(200);

// 201 Created - 资源创建成功(RESTful API 常用)
http_response_code(201);

// 204 No Content - 成功但无返回内容(DELETE 操作常用)
http_response_code(204);

// === 3xx 重定向 ===

// 301 Moved Permanently - 永久重定向
http_response_code(301);
header('Location: https://www.example.com/new-url');

// 302 Found - 临时重定向(默认)
http_response_code(302);
header('Location: /login');

// 304 Not Modified - 缓存有效,无需重新传输
http_response_code(304);

// 307 Temporary Redirect - 保持请求方法的临时重定向
http_response_code(307);
header('Location: https://example.com/new');

// 308 Permanent Redirect - 保持请求方法的永久重定向
http_response_code(308);
header('Location: https://example.com/new');

// === 4xx 客户端错误 ===

// 400 Bad Request - 请求格式错误
http_response_code(400);
echo json_encode(['error' => '无效的请求参数']);

// 401 Unauthorized - 未认证
http_response_code(401);
header('WWW-Authenticate: Basic realm="My Site"');

// 403 Forbidden - 无权限
http_response_code(403);
echo json_encode(['error' => '您没有访问权限']);

// 404 Not Found - 资源不存在
http_response_code(404);
echo json_encode(['error' => '页面不存在']);

// 405 Method Not Allowed - 请求方法不被允许
http_response_code(405);
header('Allow: GET, POST'); // 告知客户端允许的方法

// 413 Payload Too Large - 请求体过大
http_response_code(413);

// 415 Unsupported Media Type - 不支持的媒体类型
http_response_code(415);

// 422 Unprocessable Entity - 语义错误(WebDAV/REST 常用)
http_response_code(422);
echo json_encode(['error' => '参数验证失败', 'details' => $errors]);

// 429 Too Many Requests - 请求过于频繁
http_response_code(429);
header('Retry-After: 60');

// === 5xx 服务端错误 ===

// 500 Internal Server Error - 服务器内部错误
http_response_code(500);

// 502 Bad Gateway - 网关错误
http_response_code(502);

// 503 Service Unavailable - 服务不可用
http_response_code(503);
header('Retry-After: 120');

// 504 Gateway Timeout - 网关超时
http_response_code(504);

PHP 设置状态码的方式

php
<?php

declare(strict_types=1);

// 方式一:http_response_code()(推荐,PHP 5.4+)
http_response_code(404);
echo "Page not found";

// 方式二:header() 第一参数包含状态行
header('HTTP/1.1 404 Not Found');
echo "Page not found";

// 方式三:header() 配合 http_response_code()
http_response_code(404);
header('Content-Type: application/json');
echo json_encode(['error' => 'Not Found']);

// 获取当前状态码
$currentCode = http_response_code();
echo "当前状态码: {$currentCode}"; // 200

注意事项

header() 必须在向客户端输出任何内容之前调用,包括空格、BOM 和 PHP 标签前的空行。启用 输出缓冲 可以避免此问题。

HTTP 请求方法

常用请求方法

方法用途请求体幂等安全
GET获取资源
POST创建资源
PUT全量更新资源
PATCH部分更新资源
DELETE删除资源可选
HEAD获取响应头
OPTIONS获取允许的方法

在 PHP 中处理不同请求方法

php
<?php

declare(strict_types=1);

// 获取当前请求方法
$method = $_SERVER['REQUEST_METHOD'];

// 方式一:switch 匹配
match ($method) {
    'GET' => handleGet(),
    'POST' => handlePost(),
    'PUT', 'PATCH' => handleUpdate(),
    'DELETE' => handleDelete(),
    'OPTIONS' => handleOptions(),
    default => http_response_code(405),
};

// 方式二:类封装
class Request
{
    public function __construct(
        public readonly string $method,
        public readonly string $uri,
        public readonly array $headers,
        public readonly array $query,
        public readonly string $body,
        public readonly array $server,
    ) {
    }

    public static function fromGlobals(): self
    {
        return new self(
            method: $_SERVER['REQUEST_METHOD'],
            uri: $_SERVER['REQUEST_URI'],
            headers: getallheaders(),
            query: $_GET,
            body: file_get_contents('php://input'),
            server: $_SERVER,
        );
    }

    public function isMethod(string $method): bool
    {
        return strtoupper($method) === $this->method;
    }

    public function json(): array
    {
        return json_decode($this->body, true, 512, JSON_THROW_ON_ERROR);
    }
}

$request = Request::fromGlobals();

if ($request->isMethod('GET')) {
    $id = $request->query['id'] ?? null;
    // 处理 GET 请求
} elseif ($request->isMethod('POST')) {
    $data = $request->json();
    // 处理 POST 请求
}

HTTP 请求头与响应头

常用请求头

头部字段说明PHP 获取方式
Host目标主机$_SERVER['HTTP_HOST']
User-Agent客户端标识$_SERVER['HTTP_USER_AGENT']
Accept可接受的内容类型$_SERVER['HTTP_ACCEPT']
Accept-Language偏好语言$_SERVER['HTTP_ACCEPT_LANGUAGE']
Accept-Encoding可接受的内容编码$_SERVER['HTTP_ACCEPT_ENCODING']
Content-Type请求体的媒体类型$_SERVER['CONTENT_TYPE']
Content-Length请求体长度$_SERVER['CONTENT_LENGTH']
Authorization认证信息$_SERVER['HTTP_AUTHORIZATION']
CookieCookie 数据$_SERVER['HTTP_COOKIE']
Referer来源页面$_SERVER['HTTP_REFERER']
If-None-Match缓存验证(ETag)$_SERVER['HTTP_IF_NONE_MATCH']
If-Modified-Since缓存验证(时间)$_SERVER['HTTP_IF_MODIFIED_SINCE']
X-Requested-WithAJAX 标识$_SERVER['HTTP_X_REQUESTED_WITH']

常用响应头

头部字段说明PHP 设置方式
Content-Type响应内容的媒体类型header('Content-Type: ...')
Content-Length响应体长度header('Content-Length: ...')
Location重定向目标header('Location: ...')
Set-Cookie设置 Cookiesetcookie()setrawcookie()
Cache-Control缓存策略header('Cache-Control: ...')
Expires过期时间header('Expires: ...')
ETag资源标识header('ETag: ...')
Last-Modified最后修改时间header('Last-Modified: ...')
Access-Control-Allow-OriginCORS 允许源header('Access-Control-Allow-Origin: ...')
X-Frame-Options防止点击劫持header('X-Frame-Options: ...')
X-Content-Type-Options防止 MIME 嗅探header('X-Content-Type-Options: nosniff')
Strict-Transport-Security强制 HTTPSheader('Strict-Transport-Security: ...')
X-Powered-By服务器技术栈建议移除(安全考虑)

HTTP Content-Type

常见 Content-Type 值

php
<?php

declare(strict_types=1);

// HTML
header('Content-Type: text/html; charset=utf-8');

// 纯文本
header('Content-Type: text/plain; charset=utf-8');

// JSON(API 最常用)
header('Content-Type: application/json; charset=utf-8');

// XML
header('Content-Type: application/xml; charset=utf-8');

// 表单数据(multipart)
header('Content-Type: multipart/form-data; boundary=----WebKitFormBoundary');

// URL 编码表单
header('Content-Type: application/x-www-form-urlencoded');

// 文件下载
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="report.pdf"');

// 图片
header('Content-Type: image/png');
header('Content-Type: image/jpeg');
header('Content-Type: image/svg+xml');

// PDF
header('Content-Type: application/pdf');

// 流媒体
header('Content-Type: video/mp4');
header('Content-Type: audio/mpeg');

// CSS / JavaScript
header('Content-Type: text/css');
header('Content-Type: application/javascript');

实战示例:完整的 HTTP 请求信息调试页面

php
<?php

declare(strict_types=1);

/**
 * HTTP 请求信息调试工具
 * 仅在开发环境使用,生产环境必须禁用
 */

if (!defined('APP_DEBUG') || !APP_DEBUG) {
    http_response_code(403);
    exit('Access denied');
}

header('Content-Type: text/html; charset=utf-8');

$protocol = $_SERVER['SERVER_PROTOCOL'];
$method = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
$timestamp = $_SERVER['REQUEST_TIME'];
$timestampFloat = $_SERVER['REQUEST_TIME_FLOAT'];

$sections = [
    '请求基本信息' => [
        '请求方法' => $method,
        '请求 URI' => $uri,
        '协议版本' => $protocol,
        '查询字符串' => $_SERVER['QUERY_STRING'] ?? '',
        '请求时间' => date('Y-m-d H:i:s', $timestamp),
        '请求时间(微秒)' => $timestampFloat,
        'HTTPS' => isHttps() ? '是' : '否',
    ],
    '服务器信息' => [
        '服务器名称' => $_SERVER['SERVER_NAME'],
        '服务器地址' => $_SERVER['SERVER_ADDR'],
        '服务器端口' => $_SERVER['SERVER_PORT'],
        '文档根目录' => $_SERVER['DOCUMENT_ROOT'],
        'PHP 文件路径' => $_SERVER['SCRIPT_FILENAME'],
    ],
    '客户端信息' => [
        '客户端 IP' => $_SERVER['REMOTE_ADDR'],
        '客户端端口' => $_SERVER['REMOTE_PORT'],
        'User-Agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
        'Referer' => $_SERVER['HTTP_REFERER'] ?? '(无)',
        'Accept' => $_SERVER['HTTP_ACCEPT'] ?? '',
        'Accept-Language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '',
        'Accept-Encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '',
    ],
    '请求体' => [
        '原始请求体' => file_get_contents('php://input') ?: '(空)',
        'POST 数据' => $_POST,
        'GET 数据' => $_GET,
        'Cookie' => $_COOKIE,
        '请求头' => getallheaders(),
    ],
];

?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>HTTP 请求调试</title>
    <style>
        body { font-family: monospace; max-width: 960px; margin: 20px auto; background: #1e1e1e; color: #d4d4d4; }
        h1 { color: #569cd6; }
        h2 { color: #dcdcaa; margin-top: 2em; border-bottom: 1px solid #444; padding-bottom: 5px; }
        table { width: 100%; border-collapse: collapse; margin: 10px 0; }
        th { text-align: left; background: #2d2d2d; padding: 8px; color: #9cdcfe; }
        td { padding: 8px; border-bottom: 1px solid #333; word-break: break-all; }
        pre { background: #2d2d2d; padding: 12px; overflow-x: auto; border-radius: 4px; }
    </style>
</head>
<body>
    <h1>HTTP 请求调试信息</h1>
    <?php foreach ($sections as $title => $items): ?>
        <h2><?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?></h2>
        <table>
            <?php foreach ($items as $key => $value): ?>
                <tr>
                    <th><?= htmlspecialchars((string) $key, ENT_QUOTES, 'UTF-8') ?></th>
                    <td>
                        <?php if (is_array($value)): ?>
                            <pre><?= htmlspecialchars(print_r($value, true), ENT_QUOTES, 'UTF-8') ?></pre>
                        <?php else: ?>
                            <?= htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8') ?>
                        <?php endif; ?>
                    </td>
                </tr>
            <?php endforeach; ?>
        </table>
    <?php endforeach; ?>
</body>
</html>

注意事项

1. header() 必须在输出前调用

php
<?php

// 错误:echo 之后再设置 header
echo "Hello";
header('Location: /login');  // 触发 "headers already sent" 警告

// 正确:先设置 header
header('Location: /login');
echo "Hello"; // 不会执行(Location 会重定向)
exit;

2. $_SERVER 中的键可能不存在

php
<?php

// 不安全:直接访问可能不存在的键
$host = $_SERVER['HTTP_HOST']; // E_WARNING

// 安全:使用 null 合并运算符
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';

// 更安全:使用 filter_input
$host = filter_input(INPUT_SERVER, 'HTTP_HOST', FILTER_DEFAULT, FILTER_FLAG_REQUIRED);

3. 信任代理头需谨慎

php
<?php

// 危险:直接信任 X-Forwarded-For
$ip = $_SERVER['HTTP_X_FORWARDED_FOR']; // 可被伪造

// 安全:仅在已知可信代理时才使用
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && isTrustedProxy()) {
    $ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
} else {
    $ip = $_SERVER['REMOTE_ADDR'];
}

4. 隐藏 PHP 版本信息

php
<?php

// 在 php.ini 中设置
// expose_php = Off

// 或在运行时(需在所有输出之前)
header_remove('X-Powered-By');
ini_set('expose_php', '0');

最佳实践

1. 始终设置 Content-Type

php
<?php

declare(strict_types=1);

// HTML 页面
header('Content-Type: text/html; charset=utf-8');

// API 响应
header('Content-Type: application/json; charset=utf-8');

// 文件下载
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file('/path/to/file');
header('Content-Type: ' . $mimeType);
header('Content-Disposition: attachment; filename="download.pdf"');

2. 设置安全响应头

php
<?php

declare(strict_types=1);

// 防止 MIME 类型嗅探
header('X-Content-Type-Options: nosniff');

// 防止点击劫持
header('X-Frame-Options: DENY');  // 或 SAMEORIGIN

// XSS 防护(旧浏览器)
header('X-XSS-Protection: 1; mode=block');

// HSTS(仅 HTTPS)
if (isHttps()) {
    header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
}

// Content Security Policy
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");

3. 正确使用 HTTP 缓存

php
<?php

declare(strict_types=1);

// API 响应通常禁用缓存
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');

// 静态资源设置缓存
$ttl = 3600; // 1 小时
header('Cache-Control: public, max-age=' . $ttl);
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $ttl) . ' GMT');

// ETag 缓存验证
$etag = md5_file('/path/to/resource');
$ifNoneMatch = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';

if ($ifNoneMatch === '"' . $etag . '"') {
    http_response_code(304);
    exit;
}

header('ETag: "' . $etag . '"');

下一节

继续学习:GET 与 POST 请求

参考链接