Web 请求处理
这一课你将能做到
- 用 $_GET / $_POST / $_SERVER 等超全局变量拿到请求的全部信息
- 处理一个完整的 HTML 表单:校验、防 XSS 转义、PRG 重定向
- 用 Session 实现「登录一次,多页可用」的状态保持,理解 Cookie 与 Session 的分工
为什么要学
没有框架时,PHP 处理请求的原生方式就是超全局变量 + header + session——这些能力后来被 Laravel 的 Request、Session 门面包了一层,但内核没变。理解原生机制,你才能明白框架帮你做了什么(参数绑定、CSRF token、Session 驱动),也才能在排查「表单提交乱码」「Session 丢失」这类问题时定位到根因。对 Java 开发者来说,这一课相当于 Servlet 的 request/response/session 三件套。
人话:超全局变量是 PHP 帮你拆好的「快递包裹」(请求参数、请求头、文件),header() 是「盖章回执」(状态码、跳转),Session 是「储物柜」(跨请求记住你是谁)。
先建立图像
一次表单请求的数据流:
浏览器表单 POST /login
│ 请求体: username=tom&password=123
▼
PHP 引擎自动填充超全局变量
$_POST['username'] / $_SERVER['REQUEST_METHOD'] / $_COOKIE[...]
▼
你的脚本:校验 → 查库 → $_SESSION['uid']=1
▼
setcookie(PHPSESSID) + 302 重定向 → 浏览器后续请求带着 Cookie
▼
每个请求 session_start() 凭 Cookie 找回 $_SESSION
跟着做
步骤 1:跑通最小请求-响应
新建 web/ 目录,放 hello.php:
<?php
declare(strict_types=1);
header('Content-Type: text/html; charset=utf-8');
$method = $_SERVER['REQUEST_METHOD'];
$user = $_GET['name'] ?? '访客';
echo "<h1>请求方法:{$method}</h1>";
echo "<p>你好," . htmlspecialchars($user, ENT_QUOTES, 'UTF-8') . "</p>";
在该目录 php -S localhost:8000,访问 http://localhost:8000/hello.php?name=Spring。试试传 <b>粗体</b> 作为 name——被转义成文本原样显示,这就是 htmlspecialchars 的作用。
步骤 2:处理 POST 表单(PRG 模式)
login.php——自提交表单 + 校验 + 重定向:
<?php
declare(strict_types=1);
session_start();
header('Content-Type: text/html; charset=utf-8');
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
if ($username === '') {
$errors['username'] = '用户名不能为空';
}
if (strlen($password) < 6) {
$errors['password'] = '密码至少 6 位';
}
if ($errors === []) {
$_SESSION['username'] = $username; // 登录态写入 Session
header('Location: /welcome.php', true, 302); // PRG:成功后重定向
exit; // header 后必须 exit
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>登录</title></head>
<body>
<form method="post" action="login.php">
<label>用户名 <input name="username" value="<?= htmlspecialchars($_POST['username'] ?? '') ?>"></label>
<span style="color:red"><?= $errors['username'] ?? '' ?></span>
<label>密码 <input type="password" name="password"></label>
<span style="color:red"><?= $errors['password'] ?? '' ?></span>
<button type="submit">登录</button>
</form>
</body>
</html>
<?php
session_start();
if (!isset($_SESSION['username'])) {
header('Location: /login.php', true, 302);
exit;
}
echo '欢迎回来,' . htmlspecialchars($_SESSION['username'], ENT_QUOTES, 'UTF-8');
PRG(Post-Redirect-Get)解决「刷新重复提交」:POST 成功后立刻 302 跳转,浏览器地址栏变成 GET 页面,F5 刷新不会再次提交表单。
步骤 3:读懂 $_SERVER 关键键名
<?php
// 常用键速查(值因环境而异)
var_dump(
$_SERVER['REQUEST_METHOD'], // GET / POST / PUT ...
$_SERVER['REQUEST_URI'], // /hello.php?name=x
$_SERVER['QUERY_STRING'], // name=x
$_SERVER['HTTP_USER_AGENT'], // 请求头 User-Agent
$_SERVER['HTTP_CONTENT_TYPE'] ?? '-',
$_SERVER['REMOTE_ADDR'], // 客户端 IP(经代理时是代理 IP)
$_SERVER['SCRIPT_NAME'] // 当前脚本路径
);
// 自定义请求头:X-Token → $_SERVER['HTTP_X_TOKEN']
// 请求体原始内容(JSON API 场景):file_get_contents('php://input')
步骤 4:Cookie 与 Session 的分工
<?php
declare(strict_types=1);
session_start();
// Cookie:存在浏览器,每次请求自动携带,适合非敏感偏好
setcookie('theme', 'dark', [
'expires' => time() + 86400 * 7,
'path' => '/',
'secure' => true, // 仅 HTTPS 传输
'httponly' => true, // JS 不可读,防 XSS 偷取
'samesite' => 'Lax', // 缓解 CSRF
]);
// Session:数据存服务端(默认文件),浏览器只存 PHPSESSID
$_SESSION['cart'] = ['键盘', '鼠标'];
$theme = $_COOKIE['theme'] ?? 'light';
echo "主题:{$theme},购物车:" . count($_SESSION['cart']) . ' 件';
// 退出登录
// session_unset(); session_destroy();
讲清楚要点
- 超全局变量八姐妹:
$_GET、$_POST、$_COOKIE、$_SESSION、$_SERVER、$_FILES、$_ENV、$_REQUEST(= GET+POST+COOKIE 的混合,容易混淆来源,生产慎用)。它们在函数内也可直接访问,无需 global 声明。
- 所有输入都是不可信字符串:
$_GET['page'] 拿到的是 string,比较数字前要 (int) 强转;$_POST['age'] 再像数字也不是数字。校验(filter_var)+ 类型转换 + 白名单,三步缺一不可。
- header() 之前不能有任何输出:包括空行、BOM、echo。一旦输出已发送,再设状态码/头会报
headers already sent。安全姿势:业务逻辑全写在前,模板渲染放最后,header 跳转后立即 exit。
- Session 生命周期:
session_start() 按 Cookie 里的 PHPSESSID 加载服务端数据;默认存文件(session.save_path),生产可切 Redis(session.save_handler=redis)实现多机共享——这正是 Java 应用把 Session 放 Redis 的同款方案。
你可能会懵的地方
- GET 和 POST 除了位置还有什么区别?
语义区别:GET 幂等取资源,可缓存、可收藏、进日志;POST 改变状态、不缓存。长度上 URL 有实践上限(约 2KB~8KB),POST 受
post_max_size 限制。编码上 GET 只能 URL 编码,POST 表单支持 application/x-www-form-urlencoded、multipart/form-data(传文件必须)。
- JSON 请求体为什么 $_POST 里没有?
$_POST 只解析表单编码。前端 fetch 发 Content-Type: application/json 时,要用 json_decode(file_get_contents('php://input'), true) 手动解析——下一课 REST API 全靠它。
- Session 和 Cookie 到底谁存数据?
Cookie 本身存数据在浏览器(4KB 级、用户可见可改);Session 只在 Cookie 里存一个不记名 ID,数据在服务端。敏感信息(用户身份、权限)只能放 Session,Cookie 里放主题、语言这类可篡改无大碍的偏好。
- 为什么每个文件都要 session_start()?
Session 不会自动激活,
session_start() 才会解析 Cookie、加载 $_SESSION。忘了它,$_SESSION 是空数组且改动不会保存——这是「Session 突然失效」的头号原因。
headers already sent by ... 怎么回事?
在 header()/session_start()/setcookie() 之前已有输出。检查该行号之前的 echo、HTML、UTF-8 BOM(保存为「无 BOM」格式)。
错法对照
| 错法 | 问题 | 正解 |
|---|
echo $_GET['name'] 直接输出 | 反射型 XSS 注入点 | htmlspecialchars($x, ENT_QUOTES, 'UTF-8') |
$_REQUEST['id'] 混取参数 | 参数来源不可控,易被 COOKIE 注入 | 明确用 $_GET 或 $_POST |
| header 跳转后不 exit | 后续代码继续执行 | header(...); exit; |
| 把密码、手机号存进 Cookie | 可被用户查看与篡改 | 敏感数据放 Session 或服务端 |
直接信任 $_POST['age'] 是 int | 实际是 string,比较出错 | 先 filter 校验再 (int) 强转 |
上传文件后用 $_POST['file'] | 文件在 $_FILES,POST 里没有 | 读 $_FILES['file']['tmp_name'] 并 move_uploaded_file |
卡点急救
- POST 了但
$_POST 是空的:查 Content-Type 是否 application/x-www-form-urlencoded 或 multipart/form-data;JSON 请求体必须走 php://input;再检查 post_max_size 是否被超过。
- Session 每次刷新都丢失:确认每个文件头部都有
session_start();浏览器没禁 Cookie;服务端 session.save_path 目录存在且可写;多机部署时 Session 已切 Redis。
- 中文表单提交乱码:三处统一 UTF-8——页面
<meta charset="UTF-8">、header('Content-Type: ...; charset=utf-8')、数据库连接 DSN 里 charset=utf8mb4。
面试常问
- Cookie 和 Session 的区别与联系?
Cookie 存于浏览器、随请求自动携带、容量小且可被用户篡改;Session 数据存服务端,Cookie 仅保存会话 ID。Session 通常以 Cookie 为载体建立关联;禁用 Cookie 时可用 URL 重写传递 SID。敏感状态一律放服务端。
- 如何防止 XSS 和 CSRF?
XSS:输出时按上下文转义(HTML 用 htmlspecialchars、JS 用 json_encode + JSON_HEX),配合 CSP 响应头与 Cookie 的 httponly。CSRF:表单嵌入随机 token,服务端 Session 校验;辅以 SameSite Cookie 属性和校验 Origin 头。
- PHP 里如何接收 JSON 请求体并返回 JSON 响应?
请求体用
json_decode(file_get_contents('php://input'), true) 解析($_POST 不解析 JSON);响应前设 header('Content-Type: application/json; charset=utf-8'),echo json_encode($data, JSON_UNESCAPED_UNICODE) 输出。框架里的 Request::json() / JsonResponse 就是这两步的封装。
自测三问
- PRG 模式解决什么问题?三个字母分别是什么?
$_FILES、php://input、$_POST 各在什么请求形态下使用?
- Session 数据默认存在哪里?多台 FPM 机器部署时会遇到什么问题?
小结一句话
Web 请求处理 = 超全局变量读输入 + htmlspecialchars 输出转义 + header 控制响应 + Session/Cookie 维持状态,四件套是框架封装前的原始真相。
延伸