HTTP 用戶端是什麼
Laravel 的 HTTP 用戶端是包裝 Guzzle 的易用 API。 透過Http Facade,你可以簡潔地寫出對外部 Web 服務或 API 的 HTTP 請求。
use Illuminate\Support\Facades\Http;
$response = Http::get('https://api.example.com/users');
Guzzle 已預先安裝,你不需要額外設定即可使用。
基本請求
GET 請求
use Illuminate\Support\Facades\Http;
$response = Http::get('https://api.example.com/users');
$response = Http::get('https://api.example.com/users', [
'page' => 1,
'per_page' => 20,
]);
POST 請求
資料預設會以application/json 傳送。
$response = Http::post('https://api.example.com/users', [
'name' => '山田 太郎',
'email' => '[email protected]',
]);
PUT / PATCH / DELETE
// PUT 請求
$response = Http::put('https://api.example.com/users/1', [
'name' => '山田 花子',
]);
// PATCH 請求
$response = Http::patch('https://api.example.com/users/1', [
'email' => '[email protected]',
]);
// DELETE 請求
$response = Http::delete('https://api.example.com/users/1');
處理回應
Http::get() 等方法會回傳 Illuminate\Http\Client\Response 實例。
此物件提供大量檢查回應的方法。
$response = Http::get('https://api.example.com/users/1');
// 回應內文
$response->body(); // 字串
$response->json(); // 轉為陣列
$response->json('name'); // 取得 JSON 特定 key
$response->object(); // stdClass 物件
$response->collect(); // 集合
// 狀態
$response->status(); // 狀態碼(例:200)
$response->successful(); // 200 系列時 true
$response->failed(); // 400 以上時 true
$response->clientError(); // 400 系列時 true
$response->serverError(); // 500 系列時 true
// 常見狀態碼判斷
$response->ok(); // 200
$response->created(); // 201
$response->noContent(); // 204
$response->notFound(); // 404
$response->unauthorized(); // 401
$response->forbidden(); // 403
$response->unprocessableEntity(); // 422
$response->tooManyRequests(); // 429
$name = Http::get('https://api.example.com/users/1')['name'];
請求選項
設定標頭
$response = Http::withHeaders([
'X-Api-Version' => '2',
'Accept-Language' => 'ja',
])->get('https://api.example.com/users');
application/json,可用 acceptJson():
$response = Http::acceptJson()->get('https://api.example.com/users');
瀏覽器對 Laravel 的 AJAX 請求 CSRF 標頭(
X-CSRF-TOKEN / X-XSRF-TOKEN)請參考 CSRF 保護。認證
Bearer token 認證(最常見):$response = Http::withToken($token)->get('https://api.example.com/me');
$response = Http::withBasicAuth('[email protected]', 'password')
->get('https://api.example.com/private');
設定 base URL
若對同一主機有多個請求,可用baseUrl() 統一。
$response = Http::baseUrl('https://api.example.com')
->withToken($token)
->get('/users/1');
傳送表單資料
若要用application/x-www-form-urlencoded 送出,可用 asForm()。
$response = Http::asForm()->post('https://api.example.com/login', [
'username' => 'taro',
'password' => 'secret',
]);
逾時
// 回應逾時(預設 30 秒)
$response = Http::timeout(10)->get('https://api.example.com/slow-endpoint');
// 連線逾時(預設 10 秒)
$response = Http::connectTimeout(5)->get('https://api.example.com/endpoint');
超過逾時會拋出
Illuminate\Http\Client\ConnectionException。
呼叫外部 API 建議務必設定逾時。重試
可為暫時性網路故障或伺服器錯誤設定自動重試。// 最多 3 次、間隔 100 毫秒
$response = Http::retry(3, 100)->post('https://api.example.com/orders', $data);
use Illuminate\Http\Client\PendingRequest;
use Throwable;
use Illuminate\Http\Client\ConnectionException;
$response = Http::retry(3, 100, function (Throwable $exception, PendingRequest $request) {
return $exception instanceof ConnectionException;
})->post('https://api.example.com/orders', $data);
錯誤處理
手動檢查錯誤
Laravel HTTP 用戶端預設不會對 4xx / 5xx 回應拋出例外。 請透過failed()、clientError() 等明確檢查。
$response = Http::get('https://api.example.com/users/999');
if ($response->notFound()) {
// 404 處理
}
if ($response->failed()) {
// 記錄錯誤日誌
logger()->error('API request failed', ['status' => $response->status()]);
}
拋出例外
使用throw(),錯誤時會拋出 Illuminate\Http\Client\RequestException。
// 若錯誤回應(4xx/5xx)則拋出例外
$response = Http::post('https://api.example.com/users', $data)->throw();
// throwIf(): 條件為 true 時拋出(以下為獨立範例)
$response = Http::get('https://api.example.com/users/1');
$response->throwIf($response->status() === 422);
// throwUnlessStatus(): 非指定狀態碼時拋出(以下為獨立範例)
$response = Http::post('https://api.example.com/orders', $data);
$response->throwUnlessStatus(201);
throw() 會回傳回應實例,因此可用方法鏈。
$user = Http::post('https://api.example.com/users', $data)
->throw()
->json();
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\ConnectionException;
try {
$response = Http::timeout(5)
->post('https://api.example.com/users', $data)
->throw();
} catch (ConnectionException $e) {
// 逾時或連線錯誤
logger()->error('Connection failed: ' . $e->getMessage());
} catch (RequestException $e) {
// 4xx / 5xx 錯誤
logger()->error('API error', ['status' => $e->response->status()]);
}
並行請求
若要同時呼叫多個 API,可用pool() 並行執行。
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('users')->get('https://api.example.com/users'),
$pool->as('posts')->get('https://api.example.com/posts'),
$pool->as('comments')->get('https://api.example.com/comments'),
]);
$users = $responses['users']->json();
$posts = $responses['posts']->json();
$comments = $responses['comments']->json();
相較於依序執行可大幅加速,非常適合需呼叫多個外部 API 的儀表板等場景。
測試
以 Http::fake() 進行 mock
測試時使用Http::fake(),可在不發送真實 HTTP 請求下模擬回應。
use Illuminate\Support\Facades\Http;
Http::fake();
// 所有請求都回傳 200
$response = Http::get('https://api.example.com/users');
$response->successful(); // true
Http::fake([
'api.example.com/users/*' => Http::response(['id' => 1, 'name' => '山田 太郎'], 200),
'api.example.com/posts/*' => Http::response(['error' => 'Not Found'], 404),
'*' => Http::response('OK', 200), // 其他所有請求
]);
Http::fake([
// 第 1 次:200,第 2 次:200,第 3 次:429
// 序列用盡後之後的請求會拋出例外
'api.example.com/*' => Http::sequence()
->push(['id' => 1], 200)
->push(['id' => 2], 200)
->pushStatus(429),
]);
驗證請求內容
以Http::assertSent() 驗證請求內容。
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
Http::fake();
Http::withToken('my-token')->post('https://api.example.com/users', [
'name' => '山田 太郎',
]);
Http::assertSent(function (Request $request) {
return $request->url() === 'https://api.example.com/users'
&& $request->hasHeader('Authorization', 'Bearer my-token')
&& $request['name'] === '山田 太郎';
});
// 確認未送出
Http::assertNotSent(function (Request $request) {
return $request->url() === 'https://api.example.com/admin';
});
// 確認送出的請求次數
Http::assertSentCount(1);
測試時請務必於開頭呼叫
Http::fake()。
若忘記呼叫,實際請求將會送到外部 API。
也可以用 Http::preventStrayRequests() 讓未 fake 的 URL 請求觸發例外。防止漏網請求
Http::preventStrayRequests();
Http::fake([
'api.example.com/*' => Http::response(['ok' => true]),
]);
// 未 fake 的 URL 將會拋出例外
Http::get('https://other.example.com/endpoint'); // 例外
實戰範例:呼叫外部 API 的 Service 類別
實際專案中,將 HTTP 用戶端邏輯集中到 Service 類別是最佳實踐。1
建立 Service 類別
<?php
namespace App\Services;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
class GitHubService
{
private string $baseUrl = 'https://api.github.com';
public function __construct(
private readonly string $token,
) {}
/**
* 取得使用者資訊
*
* @throws ConnectionException
* @throws RequestException
*/
public function getUser(string $username): array
{
return Http::baseUrl($this->baseUrl)
->withToken($this->token)
->acceptJson()
->timeout(10)
->get("/users/{$username}")
->throw()
->json();
}
/**
* 取得 repository 清單
*
* @throws ConnectionException
* @throws RequestException
*/
public function getRepositories(string $username, int $page = 1): array
{
return Http::baseUrl($this->baseUrl)
->withToken($this->token)
->acceptJson()
->timeout(10)
->retry(2, 500)
->get("/users/{$username}/repos", [
'page' => $page,
'per_page' => 30,
'sort' => 'updated',
])
->throw()
->json();
}
}
2
在服務提供者註冊
// app/Providers/AppServiceProvider.php
use App\Services\GitHubService;
public function register(): void
{
$this->app->singleton(GitHubService::class, function () {
return new GitHubService(
token: config('services.github.token'),
);
});
}
// config/services.php
'github' => [
'token' => env('GITHUB_TOKEN'),
],
3
從控制器使用
<?php
namespace App\Http\Controllers;
use App\Services\GitHubService;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\JsonResponse;
class GitHubController extends Controller
{
public function __construct(
private readonly GitHubService $github,
) {}
public function show(string $username): JsonResponse
{
try {
$user = $this->github->getUser($username);
return response()->json($user);
} catch (ConnectionException) {
return response()->json(['error' => '無法連線至 GitHub API。'], 503);
} catch (RequestException $e) {
$status = $e->response->status();
if ($status === 404) {
return response()->json(['error' => '找不到使用者。'], 404);
}
return response()->json(['error' => 'GitHub API 發生錯誤。'], 502);
}
}
}
4
撰寫測試
<?php
namespace Tests\Unit\Services;
use App\Services\GitHubService;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class GitHubServiceTest extends TestCase
{
private GitHubService $service;
protected function setUp(): void
{
parent::setUp();
Http::fake([
'api.github.com/users/octocat' => Http::response([
'login' => 'octocat',
'name' => 'The Octocat',
'public_repos' => 8,
], 200),
'api.github.com/users/notfound' => Http::response(
['message' => 'Not Found'],
404
),
]);
$this->service = new GitHubService(token: 'test-token');
}
public function test_可以取得使用者資訊(): void
{
$user = $this->service->getUser('octocat');
$this->assertEquals('octocat', $user['login']);
$this->assertEquals('The Octocat', $user['name']);
Http::assertSent(function ($request) {
return $request->url() === 'https://api.github.com/users/octocat'
&& $request->hasHeader('Authorization', 'Bearer test-token');
});
}
public function test_不存在的使用者會拋出例外(): void
{
$this->expectException(RequestException::class);
$this->service->getUser('notfound');
}
}
總結
常用方法一覽
常用方法一覽
| 方法 | 用途 |
|---|---|
Http::get($url, $query) | GET 請求 |
Http::post($url, $data) | POST 請求(JSON) |
Http::put($url, $data) | PUT 請求 |
Http::patch($url, $data) | PATCH 請求 |
Http::delete($url) | DELETE 請求 |
->withToken($token) | Bearer 認證 |
->withHeaders($headers) | 自訂標頭 |
->timeout($seconds) | 設定逾時 |
->retry($times, $sleep) | 自動重試 |
->throw() | 錯誤時拋出例外 |
Http::fake() | 測試用 mock |
Http::pool($callback) | 並行請求 |
回應判斷方法一覽
回應判斷方法一覽
| 方法 | 說明 |
|---|---|
successful() | 200 系列 |
failed() | 400 以上 |
clientError() | 400 系列 |
serverError() | 500 系列 |
ok() | 200 |
created() | 201 |
notFound() | 404 |
unauthorized() | 401 |
forbidden() | 403 |
unprocessableEntity() | 422 |
tooManyRequests() | 429 |
與外部 API 整合的最佳實踐
與外部 API 整合的最佳實踐
- 將 HTTP 用戶端邏輯集中在 Service 類別
- 一定要設定逾時(
timeout()與connectTimeout()) - 為暫時性錯誤設定重試(
retry()) - 測試務必使用
Http::fake(),不要真的打外部 API - 在測試 setUp 中加入
Http::preventStrayRequests()更保險 - API token 或認證資訊透過環境變數與
config/services.php管理