sbstp / attohttpc

Rust lightweight HTTP 1.1 client

Home Page:https://docs.rs/attohttpc/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Provide getters for RequestBuilder

abreis opened this issue · comments

I'd like to propose methods to be able to read fields from RequestBuilder before it is prepared (or sent). These would mirror the methods available in PreparedRequest:

fn method(&self) -> &Method;
fn headers(&self) -> &HeaderMap;
fn body(&self) -> &[u8];

(and while these names don't clash with RequestBuilder's current API, I'd personally prefix them as get_method(), get_headers(), and get_body())

The goal of these is to allow users to build their own extension traits over RequestBuilder. For example, I'm working on an interface that requires signing each request with a cryptographic key, and adding that signature to the request's headers. The most convenient way would be to do something like:

trait RequestBuilderExt { .. }

impl RequestBuilderExt for RequestBuilder {
    fn sign_message(self) -> Self {
	// Sign the message using some combination of the 
	// request's method, url, and body
	let signature = ..;
	
	// Add the signature header to the request
	self.header("ACCESS-SIGNATURE", signature);
	
	self
    }
}

which would let my users simply call .sign_message() on a RequestBuilder prior to sending it.

As it stands right now, I can't read existing fields from a RequestBuilder, and I can't mutate a PreparedRequest.

Would these new APIs be welcome to attohttpc?

(and, thanks for writing attohttpc, of all http clients out there it has my favorite API)

Definitely something we can do. One thing to note however is that we are currently working on supporting streaming request bodies, which means that the body won't always be a &[u8].

It will support reading from it and seeking back to the start though, so it shouldn't be a problem for your use case.

We'll have to add the same interface on the PreparedRequest for bodies. I think the RequestBuilder and PreparedRequest should have the same inferface.

Thanks. And disregard my comment on naming the methods get_*, just remembered this is actually the opposite of Rust's guidelines.

Naming is a bit tricky, as right now the .body() method on RequestBuilder sets a body, but .body() on PreparedRequest reads it.

Yeah that will be tricky. Maybe we can provide an intermediary object to read the fields.

let builder = get("...").body("hello world");
builder.view().body() // b"hello world"

How about .inspect() that returns RequestInspector object? So that will be

let builder = get("...").body("xx").header("aa", "bb");
assert_eq!(builder.inspect().body(), "xx");

@abreis @imp @sbstp Could you check if #61 fits the bill.

It does yes, thank you very much.

One mild annoyance is that we lose access to the Body trait methods like.write() (it takes a &mut), but that would require a mutable inspector which is probably overkill. I'm 'specializing' my code to the Body types that I require.

One mild annoyance is that we lose access to the Body trait methods like.write() (it takes a &mut), but that would require a mutable inspector which is probably overkill.

I think it would not be a problem and we should provide a mutable body reference. Will open another PR...

Would that be a possible footgun? Meaning: a user calls .inspect().body().write(..) in a body type that actually gets consumed by the writer, and the user then calls .send() not knowing that the body contents are now gone.

FWIW, not sure if that's how any of this works, or if there is such a type of body.

FWIW, not sure if that's how any of this works, or if there is such a type of body.

An implementor of Body needs to assume that write is called multiple times anyway and produce the body as often as necessary, c.f. #63. If a user decides to avoid that contract and still calls inspect().body().write(..), then I think we cannot do much about it.

Makes sense. Thanks again!