-
Notifications
You must be signed in to change notification settings - Fork 495
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
base: master
Are you sure you want to change the base?
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 |
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 🙏