在 PHP 開發中,正確使用 header() 函數發送 HTTP 狀態碼是建構 RESTful API、處理重定向、錯誤回應等場景的基礎技能。本文整理了 HTTP/1.1 規範中常見的狀態碼,並提供一個可直接在專案中使用的封裝函數,幫助開發者快速查找並正確應用。
為什麼需要正確設定 HTTP 狀態碼?
HTTP 狀態碼是伺服器與客戶端(瀏覽器、API 消費者、爬蟲)溝通的關鍵語言。正確的狀態碼能:
- 讓瀏覽器正確處理重定向(301/302/307)
- 讓 API 消費者依據狀態碼判斷請求結果(200/201/400/401/404/500 等)
- 利於 SEO:搜尋引擎爬蟲依賴 301 永久移轉、404 不存在等狀態碼建立索引
- 避免安全風險:例如 403 Forbidden 比 200 OK 配合錯誤訊息更安全
常用 HTTP 狀態碼分類速查
1xx 資訊回應
| 狀態碼 | 名稱 | 說明 |
| 100 | Continue | 客戶端應繼續發送請求主體 |
| 101 | Switching Protocols | 伺服器同意切換協議(如 WebSocket 升級) |
2xx 成功
| 狀態碼 | 名稱 | 典型使用場景 |
| 200 | OK | 通用成功回應,GET/PUT/PATCH 成功 |
| 201 | Created | POST 建立資源成功,配合 Location 標頭 |
| 202 | Accepted | 非同步處理已接受,尚未完成 |
| 204 | No Content | 成功但無回應主體(如 DELETE 成功) |
| 206 | Partial Content | 範圍請求成功(斷點續傳、影音串流) |
3xx 重定向
| 狀態碼 | 名稱 | 關鍵差異 |
| 301 | Moved Permanently | 永久移轉,搜尋引擎會傳遞權重,瀏覽器會快取 |
| 302 | Found | 暫時重定向,HTTP/1.0 行為,POST 可能變 GET |
| 303 | See Other | 強制 GET 重定向,PRG 模式常用 |
| 304 | Not Modified | 快取有效,配合 ETag/Last-Modified |
| 307 | Temporary Redirect | 暫時重定向,保持原請求方法(HTTP/1.1) |
4xx 客戶端錯誤
| 狀態碼 | 名稱 | 典型場景 |
| 400 | Bad Request | 請求語法錯誤、參數驗證失敗 |
| 401 | Unauthorized | 未認證或 Token 失效 |
| 403 | Forbidden | 已認證但權限不足 |
| 404 | Not Found | 資源不存在 |
| 405 | Method Not Allowed | HTTP 方法不支援,需回應 Allow 標頭 |
| 409 | Conflict | 資源衝突(如重複建立) |
| 410 | Gone | 資源永久刪除,比 404 更明確 |
| 422 | Unprocessable Entity | 語意錯誤(WebDAV/RFC 4918),API 驗證失敗常用 |
| 429 | Too Many Requests | 速率限制,配合 Retry-After |
5xx 伺服器錯誤
| 狀態碼 | 名稱 | 說明 |
| 500 | Internal Server Error | 未預期的伺服器錯誤 |
| 501 | Not Implemented | 功能未實作 |
| 502 | Bad Gateway | 上游伺服器回應無效 |
| 503 | Service Unavailable | 暫時無法服務(維護、過載),建議加 Retry-After |
| 504 | Gateway Timeout | 上游伺服器逾時 |
可直接使用的 PHP 封裝函數
<?php
/**
* 發送 HTTP 狀態碼標頭
* @param int $code HTTP 狀態碼
* @param string $version HTTP 版本,預設 HTTP/1.1
* @return void
*/
function send_http_status(int $code, string $version = 'HTTP/1.1'): void
{
static $messages = [
100 => 'Continue', 101 => 'Switching Protocols',
200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information',
204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found',
303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden',
404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable',
407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict',
410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed',
413 => 'Payload Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 422 => 'Unprocessable Entity',
429 => 'Too Many Requests',
500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway',
503 => 'Service Unavailable', 504 => 'Gateway Timeout',
];
if (!isset($messages[$code])) {
// 未定義的狀態碼預設 500
$code = 500;
}
// 避免重複發送標頭
if (headers_sent($file, $line)) {
error_log("Headers already sent in $file:$line, cannot send status $code");
return;
}
header("$version $code {$messages[$code]}", true, $code);
}
// 使用範例
// send_http_status(404); // 404 Not Found
// send_http_status(201); // 201 Created
// send_http_status(301); // 301 Moved Permanently
// send_http_status(429); // 429 Too Many Requests
?>
進階應用技巧
1. RESTful API 統一錯誤格式
<?php
function api_error(int $code, string $message, array $errors = []): never
{
send_http_status($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => false,
'error' => [
'code' => $code,
'message' => $message,
'details' => $errors,
],
], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
exit;
}
// 使用
// api_error(422, '驗證失敗', ['email' => '格式不正確']);
// api_error(404, '找不到指定資源');
?>
2. 重定向最佳實踐
<?php
function redirect(string $url, int $code = 302): never
{
// 合法重定向碼:301, 302, 303, 307, 308
$allowed = [301, 302, 303, 307, 308];
if (!in_array($code, $allowed, true)) {
$code = 302;
}
send_http_status($code);
header("Location: $url", true, $code);
// 兼容舊瀏覽器
echo "<a href='$url'>Redirecting...</a>";
exit;
}
// 永久移轉(SEO 友善)
// redirect('https://example.com/new-url', 301);
// POST 後重定向(PRG 模式),防止重複提交
// redirect('/success', 303);
?>
3. 快取控制與 304 Not Modified
<?php
function cache_control(int $maxAge = 3600, bool $mustRevalidate = true): void
{
$directives = ["max-age=$maxAge"];
if ($mustRevalidate) $directives[] = 'must-revalidate';
header('Cache-Control: ' . implode(', ', $directives));
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $maxAge) . ' GMT');
}
function check_etag(string $etag): bool
{
$clientEtag = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';
if ($clientEtag === $etag) {
send_http_status(304);
return true;
}
header("ETag: $etag");
return false;
}
// 使用範例
// $etag = md5_file($filePath);
// if (check_etag($etag)) exit;
// cache_control(86400);
// readfile($filePath);
?>
常見錯誤與避坑指南
- 標頭已發送錯誤:確保在任何輸出(echo、空白、BOM)之前呼叫
header()。使用 headers_sent() 檢測。
- 狀態碼與標頭不匹配:
header('Location: ...', true, 302) 第三參數必須與狀態碼一致。
- 缺少 Content-Type:API 回應務必加
header('Content-Type: application/json; charset=utf-8')。
- 重定向迴圈:檢查重定向目標是否再次重定向回原 URL。
- 404 濫用:資源永久刪除應用 410 Gone,語意更清晰。
總結
掌握 HTTP 狀態碼的正確使用,能讓你的 PHP 應用程式更符合 Web 標準、更易於除錯、對 SEO 更友善、API 介接更順暢。建議將上述封裝函數納入專案共用函式庫,統一管理標頭輸出,減少人為錯誤。