Axum problem trying to set multiple headers with same name

I have this code to build my axum response:

impl IntoResponse for ServerTrans {
    fn into_response(self) -> Response<BoxBody> {
        let mybody = boxed(Full::from(self.x.rp.output));
        let mut res = Response::builder().body(mybody).unwrap();

        *res.status_mut() = StatusCode::from_u16(self.x.rp.status_code).unwrap();

        for (name, value) in &self.x.rp.headers {
            res.headers_mut().insert(
                HeaderName::from_lowercase(name.as_bytes()).unwrap(),
                HeaderValue::from_str(value).unwrap(),
            );
        }
        res
    }
}

Unfortunately, I think when I have multiple headers with the same name, only the last header is actually added. I think Axum is assuming there cannot be two headers with the same name?

Can anyone tell me if this is the case, and if so, is there any way to get around this?
[ The header name in question is "set-cookie", I am vaguely aware there is some new feature relating to cookies, not sure if it is relevant ]

That can't be the case since HeaderMap is a multi-map.

1 Like

What is the self.x.rp.headers? If it's the HashMap or similar it's the place where deduplication happened.

It is simply

pub headers: Vec<(String, String)>

per GenResponse in rustdb::gentrans - Rust

I am not sure what is going on, but at any rate my browser (Chrome) is indicating only one cookie has been set. I haven't really started to debug it in earnest to see exactly what is happening. It may be "something stupid".

I am now wondering if I need to just use headers_mut() once ( move it out of the loop )?

( Apologies : I am feeling quite confused, and have a very weak understanding of what is gong on here! )

[ I tried this, and it doesn't help. I also put some tracing in to make sure there really are two calls, and there are. So I am baffled what is going wrong. ]

[ I also just tried setting three cookies, and only the last seems to be sent in the response ( at least according to Chrome developer tools ]

[ I also tried using a different header name, "warning", and if I set two headers, again, only the second is added to the response. But headers with different names are added. So I increasingly believe that the header map or whatever it is using to store the headers eliminates "duplicates" ]

Digging a little deeper, it is clear that HeaderMap is intended to be able to hold multiple values for a particular key:

I am beginning to wonder though if when the headers are written out, somehow only the last value added ( for a given key/name ) is written?

There is this:

pub fn get<K>(&self, key: K) -> Option<&T>
where
    K: AsHeaderName, 
[src][−]
Returns a reference to the value associated with the key.

If there are multiple values associated with the key, then the first one is returned. Use get_all to get all values associated with a given key. Returns None if there are no values associated with the key.

I added some tracing, my code now reads:

        let hdrs = res.headers_mut();
        for (name, value) in &self.x.rp.headers {
            hdrs.insert(
                HeaderName::from_lowercase(name.as_bytes()).unwrap(),
                HeaderValue::from_str(value).unwrap(),
            );
            println!("Set hdrs name={} value={} len()={}", name, value, hdrs.len());
        }

The tracing output is

Set hdrs name=set-cookie value=x=1;  len()=1
Set hdrs name=set-cookie value=y=2;  len()=1
Set hdrs name=set-cookie value=z=3;  len()=1
Set hdrs name=warning value=something 1 len()=2
Set hdrs name=warning value=something 2 len()=2

The documentation for len() suggests HeaderMap is not working correctly!

This number represents the total number of values stored in the map. This number can be greater than or equal to the number of keys stored given that a single key may have more than one associated value.

So I think there is a bug in HeaderMap !

Edit: Solution found. I need to use Append not Insert!!

Nope it is correct, you need to use the append method, which appends values for the same key. But you're using insert, which replaces.

2 Likes

Thanks yes, I figured it out just as you posted!