Reusing connections with reqwest
Asked Answered
K

2

6

I need to issue a long sequence of REST calls to the same server (let's call it myapi.com). At the moment, I am using the Rust library reqwest as follows:

  • I create a reqwest::Client with all default settings.
  • For each REST call:
    • I use client.post("https://myapi.com/path/to/the/api") to create a reqwest::RequestBuilder.
    • I configure the RequestBuilder to obtain a reqwest::Request.
    • I send() the Request, read the reqwest::Response.
    • I drop everything except the Client, start again.

I read in the docs that reqwest is supposed to pool connections within the same Client. Given that I always reuse the same Client, I would expect the first API call to take some more (owing to the initial TCP and HTTPS handshakes). However, I observe always a consistent, quite high latency across all requests. So, I am wondering if connections are reused at all, or re-established every time. If they are not, how do I get to recycle the same connection? I feel that latency would be drastically reduced if I could save myself some roundtrips.

Kazachok answered 22/8, 2021 at 21:22 Comment(2)
Can't you track the accepted TCP connections on your server, or use tcpdump/wireshark in order to detect the SYN/FIN segments? (just to be certain that these multiple connections actually happen before refactoring the code)Sematic
This is a very good point! The server's not mine, sadly. I'm not very expert on low-level networking, can you elaborate on the wireshark strategy you proposed?Kazachok
H
1

(could not comment, have to leave a new answer)

ynn's answer is pretty helpful. However, it's not correct about the log part, at least for now and for me.

It would still create 4 connections rather than 2.

According to https://users.rust-lang.org/t/hyper-reqwest-connection-not-being-kept-alive/10895/3, you need to "read to end". And after doing so, it works!

Hooligan answered 31/1, 2024 at 8:29 Comment(0)
C
0

The behavior depends on whether you're using reqwest::blocking::Client (sync) or reqwest::Client (async).

Whether or not the existing connection is reused can be checked by enabling debug log.

reqwest::blocking::Client

When using synchronous API, just reusing a client means reusing a connection.

This is because, at the second (or third or ...) time we use the client, it is guaranteed the first call has completed and we have a connection.

use std::env;

use reqwest::blocking::Client;

fn main() {
    env::set_var("RUST_LOG", "debug");
    env_logger::init();

    let client = Client::new();
    for _ in 0..3 {
        //calls an API which returns a random string
        let res = client
            .get("https://ciprand.p3p.repl.co/api?len=10&count=1")
            .send()
            .unwrap();
        println!("{}", res.text().unwrap());
    }
}
[2023-05-17T07:11:13Z DEBUG reqwest::connect] starting new connection: https://ciprand.p3p.repl.co/
{"Strings":["fa749eda765"],"Count":1,"Length":10}
{"Strings":["dd0a8bfdc57"],"Count":1,"Length":10}
{"Strings":["cdedd8e3982"],"Count":1,"Length":10}

(Only one starting new connection is printed.)

reqwest::Client

When using asynchronous API, just reusing a client does NOT mean reusing a connection.

This is because, at the second (or third or ...) time we use the client, it is NOT guaranteed the first call has completed and we have a connection.

(The code below is for experiment purpose: never write an async code like this.)

use std::{env, sync::Arc};

use reqwest::Client;

async fn call(url: &str, client: Arc<Client>) {
    client.get(url).send().await.unwrap();
    println!("completed");
}

#[tokio::main]
async fn main() {
    env::set_var("RUST_LOG", "debug");
    env_logger::init();

    let client = Arc::new(Client::new());

    for _ in 0..2 {
        tokio::spawn(call(
            "https://ciprand.p3p.repl.co/api?len=10&count=1",
            client.clone(),
        ));
    }

    std::thread::sleep(std::time::Duration::from_millis(1000));

    for _ in 0..2 {
        tokio::spawn(call(
            "https://ciprand.p3p.repl.co/api?len=10&count=1",
            client.clone(),
        ));
    }

    std::thread::sleep(std::time::Duration::from_millis(1000));
}
[2023-05-17T07:14:25Z DEBUG reqwest::connect] starting new connection: https://ciprand.p3p.repl.co/
[2023-05-17T07:14:25Z DEBUG reqwest::connect] starting new connection: https://ciprand.p3p.repl.co/
completed
completed
completed
completed

(Only two starting new connection are printed. This is because, at the third and fourth time we use the client, the first (or maybe the second) call has completed by chance and we have a connection.)

Cementation answered 17/5, 2023 at 7:21 Comment(1)
In async mode reusing the connection is not guaranteed, but connections from a pool are reused: docs.rs/reqwest/latest/reqwest/struct.Client.htmlKresic

© 2022 - 2025 — McMap. All rights reserved.