PHP 发送 HTTP 响应与文件下载
概述
一个完整的 HTTP 响应报文包含状态行、响应头和响应实体,关于 HTTP 响应底层结构你可以参考 HTTP 报文简介及组成结构这篇教程了解明细,这里不再重复介绍了。
在 PHP 中可以通过内置的 header 函数设置状态行及响应头,而对于响应实体,也就是我们通常看到的 API 响应数据或者 Web 页面响应视图(HTML 文档),通过 PHP 的打印函数输出即可,比如 echo
、printf
、var_dump
等,如果 HTML 和 PHP 脚本混合在一起,则也会解析其中的 PHP 代码,然后渲染对应的 HTML 文档作为响应实体。
耳听为虚,眼见为实,下面学院君结合常见的使用场景来演示如何在 PHP 中设置 HTTP 响应并发送给客户端。
响应状态码
我们在 http
目录下新建一个 response.php
来保存本篇教程编写的代码。默认情况下,PHP 返回的响应状态码是 200:
比如我们只通过 echo
设置响应实体,然后在浏览器中访问 http://localhost:9000/response.php
访问这个脚本,在 Chrome 扩展台中可以看到响应状态码正是 200,这是 PHP 底层自动设置的:
我们也可以显式在代码中设置状态码:
<?php
header('HTTP/1.1 200 OK');
echo '你好,学院君';
效果完全一致,响应状态行分三部分,第一部分是 HTTP 协议版本,第二部分是状态码,第三部分是描述状态码的短语。
除了 200 之外,还有很多其他响应状态码,比如 301、403、404、500 等,分别表征不同的含义,比如 301 表示永久重定向、403 表示没有权限、404 表示资源不存在、500 表示服务器错误。
比如说,我们设置一个 404 响应如下:
对应的响应状态行字符串格式需要和 HTTP 协议规范保持一致。合理的使用响应状态码可以对响应状态进行准确的描述,尤其是在 API 接口设计时,调用者根据响应状态码就可以大致得知错误原因。
重定向
在 PHP 中,可以通过设置 Location
响应头对用户请求进行重定向:
此时当我们访问 http://localhost:9000/response.php
时,页面会重定向到 https://laravel.geekai.co
:
默认情况下状态码是 302,表示临时重定向,你也可以显示设置这个状态码:
header('HTTP/1.1 302 Found');
header('Location: https://laravel.geekai.co');
还有一个表示永久重定向的状态码 301,要设置 301 重定向,可以这样设置:
header('HTTP/1.1 301 Move Permanently');
header('Location: https://laravel.geekai.co');
重新在浏览器访问该脚本,可以发现重定向状态码已经变成 301 了:
HTTP 基本认证
如果某个页面需要经过 HTTP 基本认证才能访问,可以通过设置 WWW-Authenticate
响应头来告知客户端请求用户:
此时访问 http://localhost:9000/response.php
,就会弹出认证表单输入框:
对于这种 HTTP 基本认证中提交的用户名和密码,PHP 默认已经将它们封装到超全局变量 $_SERVER
的 PHP_AUTH_USER
和 PHP_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 状态码:
刷新页面重试,如果认证成功,则返回如下提示信息:
你可以在请求头中看到经过 Base64 编码加密的包含用户名和密码字段的 Authorization
字段(Basic 表示基本认证,还有 Digest 表示摘要认证,更安全一些):
不过这种级别的认证等同于明文传输密码了,所以实际项目中不建议通过使用这种认证方案。
关于 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 数据渲染进行了优化,这样看起来更加美观):
非常方便。
文件下载
接下来,我们来看原生 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
文件:
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
,会弹出一个下载会话框:
点击右下角「存储」按钮保存,即可开始下载该文件。Windows 下也是类似:
小结
关于 HTTP 服务器、请求和响应部分我们就简单介绍到这里,由于 HTTP 协议本身是无状态的,而在某些场景中我们希望 HTTP 请求能够「记住」用户状态,比如实现用户认证、记住记录登录状态、电商网站中加入购物车到下单支付,这些都涉及到多次请求,多个页面,但是我们希望 HTTP 请求能够识别来自同一个用户的不同请求,为此,又引入了 Cookie 和 Session 的概念。下篇教程,我们将一起来探索 Cookie 和 Session,以及基于它们实现更加安全的用户认证解决方案(相对于前面的 HTTP 基本认证)。
No Comments