Skip to content
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

Fix HTTP request builder setHeader case sensitivity #1165

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

findepi
Copy link
Contributor

@findepi findepi commented May 17, 2024

No description provided.

@findepi findepi requested review from electrum and wendigo May 17, 2024 09:28
@@ -232,7 +232,7 @@ public Builder setMethod(String method)

public Builder setHeader(String name, String value)
{
this.headers.removeAll(name);
this.headers.keys().removeIf(key -> key.equalsIgnoreCase(name));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch!
Does addHeader and addHeaders behave correctly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add are supposed to accumulate

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it accumulates so not a problem, but there is still a difference and results in names not normalized. But maybe deduplication and normalization is a responsibility of a different class.

So it depends if we want:

ListMultimap<String, String> headers = ArrayListMultimap.create();
        headers.put("Key-One", "v1");
        headers.put("Key-One", "v1");
        headers.put("Key-One", "v2");

        headers.put("key-one", "v1");
        
        System.out.println(headers);

To result in:

{Key-One=[v1, v1, v2], key-one=[v1]}

or in:

{key-one=[v1, v1, v1, v2]}

Copy link
Collaborator

@wendigo wendigo May 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom multimap does that:

ListMultimap<String, String> headers = Multimaps.newListMultimap(new TreeMap<>(CASE_INSENSITIVE_ORDER), ArrayList::new);
headers.put("Key-One", "v1");
headers.put("Key-One", "v1");
headers.put("Key-One", "v2");
headers.put("key-one", "v1");
System.out.println(headers);
{Key-One=[v1, v1, v2, v1]}

@findepi findepi force-pushed the findepi/fix-http-request-builder-setheader-case-sensitivity-be0601 branch from 2cb4603 to 445e70e Compare May 17, 2024 11:51
@findepi
Copy link
Contributor Author

findepi commented May 17, 2024

added lowercasing and a test

@brybacki
Copy link

Error:    TestBasicAuthRequestFilter.testBasicAuthentication:39 expected [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==] but found [null]
Error:    TestTraceTokenRequestFilter.testBasic:46 lists don't have the same size expected [1] but found [0]

@findepi findepi force-pushed the findepi/fix-http-request-builder-setheader-case-sensitivity-be0601 branch 3 times, most recently from fce655d to f4998a8 Compare May 17, 2024 13:49
@findepi findepi force-pushed the findepi/fix-http-request-builder-setheader-case-sensitivity-be0601 branch from f4998a8 to d0bfbd7 Compare May 17, 2024 13:51
@findepi
Copy link
Contributor Author

findepi commented May 17, 2024

replaced lowercasing with case-preserving, per @losipiuk suggestion

@findepi findepi force-pushed the findepi/fix-http-request-builder-setheader-case-sensitivity-be0601 branch from d0bfbd7 to 257a049 Compare May 17, 2024 13:58
@findepi
Copy link
Contributor Author

findepi commented May 17, 2024

the code is not simplest possible, because it preserves the case of headers. the simplest would be to just lowercase.
preserving header case is not important from http spec perspective, but we've seen cases where not preservice headers breaks some buggy applications

cc @losipiuk @electrum @wendigo

@wendigo
Copy link
Collaborator

wendigo commented May 17, 2024

Alternative #1166

@findepi findepi force-pushed the findepi/fix-http-request-builder-setheader-case-sensitivity-be0601 branch from 257a049 to 08e57c4 Compare May 18, 2024 06:16
@findepi findepi force-pushed the findepi/fix-http-request-builder-setheader-case-sensitivity-be0601 branch from 08e57c4 to fe4b7fe Compare May 18, 2024 06:19
@@ -43,7 +44,7 @@ public void testBasic()
assertEquals(filtered.getUri(), original.getUri());
assertEquals(original.getHeaders().size(), 0);
assertEquals(filtered.getHeaders().size(), 1);
assertEquals(filtered.getHeaders().get(TRACETOKEN_HEADER), ImmutableList.of("testBasic"));
assertEquals(filtered.getHeaders().get(TRACETOKEN_HEADER.toLowerCase(ENGLISH)), ImmutableList.of("testBasic"));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably make Request.getHeaders() return a case-insensitive map

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

happy to apply once we know for sure (without "probably"). there was quite a bit back and forth already on this PR. It doesn't cost much because it is small, but it's not cool nonetheless

@electrum @martint can you explicitly decide on the behavior you want to see implemented? happy to apply whatever is needed

@findepi
Copy link
Contributor Author

findepi commented May 18, 2024

added lowercasing again.

ptal

@findepi
Copy link
Contributor Author

findepi commented May 19, 2024

@electrum @wendigo @losipiuk can you please review?

@losipiuk
Copy link
Contributor

Non preserving hurts my aesthetic soul but I hope I can live with that.

@martint
Copy link
Member

martint commented May 19, 2024

We shouldn’t lower case header names. What problem are we trying to solve?

@findepi
Copy link
Contributor Author

findepi commented May 20, 2024

What problem are we trying to solve?

setHeader was incorrectly replacing headers only if they were given in exact same case. See tests.

Of course the simplest implementation would be to just change setHeader:

--- http-client/src/main/java/io/airlift/http/client/Request.java
+++ http-client/src/main/java/io/airlift/http/client/Request.java
@@ -232,7 +232,7 @@ public final class Request
 
         public Builder setHeader(String name, String value)
         {
-            this.headers.removeAll(name);
+            this.headers.keySet().removeIf(key -> key.equalsIgnoreCase(name));
             this.headers.put(name, value);
             return this;
         }

in fact this was the first version of this PR.
However i realized that keeping io.airlift.http.client.Request.Builder#headers as ListMultimap would no longer make sense, if we always search it linearly.

then the PR had case-preserving version (see "before" in this diff https://github.com/airlift/airlift/compare/257a0490937b4f1e23ddbd44e0bf47612c81704a..08e57c46f089d59d97cf29481e2b2d69edcce336)

Then @electrum suggested case-preserving isn't worth it.

BTW once headers gets thru airlift request builder, their case is later changed in jetty client. It looks like the case normalization in the jetty client happens for all headers registered with org.eclipse.jetty.http.HttpHeader enum.

We shouldn’t lower case header names.

Why?

@findepi
Copy link
Contributor Author

findepi commented May 20, 2024

@martint @electrum hope you can converge on what this should do (lowercase vs case-preserving vs something else).
Happy to implement whatever we want to do. If this helps, i can create separate PRs for every option considered, so that we don't need to use this PR push history.

@electrum
Copy link
Member

Given the HTTP/2 is always lowercase, it's not clear that there is value in preserving header case. This seems like a bad assumption in applications that can appear to work, but will break depending on the protocol version used at runtime. Lowercasing headers makes code simpler and avoids the surprise.

@findepi
Copy link
Contributor Author

findepi commented May 22, 2024

Hopefully we have agreement that the current behavior of setHeader is wrong. It caused an incident internally at Starburst.

@electrum @martint how would you like the code to behave?

  • preserve case vs lower-case in the builder?
  • getHeader to stay as is OR match case-insensitively in request getHeader()
  • case-sensitive OR case-insensitive map keys in request getHeaders(), OR something else (like removing this method altogether)
    • case-insensitive has the problem similar to identity-based collections -- taking defensive immutable copy changes semantics; so i'd actually prefer to have dedicated wrapper class to represent header name (either lowercasing or preserving case but comparing case insensitively; depending on the answer to the first question)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants