响应
这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。
HTTP 响应
创建响应
字符串和数组
所有路由和控制器都应返回一个响应,以发送回用户的浏览器。Laravel 提供了几种不同的响应返回方式。最基本的响应是从路由或控制器返回一个字符串。框架会自动将该字符串转换为完整的 HTTP 响应:
Route::get('/', function () {
return 'Hello World';
});
除了从路由和控制器返回字符串之外,你还可以返回数组。框架会自动将数组转换为 JSON 响应:
Route::get('/', function () {
return [1, 2, 3];
});
[!注意]
你知道吗,你也可以从路由或控制器返回 Eloquent 集合?它们会被自动转换为 JSON。试试看!
响应对象
通常,你不会只从路由动作中返回简单的字符串或数组。相反,你会返回完整的 Illuminate\Http\Response 实例或 视图。
返回完整的 Response 实例允许你自定义响应的 HTTP 状态码和请求头。Response 实例继承自 Symfony\Component\HttpFoundation\Response 类,该类提供了多种用于构建 HTTP 响应的方法:
Route::get('/home', function () {
return response('Hello World', 200)
->header('Content-Type', 'text/plain');
});
Eloquent 模型和集合
你也可以直接从路由和控制器返回 Eloquent ORM 模型和集合。这样做时,Laravel 会自动将模型和集合转换为 JSON 响应,同时遵循模型的 隐藏属性 设置:
use App\Models\User;
Route::get('/user/{user}', function (User $user) {
return $user;
});
将请求头附加到响应
请记住,大多数响应方法都可以链式调用,从而可以流畅地构建响应实例。例如,你可以使用 header 方法在将响应发送回用户之前,向响应添加一系列请求头:
return response($content)
->header('Content-Type', $type)
->header('X-Header-One', 'Header Value')
->header('X-Header-Two', 'Header Value');
或者,你可以使用 withHeaders 方法指定一个请求头数组,并将其添加到响应中:
return response($content)
->withHeaders([
'Content-Type' => $type,
'X-Header-One' => 'Header Value',
'X-Header-Two' => 'Header Value',
]);
你可以使用 withoutHeader 方法从即将发出的响应中移除指定请求头:
return response($content)->withoutHeader('X-Debug');
return response($content)->withoutHeader(['X-Debug', 'X-Powered-By']);
缓存控制中间件
Laravel 包含一个 cache.headers 中间件,可用于为一组路由快速设置 Cache-Control 请求头。指令应使用对应 cache-control 指令的“蛇形命名”形式提供,并且应使用分号分隔。如果在指令列表中指定了 etag,则响应内容的 MD5 哈希值将自动被设置为 ETag 标识符:
Route::middleware('cache.headers:public;max_age=30;s_maxage=300;stale_while_revalidate=600;etag')->group(function () {
Route::get('/privacy', function () {
// ...
});
Route::get('/terms', function () {
// ...
});
});
将 Cookie 附加到响应
你可以使用 cookie 方法将 Cookie 附加到即将发出的 Illuminate\Http\Response 实例。你应该向此方法传递 Cookie 的名称、值,以及该 Cookie 应被视为有效的分钟数:
return response('Hello World')->cookie(
'name', 'value', $minutes
);
cookie 方法还接受一些较少使用的额外参数。通常,这些参数与传递给 PHP 原生 setcookie 方法的参数具有相同的用途和含义:
return response('Hello World')->cookie(
'name', 'value', $minutes, $path, $domain, $secure, $httpOnly
);
如果你想确保某个 Cookie 会随即将发出的响应一起发送,但你还没有该响应的实例,可以使用 Cookie 门面将 Cookie “排队”,以便在响应发送时附加到响应上。queue 方法接受创建 Cookie 实例所需的参数。这些 Cookie 会在即将发出的响应发送到浏览器之前被附加到响应上:
use Illuminate\Support\Facades\Cookie;
Cookie::queue('name', 'value', $minutes);
生成 Cookie 实例
如果你想生成一个 Symfony\Component\HttpFoundation\Cookie 实例,以便稍后将其附加到响应实例,可以使用全局 cookie 辅助函数。除非该 Cookie 被附加到响应实例,否则它不会被发送回客户端:
$cookie = cookie('name', 'value', $minutes);
return response('Hello World')->cookie($cookie);
提前使 Cookie 过期
你可以通过即将发出的响应的 withoutCookie 方法使 Cookie 过期,从而移除该 Cookie:
return response('Hello World')->withoutCookie('name');
如果你还没有即将发出的响应实例,可以使用 Cookie 门面的 expire 方法使 Cookie 过期:
Cookie::expire('name');
Cookie 与加密
默认情况下,得益于 Illuminate\Cookie\Middleware\EncryptCookies 中间件,Laravel 生成的所有 Cookie 都会被加密和签名,因此客户端无法修改或读取它们。如果你想为应用程序生成的部分 Cookie 禁用加密,可以在应用程序的 bootstrap/app.php 文件中使用 encryptCookies 方法:
->withMiddleware(function (Middleware $middleware): void {
$middleware->encryptCookies(except: [
'cookie_name',
]);
})
[!注意]
通常,不应禁用 Cookie 加密,因为这会使你的 Cookie 面临潜在的客户端数据暴露和篡改风险。
重定向
重定向响应是 Illuminate\Http\RedirectResponse 类的实例,并包含将用户重定向到另一个 URL 所需的正确请求头。有几种方法可以生成 RedirectResponse 实例。最简单的方法是使用全局 redirect 辅助函数:
Route::get('/dashboard', function () {
return redirect('/home/dashboard');
});
有时你可能希望将用户重定向回他们之前的位置,例如提交的表单无效时。你可以使用全局 back 辅助函数来实现。由于此功能使用了 session,请确保调用 back 函数的路由正在使用 web 中间件组:
Route::post('/user/profile', function () {
// Validate the request...
return back()->withInput();
});
重定向到命名路由
当你不带任何参数调用 redirect 辅助函数时,会返回一个 Illuminate\Routing\Redirector 实例,允许你调用 Redirector 实例上的任何方法。例如,要生成一个指向命名路由的 RedirectResponse,你可以使用 route 方法:
return redirect()->route('login');
如果你的路由有参数,可以将它们作为第二个参数传递给 route 方法:
// For a route with the following URI: /profile/{id}
return redirect()->route('profile', ['id' => 1]);
通过 Eloquent 模型填充参数
如果你要重定向到一个带有“ID”参数的路由,而该参数是由 Eloquent 模型填充的,则可以传递模型本身。ID 会被自动提取:
// 对于具有以下 URI 的路由:/profile/{id}
return redirect()->route('profile', [$user]);
如果你想自定义放入路由参数中的值,可以在路由参数定义中指定列(/profile/{id:slug}),或者可以在你的 Eloquent 模型上重写 getRouteKey 方法:
/**
* 获取模型路由键的值。
*/
public function getRouteKey(): mixed
{
return $this->slug;
}
重定向到控制器动作
你也可以生成指向 控制器动作 的重定向。为此,请将控制器和动作名称传递给 action 方法:
use App\Http\Controllers\UserController;
return redirect()->action([UserController::class, 'index']);
如果你的控制器路由需要参数,可以将它们作为第二个参数传递给 action 方法:
return redirect()->action(
[UserController::class, 'profile'], ['id' => 1]
);
重定向到外部域名
有时你可能需要重定向到应用程序之外的域名。你可以通过调用 away 方法来实现,该方法会创建一个 RedirectResponse,不会进行任何额外的 URL 编码、验证或校验:
return redirect()->away('https://www.google.com');
重定向并携带闪存 Session 数据
重定向到新的 URL 和 将数据闪存到 session 通常会同时完成。通常,这是在成功执行某个操作后,将成功消息闪存到 session 中。为了方便起见,你可以创建一个 RedirectResponse 实例,并通过一个流畅的方法链将数据闪存到 session 中:
Route::post('/user/profile', function () {
// ...
return redirect('/dashboard')->with('status', 'Profile updated!');
});
用户被重定向后,你可以从 session 中显示闪存消息。例如,使用 Blade 语法:
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
重定向并携带输入
You may use the withInput method provided by the RedirectResponse instance to flash the current request's input data to the session before redirecting the user to a new location. This is typically done if the user has encountered a validation error. Once the input has been flashed to the session, you may easily retrieve it during the next request to repopulate the form:
return back()->withInput();
Other Response Types
The response helper may be used to generate other types of response instances. When the response helper is called without arguments, an implementation of the Illuminate\Contracts\Routing\ResponseFactory contract is returned. This contract provides several helpful methods for generating responses.
View Responses
If you need control over the response's status and headers but also need to return a view as the response's content, you should use the view method:
return response()
->view('hello', $data, 200)
->header('Content-Type', $type);
Of course, if you do not need to pass a custom HTTP status code or custom headers, you may use the global view helper function.
JSON Responses
The json method will automatically set the Content-Type header to application/json, as well as convert the given array to JSON using the json_encode PHP function:
return response()->json([
'name' => 'Abigail',
'state' => 'CA',
]);
If you would like to create a JSONP response, you may use the json method in combination with the withCallback method:
return response()
->json(['name' => 'Abigail', 'state' => 'CA'])
->withCallback($request->input('callback'));
File Downloads
The download method may be used to generate a response that forces the user's browser to download the file at the given path. The download method accepts a filename as the second argument to the method, which will determine the filename that is seen by the user downloading the file. Finally, you may pass an array of HTTP headers as the third argument to the method:
return response()->download($pathToFile);
return response()->download($pathToFile, $name, $headers);
[!WARNING]
Symfony HttpFoundation, which manages file downloads, requires the file being downloaded to have an ASCII filename.
File Responses
The file method may be used to display a file, such as an image or PDF, directly in the user's browser instead of initiating a download. This method accepts the absolute path to the file as its first argument and an array of headers as its second argument:
return response()->file($pathToFile);
return response()->file($pathToFile, $headers);
Streamed Responses
By streaming data to the client as it is generated, you can significantly reduce memory usage and improve performance, especially for very large responses. Streamed responses allow the client to begin processing data before the server has finished sending it:
Route::get('/stream', function () {
return response()->stream(function (): void {
foreach (['developer', 'admin'] as $string) {
echo $string;
ob_flush();
flush();
sleep(2); // Simulate delay between chunks...
}
}, 200, ['X-Accel-Buffering' => 'no']);
});
For convenience, if the closure you provide to the stream method returns a Generator, Laravel will automatically flush the output buffer between strings returned by the generator, as well as disable Nginx output buffering:
Route::post('/chat', function () {
return response()->stream(function (): Generator {
$stream = OpenAI::client()->chat()->createStreamed(...);
foreach ($stream as $response) {
yield $response->choices[0];
}
});
});
Consuming Streamed Responses
Streamed responses may be consumed using Laravel's stream npm package, which provides a convenient API for interacting with Laravel response and event streams. To get started, install the @laravel/stream-react, @laravel/stream-vue, or @laravel/stream-svelte package:
npm install @laravel/stream-react
npm install @laravel/stream-vue
npm install @laravel/stream-svelte
Then, useStream may be used to consume the event stream. After providing your stream URL, the hook will automatically update the data with the concatenated response as content is returned from your Laravel application:
import { useStream } from "@laravel/stream-react";
function App() {
const { data, isFetching, isStreaming, send } = useStream("chat");
const sendMessage = () => {
send({
message: `Current timestamp: ${Date.now()}`,
});
};
return (
<div>
<div>{data}</div>
{isFetching && <div>Connecting...</div>}
{isStreaming && <div>Generating...</div>}
<button onClick={sendMessage}>Send Message</button>
</div>
);
}
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
const { data, isFetching, isStreaming, send } = useStream("chat");
const sendMessage = () => {
send({
message: `Current timestamp: ${Date.now()}`,
});
};
</script>
<template>
<div>
<div>{{ data }}</div>
<div v-if="isFetching">Connecting...</div>
<div v-if="isStreaming">Generating...</div>
<button @click="sendMessage">Send Message</button>
</div>
</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>
When sending data back to the stream via send, the active connection to the stream is canceled before sending the new data. All requests are sent as JSON POST requests.
[!WARNING]
Since theuseStreamhook makes aPOSTrequest to your application, a valid CSRF token is required. The easiest way to provide the CSRF token is to include it via a meta tag in your application layout's head.
The second argument given to useStream is an options object that you may use to customize the stream consumption behavior. The default values for this object are shown below:
import { useStream } from "@laravel/stream-react";
function App() {
const { data } = useStream("chat", {
id: undefined,
initialInput: undefined,
headers: undefined,
csrfToken: undefined,
onResponse: (response: Response) => void,
onData: (data: string) => void,
onCancel: () => void,
onFinish: () => void,
onError: (error: Error) => void,
});
return <div>{data}</div>;
}
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
const { data } = useStream("chat", {
id: undefined,
initialInput: undefined,
headers: undefined,
csrfToken: undefined,
onResponse: (response: Response) => void,
onData: (data: string) => void,
onCancel: () => void,
onFinish: () => void,
onError: (error: Error) => void,
});
</script>
<template>
<div>{{ data }}</div>
</template>
<script>
import { useStream } from "@laravel/stream-svelte";
const stream = useStream("chat", {
id: undefined,
initialInput: undefined,
headers: undefined,
csrfToken: undefined,
onResponse: (response) => {},
onData: (data) => {},
onCancel: () => {},
onFinish: () => {},
onError: (error) => {},
});
</script>
<div>{$stream.data}</div>
onResponse is triggered after a successful initial response from the stream and the raw Response is passed to the callback. onData is called as each chunk is received - the current chunk is passed to the callback. onFinish is called when a stream has finished and when an error is thrown during the fetch / read cycle.
By default, a request is not made to the stream on initialization. You may pass an initial payload to the stream by using the initialInput option:
import { useStream } from "@laravel/stream-react";
function App() {
const { data } = useStream("chat", {
initialInput: {
message: "Introduce yourself.",
},
});
return <div>{data}</div>;
}
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
const { data } = useStream("chat", {
initialInput: {
message: "Introduce yourself.",
},
});
</script>
<template>
<div>{{ data }}</div>
</template>
<script>
import { useStream } from "@laravel/stream-svelte";
const stream = useStream("chat", {
initialInput: {
message: "Introduce yourself.",
},
});
</script>
<div>{$stream.data}</div>
To cancel a stream manually, you may use the cancel method returned from the hook:
import { useStream } from "@laravel/stream-react";
function App() {
const { data, cancel } = useStream("chat");
return (
<div>
<div>{data}</div>
<button onClick={cancel}>Cancel</button>
</div>
);
}
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
const { data, cancel } = useStream("chat");
</script>
<template>
<div>
<div>{{ data }}</div>
<button @click="cancel">Cancel</button>
</div>
</template>
<script>
import { useStream } from "@laravel/stream-svelte";
const stream = useStream("chat");
</script>
<div>
<div>{$stream.data}</div>
<button onclick={() => stream.cancel()}>Cancel</button>
</div>
Each time the useStream hook is used, a random id is generated to identify the stream. This is sent back to the server with each request in the X-STREAM-ID header. When consuming the same stream from multiple components, you can read and write to the stream by providing your own id:
// App.tsx
import { useStream } from "@laravel/stream-react";
function App() {
const { data, id } = useStream("chat");
return (
<div>
<div>{data}</div>
<StreamStatus id={id} />
</div>
);
}
// StreamStatus.tsx
import { useStream } from "@laravel/stream-react";
function StreamStatus({ id }) {
const { isFetching, isStreaming } = useStream("chat", { id });
return (
<div>
{isFetching && <div>Connecting...</div>}
{isStreaming && <div>Generating...</div>}
</div>
);
}
<!-- App.vue -->
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
import StreamStatus from "./StreamStatus.vue";
const { data, id } = useStream("chat");
</script>
<template>
<div>
<div>{{ data }}</div>
<StreamStatus :id="id" />
</div>
</template>
<!-- StreamStatus.vue -->
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
const props = defineProps<{
id: string;
}>();
const { isFetching, isStreaming } = useStream("chat", { id: props.id });
</script>
<template>
<div>
<div v-if="isFetching">Connecting...</div>
<div v-if="isStreaming">Generating...</div>
</div>
</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>
Streamed JSON Responses
If you need to stream JSON data incrementally, you may utilize the streamJson method. This method is especially useful for large datasets that need to be sent progressively to the browser in a format that can be easily parsed by JavaScript:
use App\Models\User;
Route::get('/users.json', function () {
return response()->streamJson([
'users' => User::cursor(),
]);
});
The useJsonStream hook is identical to the useStream hook except that it will attempt to parse the data as JSON once it has finished streaming:
import { useJsonStream } from "@laravel/stream-react";
type User = {
id: number;
name: string;
email: string;
};
function App() {
const { data, send } = useJsonStream<{ users: User[] }>("users");
const loadUsers = () => {
send({
query: "taylor",
});
};
return (
<div>
<ul>
{data?.users.map((user) => (
<li>
{user.id}: {user.name}
</li>
))}
</ul>
<button onClick={loadUsers}>Load Users</button>
</div>
);
}
<script setup lang="ts">
import { useJsonStream } from "@laravel/stream-vue";
type User = {
id: number;
name: string;
email: string;
};
const { data, send } = useJsonStream<{ users: User[] }>("users");
const loadUsers = () => {
send({
query: "taylor",
});
};
</script>
<template>
<div>
<ul>
<li v-for="user in data?.users" :key="user.id">
{{ user.id }}: {{ user.name }}
</li>
</ul>
<button @click="loadUsers">Load Users</button>
</div>
</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>
Event Streams (SSE)
The eventStream method may be used to return a server-sent events (SSE) streamed response using the text/event-stream content type. The eventStream method accepts a closure which should yield responses to the stream as the responses become available:
Route::get('/chat', function () {
return response()->eventStream(function () {
$stream = OpenAI::client()->chat()->createStreamed(...);
foreach ($stream as $response) {
yield $response->choices[0];
}
});
});
If you would like to customize the name of the event, you may yield an instance of the StreamedEvent class:
use Illuminate\Http\StreamedEvent;
yield new StreamedEvent(
event: 'update',
data: $response->choices[0],
);
Consuming Event Streams
Event streams may be consumed using Laravel's stream npm package, which provides a convenient API for interacting with Laravel event streams. To get started, install the @laravel/stream-react, @laravel/stream-vue, or @laravel/stream-svelte package:
npm install @laravel/stream-react
npm install @laravel/stream-vue
npm install @laravel/stream-svelte
Then, useEventStream may be used to consume the event stream. After providing your stream URL, the hook will automatically update the message with the concatenated response as messages are returned from your Laravel application:
import { useEventStream } from "@laravel/stream-react";
function App() {
const { message } = useEventStream("/chat");
return <div>{message}</div>;
}
<script setup lang="ts">
import { useEventStream } from "@laravel/stream-vue";
const { message } = useEventStream("/chat");
</script>
<template>
<div>{{ message }}</div>
</template>
<script>
import { useEventStream } from "@laravel/stream-svelte";
const eventStream = useEventStream("/chat");
</script>
<div>{$eventStream.message}</div>
The second argument given to useEventStream is an options object that you may use to customize the stream consumption behavior. The default values for this object are shown below:
import { useEventStream } from "@laravel/stream-react";
function App() {
const { message } = useEventStream("/stream", {
eventName: "update",
onMessage: (message) => {
//
},
onError: (error) => {
//
},
onComplete: () => {
//
},
endSignal: "</stream>",
glue: " ",
});
return <div>{message}</div>;
}
<script setup lang="ts">
import { useEventStream } from "@laravel/stream-vue";
const { message } = useEventStream("/chat", {
eventName: "update",
onMessage: (message) => {
// ...
},
onError: (error) => {
// ...
},
onComplete: () => {
// ...
},
endSignal: "</stream>",
glue: " ",
});
</script>
<script>
import { useEventStream } from "@laravel/stream-svelte";
const eventStream = useEventStream("/chat", {
eventName: "update",
onMessage: (event) => {
//
},
onError: (error) => {
//
},
onComplete: () => {
//
},
endSignal: "</stream>",
glue: " ",
replace: false,
});
</script>
Event streams may also be manually consumed via an EventSource object by your application's frontend. The eventStream method will automatically send a </stream> update to the event stream when the stream is complete:
const source = new EventSource('/chat');
source.addEventListener('update', (event) => {
if (event.data === '</stream>') {
source.close();
return;
}
console.log(event.data);
});
To customize the final event that is sent to the event stream, you may provide a StreamedEvent instance to the eventStream method's endStreamWith argument:
return response()->eventStream(function () {
// ...
}, endStreamWith: new StreamedEvent(event: 'update', data: '</stream>'));
Streamed Downloads
Sometimes you may wish to turn the string response of a given operation into a downloadable response without having to write the contents of the operation to disk. You may use the streamDownload method in this scenario. This method accepts a callback, filename, and an optional array of headers as its arguments:
use App\Services\GitHub;
return response()->streamDownload(function () {
echo GitHub::api('repo')
->contents()
->readme('laravel', 'laravel')['contents'];
}, 'laravel-readme.md');
Response Macros
If you would like to define a custom response that you can re-use in a variety of your routes and controllers, you may use the macro method on the Response facade. Typically, you should call this method from the boot method of one of your application's service providers, such as the App\Providers\AppServiceProvider service provider:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Response::macro('caps', function (string $value) {
return Response::make(strtoupper($value));
});
}
}
The macro function accepts a name as its first argument and a closure as its second argument. The macro's closure will be executed when calling the macro name from a ResponseFactory implementation or the response helper:
return response()->caps('foo');
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
Laravel 13 中文文档
关于 LearnKu
推荐文章: