HTTP 응답
번역일: 2026년 6월 20일
HTTP 응답
응답 생성
문자열과 배열
모든 라우트와 컨트롤러는 사용자의 브라우저로 돌려보낼 응답을 반환해야 합니다. Laravel은 응답을 반환하는 다양한 방법을 제공합니다. 가장 간단한 방법은 라우트나 컨트롤러에서 문자열을 반환하는 것입니다. 프레임워크가 이 문자열을 완전한 HTTP 응답으로 자동 변환해 줍니다.
Route::get('/', function () {
return 'Hello World';
});문자열 외에도 배열을 반환할 수 있습니다. 배열을 반환하면 프레임워크가 자동으로 JSON 응답으로 변환합니다.
Route::get('/', function () {
return [1, 2, 3];
});NOTE
라우트나 컨트롤러에서 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']);Cache Control 미들웨어
Laravel에는 cache.headers 미들웨어가 내장되어 있어 라우트 그룹에 Cache-Control 헤더를 손쉽게 설정할 수 있습니다. 각 디렉티브는 캐시 제어 지시어의 스네이크 케이스(snake_case) 형태로 작성하고 세미콜론으로 구분합니다. 디렉티브 목록에 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 메서드를 사용해 응답에 쿠키를 추가할 수 있습니다. 쿠키 이름, 값, 유효 시간(분)을 인수로 전달합니다.
return response('Hello World')->cookie(
'name', 'value', $minutes
);PHP 기본 함수 setcookie와 동일한 추가 인수도 지원합니다.
return response('Hello World')->cookie(
'name', 'value', $minutes, $path, $domain, $secure, $httpOnly
);아직 응답 인스턴스가 없는 상태에서 쿠키를 설정하고 싶다면 Cookie 파사드의 queue 메서드를 사용하세요. 이 방법으로 큐에 추가된 쿠키는 응답이 브라우저로 전송되기 직전에 자동으로 첨부됩니다.
use Illuminate\Support\Facades\Cookie;
Cookie::queue('name', 'value', $minutes);쿠키 인스턴스 생성
나중에 응답에 첨부할 Symfony\Component\HttpFoundation\Cookie 인스턴스를 미리 생성해 두려면 전역 cookie 헬퍼를 사용하세요. 이렇게 생성된 쿠키는 응답 인스턴스에 명시적으로 첨부하지 않으면 클라이언트에 전송되지 않습니다.
$cookie = cookie('name', 'value', $minutes);
return response('Hello World')->cookie($cookie);쿠키 조기 만료
응답 인스턴스의 withoutCookie 메서드를 사용하면 쿠키를 즉시 만료시켜 삭제할 수 있습니다.
return response('Hello World')->withoutCookie('name');응답 인스턴스가 없는 경우에는 Cookie 파사드의 expire 메서드를 사용하세요.
Cookie::expire('name');쿠키와 암호화
기본적으로 Laravel은 Illuminate\Cookie\Middleware\EncryptCookies 미들웨어를 통해 생성하는 모든 쿠키를 암호화하고 서명합니다. 덕분에 클라이언트가 쿠키를 읽거나 변조할 수 없습니다. 일부 쿠키에 대해 암호화를 비활성화하려면 bootstrap/app.php 파일에서 encryptCookies 메서드를 사용하세요.
->withMiddleware(function (Middleware $middleware): void {
$middleware->encryptCookies(except: [
'cookie_name',
]);
})NOTE
쿠키 암호화를 비활성화하면 클라이언트 측에서 쿠키 내용이 노출되거나 변조될 수 있습니다. 특별한 이유가 없다면 암호화를 유지하는 것을 강력히 권장합니다.
리다이렉트
리다이렉트 응답은 Illuminate\Http\RedirectResponse 클래스의 인스턴스로, 사용자를 다른 URL로 보내기 위한 헤더 정보를 포함합니다. 가장 간단한 방법은 전역 redirect 헬퍼를 사용하는 것입니다.
Route::get('/dashboard', function () {
return redirect('/home/dashboard');
});폼 유효성 검사 실패 등의 상황에서 이전 페이지로 되돌아가야 할 때는 전역 back 헬퍼를 사용하세요. 이 기능은 세션을 활용하므로, back 함수를 호출하는 라우트가 web 미들웨어 그룹을 사용하고 있는지 확인하세요.
Route::post('/user/profile', function () {
// 요청 유효성 검사...
return back()->withInput();
});이름 있는 라우트로 리다이렉트
redirect 헬퍼를 인수 없이 호출하면 Illuminate\Routing\Redirector 인스턴스가 반환됩니다. 이 인스턴스의 route 메서드를 사용해 이름 있는 라우트로 리다이렉트할 수 있습니다.
return redirect()->route('login');라우트에 파라미터가 있다면 두 번째 인수로 전달하세요.
// 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']);컨트롤러 라우트에 파라미터가 필요하다면 두 번째 인수로 전달하세요.
return redirect()->action(
[UserController::class, 'profile'], ['id' => 1]
);외부 도메인으로 리다이렉트
애플리케이션 외부 도메인으로 리다이렉트해야 할 때는 away 메서드를 사용하세요. 이 메서드는 URL 인코딩이나 유효성 검사 없이 RedirectResponse를 생성합니다.
return redirect()->away('https://www.google.com');세션 플래시 데이터와 함께 리다이렉트
리다이렉트와 세션 플래시는 함께 사용하는 경우가 많습니다. 예를 들어 작업 성공 후 성공 메시지를 세션에 담아 다음 페이지에서 표시할 때 자주 활용됩니다. 메서드 체이닝으로 한 번에 처리할 수 있습니다.
Route::post('/user/profile', function () {
// ...
return redirect('/dashboard')->with('status', '프로필이 업데이트되었습니다!');
});리다이렉트 후 Blade 문법을 사용해 세션에 저장된 메시지를 표시하세요.
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif입력 데이터와 함께 리다이렉트
유효성 검사 오류가 발생했을 때 withInput 메서드를 사용하면 현재 요청의 입력 데이터를 세션에 플래시하고 리다이렉트할 수 있습니다. 다음 요청에서 이전 입력값을 가져와 폼을 다시 채울 수 있습니다.
return back()->withInput();기타 응답 유형
response 헬퍼는 다양한 유형의 응답 인스턴스를 생성하는 데 사용할 수 있습니다. 인수 없이 호출하면 Illuminate\Contracts\Routing\ResponseFactory 컨트랙트 구현체가 반환되며, 이를 통해 다양한 응답 생성 메서드를 활용할 수 있습니다.
뷰 응답
HTTP 상태 코드와 헤더를 직접 제어하면서 뷰를 응답 내용으로 반환하려면 view 메서드를 사용하세요.
return response()
->view('hello', $data, 200)
->header('Content-Type', $type);상태 코드나 커스텀 헤더가 필요 없다면 전역 view 헬퍼 함수를 사용하는 것이 더 간단합니다.
JSON 응답
json 메서드는 Content-Type 헤더를 자동으로 application/json으로 설정하고, 주어진 배열을 json_encode를 사용해 JSON으로 변환합니다.
return response()->json([
'name' => '홍길동',
'state' => 'KR',
]);JSONP 응답을 생성하려면 json 메서드와 withCallback 메서드를 함께 사용하세요.
return response()
->json(['name' => '홍길동', 'state' => 'KR'])
->withCallback($request->input('callback'));파일 다운로드
download 메서드는 지정한 경로의 파일을 브라우저가 강제로 다운로드하도록 하는 응답을 생성합니다. 두 번째 인수로 다운로드 파일명을 지정할 수 있으며, 세 번째 인수로 HTTP 헤더 배열을 전달할 수 있습니다.
return response()->download($pathToFile);
return response()->download($pathToFile, $name, $headers);WARNING
파일 다운로드를 관리하는 Symfony HttpFoundation은 파일명이 ASCII 문자여야 합니다. 한글 파일명 등을 사용할 때는 별도의 인코딩 처리가 필요할 수 있습니다.
파일 응답
file 메서드는 이미지나 PDF 같은 파일을 다운로드하지 않고 브라우저에서 직접 표시할 때 사용합니다. 첫 번째 인수로 파일의 절대 경로를, 두 번째 인수로 헤더 배열을 전달합니다.
return response()->file($pathToFile);
return response()->file($pathToFile, $headers);스트림 응답
데이터를 생성하면서 실시간으로 클라이언트에 전송하면 메모리 사용량을 크게 줄이고 성능을 개선할 수 있습니다. 특히 대용량 응답에서 효과적이며, 클라이언트는 서버가 모든 데이터를 전송하기 전에 먼저 수신된 데이터를 처리하기 시작할 수 있습니다.
Route::get('/stream', function () {
return response()->stream(function (): void {
foreach (['developer', 'admin'] as $string) {
echo $string;
ob_flush();
flush();
sleep(2); // 청크 사이의 지연 시뮬레이션...
}
}, 200, ['X-Accel-Buffering' => 'no']);
});stream 메서드에 전달하는 클로저가 Generator를 반환하면, Laravel이 Generator에서 반환하는 각 문자열 사이에 자동으로 출력 버퍼를 플러시하고 Nginx 출력 버퍼링을 비활성화합니다.
Route::post('/chat', function () {
return response()->stream(function (): Generator {
$stream = OpenAI::client()->chat()->createStreamed(...);
foreach ($stream as $response) {
yield $response->choices[0];
}
});
});스트림 응답 소비하기
스트림 응답은 Laravel의 stream npm 패키지를 통해 편리하게 소비할 수 있습니다. 먼저 사용하는 프레임워크에 맞는 패키지를 설치하세요.
React
npm install @laravel/stream-reactVue
npm install @laravel/stream-vueSvelte
npm install @laravel/stream-svelteuseStream을 사용해 스트림을 소비할 수 있습니다. 스트림 URL을 지정하면 Laravel 애플리케이션에서 데이터가 반환될 때마다 data가 자동으로 누적 업데이트됩니다.
React
import { useStream } from "@laravel/stream-react";
function App() {
const { data, isFetching, isStreaming, send } = useStream("chat");
const sendMessage = () => {
send({
message: `현재 타임스탬프: ${Date.now()}`,
});
};
return (
<div>
<div>{data}</div>
{isFetching && <div>연결 중...</div>}
{isStreaming && <div>생성 중...</div>}
<button onClick={sendMessage}>메시지 전송</button>
</div>
);
}Vue
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
const { data, isFetching, isStreaming, send } = useStream("chat");
const sendMessage = () => {
send({
message: `현재 타임스탬프: ${Date.now()}`,
});
};
</script>
<template>
<div>
<div>{{ data }}</div>
<div v-if="isFetching">연결 중...</div>
<div v-if="isStreaming">생성 중...</div>
<button @click="sendMessage">메시지 전송</button>
</div>
</template>Svelte
<script>
import { useStream } from "@laravel/stream-svelte";
const stream = useStream("chat");
const sendMessage = () => {
stream.send({
message: `현재 타임스탬프: ${Date.now()}`,
});
};
</script>
<div>
<div>{$stream.data}</div>
{#if $stream.isFetching}
<div>연결 중...</div>
{/if}
{#if $stream.isStreaming}
<div>생성 중...</div>
{/if}
<button onclick={sendMessage}>메시지 전송</button>
</div>send를 통해 데이터를 전송하면 기존 스트림 연결이 취소된 후 새 데이터가 전송됩니다. 모든 요청은 JSON POST 요청으로 전송됩니다.
WARNING
useStream 훅은 POST 요청을 보내므로 유효한 CSRF 토큰이 필요합니다. 가장 쉬운 방법은 애플리케이션 레이아웃의 <head>에 메타 태그로 CSRF 토큰을 포함시키는 것입니다.
useStream의 두 번째 인수로 옵션 객체를 전달해 스트림 소비 동작을 커스터마이즈할 수 있습니다. 기본값은 다음과 같습니다.
React
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>;
}Vue
<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>Svelte
<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: 스트림의 첫 응답이 성공적으로 수신되면 호출됩니다. 원시 Response 객체가 콜백으로 전달됩니다.onData: 각 청크가 수신될 때마다 호출됩니다. 현재 청크가 콜백으로 전달됩니다.onFinish: 스트림이 완료되거나 fetch/read 중 오류가 발생할 때 호출됩니다.
기본적으로 초기화 시점에 스트림으로 요청이 바로 전송되지 않습니다. initialInput 옵션을 사용하면 초기 페이로드를 즉시 전송할 수 있습니다.
React
import { useStream } from "@laravel/stream-react";
function App() {
const { data } = useStream("chat", {
initialInput: {
message: "자기소개를 해주세요.",
},
});
return <div>{data}</div>;
}Vue
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
const { data } = useStream("chat", {
initialInput: {
message: "자기소개를 해주세요.",
},
});
</script>
<template>
<div>{{ data }}</div>
</template>Svelte
<script>
import { useStream } from "@laravel/stream-svelte";
const stream = useStream("chat", {
initialInput: {
message: "자기소개를 해주세요.",
},
});
</script>
<div>{$stream.data}</div>스트림을 수동으로 취소하려면 훅에서 반환되는 cancel 메서드를 사용하세요.
React
import { useStream } from "@laravel/stream-react";
function App() {
const { data, cancel } = useStream("chat");
return (
<div>
<div>{data}</div>
<button onClick={cancel}>취소</button>
</div>
);
}Vue
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
const { data, cancel } = useStream("chat");
</script>
<template>
<div>
<div>{{ data }}</div>
<button @click="cancel">취소</button>
</div>
</template>undefined