Skip to content
This repository has been archived by the owner on Sep 28, 2021. It is now read-only.

Look up headers in a case insensitive way #199

Merged
merged 3 commits into from
Apr 25, 2017

Conversation

martina-if
Copy link
Contributor

@martina-if martina-if commented Apr 18, 2017

In this first step apollo will look up headers in a case insensitive way but it will still preserve the original letter case of header fields when propagated. This makes services compatible with services that send lowercase headers (like those who propagate headers from an HTTP 2 connection).

I recommend looking at the commits separately.

@pettermahlen @rouzwawi

@codecov-io
Copy link

codecov-io commented Apr 18, 2017

Codecov Report

Merging #199 into 1.x will decrease coverage by 0.05%.
The diff coverage is 70.68%.

Impacted file tree graph

@@             Coverage Diff              @@
##                1.x     #199      +/-   ##
============================================
- Coverage     69.57%   69.51%   -0.06%     
- Complexity        0      651     +651     
============================================
  Files           138      139       +1     
  Lines          2879     2916      +37     
  Branches        226      172      -54     
============================================
+ Hits           2003     2027      +24     
- Misses          833      846      +13     
  Partials         43       43
Impacted Files Coverage Δ Complexity Δ
...api/src/main/java/com/spotify/apollo/Response.java 20% <0%> (-30%) 2 <0> (+2)
...-api/src/main/java/com/spotify/apollo/Request.java 35.71% <0%> (-30.96%) 3 <0> (+3)
...ava/com/spotify/apollo/http/client/HttpClient.java 93.1% <100%> (ø) 9 <1> (+9) ⬆️
...-api/src/main/java/com/spotify/apollo/Headers.java 100% <100%> (ø) 11 <11> (?)
...apollo/http/server/AsyncContextOngoingRequest.java 85.29% <50%> (-2.59%) 9 <0> (+9)
...com/spotify/apollo/test/unit/ResponseMatchers.java 41.46% <60%> (ø) 6 <0> (+6) ⬆️
...src/main/java/com/spotify/apollo/ResponseImpl.java 81.81% <83.33%> (+1.81%) 11 <3> (+11) ⬆️
...src/main/java/com/spotify/apollo/RequestValue.java 94.44% <90.9%> (+0.69%) 14 <9> (+14) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4d9e653...2975a92. Read the comment docs.

The headers are still sorted by insertion order and preserve their
original letter casing when read.
@martina-if
Copy link
Contributor Author

Maybe it's not worth having Headers being an AutoValue type, what do you think?

Copy link
Member

@pettermahlen pettermahlen left a comment

Choose a reason for hiding this comment

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

Some questions/comments. I think it's best to leave Headers as an AutoValue, it saves some implementation+conceptual overhead wrt especially equality and such.

}

public Optional<String> get(String name) {
Preconditions.checkArgument(name != null, "Header names cannot be null");
Copy link
Member

Choose a reason for hiding this comment

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

I think it's better to use Objects.requireNonNull in this case - generally null arguments lead to NPEs, and it's better to use the JDK classes than Guava where feasible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Fixed

*/
default Optional<String> header(String name) {
return Optional.ofNullable(headers().get(name));
Preconditions.checkArgument(name != null, "Header names cannot be null");
Copy link
Member

Choose a reason for hiding this comment

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

Would it be better to permit nulls here, and reverse the comparison below so that it would just fail to find something, rather than throw an exception for a null? I'm not sure. Either way, if nulls shouldn't be permitted, then I think we should use Objects.requireNonNull for the argument check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Talked offline about this. I will leave the check with Objects.requireNonNull

Map<String, String> headers();

/**
* The response headers
*/
Iterable<Map.Entry<String, String>> headerEntries();
Copy link
Member

Choose a reason for hiding this comment

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

Why Iterable here? Since we're spending some effort on retaining order of headers, would it not make more sense to return a List?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was trying to be conservative and restrictive, conveying the message that this is only for iterating over all the headers. I want to prevent users from looking up keys from whatever object this method returns. Also, I didn't want to lock a specific implementation (we could change the internal implementation for a Map, etc). But I see now that I chose the wrong interface and it should have been Iterator, but maybe it's too annoying, specially since it does not have size(). What do you think would be better: Iterator or List?

Copy link
Member

Choose a reason for hiding this comment

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

Iterator is generally wrong, since they are one-use only. Iterable is better in that case.

I would still prefer List; it's still quite generic, and I feel like we have committed to retaining order. We could even specify that in the javadocs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough. Changed to List.

@@ -119,4 +144,5 @@
static <T> Response<T> of(StatusType statusCode, T payload) {
return ResponseImpl.create(statusCode, payload);
}

Copy link
Member

Choose a reason for hiding this comment

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

Probably remove this empty line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

return Optional.empty();
}

public Map<String, String> asMap() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is used a lot in apollo. Should I memoized it already in the constructor?

Copy link
Member

Choose a reason for hiding this comment

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

Not sure; doing so would increase the memory footprint - you'd need some measurements to be sure, I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll leave the optimizations for later then.


public abstract ImmutableList<Map.Entry<String, String>> entries();

private static void insertIfAbsent(List<Map.Entry<String, String>> headerList,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm confused here. Method name suggest that the value would not be overwritten (as it will be entered only if it's absent), but that's what the method does?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. I fixed the name to insertOrReplace(). Is this better?

private static final Map<String, String> TEST_BIG_MAP = createBigMap();

private static Map<String, String> createDuplicateKeysMap() {
LinkedHashMap<String, String> testMap = new LinkedHashMap<>(3);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe declare test map asMap instead of LinkedHashMap ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

}

@Test
public void shouldReturnHeaderCaseInsensitive() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to test it again as we already do in HeadersTest.java?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think the test should be implementation agnostic. At least the most important parts.

@martina-if martina-if merged commit ba73aa9 into spotify:1.x Apr 25, 2017
@pettermahlen pettermahlen mentioned this pull request Oct 27, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants