使用 Laravel HTTP 客户端的 5 个提示和技巧

132 阅读4分钟

这些技巧包括使用 HTTP 宏、为容器服务配置 HTTP 客户端、可移植的基本 URL 配置、防止测试中的杂散请求以及侦听 HTTP 事件。通过掌握这些技巧,您可以简化 API 交互并创建更强大且可维护的 Laravel 应用程序。

HTTP 宏

Laravel 的许多服务都有一个“宏”功能,允许您为应用程序定义自定义方法。您可以将这些宏添加到服务提供商中的 boot() 方法中,而不是从 Laravel 框架扩展核心类。

HTTP 文档显示了可用于定义常见设置的宏示例

public function boot(): void
{
    Http::macro('github', function () {
        return Http::withHeaders([
            'X-Example' => 'example',
        ])->baseUrl('https://github.com');
    });
}
 
// Usage
response = Http::github()->get('/');

宏可以定义您希望在应用程序中定义和重用的任何便捷方法。宏的文档示例涉及配置 HTTP 客户端以用于其他服务的另一个技巧。

#为容器服务配置 HTTP 客户端

当从 Laravel 应用程序与 API 交互时,您可能需要为客户端进行各种可配置的设置。例如,如果 API 有多个环境,您将需要可配置的基 URL、令牌、超时设置等。

我们可以利用宏来定义客户端,将客户端表示为它可以自己的服务,注入其他服务,或者两者兼而有之。 首先,让我们看看在服务提供商的 register() 方法中定义客户端设置:

public function register(): void
{
    $this->app->singleton(ExampleService::class, function (Application $app) {
        $client = Http::withOptions([
            'base_uri' => config('services.example.base_url'),
            'timeout' => config('services.example.timeout', 10),
            'connect_timeout' => config('services.example.connect_timeout', 2),
        ])->withToken(config('services.example.token'));
 
        return new ExampleService($client);
    });
}

在单一实例服务定义中,我们链接了一些调用来配置客户端。结果是一个 PendingRequest 实例,我们可以将其传递给我们的服务构造函数,如下所示:

class ExampleService
{
    public function __construct(
        private PendingRequest $client
    ) {}
 
    public function getWidget(string $uid)
    {
        $response = $this->client
            ->withUrlParameters(['uid' => $uid])
            ->get('widget/{uid}');
 
        return new Widget($response->json());
    }
}

该服务使用 withOptions() 方法直接配置 Guzzle 选项,但我们也可以使用 HTTP 客户端提供的一些便捷方法:

$this->app->singleton(ExampleService::class, function (Application $app) {
    $client = Http::baseUrl(config('services.example.base_url'))
        ->timeout(config('services.example.timeout', 10))
        ->connectTimeout(config('services.example.connect_timeout', 2))
        ->withToken(config('services.example.token'));
 
    return new ExampleService($client);
});

或者,如果你想将宏与服务组合在一起,你可以使用你在 AppServiceProvider 的 boot() 方法中定义的宏:

$this->app->singleton(ExampleService::class, function (Application $app) {
    return new ExampleService(Http::github());
});

您可能已经看到默认基本 URL 包含尾部 /,因为根据 RFC 3986,它在我的选项中提供了最大的可移植性。 以下示例服务配置为例(请注意默认base_url):

return [
    'example' => [
        'base_url' => env('EXAMPLE_BASE_URI', 'https://api.example.com/v1/'),
        'token' => env('EXAMPLE_SERVICE_TOKEN'),
        'timeout' => env('EXAMPLE_SERVICE_TIMEOUT', 10),
        'connect_timeout' => env('EXAMPLE_SERVICE_TIMEOUT', 2),
    ],
];

如果我们的 API 在生产和暂存中有一个路径前缀 /v1/,也许它只是 https://stg-api.example.com/;使用尾部斜杠可使 URL 按预期工作,而无需更改代码。在配置尾随 / 的同时,请注意,我的代码中的所有 API 调用都使用相对路径:

$this->client
    ->withUrlParameters(['uid' => $uid])
    // Example:
    // Staging - https://stg-api.example.com/widget/123
    // Production - https://api.example.com/v1/widget/123
    ->get('widget/{uid}');

请参阅 Guzzle 的创建客户端文档,了解不同的 base_uri 样式如何影响 URI 的解析方式。

#防止测试中的杂散请求

Laravel 的 HTTP 客户端提供了出色的测试工具,使编写测试变得轻而易举。当我编写与 API 交互的代码时,我对我的测试以某种方式发生实际网络请求感到不安。使用 Laravel 的 HTTP 客户端输入防止杂散请求

Http::preventStrayRequests();
 
Http::fake([
    'github.com/*' => Http::response('ok'),
]);

在我看来,使用 preventStrayRequests() 的最佳方法是在您希望与 API 交互的测试类中定义一个 setUp() 方法。也许您也可以将其添加到应用程序的基 TestCase 类中:

namespace Tests;
 
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Http;
 
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
 
    public function setUp(): void
    {
        parent::setUp();
 
        Http::preventStrayRequests();
    }
}

这样做可以确保在测试套件中触发的每个 HTTP 客户端调用都有一个虚假请求作为备份。使用这种方法给了我巨大的信心,因为我已经用等效的假文件覆盖了测试中的所有出站请求。

#HTTP 事件的日志记录处理程序

Laravel 的 HTTP 客户端包含有价值的事件,您可以使用这些事件快速进入请求/响应生命周期的关键阶段。在撰写本文时,将触发三个事件:

  • Illuminate\Http\Client\Events\RequestSending
  • Illuminate\Http\Client\Events\ResponseReceived
  • Illuminate\Http\Client\Events\ConnectionFailed

假设您希望可视化应用程序发出请求的每个 URL。我们可以很容易地进入 RequestSending 事件并注销每个请求:

namespace App\Listeners;
 
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
 
class LogRequestSending
{
    public function handle(object $event): void
    {
        Log::debug('HTTP request is being sent.', [
            'url' => $event->request->url(),
        ]);
    }
}

要使事件处理程序正常工作,请将以下内容添加到 EventServiceProvider 类中:

use App\Listeners\LogRequestSending;
use Illuminate\Http\Client\Events\RequestSending;
// ...
protected $listen = [
    Registered::class => [
        SendEmailVerificationNotification::class,
    ],
    RequestSending::class => [
        LogRequestSending::class,
    ],
];

一旦全部挂接起来,您将在日志中看到与 HTTP 客户端尝试的每个请求的类似以下内容: [2023-03-17 04:06:03] local.DEBUG: HTTP request is being sent. {"url":"https://api.example.com/v1/widget/123"}