跳轉至內容

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