Search code examples
phplaravelhttp

How to change default timeout of Http Client in Laravel


In Laravel default timeout for Http Client requests is 30 seconds. But I want to change it to 5 seconds. I already know that it is possible by calling timeout(5) function on Http (PendingRequest) instance:

Http::timeout(5)->get('www.example.com');

But I don't want to call it each time I access Http facade. I want it to be its default behaviour. What I tried so far:

use Illuminate\Http\Client\Factory;

class AppServiceProvider extends ServiceProvider

public function register()

    $this->app->bind(Factory::class, static fn () => (new Factory)->withOptions(['timeout' => 5]));
}

It was doing what I need but I got faced a problem when I call Http client more than one time in one API request.

For example I have an API to upload document and then generate contract number for that to store in database:

$response1 = Http::baseUrl('www.storageservice.com')->attach('document', 'file_contents', 'document.pdf')->post('/upload');
$response2 = Http::baseUrl('www.numbergenerator.com')
// ->post('/generate/number');

If I debug $response2 I can see the document file attached to its body. Which means I am using exact same Http instance with first API call for second one.

That happens just because the code I have written in AppServiceProvider. When I delete the line in AppServiceProvider everything works well again but default timeout is still 30 seconds.

I know that for each Http call I can call new() method to create new instance of it like:

$response1 = Http::new()->baseUrl('www.storageservice.com')->attach('document', 'file_contents', 'document.pdf')->post('/upload');
$response2 = Http::new()->baseUrl('www.numbergenerator.com')
// ->post('/generate/number');

but it seems reverse engineering a bit. If I need to call a function each time so it would be timeout surely.

Any thought?


Solution

  • I solved my problem creating new Class that extends Factory class and binding them.

    use Illuminate\Http\Client\Factory;
    use Illuminate\Contracts\Events\Dispatcher;
    
    class AppServiceProvider extends ServiceProvider
    
    public function boot(): void
    {
        $this->app->bind(Factory::class, function ($app) {
            return new MyCustomHttpClientFactory($app->make(Dispatcher::class));
        });
    }
    

    MyCustomHttpClientFactory:

    use Illuminate\Http\Client\Factory;
    
    class MyCustomHttpClientFactory extends Factory
    {
        public function __call($method, $parameters)
        {
            if (static::hasMacro($method)) {
                return $this->macroCall($method, $parameters);
            }
    
            return tap($this->newPendingRequest(), function ($request) {
                $request
                    //This line has changed from the base factory class
                    ->withOptions(['timeout' => 5]);
            })->{$method}(...$parameters);
        }
    }
    

    Whenever Factory class get needed by app new instance of MyCustomHttpClientFactory will be used. And in MyCustomHttpClientFactory I am looking for any method call then catch $this->newPendingRequest() by tap function then attach withOptions method to it.

    Btw timeout set in __constructor of PendingRequest class:

    public function __construct(Factory $factory = null)
    {
        $this->factory = $factory;
        $this->middleware = new Collection;
        $this->asJson();
        $this->options = [
            'connect_timeout' => 10,
            'http_errors' => false,
            'timeout' => 30,
        ];
        $this->beforeSendingCallbacks = collect([function (Request $request, array $options, PendingRequest $pendingRequest) {
            $pendingRequest->request = $request;
            $pendingRequest->cookies = $options['cookies'];
            $pendingRequest->dispatchRequestSendingEvent();
        }]);
    }