← Back to Index

Chapter 5: Network Programming

HTTP Request Handling & Web API Development

1 PHP & Web Servers

PHP was born for the Web. Unlike Node.js or Python, PHP doesn't require you to manually create an HTTP server — the web server (Apache/Nginx) handles receiving requests and passes them to PHP for processing.

Two Mainstream Deployment Models

Apache + mod_php

PHP runs directly as an Apache module. Simple configuration, suitable for development environments.

# Apache 配置示例
LoadModule php_module modules/libphp.so
AddHandler php-script .php

# .htaccess 路由重写
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]

Nginx + PHP-FPM (Recommended for Production)

Nginx handles static files while dynamic requests are forwarded to the PHP-FPM process pool for better performance.

# Nginx 配置示例
server {
    listen 80;
    root /var/www/html;
    index index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Request Lifecycle

# PHP 请求生命周期(每次请求都是独立的!)
客户端请求 → Web 服务器 → PHP 进程启动 → 执行脚本 → 返回响应 → 进程结束

# 这意味着:
# 1. 每次请求都是全新的环境(无状态)
# 2. 变量不会在请求间保留
# 3. 需要 Session/数据库来持久化数据

Superglobals

PHP automatically injects request information into predefined superglobal variables, requiring no imports or initialization:

<?php
// $_SERVER - 服务器和请求环境信息
echo $_SERVER['REQUEST_METHOD'];  // GET, POST, PUT, DELETE...
echo $_SERVER['REQUEST_URI'];     // /api/users?page=1
echo $_SERVER['HTTP_HOST'];       // www.example.com
echo $_SERVER['REMOTE_ADDR'];     // 客户端 IP
echo $_SERVER['HTTP_USER_AGENT']; // 浏览器信息
echo $_SERVER['CONTENT_TYPE'];    // 请求的 Content-Type

// $_GET - URL 查询参数
// URL: /search?q=php&page=2
echo $_GET['q'];    // "php"
echo $_GET['page']; // "2"

// $_POST - POST 表单数据(Content-Type: application/x-www-form-urlencoded)
echo $_POST['username'];
echo $_POST['password'];

// $_REQUEST - 合并 $_GET + $_POST + $_COOKIE(不推荐,来源不明确)
echo $_REQUEST['key'];

// $_FILES - 上传的文件信息
$file = $_FILES['avatar'];
echo $file['name'];     // 原始文件名
echo $file['tmp_name']; // 服务器临时路径
echo $file['size'];     // 文件大小(字节)
echo $file['type'];     // MIME 类型
echo $file['error'];    // 错误码,0 表示成功

🔄 Comparison with Other Languages

PHP's web model is "passive response" — you only write the handling logic, and the server calls your code. Other languages require you to actively create a server:

// PHP:直接写逻辑,放到 Web 目录即可
// index.php
$name = $_GET['name'] ?? 'World';
echo "Hello, $name!";
# Node.js:必须手动创建 HTTP 服务器
# const http = require('http');
# http.createServer((req, res) => {
#     res.end('Hello!');
# }).listen(3000);

# Python Flask:也需要显式创建应用
# from flask import Flask, request
# app = Flask(__name__)
# @app.route('/')
# def hello():
#     return f"Hello, {request.args.get('name', 'World')}!"

Advantage: PHP deployment is extremely simple — create a .php file, place it in the web directory, and it's accessible with zero configuration.
Disadvantage: Each request reinitializes everything, making it unsuitable for scenarios requiring persistent connections (e.g., WebSocket).

Built-in Development Server

# 快速启动开发服务器(仅用于开发!)
php -S localhost:8080

# 指定文档根目录
php -S localhost:8080 -t public/

# 使用路由脚本
php -S localhost:8080 router.php

2 Handling GET/POST Requests

Reading Request Parameters

<?php
// 安全地读取 GET 参数(避免 undefined index 警告)
$page = $_GET['page'] ?? 1;
$keyword = $_GET['q'] ?? '';

// 安全地读取 POST 参数
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';

// 检查参数是否存在
if (isset($_GET['id'])) {
    $id = (int)$_GET['id'];
}

// 使用 empty() 检查是否为空
if (!empty($_POST['username'])) {
    $username = $_POST['username'];
}

Input Filtering & Validation

Never trust user input. PHP provides filter_input() and filter_var() for filtering and validating data:

<?php
// filter_input() - 从指定来源获取并过滤
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
// 返回 int 值或 false(验证失败)或 null(参数不存在)

$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$url = filter_input(INPUT_GET, 'url', FILTER_VALIDATE_URL);

// 带选项的过滤
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 1, 'max_range' => 150]
]);

// filter_var() - 过滤任意变量
$email = filter_var($rawEmail, FILTER_VALIDATE_EMAIL);
$ip = filter_var($rawIp, FILTER_VALIDATE_IP);

// 净化(Sanitize)而非验证——移除非法字符
$cleanEmail = filter_var($input, FILTER_SANITIZE_EMAIL);
$cleanString = filter_var($input, FILTER_SANITIZE_SPECIAL_CHARS);
$cleanInt = filter_var($input, FILTER_SANITIZE_NUMBER_INT);

// 批量过滤
$filters = [
    'username' => FILTER_SANITIZE_SPECIAL_CHARS,
    'email'    => FILTER_VALIDATE_EMAIL,
    'age'      => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_range' => 0]],
];
$result = filter_input_array(INPUT_POST, $filters);

XSS Defense: htmlspecialchars()

<?php
// 输出到 HTML 时必须转义,防止 XSS 攻击
$userInput = '<script>alert("XSS")</script>';

// 错误:直接输出(会执行恶意脚本)
echo $userInput;

// 正确:转义后输出
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// 输出: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// 封装一个简便函数
function e(string $str): string {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

echo '<p>Welcome, ' . e($username) . '</p>';

Complete Form Handling Example

<?php
// contact.php - 同时处理表单显示和提交
$errors = [];
$success = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 验证姓名
    $name = trim($_POST['name'] ?? '');
    if (empty($name)) {
        $errors['name'] = '姓名不能为空';
    } elseif (mb_strlen($name) > 50) {
        $errors['name'] = '姓名不能超过 50 个字符';
    }

    // 验证邮箱
    $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
    if ($email === false || $email === null) {
        $errors['email'] = '请输入有效的邮箱地址';
    }

    // 验证消息
    $message = trim($_POST['message'] ?? '');
    if (empty($message)) {
        $errors['message'] = '消息不能为空';
    }

    // 无错误则处理提交
    if (empty($errors)) {
        // 保存到数据库或发送邮件...
        $success = true;
    }
}
?>
<!DOCTYPE html>
<html>
<body>
    <?php if ($success): ?>
        <div class="success">提交成功!感谢您的反馈。</div>
    <?php else: ?>
        <form method="POST" action="">
            <div>
                <label>姓名</label>
                <input type="text" name="name"
                       value="<?= htmlspecialchars($name ?? '', ENT_QUOTES, 'UTF-8') ?>">
                <?php if (isset($errors['name'])): ?>
                    <span class="error"><?= $errors['name'] ?></span>
                <?php endif; ?>
            </div>
            <div>
                <label>邮箱</label>
                <input type="email" name="email"
                       value="<?= htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
                <?php if (isset($errors['email'])): ?>
                    <span class="error"><?= $errors['email'] ?></span>
                <?php endif; ?>
            </div>
            <div>
                <label>消息</label>
                <textarea name="message"><?= htmlspecialchars($message ?? '', ENT_QUOTES, 'UTF-8') ?></textarea>
                <?php if (isset($errors['message'])): ?>
                    <span class="error"><?= $errors['message'] ?></span>
                <?php endif; ?>
            </div>
            <button type="submit">提交</button>
        </form>
    <?php endif; ?>
</body>
</html>

3 Building JSON APIs

Key Point: In modern PHP development, a large number of scenarios involve building JSON APIs rather than traditional server-side rendered pages. PHP handles JSON very concisely — json_encode() / json_decode() are built-in functions requiring no additional libraries.

JSON Encoding/Decoding Basics

<?php
// PHP 数组/对象 → JSON 字符串
$data = ['name' => '张三', 'age' => 30, 'skills' => ['PHP', 'MySQL']];
$json = json_encode($data);
// {"name":"张三","age":30,"skills":["PHP","MySQL"]}

// 美化输出 + 不转义 Unicode
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

// JSON 字符串 → PHP 关联数组
$decoded = json_decode($json, true); // true 返回数组,false/省略返回 stdClass 对象
echo $decoded['name']; // 张三

// 错误处理(PHP 8.4)
$result = json_decode('invalid json', true);
if (json_last_error() !== JSON_ERROR_NONE) {
    echo json_last_error_msg(); // "Syntax error"
}

// 或使用 JSON_THROW_ON_ERROR 标志抛出异常
try {
    $data = json_decode('invalid', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo $e->getMessage();
}

Reading the Raw Request Body

<?php
// 当客户端发送 JSON(Content-Type: application/json)时,
// $_POST 不会包含数据,需要手动读取原始输入
$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody, true);

if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    echo json_encode(['error' => '无效的 JSON 数据']);
    exit;
}

// 现在可以使用 $data['key'] 访问数据
$username = $data['username'] ?? null;

Complete REST API Example: User CRUD

<?php
// api/users.php - 简单的用户 REST API
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');

// 处理预检请求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

// 模拟数据库连接(实际中使用 PDO)
function getDb(): PDO {
    $pdo = new PDO('mysql:host=localhost;dbname=myapp;charset=utf8mb4', 'root', '');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    return $pdo;
}

// 封装 JSON 响应
function jsonResponse(mixed $data, int $code = 200): never {
    http_response_code($code);
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}

$method = $_SERVER['REQUEST_METHOD'];
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);

try {
    $db = getDb();

    match ($method) {
        'GET' => handleGet($db, $id),
        'POST' => handlePost($db),
        'PUT' => handlePut($db, $id),
        'DELETE' => handleDelete($db, $id),
        default => jsonResponse(['error' => '不支持的方法'], 405),
    };
} catch (PDOException $e) {
    jsonResponse(['error' => '数据库错误'], 500);
}

function handleGet(PDO $db, ?int $id): never {
    if ($id) {
        $stmt = $db->prepare('SELECT id, name, email FROM users WHERE id = ?');
        $stmt->execute([$id]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        $user ? jsonResponse($user) : jsonResponse(['error' => '用户不存在'], 404);
    }
    // 列表(带分页)
    $page = max(1, (int)($_GET['page'] ?? 1));
    $limit = 20;
    $offset = ($page - 1) * $limit;

    $stmt = $db->prepare('SELECT id, name, email FROM users LIMIT ? OFFSET ?');
    $stmt->execute([$limit, $offset]);
    jsonResponse([
        'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
        'page' => $page,
    ]);
}

function handlePost(PDO $db): never {
    $input = json_decode(file_get_contents('php://input'), true);
    if (!$input || empty($input['name']) || empty($input['email'])) {
        jsonResponse(['error' => '缺少必填字段 name, email'], 422);
    }

    $stmt = $db->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
    $stmt->execute([$input['name'], $input['email']]);
    jsonResponse(['id' => (int)$db->lastInsertId(), 'message' => '创建成功'], 201);
}

function handlePut(PDO $db, ?int $id): never {
    if (!$id) jsonResponse(['error' => '缺少用户 ID'], 400);

    $input = json_decode(file_get_contents('php://input'), true);
    $stmt = $db->prepare('UPDATE users SET name = ?, email = ? WHERE id = ?');
    $stmt->execute([$input['name'], $input['email'], $id]);
    jsonResponse(['message' => '更新成功']);
}

function handleDelete(PDO $db, ?int $id): never {
    if (!$id) jsonResponse(['error' => '缺少用户 ID'], 400);

    $stmt = $db->prepare('DELETE FROM users WHERE id = ?');
    $stmt->execute([$id]);
    $stmt->rowCount() > 0
        ? jsonResponse(['message' => '删除成功'])
        : jsonResponse(['error' => '用户不存在'], 404);
}

Usage examples:

# 获取用户列表
curl http://localhost:8080/api/users.php

# 获取单个用户
curl http://localhost:8080/api/users.php?id=1

# 创建用户
curl -X POST http://localhost:8080/api/users.php \
  -H "Content-Type: application/json" \
  -d '{"name":"李四","email":"lisi@example.com"}'

# 更新用户
curl -X PUT "http://localhost:8080/api/users.php?id=1" \
  -H "Content-Type: application/json" \
  -d '{"name":"张三丰","email":"zsf@example.com"}'

# 删除用户
curl -X DELETE "http://localhost:8080/api/users.php?id=1"

4 Sending HTTP Requests with cURL

PHP's cURL extension is the standard way to send HTTP requests, offering powerful functionality with support for various protocols and options.

GET Request

<?php
$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://api.example.com/users?page=1',
    CURLOPT_RETURNTRANSFER => true,  // 返回响应内容而非直接输出
    CURLOPT_TIMEOUT        => 10,    // 超时秒数
    CURLOPT_HTTPHEADER     => [
        'Accept: application/json',
        'Authorization: Bearer your-token-here',
    ],
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);

