PHP 发送 HTTP 响应与文件下载

概述

一个完整的 HTTP 响应报文包含状态行、响应头和响应实体,关于 HTTP 响应底层结构你可以参考 HTTP 报文简介及组成结构这篇教程了解明细,这里不再重复介绍了。

在 PHP 中可以通过内置的 header 函数设置状态行及响应头,而对于响应实体,也就是我们通常看到的 API 响应数据或者 Web 页面响应视图(HTML 文档),通过 PHP 的打印函数输出即可,比如 echoprintfvar_dump 等,如果 HTML 和 PHP 脚本混合在一起,则也会解析其中的 PHP 代码,然后渲染对应的 HTML 文档作为响应实体。

耳听为虚,眼见为实,下面学院君结合常见的使用场景来演示如何在 PHP 中设置 HTTP 响应并发送给客户端。

响应状态码

我们在 http 目录下新建一个 response.php 来保存本篇教程编写的代码。默认情况下,PHP 返回的响应状态码是 200:

-w649

比如我们只通过 echo 设置响应实体,然后在浏览器中访问 http://localhost:9000/response.php 访问这个脚本,在 Chrome 扩展台中可以看到响应状态码正是 200,这是 PHP 底层自动设置的:

-w952

我们也可以显式在代码中设置状态码:

<?php

header('HTTP/1.1 200 OK');
echo '你好,学院君';

效果完全一致,响应状态行分三部分,第一部分是 HTTP 协议版本,第二部分是状态码,第三部分是描述状态码的短语。

除了 200 之外,还有很多其他响应状态码,比如 301、403、404、500 等,分别表征不同的含义,比如 301 表示永久重定向、403 表示没有权限、404 表示资源不存在、500 表示服务器错误。

比如说,我们设置一个 404 响应如下:

-w782

-w1008

对应的响应状态行字符串格式需要和 HTTP 协议规范保持一致。合理的使用响应状态码可以对响应状态进行准确的描述,尤其是在 API 接口设计时,调用者根据响应状态码就可以大致得知错误原因。

重定向

在 PHP 中,可以通过设置 Location 响应头对用户请求进行重定向:

-w827

此时当我们访问 http://localhost:9000/response.php 时,页面会重定向到 https://xueyuanjun.com

-w1016

默认情况下状态码是 302,表示临时重定向,你也可以显示设置这个状态码:

header('HTTP/1.1 302 Found');
header('Location: https://xueyuanjun.com');

还有一个表示永久重定向的状态码 301,要设置 301 重定向,可以这样设置:

header('HTTP/1.1 301 Move Permanently');
header('Location: https://xueyuanjun.com');

重新在浏览器访问该脚本,可以发现重定向状态码已经变成 301 了:

-w1023

HTTP 基本认证

如果某个页面需要经过 HTTP 基本认证才能访问,可以通过设置 WWW-Authenticate 响应头来告知客户端请求用户:

-w820

此时访问 http://localhost:9000/response.php,就会弹出认证表单输入框:

-w1078

对于这种 HTTP 基本认证中提交的用户名和密码,PHP 默认已经将它们封装到超全局变量 $_SERVERPHP_AUTH_USERPHP_AUTH_PW 字段中(HTTP 协议默认会通过请求头 Authorization 提交这些信息到服务端,关于相关的底层原理可以阅读 HTTP 认证实现方案介绍这篇教程)。

我们在服务端编写对应的处理代码:

// HTTP Basic 认证简单实现
if (empty($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic');
} else {
    $name = $_SERVER['PHP_AUTH_USER'];
    $pass = $_SERVER['PHP_AUTH_PW'];
    if ($name == '学院君' && $pass == '123456') {
        echo '用户认证成功,可以访问该页面';
    } else {
        header('HTTP/1.1 401 Unauthorized');
        echo '用户认证失败,请刷新页面重试';
    }
}

如果用户提交的用户名或密码不正确,则返回 401 Unauthorized 状态码:

-w958

刷新页面重试,如果认证成功,则返回如下提示信息:

-w650

你可以在请求头中看到经过 Base64 编码加密的包含用户名和密码字段的 Authorization 字段(Basic 表示基本认证,还有 Digest 表示摘要认证,更安全一些):

-w944

不过这种级别的认证等同于明文传输密码了,所以实际项目中不建议通过使用这种认证方案。

关于 PHP 设置 HTTP 响应头学院君就简单介绍到这里,已经覆盖了日常我们经常使用到的场景,当然,还有一块就是 HTTP 缓存的设置,这是一个比较宏大的话题,之前已经在 HTTP 协议详解相关教程中详细介绍过了,除非你想要系统了解 HTTP 缓存实现和性能优化,否则对初学者来说,平时也不太会用到,这里不再单独介绍,我们接下来看看响应实体设置部分。

JSON 响应

关于 Web 页面的响应实体输出(主要是 HTML 文档,或者一些调试信息输出,包括文本字符串、数组等),已经都看到过相关的演示实例了,这里我们介绍两种其他的响应输出格式,首先来看 JSON 响应。

在 API 接口中,通常返回的是 JSON 格式数据,JSON 本质上也就是对象字符串,所以在请求处理代码的最后,通过 echo 输出对应的 JSON 对象字符串即可,在 PHP 中,可以通过 PHP 内置的 json_encode 函数快速将对象、数组等格式数据转化为对应的 JSON 字符串。

我们在 http/response.php 中注释掉之前的代码,新增如下 JSON 响应代码:

// JSON 响应
$album = new stdClass();
$album->title = 'PHP 全栈工程师指南';
$album->summary = '基于 Laravel + Vue.js 框架的学习和实战,快速成为合格的 PHP 全栈开发工程师';
$album->author = '学院君';
$album->posts = [
    [
        'id' => 1,
        'title' => 'PHP 入门指南'
    ],
    [
        'id' => 2,
        'title' => 'Laravel 入门指南'
    ]
];
echo json_encode($album);

在浏览器中访问 http://localhost:9000/response.php,返回的 JSON 格式响应数据如下(通过 Chrome 插件 FeHelper 对 JSON 数据渲染进行了优化,这样看起来更加美观):

-w845

非常方便。

文件下载

接下来,我们来看原生 PHP 代码中如何通过 HTTP 响应实现文件下载。其实也很简单,通过设置相关响应头,然后再通过内置的 readfile 函数读取二进制文件流通过网络输出给客户端浏览器即可。

注释掉 response.php 中的所有代码,新增如下文件下载代码:

// 文件下载
// 设置下载文件内容格式
header('Content-type: application/octet-stream');
// 设置下载文件名
header('Content-Disposition: attachment; filename="laravel.zip"');
// 读取二进制文件流返回给客户端浏览器
$filepath = __DIR__ . '/files/laravel7.zip';
readfile($filepath);

这里我们下载一个位于 Web 根目录下 files 子目录下的 laravel7.zip 文件:

-w632

zip 格式文件对应的 MIME 类型是 application/octet-stream(映射关系可以在这里查询:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types),我们通过 Content-Type 响应头设置即可,然后通过 Content-Disposition 设置下载到本地对应的文件名,最后读取二进制文件流返回给客户端。

在浏览器中访问 http://localhost:9000/response.php,会弹出一个下载会话框:

-w829

点击右下角「存储」按钮保存,即可开始下载该文件。Windows 下也是类似:

-w564

小结

关于 HTTP 服务器、请求和响应部分我们就简单介绍到这里,由于 HTTP 协议本身是无状态的,而在某些场景中我们希望 HTTP 请求能够「记住」用户状态,比如实现用户认证、记住记录登录状态、电商网站中加入购物车到下单支付,这些都涉及到多次请求,多个页面,但是我们希望 HTTP 请求能够识别来自同一个用户的不同请求,为此,又引入了 Cookie 和 Session 的概念。下篇教程,我们将一起来探索 Cookie 和 Session,以及基于它们实现更加安全的用户认证解决方案(相对于前面的 HTTP 基本认证)。

上一篇: PHP 用户请求数据获取与文件上传

下一篇: 如何在 PHP 中使用和管理 Cookie