kitetail / zttp

A developer-experience focused HTTP client, optimized for most common use cases.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use shared Guzzle client instance to reuse cURL handle

bastien-phi opened this issue Β· comments

Hi !

it looks like the feature developed in commit f49f921 has been reverted recently.
Modifying PendingZttpRequest::buildClient with

function buildClient()
{
    echo "building a client\n";
    return new \GuzzleHttp\Client(['handler' => $this->buildHandlerStack()]);
}

we expect to have a single building a client when calling

Zttp::get('http://some.url');
Zttp::get('http://some.url');

Actually, building a client is echoed twice.

Is there any breaking change that makes the reusability of the Guzzle client instance ?

Hey @bastien-phi, I had to revert to creating a new client instance per request because each request can potentially have a different handler stack.

Consider:

Zttp::beforeSending(function ($request) {
    echo $request->url();
})->get('http://example.com/1);


Zttp::beforeSending(function ($request) {
    echo $request->url();
})->get('http://example.com/2);

If we reused a single client, the first callback would run again when making the second request.

If you know of a way around this definitely share πŸ‘

what about that ?

class PendingZttpRequest {
    static $client;

    //...

    function send($method, $url, $options)
    {
        return new ZttpResponse(
            tap($this->buildClient(),function ($client) {
                tap($client->getConfig('handler'),function ($handlerStack) {
                    $handlerStack->remove('zttp');
                })->push($this->buildBeforeSendingHandler(), 'zttp');
            })->request($method,$url,$this->mergeOptions([
                 'query' => $this->parseQueryParams($url),
            ],$options)));
    }

    function buildClient()
    {
        // for testing purpose only, use return static::$client ?: static::$client = new \GuzzleHttp\Client(); instead
        if (static::$client) {
            return static::$client;
        }
        echo "building a client\n";
        return static::$client = new \GuzzleHttp\Client();
    }

Testing it with

Zttp::beforeSending(function ($request) {
    echo $request->url() . PHP_EOL;
    echo $request->method() . PHP_EOL;
})->get($this->url('/get'));
Zttp::beforeSending(function ($request) {
     echo $request->method() . PHP_EOL;
     echo $request->url() . PHP_EOL;
})->post($this->url('/post'));

will echo what we expect :

building a client
http://localhost:9000/get
GET
POST
http://localhost:9000/post

What do you think about that ?

Interesting! How much benefit is there to reusing the same client, is it worth it you think? I haven't benchmarked it or anything myself. What are the costs of using a new client each time?

I have no idea if it costs a lot to using a new client each time. I was just referring what @jeremeamia said in #14 . Maybe he has more information about that.

The main benefit comes when using the same cURL handle, not necessarily the Guzzle client itself. If used to make multiple requests to the same domain, it'll only have to make the SSL handshake the first time, thus improving performance in that case.

Probably the best way to accomplish that, while allowing a unique HandlerStack each request, is to preserve the same underlying HandlerStack's $handler value (e.g., the result of GuzzleHttp\choose_handler(). See: https://github.com/guzzle/guzzle/blob/master/src/HandlerStack.php#L38-L47

So, in Zttp\PendingHttpRequest::buildHandlerStack(), you could do something like:

    function buildHandlerStack()
    {
        static $handler;
        if (!$handler) {
            $handler = \GuzzleHttp\choose_handler();
        }

        return tap(\GuzzleHttp\HandlerStack::create($handler), function ($stack) {
            $stack->push($this->buildBeforeSendingHandler());
        });
    }

...Or however you want to code that with your Zttp coding styles. πŸ˜‰

...Or however you want to code that with your Zttp coding styles.

πŸ˜† πŸ˜† πŸ˜†

Thanks @jeremeamia for the precision.
The fact that the Guzzle client was previously shared allowed me to keep a cookie jar shared between the requests and use cookie-based authentication :

Zttp::$client = new \GuzzleHttp\Client(['cookie' => true]);
Zttp::post('http://some.url/login',['email'=>$email,'password'=>$password]);
Zttp::get('http://some.url/my-profile'); // authenticated thanks to the cookie 

I did not think about all possibilities given by the fact that we were able to custom the Guzzle client but I think that was quite nice !
Is that a good reason to reuse the client again ?

Thanks @jeremeamia that's all super useful to know! I'll probably mess with this a bit over the weekend and see if I can come up with an approach I like that benefits from that optimization.

@bastien-phi That is cool! I don't really want to expose that as a real feature personally, because I don't want to be committed to supporting backwards compatibility for that. Ideally I don't want anything related to Guzzle to leak outside of Zttp if possible; I like knowing that I could switch to a totally different HTTP library under the hood without breaking anyone's stuff if I really wanted to.

I'll think about another way that you could share cookies between requests if you wanted to though, without having to dig into the internal implementation.

@adamwathan

Ideally I don't want anything related to Guzzle to leak outside of Zttp if possible

I can't disagree with you !
By the way, thanks for your work !