HTTP 响应
创建回复
字符串和数组
所有路由和控制器都应返回一个响应,并将其发送回用户的浏览器。Laravel 提供了几种不同的响应返回方式。最基本的响应是从路由或控制器返回一个字符串。框架会自动将该字符串转换为完整的 HTTP 响应:
1Route::get('/', function () {2 return 'Hello World';3});
除了从路由和控制器返回字符串之外,你还可以返回数组。框架会自动将数组转换为 JSON 响应:
1Route::get('/', function () {2 return [1, 2, 3];3});
你知道吗?路由或控制器 也可以返回Eloquent 集合。它们会自动转换为 JSON。快来试试吧!
响应对象
通常,你不会从路由操作中返回简单的字符串或数组,而是返回完整的Illuminate\Http\Response
实例或视图。
返回完整Response
实例允许您自定义响应的 HTTP 状态代码和标头。Response
实例继承自该类Symfony\Component\HttpFoundation\Response
,该类提供了多种用于构建 HTTP 响应的方法:
1Route::get('/home', function () {2 return response('Hello World', 200)3 ->header('Content-Type', 'text/plain');4});
Eloquent 模型和集合
你也可以直接从路由和控制器返回Eloquent ORM模型和集合。当你这样做时,Laravel 会自动将模型和集合转换为 JSON 响应,同时保留模型的隐藏属性:
1use App\Models\User;2 3Route::get('/user/{user}', function (User $user) {4 return $user;5});
将标头附加到响应
请记住,大多数响应方法都是可链接的,从而允许流畅地构建响应实例。例如,你可以使用该header
方法在将响应发送回用户之前向其添加一系列标头:
1return response($content)2 ->header('Content-Type', $type)3 ->header('X-Header-One', 'Header Value')4 ->header('X-Header-Two', 'Header Value');
或者,您可以使用该withHeaders
方法指定要添加到响应中的标头数组:
1return response($content)2 ->withHeaders([3 'Content-Type' => $type,4 'X-Header-One' => 'Header Value',5 'X-Header-Two' => 'Header Value',6 ]);
缓存控制中间件
Laravel 包含一个cache.headers
中间件,可用于快速设置Cache-Control
一组路由的标头。指令应使用与相应的 cache-control 指令等效的“蛇形命名法”来提供,并以分号分隔。如果etag
在指令列表中指定了 ,则响应内容的 MD5 哈希值将自动设置为 ETag 标识符:
1Route::middleware('cache.headers:public;max_age=2628000;etag')->group(function () {2 Route::get('/privacy', function () {3 // ...4 });5 6 Route::get('/terms', function () {7 // ...8 });9});
将 Cookie 附加到响应
Illuminate\Http\Response
您可以使用该方法将 Cookie 附加到传出实例cookie
。您需要将名称、值以及 Cookie 的有效分钟数传递给此方法:
1return response('Hello World')->cookie(2 'name', 'value', $minutes3);
该cookie
方法还接受一些不常用的参数。通常,这些参数的用途和含义与 PHP 原生setcookie方法的参数相同:
1return response('Hello World')->cookie(2 'name', 'value', $minutes, $path, $domain, $secure, $httpOnly3);
如果您希望确保 Cookie 随传出响应一起发送,但您尚未拥有该响应的实例,则可以使用Cookie
外观将 Cookie “排队”以在发送响应时附加到响应中。该queue
方法接受创建 Cookie 实例所需的参数。这些 Cookie 将在传出响应发送到浏览器之前附加到该响应中:
1use Illuminate\Support\Facades\Cookie;2 3Cookie::queue('name', 'value', $minutes);
生成 Cookie 实例
如果您希望生成一个Symfony\Component\HttpFoundation\Cookie
稍后可以附加到响应实例的实例,可以使用全局cookie
Helpers。除非此 Cookie 已附加到响应实例,否则它不会被发送回客户端:
1$cookie = cookie('name', 'value', $minutes);2 3return response('Hello World')->cookie($cookie);
提前使 Cookie 过期
withoutCookie
您可以通过传出响应的方法使 cookie 过期来删除它:
1return response('Hello World')->withoutCookie('name');
如果您还没有传出响应的实例,您可以使用Cookie
外观的expire
方法使 cookie 过期:
1Cookie::expire('name');
Cookie 和加密
默认情况下,得益于Illuminate\Cookie\Middleware\EncryptCookies
中间件,Laravel 生成的所有 Cookie 都经过加密和签名,因此客户端无法修改或读取它们。如果您希望禁用应用程序生成的部分 Cookie 的加密,可以encryptCookies
在应用程序bootstrap/app.php
文件中使用以下方法:
1->withMiddleware(function (Middleware $middleware) {2 $middleware->encryptCookies(except: [3 'cookie_name',4 ]);5})
重定向
重定向响应是该类的实例Illuminate\Http\RedirectResponse
,包含将用户重定向到另一个 URL 所需的正确标头。有几种方法可以生成RedirectResponse
实例。最简单的方法是使用全局redirect
Helpers:
1Route::get('/dashboard', function () {2 return redirect('/home/dashboard');3});
有时你可能希望将用户重定向到他们之前的位置,例如当提交的表单无效时。你可以使用全局back
辅助函数来实现。由于此功能使用了session,请确保调用该函数的路由back
使用了web
中间件组:
1Route::post('/user/profile', function () {2 // Validate the request...3 4 return back()->withInput();5});
重定向到命名路由
当你redirect
不带参数调用辅助函数时,会返回一个 的实例Illuminate\Routing\Redirector
,允许你调用该Redirector
实例上的任何方法。例如,要生成指向RedirectResponse
命名路由的 ,你可以使用如下route
方法:
1return redirect()->route('login');
如果您的路由有参数,您可以将它们作为第二个参数传递给route
方法:
1// For a route with the following URI: /profile/{id}2 3return redirect()->route('profile', ['id' => 1]);
通过 Eloquent 模型填充参数
如果你要重定向到带有“ID”参数的路由,并且该参数由 Eloquent 模型填充,则可以传递模型本身。ID 将被自动提取:
1// For a route with the following URI: /profile/{id}2 3return redirect()->route('profile', [$user]);
如果您想要自定义放置在路由参数中的值,您可以在路由参数定义中指定列(/profile/{id:slug}
),也可以getRouteKey
在 Eloquent 模型上覆盖该方法:
1/**2 * Get the value of the model's route key.3 */4public function getRouteKey(): mixed5{6 return $this->slug;7}
重定向到控制器操作
你也可以生成到控制器动作的重定向。为此,请将控制器和动作名称传递给action
方法:
1use App\Http\Controllers\UserController;2 3return redirect()->action([UserController::class, 'index']);
如果您的控制器路由需要参数,您可以将它们作为第二个参数传递给action
方法:
1return redirect()->action(2 [UserController::class, 'profile'], ['id' => 1]3);
重定向到外部域
有时你可能需要重定向到应用程序外部的域名。你可以调用该away
方法来实现,该方法RedirectResponse
无需任何额外的 URL 编码、验证或确认:
1return redirect()->away('https://www.google.com');
使用闪存会话数据进行重定向
重定向到新的 URL 和将数据刷入会话通常是同时进行的。通常,这是在成功执行操作后,将成功消息刷入会话时完成的。为了方便起见,你可以RedirectResponse
在一个单一、流畅的方法链中创建一个实例并将数据刷入会话:
1Route::post('/user/profile', function () {2 // ...3 4 return redirect('/dashboard')->with('status', 'Profile updated!');5});
用户重定向后,你可以显示来自会话的闪现消息。例如,使用Blade 语法:
1@if (session('status'))2 <div class="alert alert-success">3 {{ session('status') }}4 </div>5@endif
使用输入重定向
您可以使用实例withInput
提供的方法,RedirectResponse
在将用户重定向到新位置之前,将当前请求的输入数据刷写到会话中。这通常是在用户遇到验证错误时执行的。一旦输入被刷写到会话中,您就可以在下一个请求中轻松检索它以重新填充表单:
1return back()->withInput();
其他响应类型
该response
Helpers可用于生成其他类型的响应实例。当response
不带参数调用Helpers时,将返回该Illuminate\Contracts\Routing\ResponseFactory
契约的实现。该契约提供了几种用于生成响应的实用方法。
查看回复
如果您需要控制响应的状态和标头,但也需要返回视图作为响应的内容,则应使用该view
方法:
1return response()2 ->view('hello', $data, 200)3 ->header('Content-Type', $type);
当然,如果您不需要传递自定义 HTTP 状态代码或自定义标头,您可以使用全局view
帮助函数。
JSON 响应
该json
方法将自动将Content-Type
标头设置为application/json
,并使用json_encode
PHP 函数将给定的数组转换为 JSON:
1return response()->json([2 'name' => 'Abigail',3 'state' => 'CA',4]);
如果您想创建 JSONP 响应,您可以将该json
方法与该方法结合使用withCallback
:
1return response()2 ->json(['name' => 'Abigail', 'state' => 'CA'])3 ->withCallback($request->input('callback'));
文件下载
该download
方法可用于生成强制用户浏览器下载指定路径文件的响应。该download
方法接受一个文件名作为第二个参数,该参数将决定用户下载文件时看到的文件名。最后,你可以将一个 HTTP 标头数组作为第三个参数传递给该方法:
1return response()->download($pathToFile);2 3return response()->download($pathToFile, $name, $headers);
管理文件下载的 Symfony HttpFoundation 要求下载的文件具有 ASCII 文件名。
文件响应
该file
方法可用于直接在用户浏览器中显示文件(例如图像或 PDF),而无需启动下载。此方法接受文件的绝对路径作为其第一个参数,并接受一个包含文件头的数组作为其第二个参数:
1return response()->file($pathToFile);2 3return response()->file($pathToFile, $headers);
流式响应
通过在数据生成时将其流式传输到客户端,可以显著减少内存使用量并提高性能,尤其是对于非常大的响应。流式响应允许客户端在服务器完成发送数据之前就开始处理数据:
1Route::get('/stream', function () { 2 return response()->stream(function (): void { 3 foreach (['developer', 'admin'] as $string) { 4 echo $string; 5 ob_flush(); 6 flush(); 7 sleep(2); // Simulate delay between chunks... 8 } 9 }, 200, ['X-Accel-Buffering' => 'no']);10});
为了方便起见,如果您提供给方法的闭包stream
返回一个Generator,Laravel 将自动刷新生成器返回的字符串之间的输出缓冲区,并禁用 Nginx 输出缓冲:
1Route::get('/chat', function () {2 return response()->stream(function (): void {3 $stream = OpenAI::client()->chat()->createStreamed(...);4 5 foreach ($stream as $response) {6 yield $response->choices[0];7 }8 });9});
使用流式响应
可以使用 Laravel 的 npm 包来处理流式响应stream
,该包提供了便捷的 API 用于与 Laravel 响应和事件流进行交互。首先,请安装@laravel/stream-react
或@laravel/stream-vue
包:
1npm install @laravel/stream-react
1npm install @laravel/stream-vue
然后,useStream
可以用来消费事件流。提供流 URL 后,钩子会data
在 Laravel 应用程序返回内容时,自动使用连接的响应更新:
1import { useStream } from "@laravel/stream-react"; 2 3function App() { 4 const { data, isFetching, isStreaming, send } = useStream("chat"); 5 6 const sendMessage = () => { 7 send({ 8 message: `Current timestamp: ${Date.now()}`, 9 });10 };11 12 return (13 <div>14 <div>{data}</div>15 {isFetching && <div>Connecting...</div>}16 {isStreaming && <div>Generating...</div>}17 <button onClick={sendMessage}>Send Message</button>18 </div>19 );20}
1<script setup lang="ts"> 2import { useStream } from "@laravel/stream-vue"; 3 4const { data, isFetching, isStreaming, send } = useStream("chat"); 5 6const sendMessage = () => { 7 send({ 8 message: `Current timestamp: ${Date.now()}`, 9 });10};11</script>12 13<template>14 <div>15 <div>{{ data }}</div>16 <div v-if="isFetching">Connecting...</div>17 <div v-if="isStreaming">Generating...</div>18 <button @click="sendMessage">Send Message</button>19 </div>20</template>
通过 向流发送数据时send
,在发送新数据之前,流的Events连接将被取消。所有请求均以 JSONPOST
请求的形式发送。
第二个参数useStream
是一个选项对象,你可以使用它来自定义流的消费行为。该对象的默认值如下所示:
1import { useStream } from "@laravel/stream-react"; 2 3function App() { 4 const { data } = useStream("chat", { 5 id: undefined, 6 initialInput: undefined, 7 headers: undefined, 8 csrfToken: undefined, 9 onResponse: (response: Response) => void,10 onData: (data: string) => void,11 onCancel: () => void,12 onFinish: () => void,13 onError: (error: Error) => void,14 });15 16 return <div>{data}</div>;17}
1<script setup lang="ts"> 2import { useStream } from "@laravel/stream-vue"; 3 4const { data } = useStream("chat", { 5 id: undefined, 6 initialInput: undefined, 7 headers: undefined, 8 csrfToken: undefined, 9 onResponse: (response: Response) => void,10 onData: (data: string) => void,11 onCancel: () => void,12 onFinish: () => void,13 onError: (error: Error) => void,14});15</script>16 17<template>18 <div>{{ data }}</div>19</template>
onResponse
在流成功初始响应后触发,并将原始响应传递给回调。onData
在接收到每个块时调用 - 当前块传递给回调。onFinish
在流完成时以及在获取/读取周期期间引发错误时调用。
默认情况下,初始化时不会向流发出请求。您可以使用以下initialInput
选项将初始负载传递给流:
1import { useStream } from "@laravel/stream-react"; 2 3function App() { 4 const { data } = useStream("chat", { 5 initialInput: { 6 message: "Introduce yourself.", 7 }, 8 }); 9 10 return <div>{data}</div>;11}
1<script setup lang="ts"> 2import { useStream } from "@laravel/stream-vue"; 3 4const { data } = useStream("chat", { 5 initialInput: { 6 message: "Introduce yourself.", 7 }, 8}); 9</script>10 11<template>12 <div>{{ data }}</div>13</template>
要手动取消流,您可以使用cancel
钩子返回的方法:
1import { useStream } from "@laravel/stream-react"; 2 3function App() { 4 const { data, cancel } = useStream("chat"); 5 6 return ( 7 <div> 8 <div>{data}</div> 9 <button onClick={cancel}>Cancel</button>10 </div>11 );12}
1<script setup lang="ts"> 2import { useStream } from "@laravel/stream-vue"; 3 4const { data, cancel } = useStream("chat"); 5</script> 6 7<template> 8 <div> 9 <div>{{ data }}</div>10 <button @click="cancel">Cancel</button>11 </div>12</template>
每次useStream
使用钩子时,id
都会生成一个随机数来标识流。该随机数会随每个请求的标头一起发送回服务器X-STREAM-ID
。当从多个组件使用同一个流时,您可以通过提供自己的 来读取和写入流id
:
1// App.tsx 2import { useStream } from "@laravel/stream-react"; 3 4function App() { 5 const { data, id } = useStream("chat"); 6 7 return ( 8 <div> 9 <div>{data}</div>10 <StreamStatus id={id} />11 </div>12 );13}14 15// StreamStatus.tsx16import { useStream } from "@laravel/stream-react";17 18function StreamStatus({ id }) {19 const { isFetching, isStreaming } = useStream("chat", { id });20 21 return (22 <div>23 {isFetching && <div>Connecting...</div>}24 {isStreaming && <div>Generating...</div>}25 </div>26 );27}
1<!-- App.vue --> 2<script setup lang="ts"> 3import { useStream } from "@laravel/stream-vue"; 4import StreamStatus from "./StreamStatus.vue"; 5 6const { data, id } = useStream("chat"); 7</script> 8 9<template>10 <div>11 <div>{{ data }}</div>12 <StreamStatus :id="id" />13 </div>14</template>15 16<!-- StreamStatus.vue -->17<script setup lang="ts">18import { useStream } from "@laravel/stream-vue";19 20const props = defineProps<{21 id: string;22}>();23 24const { isFetching, isStreaming } = useStream("chat", { id: props.id });25</script>26 27<template>28 <div>29 <div v-if="isFetching">Connecting...</div>30 <div v-if="isStreaming">Generating...</div>31 </div>32</template>
流式 JSON 响应
如果需要逐步传输 JSON 数据,可以使用该streamJson
方法。对于需要以 JavaScript 易于解析的格式逐步发送到浏览器的大型数据集,此方法特别有用:
1use App\Models\User;2 3Route::get('/users.json', function () {4 return response()->streamJson([5 'users' => User::cursor(),6 ]);7});
该钩子与钩子useJsonStream
相同,只是它会在流式传输完成后尝试将数据解析为 JSON:useStream
1import { useJsonStream } from "@laravel/stream-react"; 2 3type User = { 4 id: number; 5 name: string; 6 email: string; 7}; 8 9function App() {10 const { data, send } = useJsonStream<{ users: User[] }>("users");11 12 const loadUsers = () => {13 send({14 query: "taylor",15 });16 };17 18 return (19 <div>20 <ul>21 {data?.users.map((user) => (22 <li>23 {user.id}: {user.name}24 </li>25 ))}26 </ul>27 <button onClick={loadUsers}>Load Users</button>28 </div>29 );30}
1<script setup lang="ts"> 2import { useJsonStream } from "@laravel/stream-vue"; 3 4type User = { 5 id: number; 6 name: string; 7 email: string; 8}; 9 10const { data, send } = useJsonStream<{ users: User[] }>("users");11 12const loadUsers = () => {13 send({14 query: "taylor",15 });16};17</script>18 19<template>20 <div>21 <ul>22 <li v-for="user in data?.users" :key="user.id">23 {{ user.id }}: {{ user.name }}24 </li>25 </ul>26 <button @click="loadUsers">Load Users</button>27 </div>28</template>
事件流(SSE)
该eventStream
方法可用于返回使用text/event-stream
内容类型的服务器发送事件 (SSE) 流响应。该eventStream
方法接受一个闭包,该闭包应在响应可用时将响应生成到流中:
1Route::get('/chat', function () {2 return response()->eventStream(function () {3 $stream = OpenAI::client()->chat()->createStreamed(...);4 5 foreach ($stream as $response) {6 yield $response->choices[0];7 }8 });9});
如果您想自定义事件的名称,您可以生成该类的实例StreamedEvent
:
1use Illuminate\Http\StreamedEvent;2 3yield new StreamedEvent(4 event: 'update',5 data: $response->choices[0],6);
消费事件流
您可以使用 Laravel 的stream
npm 包来使用事件流,该包提供了与 Laravel 事件流交互的便捷 API。首先,请安装@laravel/stream-react
或@laravel/stream-vue
包:
1npm install @laravel/stream-react
1npm install @laravel/stream-vue
然后,useEventStream
可以用来消费事件流。提供流 URL 后,钩子会message
在 Laravel 应用程序返回消息时,自动使用串联的响应更新:
1import { useEventStream } from "@laravel/stream-react";2 3function App() {4 const { message } = useEventStream("/chat");5 6 return <div>{message}</div>;7}
1<script setup lang="ts">2import { useEventStream } from "@laravel/stream-vue";3 4const { message } = useEventStream("/chat");5</script>6 7<template>8 <div>{{ message }}</div>9</template>
第二个参数useEventStream
是一个选项对象,你可以使用它来自定义流的消费行为。该对象的默认值如下所示:
1import { useEventStream } from "@laravel/stream-react"; 2 3function App() { 4 const { message } = useEventStream("/stream", { 5 event: "update", 6 onMessage: (message) => { 7 // 8 }, 9 onError: (error) => {10 //11 },12 onComplete: () => {13 //14 },15 endSignal: "</stream>",16 glue: " ",17 });18 19 return <div>{message}</div>;20}
1<script setup lang="ts"> 2import { useEventStream } from "@laravel/stream-vue"; 3 4const { message } = useEventStream("/chat", { 5 event: "update", 6 onMessage: (message) => { 7 // ... 8 }, 9 onError: (error) => {10 // ...11 },12 onComplete: () => {13 // ...14 },15 endSignal: "</stream>",16 glue: " ",17});18</script>
事件流也可以由应用程序前端通过EventSource对象手动使用。该eventStream
方法会</stream>
在事件流完成时自动向其发送更新:
1const source = new EventSource('/chat'); 2 3source.addEventListener('update', (event) => { 4 if (event.data === '</stream>') { 5 source.close(); 6 7 return; 8 } 9 10 console.log(event.data);11});
要自定义发送到事件流的最终事件,您可以为方法的参数提供一个StreamedEvent
实例:eventStream
endStreamWith
1return response()->eventStream(function () {2 // ...3}, endStreamWith: new StreamedEvent(event: 'update', data: '</stream>'));
流式下载
有时,您可能希望将给定操作的字符串响应转换为可下载的响应,而无需将操作内容写入磁盘。streamDownload
在这种情况下,您可以使用该方法。此方法接受回调、文件名和一个可选的标头数组作为参数:
1use App\Services\GitHub;2 3return response()->streamDownload(function () {4 echo GitHub::api('repo')5 ->contents()6 ->readme('laravel', 'laravel')['contents'];7}, 'laravel-readme.md');
响应宏
如果您想定义一个可以在各种路由和控制器中重复使用的自定义响应,您可以使用外观层macro
上的方法。通常,您应该从应用程序的某个服务提供商Response
的方法中调用此方法,例如服务提供商:boot
App\Providers\AppServiceProvider
1<?php 2 3namespace App\Providers; 4 5use Illuminate\Support\Facades\Response; 6use Illuminate\Support\ServiceProvider; 7 8class AppServiceProvider extends ServiceProvider 9{10 /**11 * Bootstrap any application services.12 */13 public function boot(): void14 {15 Response::macro('caps', function (string $value) {16 return Response::make(strtoupper($value));17 });18 }19}
该函数接受一个名称作为其第一个参数,一个闭包作为其第二个参数。当从实现或辅助函数macro
调用宏名称时,宏的闭包将被执行:ResponseFactory
response
1return response()->caps('foo');