if ($error) {
    echo "cURL 错误: $error";
} elseif ($httpCode === 200) {
    $data = json_decode($response, true);
    print_r($data);
} else {
    echo "HTTP 错误: $httpCode";
}

POST Request (Sending JSON)

<?php
$postData = json_encode([
    'name'  => '王五',
    'email' => 'wangwu@example.com',
]);

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://api.example.com/users',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $postData,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Content-Length: ' . strlen($postData),
    ],
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode === 201) {
    echo '创建成功: ' . $response;
}

POST Form Data & File Upload

<?php
// 发送表单数据(application/x-www-form-urlencoded)
$ch = curl_init('https://api.example.com/login');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'username' => 'admin',
        'password' => 'secret',
    ]),
]);
$response = curl_exec($ch);
curl_close($ch);

// 上传文件(multipart/form-data)
$ch = curl_init('https://api.example.com/upload');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => [
        'file'        => new CURLFile('/path/to/photo.jpg', 'image/jpeg', 'photo.jpg'),
        'description' => '个人照片',
    ],
]);
$response = curl_exec($ch);
curl_close($ch);

Encapsulating an HTTP Client Function

<?php
function httpRequest(
    string $url,
    string $method = 'GET',
    ?array $data = null,
    array $headers = [],
    int $timeout = 10,
): array {
    $ch = curl_init();

    $options = [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => $timeout,
        CURLOPT_FOLLOWLOCATION => true,  // 自动跟随重定向
        CURLOPT_MAXREDIRS      => 5,
    ];

    if ($method === 'POST') {
        $options[CURLOPT_POST] = true;
    } elseif ($method !== 'GET') {
        $options[CURLOPT_CUSTOMREQUEST] = $method;
    }

    if ($data !== null) {
        $json = json_encode($data, JSON_UNESCAPED_UNICODE);
        $options[CURLOPT_POSTFIELDS] = $json;
        $headers[] = 'Content-Type: application/json';
        $headers[] = 'Content-Length: ' . strlen($json);
    }

    if (!empty($headers)) {
        $options[CURLOPT_HTTPHEADER] = $headers;
    }

    curl_setopt_array($ch, $options);

    $response = curl_exec($ch);
    $info = curl_getinfo($ch);
    $error = curl_error($ch);
    curl_close($ch);

    return [
        'status'  => $info['http_code'],
        'body'    => $response,
        'json'    => json_decode($response, true),
        'error'   => $error ?: null,
        'time'    => $info['total_time'],
    ];
}

// 使用示例
$result = httpRequest('https://api.example.com/users');
if ($result['status'] === 200) {
    foreach ($result['json'] as $user) {
        echo "{$user['name']} - {$user['email']}\n";
    }
}

$result = httpRequest(
    'https://api.example.com/users',
    'POST',
    ['name' => '赵六', 'email' => 'zhaoliu@example.com'],
    ['Authorization: Bearer token123'],
);
echo "状态码: {$result['status']}, 耗时: {$result['time']}s\n";

5 Simple Requests with file_get_contents

For simple HTTP requests, file_get_contents() offers the most concise approach without requiring the cURL extension.

Simple GET Request

<?php
// 一行代码发起 GET 请求(需要 php.ini 中 allow_url_fopen = On)
$response = file_get_contents('https://api.example.com/users');
$users = json_decode($response, true);

// 错误处理
$response = @file_get_contents('https://api.example.com/data');
if ($response === false) {
    echo "请求失败";
}

// 获取响应头
$response = file_get_contents('https://api.example.com/data');
// 请求后 $http_response_header 自动可用
print_r($http_response_header);
// ["HTTP/1.1 200 OK", "Content-Type: application/json", ...]

Sending POST with Stream Context

<?php
$data = json_encode(['name' => '钱七', 'email' => 'qianqi@example.com']);

$context = stream_context_create([
    'http' => [
        'method'  => 'POST',
        'header'  => implode("\r\n", [
            'Content-Type: application/json',
            'Authorization: Bearer token123',
            'Content-Length: ' . strlen($data),
        ]),
        'content' => $data,
        'timeout' => 10,
    ],
]);

$response = file_get_contents('https://api.example.com/users', false, $context);
$result = json_decode($response, true);

file_get_contents vs cURL

Feature file_get_contents cURL
Simplicity ⭐ Extremely simple, one line of code Requires multi-line configuration
Feature Completeness Basic GET/POST ⭐ Full HTTP support
Error Handling Limited (requires @ suppression) ⭐ Detailed error messages
File Upload ✘ Not supported ⭐ CURLFile
Cookie Management ✘ Not supported ⭐ CURLOPT_COOKIEJAR
Use Case Quick data fetching, scripts Production API calls

Recommendation: Use file_get_contents for simple scenarios; for production environments, cURL or the higher-level Guzzle library is recommended.

6 Sessions & Cookies

HTTP is a stateless protocol. PHP implements state persistence through Sessions and Cookies. Session data is stored on the server side, while Cookies are stored on the client side.

Cookie Operations

<?php
// 设置 Cookie(必须在任何输出之前调用)
setcookie('theme', 'dark', [
    'expires'  => time() + 86400 * 30, // 30 天后过期
    'path'     => '/',                  // 整个站点有效
    'domain'   => '.example.com',       // 含子域名
    'secure'   => true,                 // 仅 HTTPS
    'httponly'  => true,                // JS 不可访问(防 XSS)
    'samesite' => 'Lax',               // CSRF 防护
]);

// 读取 Cookie
$theme = $_COOKIE['theme'] ?? 'light';

// 删除 Cookie(设置过期时间为过去)
setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']);

Session Basics

<?php
// 启动 Session(必须在任何输出之前调用)
session_start();

// 写入 Session 数据
$_SESSION['user_id'] = 42;
$_SESSION['username'] = '张三';
$_SESSION['role'] = 'admin';

// 读取 Session
echo $_SESSION['username']; // 张三

// 检查 Session 值是否存在
if (isset($_SESSION['user_id'])) {
    echo '已登录';
}

// 删除特定 Session 值
unset($_SESSION['role']);

// 销毁整个 Session(登出时使用)
session_unset();   // 清除所有 Session 变量
session_destroy(); // 销毁 Session 文件

// 同时清除 Session Cookie
setcookie(session_name(), '', ['expires' => time() - 3600, 'path' => '/']);

How Sessions Work

# Session 生命周期:
1. session_start() → PHP 生成唯一 Session ID(如 abc123def456)
2. 通过 Cookie(PHPSESSID=abc123def456)发送给客户端
3. 数据序列化存储到服务器(默认:/tmp/sess_abc123def456)
4. 下次请求时,PHP 根据 Cookie 中的 ID 找到对应文件
5. 反序列化恢复 $_SESSION 数据

# 默认存储路径
# Linux: /tmp/
# 可通过 session.save_path 配置

# 查看当前 Session 配置
# php -i | grep session

Session Security Configuration

<?php
// 推荐的 Session 安全配置(在 session_start() 之前)
ini_set('session.cookie_httponly', '1');  // 禁止 JS 访问
ini_set('session.cookie_secure', '1');    // 仅 HTTPS
ini_set('session.cookie_samesite', 'Lax');
ini_set('session.use_strict_mode', '1');  // 拒绝未初始化的 Session ID

// 重新生成 Session ID(防止 Session 固定攻击)
session_regenerate_id(true); // true 表示删除旧 Session 文件

Complete Login Flow Example

