-
Notifications
You must be signed in to change notification settings - Fork 502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add curl -H/--header option to globally add headers to all requests #3419
Add curl -H/--header option to globally add headers to all requests #3419
Conversation
Hi @theoforger Thanks for the PR, I think we should change some things: client.rs
For example with this file
Running : $ hurl --header "foo:toto" --header "bar:tutu" test.hurl --verbose
...
> GET /hello HTTP/1.1
> Host: localhost:8000
> Accept: */*
> User-Agent: hurl/6.0.0-SNAPSHOT
> foo: toto
> bar: tutu
... If we define header in
$ hurl --header "foo:toto" --header "bar:tutu" test.hurl --verbose
...
> GET /hello HTTP/1.1
> Host: localhost:8000
> Accept: */*
> User-Agent: hurl/6.0.0-SNAPSHOT
> foo: toto
> bar: tutu
> baz: titi
... Headers defined in A particular case is if we use the same header name:
$ hurl --header "foo:toto" --header "foo:tutu" test.hurl --verbose
...
> GET /hello HTTP/1.1
> Host: localhost:8000
> Accept: */*
> User-Agent: hurl/6.0.0-SNAPSHOT
> foo: toto
> foo: tutu
> foo: titi
... Even if the name is similar, we add user headers from the file and from the
I think there is subtil bug in the new code. For instance we have this code: fn set_headers(
&mut self,
request_spec: &RequestSpec,
options: &ClientOptions,
) -> Result<(), HttpError> {
// ...
// If request has no Content-Type header, we set it if the content type has been set
// implicitly on this request.
if !request_spec.headers.contains_key(CONTENT_TYPE) {
if let Some(s) = &request_spec.implicit_content_type {
headers.push(format!("{}: {s}", CONTENT_TYPE));
} else {
// We remove default Content-Type headers added by curl because we want
// to explicitly manage this header.
// For instance, with --data option, curl will send a 'Content-type: application/x-www-form-urlencoded'
// header.
headers.push(format!("{}:", CONTENT_TYPE));
}
} We set an implicit content type given the request headers. Now, with Trying to make less modification and and avoiding regression, what we could do is:
From: fn set_headers(
&mut self,
request_spec: &RequestSpec,
options: &ClientOptions,
) -> Result<(), HttpError> to fn set_headers(
&mut self,
headers_spec: &HeaderVec,
options: &ClientOptions,
) -> Result<(), HttpError> We previously take headers from a In the
impl HeaderVec {
fn with_raw_headers(&self, options_headers: &[&str]) -> HeaderVec {
// ...
}
} This method takes a list of "raw" headers (maybe find a better name) i.e: With this new builder method
self.set_body(request_spec_body)?;
self.set_headers(request_spec, options)?;
if let Some(aws_sigv4) = &options.aws_sigv4 {
// ...
} To become: self.set_body(request_spec_body)?;
let headers_spec = request_spec.headers.with_raw_headers(options.headers):
self.set_headers(headers_spec, options)?;
if let Some(aws_sigv4) = &options.aws_sigv4 {
// ...
} And that should bee good! The idea is really to be able to easily unit test the aggregation of headers from file and header from option. curl_cmd.rsThe command line construction regarding headers should be done only here: Instead of this, we should call integration testFor the integation tests that are failing, you should look at I think we could split this in 2 PRs, it could be easier to review:
|
@jcamiel Wow! Really appreciate the detailed response 🙏 One more question regarding the behavior. I understand that # test.hurl
GET http://localhost:8000/hello $ hurl --header "User-Agent: some-text" test.hurl --verbose Would we get: ...
> GET /hello HTTP/1.1
> Host: localhost:8000
> Accept: */*
> User-Agent: some-text #------------ Override (this is the behavior with `curl --header`)
... or: ...
> GET /hello HTTP/1.1
> Host: localhost:8000
> Accept: */*
> User-Agent: hurl/6.0.0-SNAPSHOT
> User-Agent: some-text #----------------- Append
... |
I think if we modify the code from if !request_spec.headers.contains_key(USER_AGENT) {
let user_agent = match options.user_agent {
Some(ref u) => u.clone(),
None => format!("hurl/{}", clap::crate_version!()),
};
headers.push(format!("{}: {user_agent}", USER_AGENT));
} to if !headers_spec.contains_key(USER_AGENT) {
let user_agent = match options.user_agent {
Some(ref u) => u.clone(),
None => format!("hurl/{}", clap::crate_version!()),
};
headers.push(format!("{}: {user_agent}", USER_AGENT));
} we will add a User-Agent only if there is no So we want: $ hurl --header "User-Agent: some-text" test.hurl --verbose
|
/keepopen |
Hi @theoforger just to warn you, we're doing a 6.0.0 release now; no rush to code/develop the PRs, we'll put this feature in the next release (should be 6.1.0) |
@jcamiel Thanks for letting me know! 🙏 I'll update you when I have something. Just been busy with school lately haha |
@jcamiel Hello! I was looking into this:
Since we are changing the signature, how do we want to handle implicit content type? hurl/packages/hurl/src/http/client.rs Lines 547 to 549 in 1025eae
Here we only append the implicit content type if it exists, but if the method takes a We could simply add another parameter to this method, something like: fn set_headers(
&mut self,
headers_spec: &HeaderVec,
implicit_content_type: Option<String>,
options: &ClientOptions,
) -> Result<(), HttpError> {
... What do you think? Is there a better solution? |
Yes, you're totally right, we have to pass the (potential) implicit content type as parameter now! What you propose is totally fine, maybe the type of the parameter can be |
@jcamiel I just submit another PR since we're going to split this one in half. I'll do the rest of the work here 😄 |
@jcamiel I'm working on the second half. One slight issue: To aggregate the headers, the new method takes a
However, in ClientOptions , the headers are defined as Vec<String> :hurl/packages/hurl/src/cli/options/mod.rs Line 64 in c0a5126
We could map it to a Appreciate the guidance 🙏 |
Hi,
It's just a feeling so don't take this as authoritative. We can go with the initial proposition. You could ask also this question on the Rust forum https://users.rust-lang.org I would be curious of the answer! |
If my understanding is correct, with a request_spec.headers.aggregate_raw_headers(&options.headers);
Inside the Correct me if I'm wrong though. I'm still learning Rust 😆 |
Yes it's correct. Still, I still prefer to have
What could be tried, but not very found of, also is to make a generic method that can both accept
|
No problem! Totally understandable 👍
Just to clarify, are you suggesting that we make two separate methods with different parameter types? |
No 😅 I mean: choose only one preferred method/ API signature and adapt the client call code to this API... (not sure if it's clearer) |
Ahh I see. In that case I'll simply go with |
307f4b2
to
eb88ae7
Compare
@jcamiel Hello! I made some more progress 😄. Let me know if anything needs to be changed. (Sorry I had to push it twice. Messed up the rebase earlier)
Btw, should I add it to this branch or make another PR for this? |
Good job @theoforger I will look at it this week-end. For the units test, you can make another independent PR, as it's much more simple and with not a lot of code, I will merge it asap (the current PR needs a little more time to review 😅). Once again thanks for the PRs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some changes and we've almost done this one @theoforger . Could you please squash the new commits after the modification ? Thanks !
eb88ae7
to
3971e91
Compare
Well done @theoforger it's a good new feature! I will create an issue to add an integration test for it, fell free to work on it, if you want of course! |
/accept |
🕗 /accept is running, please wait for completion. |
✅ Pull request merged with fast forward by
|
Issue for integration test => #3504 |
@jcamiel Thanks for the approval! It's been such a pleasure working with you🙏 I might as well take the integration test issue 😆. And there's also the header option from inside the hurl file right? Which one should I work on first? |
The integration test might be a good one so as --header is completely done. And after the header option in [Options] section. I think both will be easier than this one! |
Description
This PR implements the curl option
--header
. It is the first part of #2144. For now this is a cli-only option.What's happening in
hurl/src/http/client.rs
?In curl,
--header
is able to override internal headers added by curl. For example:Originally, at
packages/hurl/src/http/client.rs#L534
, the functionset_headers
pushes all headers into a list before applying them at the end. However,curl::easy::List
doesn't seem to provide an easy way to override its items.In this PR, I decide to store the headers in a
Vec<String>
instead. This way it's easier to iterate through them. Then theto_list
function is called at the end to convert the vector to acurl::easy::List
.Do let me know if you prefer handing this a different way 🙏