跳转至内容

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 ]);

你可以使用 withoutHeader 方法从外发响应中移除指定的响应头。

1return response($content)->withoutHeader('X-Debug');
2 
3return response($content)->withoutHeader(['X-Debug', 'X-Powered-By']);

缓存控制中间件

Laravel 包含一个 cache.headers 中间件,可用于快速为一组路由设置 Cache-Control 响应头。指令应使用对应 Cache-Control 指令的“蛇形命名法(snake case)”等效项,并以分号分隔。如果指令列表中指定了 etag,则响应内容的 MD5 哈希值将自动设置为 ETag 标识符。

1Route::middleware('cache.headers:public;max_age=30;s_maxage=300;stale_while_revalidate=600;etag')->group(function () {
2 Route::get('/privacy', function () {
3 // ...
4 });
5 
6 Route::get('/terms', function () {
7 // ...
8 });
9});

向响应添加 Cookie

你可以使用 cookie 方法将 Cookie 添加到外发的 Illuminate\Http\Response 实例中。你应该向此方法传递名称、值以及 Cookie 有效的分钟数。

1return response('Hello World')->cookie(
2 'name', 'value', $minutes
3);

cookie 方法还接受一些不太常用的附加参数。通常,这些参数的作用和意义与 PHP 原生 setcookie 方法的参数相同。

1return response('Hello World')->cookie(
2 'name', 'value', $minutes, $path, $domain, $secure, $httpOnly
3);

如果你希望确保 Cookie 随外发响应发送,但你还没有该响应的实例,你可以使用 Cookie 门面(facade)来“排队”等待发送响应时附加这些 Cookie。queue 方法接受创建 Cookie 实例所需的参数。这些 Cookie 将在发送到浏览器之前被附加到外发响应中。

1use Illuminate\Support\Facades\Cookie;
2 
3Cookie::queue('name', 'value', $minutes);

如果你想生成一个 Symfony\Component\HttpFoundation\Cookie 实例,以便稍后附加到响应实例中,你可以使用全局的 cookie 辅助函数。除非将其附加到响应实例,否则此 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 禁用加密,可以在应用程序的 bootstrap/app.php 文件中使用 encryptCookies 方法。

1->withMiddleware(function (Middleware $middleware): void {
2 $middleware->encryptCookies(except: [
3 'cookie_name',
4 ]);
5})

通常情况下,永远不应禁用 Cookie 加密,因为这会将你的 Cookie 暴露在潜在的客户端数据泄露和篡改风险中。

重定向

重定向响应是 Illuminate\Http\RedirectResponse 类的实例,包含将用户重定向到另一个 URL 所需的正确响应头。生成 RedirectResponse 实例有多种方法,最简单的方法是使用全局 redirect 辅助函数。

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 模型填充参数

如果你要重定向到的路由包含一个从 Eloquent 模型填充的“ID”参数,你可以直接传递该模型实例。ID 将会自动提取。

1// For a route with the following URI: /profile/{id}
2 
3return redirect()->route('profile', [$user]);

如果你想自定义路由参数中使用的值,可以在路由参数定义中指定列名(例如 /profile/{id:slug}),或者在 Eloquent 模型中重写 getRouteKey 方法。

1/**
2 * Get the value of the model's route key.
3 */
4public function getRouteKey(): mixed
5{
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 方法来实现,它会创建一个不进行任何额外 URL 编码、验证或检查的 RedirectResponse

1return redirect()->away('https://www.google.com');

带闪存会话数据的重定向

重定向到新 URL 和 将数据闪存到 session 通常同时进行。这通常在成功执行某个操作后完成,例如将成功消息闪存到 session。为了方便起见,你可以在一个流畅的方法链中创建一个 RedirectResponse 实例并将数据闪存到 session。

1Route::post('/user/profile', function () {
2 // ...
3 
4 return redirect('/dashboard')->with('status', 'Profile updated!');
5});

用户被重定向后,你可以从 session 中显示闪存的消息。例如,使用 Blade 语法

1@if (session('status'))
2 <div class="alert alert-success">
3 {{ session('status') }}
4 </div>
5@endif

重定向并携带输入数据

你可以使用 RedirectResponse 实例提供的 withInput 方法,在重定向用户到新位置之前,将当前请求的输入数据闪存到 session。这通常在用户遇到验证错误时使用。一旦输入数据被闪存到 session,你就可以在下一次请求时轻松地 检索它 以重新填充表单。

1return back()->withInput();

其他响应类型

response 辅助函数可用于生成其他类型的响应实例。当不带参数调用 response 辅助函数时,会返回一个实现了 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,并使用 PHP 的 json_encode 函数将给定的数组转换为 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::post('/chat', function () {
2 return response()->stream(function (): Generator {
3 $stream = OpenAI::client()->chat()->createStreamed(...);
4 
5 foreach ($stream as $response) {
6 yield $response->choices[0];
7 }
8 });
9});

消费流式响应

流式响应可以使用 Laravel 的 stream npm 包来消费,该包为与 Laravel 响应和事件流交互提供了便捷的 API。要开始使用,请安装 @laravel/stream-react@laravel/stream-vue@laravel/stream-svelte 包。

1npm install @laravel/stream-react
1npm install @laravel/stream-vue
1npm install @laravel/stream-svelte

然后,可以使用 useStream 来消费事件流。在提供流 URL 后,该 Hook 会随着 Laravel 应用程序返回内容,自动更新 data 为拼接后的响应。

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>
<script>
import { useStream } from "@laravel/stream-svelte";
const stream = useStream("chat");
const sendMessage = () => {
stream.send({
message: `Current timestamp: ${Date.now()}`,
});
};
</script>
<div>
<div>{$stream.data}</div>
{#if $stream.isFetching}
<div>Connecting...</div>
{/if}
{#if $stream.isStreaming}
<div>Generating...</div>
{/if}
<button onclick={sendMessage}>Send Message</button>
</div>

当通过 send 将数据传回流时,活动连接会在发送新数据之前取消。所有请求都作为 JSON POST 请求发送。

由于 useStream Hook 会向你的应用程序发起 POST 请求,因此需要有效的 CSRF 令牌。提供 CSRF 令牌最简单的方法是 将其包含在应用程序布局的 head 标签中的 meta 标签里

传递给 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>
1<script>
2import { useStream } from "@laravel/stream-svelte";
3 
4const stream = useStream("chat", {
5 id: undefined,
6 initialInput: undefined,
7 headers: undefined,
8 csrfToken: undefined,
9 onResponse: (response) => {},
10 onData: (data) => {},
11 onCancel: () => {},
12 onFinish: () => {},
13 onError: (error) => {},
14});
15</script>
16 
17<div>{$stream.data}</div>

onResponse 在流成功获得初始响应后触发,原始 Response 会被传递给回调函数。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>
1<script>
2import { useStream } from "@laravel/stream-svelte";
3 
4const stream = useStream("chat", {
5 initialInput: {
6 message: "Introduce yourself.",
7 },
8});
9</script>
10 
11<div>{$stream.data}</div>

要手动取消流,你可以使用 Hook 返回的 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>
1<script>
2import { useStream } from "@laravel/stream-svelte";
3 
4const stream = useStream("chat");
5</script>
6 
7<div>
8 <div>{$stream.data}</div>
9 <button onclick={() => stream.cancel()}>Cancel</button>
10</div>

每次使用 useStream Hook 时,都会生成一个随机的 id 来标识该流。该 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.tsx
16import { 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>
<!-- App.svelte -->
<script>
import { useStream } from "@laravel/stream-svelte";
import StreamStatus from "./StreamStatus.svelte";
const stream = useStream("chat");
</script>
<div>
<div>{$stream.data}</div>
<StreamStatus id={stream.id} />
</div>
<!-- StreamStatus.svelte -->
<script>
import { useStream } from "@laravel/stream-svelte";
let { id } = $props();
const stream = useStream("chat", { id });
</script>
<div>
{#if $stream.isFetching}
<div>Connecting...</div>
{/if}
{#if $stream.isStreaming}
<div>Generating...</div>
{/if}
</div>

流式 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 Hook 与 useStream Hook 基本相同,区别在于它会在流传输完成后尝试将数据解析为 JSON。

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>
<script>
import { useJsonStream } from "@laravel/stream-svelte";
const stream = useJsonStream("users");
const loadUsers = () => {
stream.send({
query: "taylor",
});
};
</script>
<div>
<ul>
{#if $stream.data?.users}
{#each $stream.data.users as user (user.id)}
<li>{user.id}: {user.name}</li>
{/each}
{/if}
</ul>
<button onclick={loadUsers}>Load Users</button>
</div>

事件流 (SSE)

eventStream 方法可用于返回使用 text/event-stream 内容类型的服务器推送事件(SSE)流式响应。eventStream 方法接受一个闭包,该闭包应在响应可用时将其 yield 给流。

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});

如果你想自定义事件名称,可以 yield 一个 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@laravel/stream-svelte 包。

1npm install @laravel/stream-react
1npm install @laravel/stream-vue
1npm install @laravel/stream-svelte

然后,可以使用 useEventStream 来消费事件流。在提供流 URL 后,该 Hook 会随着 Laravel 应用程序返回消息,自动更新 message 为拼接后的响应。

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>
1<script>
2import { useEventStream } from "@laravel/stream-svelte";
3 
4const eventStream = useEventStream("/chat");
5</script>
6 
7<div>{$eventStream.message}</div>

传递给 useEventStream 的第二个参数是一个选项对象,你可以用它来自定义流消费行为。该对象的默认值如下所示:

1import { useEventStream } from "@laravel/stream-react";
2 
3function App() {
4 const { message } = useEventStream("/stream", {
5 eventName: "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 eventName: "update",
6 onMessage: (message) => {
7 // ...
8 },
9 onError: (error) => {
10 // ...
11 },
12 onComplete: () => {
13 // ...
14 },
15 endSignal: "</stream>",
16 glue: " ",
17});
18</script>
1<script>
2import { useEventStream } from "@laravel/stream-svelte";
3 
4const eventStream = useEventStream("/chat", {
5 eventName: "update",
6 onMessage: (event) => {
7 //
8 },
9 onError: (error) => {
10 //
11 },
12 onComplete: () => {
13 //
14 },
15 endSignal: "</stream>",
16 glue: " ",
17 replace: false,
18});
19</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});

要自定义发送到事件流的最终事件,你可以向 eventStream 方法的 endStreamWith 参数提供一个 StreamedEvent 实例。

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');

响应宏

如果你想定义一个可以在多个路由和控制器中复用的自定义响应,可以使用 Response 门面上的 macro 方法。通常,你应该在应用程序某个 服务提供者(例如 App\Providers\AppServiceProvider)的 boot 方法中调用此方法。

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(): void
14 {
15 Response::macro('caps', function (string $value) {
16 return Response::make(strtoupper($value));
17 });
18 }
19}

macro 函数接受一个名称作为第一个参数,闭包作为第二个参数。当从 ResponseFactory 实现或 response 辅助函数调用该宏名称时,宏的闭包将被执行。

1return response()->caps('foo');