<?php
// login.php - 登录处理
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';

    // 从数据库查询用户(使用预处理语句)
    $pdo = new PDO('mysql:host=localhost;dbname=myapp;charset=utf8mb4', 'root', '');
    $stmt = $pdo->prepare('SELECT id, username, password_hash FROM users WHERE username = ?');
    $stmt->execute([$username]);
    $user = $stmt->fetch(PDO::FETCH_ASSOC);

    // 验证密码(password_hash + password_verify)
    if ($user && password_verify($password, $user['password_hash'])) {
        // 登录成功:重新生成 Session ID 防止固定攻击
        session_regenerate_id(true);

        $_SESSION['user_id'] = $user['id'];
        $_SESSION['username'] = $user['username'];
        $_SESSION['login_time'] = time();

        header('Location: /dashboard.php');
        exit;
    }

    $error = '用户名或密码错误';
}

// auth_check.php - 鉴权中间件(其他页面引入)
function requireLogin(): void {
    session_start();

    if (!isset($_SESSION['user_id'])) {
        header('Location: /login.php');
        exit;
    }

    // 检查 Session 是否过期(30 分钟无操作)
    if (isset($_SESSION['login_time']) && time() - $_SESSION['login_time'] > 1800) {
        session_unset();
        session_destroy();
        header('Location: /login.php?expired=1');
        exit;
    }

    // 刷新活跃时间
    $_SESSION['login_time'] = time();
}

// logout.php - 登出
session_start();
session_unset();
session_destroy();
setcookie(session_name(), '', ['expires' => time() - 3600, 'path' => '/']);
header('Location: /login.php');
exit;

7 HTTP Response Control

Setting Status Codes

<?php
// 使用 http_response_code()(推荐)
http_response_code(200); // OK
http_response_code(201); // Created
http_response_code(301); // Moved Permanently
http_response_code(400); // Bad Request
http_response_code(401); // Unauthorized
http_response_code(403); // Forbidden
http_response_code(404); // Not Found
http_response_code(500); // Internal Server Error

// 获取当前状态码
$code = http_response_code();

Redirects

<?php
// 302 临时重定向(默认)
header('Location: /dashboard.php');
exit; // 重定向后必须 exit!

// 301 永久重定向
header('Location: https://new.example.com/page', true, 301);
exit;

// 使用 http_response_code + header
http_response_code(301);
header('Location: /new-url');
exit;

// PRG 模式(Post/Redirect/Get)——防止表单重复提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 处理表单...
    header('Location: /success.php');
    exit;
}

Content-Type Response Headers

<?php
// JSON
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['status' => 'ok']);

// HTML(默认,通常不需要显式设置)
header('Content-Type: text/html; charset=utf-8');

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

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

// 文件下载
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="report.csv"');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
exit;

Cache Control & Common Response Headers

<?php
// 禁止缓存(API 响应)
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');

// 允许缓存(静态资源)
header('Cache-Control: public, max-age=86400'); // 缓存 1 天
header('ETag: "' . md5_file($filePath) . '"');

// CORS 跨域设置
header('Access-Control-Allow-Origin: https://frontend.example.com');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');

// 安全相关头
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');

Note: All header() calls must be executed before any output (including whitespace and BOM). If output has already been sent, header() will fail with a Warning. Use headers_sent() to check if headers have already been sent.

8 Chapter Summary

🏗️ Request Model

  • • PHP natively integrates with web servers
  • • Each request runs in an independent process with no shared state
  • $_GET/$_POST/$_SERVER superglobals

🛡️ Input Security

  • filter_input() for filtering & validation
  • htmlspecialchars() to prevent XSS
  • • Never trust user input

📡 JSON API

  • json_encode() / json_decode()
  • php://input to read raw request body
  • • Set the correct Content-Type

🔗 HTTP Client

  • • cURL: Feature-complete, production-preferred
  • file_get_contents: Simple requests
  • • Guzzle library recommended for production

🔐 Session Management

  • • Sessions stored server-side, Cookies client-side
  • session_regenerate_id() prevents fixation attacks
  • • Security attributes: httponly, secure, samesite

📤 Response Control

  • http_response_code() to set status codes
  • header('Location: ...') for redirects
  • • Cache, CORS, and security header configuration

💡 Best Practices Quick Reference

Always use filter_input() to filter user input

Use htmlspecialchars() when outputting HTML

Call session_regenerate_id(true) after login

Set Cookie attributes: httponly + secure + samesite

Don't directly output $_GET/$_POST values

Don't use $_REQUEST (ambiguous source)

Don't forget exit after header()

Don't call header() after output