Reqwest: Create Request independently from the client?

Created on 8 Nov 2018  路  5Comments  路  Source: seanmonstar/reqwest

I would like to create and modify a Request object independently from a Client.
Actually, the API kind of enforce the creation of a RequestBuilder through a Client instance.

Is there any reason to this? Is it planned to update the API of RequestBuilder to offer a way to construct Request like:

let req = Request::builder()
               .header("User-Agent", "Some-Agent")
               .method(Method::Get).body(())
               .timeout(Duration::from_sec(10))
               .build();
rfc

Most helpful comment

Related: would it be possible to directly support the types in the http crate so as to consume Requests created using that crate?

All 5 comments

It's possible to make and use a Request without a Client, and then eventually call client.execute(req).

I read my first comment and I realised that I was not really clear.

I would like to be able to create a Request whose components would be provided
little by little:

// API Host and method are known here
let req_uri = reqwest::Url::parse("https://www.some.com/api/").unwrap();
let mut req = reqwest::Request::new(reqwest::Method::POST, req_uri);

// A test is done to determine if a body needs to be attached
if let Some(body) = potential_body() {
   let bytes = serde_json::ser::to_vec(&body).unwrap();
   *req.body_mut() = Some(reqwest::Body::from(bytes));
 };

 if is_protected() {
      req.headers_mut()
               .insert("Header", "Some Value".parse().unwrap());
 }

It is doable but playing with mutable references does not sound really nice.

A RequestBuilder struct exists. As its name imply, I expect it to be able to use it to create
my Request object as proposed in my first comment.

Currently, there is two ways to obtain one RequestBuilder instance:

  1. Using the non documented RequestBuilder::new method:
RequestBuilder::new(client: Client, request: ::Result<Request>)
  1. Using the Client::request or one of its derived methods:
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder

I would expect to be able to create a instance of the builder without requiring any dependencies on Client or Request:

// Or RequestBuilder::new()
let req = Request::builder()
               .method(Method::Get)
               .uri("http://some.com/api")
               .header("User-Agent", "Some-Agent")
               .timeout(Duration::from_sec(10))
               .body(()) // Or .body((())).build() 

Related: would it be possible to directly support the types in the http crate so as to consume Requests created using that crate?

I'd also be interested in something like this, or another pattern for "specify everything I need to know to execute a request" that I can then hand to a Client to be executed and handled in some uniform way. Is this something y'all would be open to, or are there reasons to keep RequestBuilder instantiation via a Client?

I've been thinking about this and I have a potential solution.

Problem

There are currently two ways to build and send a request:

  1. The "right" way:
    rust client.get() // customize... .send();
    Pros: Builder pattern. Yes please!
    Cons: I have to start building the request from scratch. There is no notion of a standalone "request template" without a Client.
  2. The "other" way:
    rust let mut request = Request::new(Method::GET, url); request.headers_mut().insert(...); request.body_mut() = ...; client.execute(&request);
    Pros: I can build a Request "template" without Client. That is, I can Request::try_clone and modify for individual requests. That way, my request defaults are not tied to the Client (see ClientBuilder::default_headers).
    Cons: Ugly. I can't do request.to_builder(). I can't use any shared logic that uses RequestBuilder.

The docs for Client::execute state

You should prefer to use the RequestBuilder and RequestBuilder::send().

This is understandable when weighing the two options above. But it's unfortunate that I can't have the best of both worlds.

I think the crux of the problem is that RequestBuilder contains a reference to Client.

My suggestion

Goals

  1. Use RequestBuilder without Client
  2. Request::to_builder
  3. One way to do things
  4. Deprecations without breaking (if so desired)

Changes

To be added

  1. Put an Optional here: RequestBuilder { client: Optional<Client> }. New functionality described below will not use that field.
  2. Add Request::<httpmethod>() -> RequestBuilder methods and Request::builder() (defaults to GET).
  3. Add Request::to_builder.
  4. Encourage usage of Client::execute.
  5. Make RequestBuilder::send work without a Client when it is not available, similar to the get convenience function. This would have not affect on existing code and avoids panicing on new code.

To be deprecated or removed

  1. Deprecate Client::<httpmethod>() -> RequestBuilder
  2. Deprecate RequestBuilder::send. Or maybe not? It could be left in as a convenience to avoid building a Client.
  3. Consider renaming Client::execute to Client::send. (purely aesthetic)
  4. Consider deprecating Request::new and all Request::*_mut methods, making the struct immutable and encouraging use of RequestBuilder.

Unfortunately this is quite invasive. But I think it is worth it as it creates a more flexible and consistent API.

Note: I am taking some inspiration from Java's java.net.http package (for example see HttpRequest). This was introduced with Java 11 (so it's kinda new) and I think it is a good comparison, very similar goals. A further consideration from that API is the way the request builder accepts the body with the http method such as HttpRequest.Builder#POST(body).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

richardanaya picture richardanaya  路  5Comments

yazaddaruvala picture yazaddaruvala  路  5Comments

AmarOk1412 picture AmarOk1412  路  5Comments

encombhat picture encombhat  路  6Comments

Arnavion picture Arnavion  路  6Comments