Skip to content

[HttpClient] Broken base_uri when using scoping & retryable client together #62697

@ngrie

Description

@ngrie

Symfony version(s) affected

7.4.1

Description

I am having this scoped client configured:

framework:
    http_client:
        default_options:
            retry_failed:
                http_codes: [0, 429, 404, 500, 502, 503, 504]
                max_retries: 3
                # ...

        scoped_clients:
            foo.client:
                base_uri: 'https://foo.example.com/app'

(Please note the /app suffix, I cannot reproduce the issue without.)

Since version 7.4.1, when I would do something like $client->request('GET', '/foo') and the endpoint returns e.g. status 503 (and the RetryableHttpClient jumps in), I get:

PHP Fatal error:  Uncaught Symfony\Component\HttpClient\Exception\InvalidArgumentException: Invalid "base_uri" option: host or scheme is missing in "/app". in /Users/niklas/code/my_project/vendor/symfony/http-client/HttpClientTrait.php:577
Stack trace
#0 /Users/niklas/code/my_project/vendor/symfony/http-client/HttpClientTrait.php(178): Symfony\Component\HttpClient\CurlHttpClient::resolveUrl(Array, Array, Array)
#1 /Users/niklas/code/my_project/vendor/symfony/http-client/CurlHttpClient.php(91): Symfony\Component\HttpClient\CurlHttpClient::prepareRequest('GET', Array, Array, Array)
#2 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncContext.php(173): Symfony\Component\HttpClient\CurlHttpClient->request('GET', 'https://foo.exa...', Array)
#3 /Users/niklas/code/my_project/vendor/symfony/http-client/RetryableHttpClient.php(157): Symfony\Component\HttpClient\Response\AsyncContext->replaceRequest('GET', 'https://foo.exa...', Array)
#4 [internal function]: Symfony\Component\HttpClient\RetryableHttpClient->{closure:Symfony\Component\HttpClient\RetryableHttpClient::request():83}(Object(Symfony\Component\HttpClient\Chunk\FirstChunk), Object(Symfony\Component\HttpClient\Response\AsyncContext))
#5 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(357): Generator->valid()
#6 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(343): Symfony\Component\HttpClient\Response\AsyncResponse::passthruStream(Object(Symfony\Component\HttpClient\Response\CurlResponse), Object(Symfony\Component\HttpClient\Response\AsyncResponse), Object(SplObjectStorage))
#7 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(292): Symfony\Component\HttpClient\Response\AsyncResponse::passthru(Object(Symfony\Component\HttpClient\CurlHttpClient), Object(Symfony\Component\HttpClient\Response\AsyncResponse), Object(Symfony\Component\HttpClient\Chunk\FirstChunk), Object(SplObjectStorage))
#8 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(66): Symfony\Component\HttpClient\Response\AsyncResponse::stream(Array, -0.0)
#9 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/CommonResponseTrait.php(144): Symfony\Component\HttpClient\Response\AsyncResponse::{closure:Symfony\Component\HttpClient\Response\AsyncResponse::__construct():60}(Object(Symfony\Component\HttpClient\Response\AsyncResponse), -0.0)
#10 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(97): Symfony\Component\HttpClient\Response\AsyncResponse::initialize(Object(Symfony\Component\HttpClient\Response\AsyncResponse))
#11 /Users/niklas/code/my_project/demo.php(46): Symfony\Component\HttpClient\Response\AsyncResponse->getStatusCode()
#12 {main}

Next Symfony\Component\HttpClient\Exception\TransportException: Invalid "base_uri" option: host or scheme is missing in "/app". in /Users/niklas/code/my_project/vendor/symfony/http-client/Chunk/ErrorChunk.php:46
Stack trace:
#0 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(67): Symfony\Component\HttpClient\Chunk\ErrorChunk->isTimeout()
#1 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/CommonResponseTrait.php(144): Symfony\Component\HttpClient\Response\AsyncResponse::{closure:Symfony\Component\HttpClient\Response\AsyncResponse::__construct():60}(Object(Symfony\Component\HttpClient\Response\AsyncResponse), -0.0)
#2 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(97): Symfony\Component\HttpClient\Response\AsyncResponse::initialize(Object(Symfony\Component\HttpClient\Response\AsyncResponse))
#3 /Users/niklas/code/my_project/demo.php(46): Symfony\Component\HttpClient\Response\AsyncResponse->getStatusCode()
#4 {main}
  thrown in /Users/niklas/code/my_project/vendor/symfony/http-client/Chunk/ErrorChunk.php on line 46

Fatal error: Uncaught Symfony\Component\HttpClient\Exception\InvalidArgumentException: Invalid "base_uri" option: host or scheme is missing in "/app". in /Users/niklas/code/my_project/vendor/symfony/http-client/HttpClientTrait.php:577
Stack trace:
#0 /Users/niklas/code/my_project/vendor/symfony/http-client/HttpClientTrait.php(178): Symfony\Component\HttpClient\CurlHttpClient::resolveUrl(Array, Array, Array)
#1 /Users/niklas/code/my_project/vendor/symfony/http-client/CurlHttpClient.php(91): Symfony\Component\HttpClient\CurlHttpClient::prepareRequest('GET', Array, Array, Array)
#2 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncContext.php(173): Symfony\Component\HttpClient\CurlHttpClient->request('GET', 'https://foo.exa...', Array)
#3 /Users/niklas/code/my_project/vendor/symfony/http-client/RetryableHttpClient.php(157): Symfony\Component\HttpClient\Response\AsyncContext->replaceRequest('GET', 'https://foo.exa...', Array)
#4 [internal function]: Symfony\Component\HttpClient\RetryableHttpClient->{closure:Symfony\Component\HttpClient\RetryableHttpClient::request():83}(Object(Symfony\Component\HttpClient\Chunk\FirstChunk), Object(Symfony\Component\HttpClient\Response\AsyncContext))
#5 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(357): Generator->valid()
#6 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(343): Symfony\Component\HttpClient\Response\AsyncResponse::passthruStream(Object(Symfony\Component\HttpClient\Response\CurlResponse), Object(Symfony\Component\HttpClient\Response\AsyncResponse), Object(SplObjectStorage))
#7 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(292): Symfony\Component\HttpClient\Response\AsyncResponse::passthru(Object(Symfony\Component\HttpClient\CurlHttpClient), Object(Symfony\Component\HttpClient\Response\AsyncResponse), Object(Symfony\Component\HttpClient\Chunk\FirstChunk), Object(SplObjectStorage))
#8 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(66): Symfony\Component\HttpClient\Response\AsyncResponse::stream(Array, -0.0)
#9 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/CommonResponseTrait.php(144): Symfony\Component\HttpClient\Response\AsyncResponse::{closure:Symfony\Component\HttpClient\Response\AsyncResponse::__construct():60}(Object(Symfony\Component\HttpClient\Response\AsyncResponse), -0.0)
#10 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(97): Symfony\Component\HttpClient\Response\AsyncResponse::initialize(Object(Symfony\Component\HttpClient\Response\AsyncResponse))
#11 /Users/niklas/code/my_project/demo.php(46): Symfony\Component\HttpClient\Response\AsyncResponse->getStatusCode()
#12 {main}

Next Symfony\Component\HttpClient\Exception\TransportException: Invalid "base_uri" option: host or scheme is missing in "/app". in /Users/niklas/code/my_project/vendor/symfony/http-client/Chunk/ErrorChunk.php:46
Stack trace:
#0 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(67): Symfony\Component\HttpClient\Chunk\ErrorChunk->isTimeout()
#1 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/CommonResponseTrait.php(144): Symfony\Component\HttpClient\Response\AsyncResponse::{closure:Symfony\Component\HttpClient\Response\AsyncResponse::__construct():60}(Object(Symfony\Component\HttpClient\Response\AsyncResponse), -0.0)
#2 /Users/niklas/code/my_project/vendor/symfony/http-client/Response/AsyncResponse.php(97): Symfony\Component\HttpClient\Response\AsyncResponse::initialize(Object(Symfony\Component\HttpClient\Response\AsyncResponse))
#3 /Users/niklas/code/my_project/demo.php(46): Symfony\Component\HttpClient\Response\AsyncResponse->getStatusCode()
#4 {main}
  thrown in /Users/niklas/code/my_project/vendor/symfony/http-client/Chunk/ErrorChunk.php on line 46

How to reproduce

<?php
// First, run "composer require symfony/http-client"
// Then, execute this file:

declare(strict_types=1);

use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Retry\GenericRetryStrategy;
use Symfony\Component\HttpClient\RetryableHttpClient;
use Symfony\Component\HttpClient\ScopingHttpClient;
use Symfony\Component\HttpClient\UriTemplateHttpClient;

require_once __DIR__.'/vendor/autoload.php';

$client = new UriTemplateHttpClient(
    ScopingHttpClient::forBaseUri(
        new RetryableHttpClient(
            HttpClient::create(),
            new GenericRetryStrategy([0, 429, 500, 502, 503, 504], 1000, 3, 5000, 0.3),
            3,
        ),
        'https://foo.example.com/app',
    ),
);

echo $client->request('GET', '/foo')->getStatusCode();
echo "\n";

Possible Solution

No response

Additional Context

Prior to #62652, this is how my HTTP client has been composed (simplified):

$client = new RetryableHttpClient(
    ScopingHttpClient::forBaseUri(
        new UriTemplateHttpClient(HttpClient::create()),
        'https://foo.example.com/app',
    ),
    new GenericRetryStrategy([0, 429, 500, 502, 503, 504], 1000, 3, 5000, 0.3),
    3,
);

Now, it looks like this:

$client = new UriTemplateHttpClient(
    ScopingHttpClient::forBaseUri(
        new RetryableHttpClient(
            HttpClient::create(),
            new GenericRetryStrategy([0, 429, 500, 502, 503, 504], 1000, 3, 5000, 0.3),
            3,
        ),
        'https://foo.example.com/app',
    ),
);

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions