-
Notifications
You must be signed in to change notification settings - Fork 902
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
Remove pseudo headers when converting to Spring headers #4293
Conversation
Motivation: We store HTTP/2 pseudo headers to the map in `HttpHeaders` with other headers. Then, the entries of the map are copied to Spring `HttpHeaders` in Spring integration. However, it can be a problem when the copied Spring `HttpHeaders` is converted to Netty Headers later: https://github.com/spring-cloud/spring-cloud-gateway/blob/v3.1.3/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java#L125-L128 The validation is failed when the pseudo headers are set. Modifications: - Remove HTTP/2 pseudo headers when creating Spring `HttpHeaders` from Armeria `HttpHeaders`. - Convert `:authority` header to `HOST` header. Result: - You no longer see `IllegalArgumentException` indicating prohibited character of a header name in an environment that Spring integration module and Netty client are used togeter.(e.g. Spring Cloud Gateway)
|
||
final class ArmeriaHttpHeadersUtil { | ||
|
||
static HttpHeaders fromArmeriaHttpHeaders(com.linecorp.armeria.common.HttpHeaders armeriaHeaders) { |
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.
Can't we use ArmeriaHttpUtil.toNettyHttp1ClientHeaders()
?
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.
We can't because this converts to Spring HttpHeaders(org.springframework.http.HttpHeaders)
but the method converts to Netty HttpHeaders(io.netty.handler.codec.http)
. 😄
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.
I am concerned that some disallowed HTTP/2 headers unfiltered by this method could be added to springHeaders
.
How about adding a variant of ArmeriaHttpUtil.toNettyHttp1ClientHeaders()
like ArmeriaHttpUtil.toHttp1ClientHeaders()
?
void toHttp1ClientHeaders(
HttpHeaders inputHeaders, BiConsumer<CharSequence, String> outputHeadersWriter) {
...
}
var springHeaders = ...;
toHttp1ClientHeaders(armeriaHeaders, (key, value) -> springHeaders.add(key, value));
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.
Let me just run a quick benchmark to see if there's any degradation using BiConsumer
. Anyway, I guess it's time to gather the same logic:
https://github.com/line/armeria/blob/master/tomcat9/src/main/java/com/linecorp/armeria/server/tomcat/TomcatService.java#L527-L551
https://github.com/line/armeria/blob/master/jetty9/src/main/java/com/linecorp/armeria/server/jetty/JettyService.java#L378-L390
I am concerned that some disallowed HTTP/2 headers unfiltered by this method could be added to springHeaders.
Could you tell me which one you are concerned?
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.
Had a chat with the team and decided to create a method in ArmeriaHttpUtil
to dedup the logic. 😉
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.
Could you tell me which one you are concerned?
If an HTTP/2 request is downgraded to HTTP/1, we may need to remove disallowed headers from HTTP/2 headers as we did in toNettyHttp1ClientHeaders
.
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.
Got it. 😄
Because this will be also used to convert Armeria HTTP/1.1 headers, which has pseudo headers, to other framework headers, let's keep it as it is. 😉
@@ -15,6 +15,7 @@ | |||
*/ | |||
package com.linecorp.armeria.spring.web.reactive; | |||
|
|||
import static com.linecorp.armeria.spring.web.reactive.ArmeriaHttpHeadersUtil.fromArmeriaHttpHeaders; |
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.
.. and ArmeriaHttpUtil.toNettyHttp1ServerHeaders()
?
Codecov Report
@@ Coverage Diff @@
## master #4293 +/- ##
============================================
- Coverage 73.37% 73.36% -0.02%
- Complexity 16974 16985 +11
============================================
Files 1450 1450
Lines 64069 64107 +38
Branches 8045 8062 +17
============================================
+ Hits 47009 47030 +21
- Misses 12970 12986 +16
- Partials 4090 4091 +1
Continue to review full report at Codecov.
|
final AsciiString k = e.getKey(); | ||
final String v = e.getValue(); | ||
|
||
if (k.isEmpty()) { |
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.
Is it possible to create a header with an empty key?
I'm not strong here but an assert may show our logic clearer.
assert !k.isEmpty();
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.
I don't think so. Just copied it without much thinking 😆
https://github.com/line/armeria/blob/master/jetty9/src/main/java/com/linecorp/armeria/server/jetty/JettyService.java#L380
|
||
final class ArmeriaHttpHeadersUtil { | ||
|
||
static HttpHeaders fromArmeriaHttpHeaders(com.linecorp.armeria.common.HttpHeaders armeriaHeaders) { |
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.
I am concerned that some disallowed HTTP/2 headers unfiltered by this method could be added to springHeaders
.
How about adding a variant of ArmeriaHttpUtil.toNettyHttp1ClientHeaders()
like ArmeriaHttpUtil.toHttp1ClientHeaders()
?
void toHttp1ClientHeaders(
HttpHeaders inputHeaders, BiConsumer<CharSequence, String> outputHeadersWriter) {
...
}
var springHeaders = ...;
toHttp1ClientHeaders(armeriaHeaders, (key, value) -> springHeaders.add(key, value));
Functionally looks good to me 👍 I think it would be nice to dedup/group header conversion logic somehow, but looks good to me apart from this 🙇 |
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.
Thanks @minwoox 🙇 👍 🙇
* Copies header value pairs of the specified {@linkplain HttpHeaders Armeria headers} to the | ||
* {@link BiConsumer} excluding HTTP/2 pseudo headers that starts with ':'. This also converts | ||
* {@link HttpHeaderNames#AUTHORITY} header to {@link HttpHeaderNames#HOST} header if | ||
* the {@linkplain HttpHeaders Armeria headers} does not have one. |
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.
I think you meant the opposite
* the {@linkplain HttpHeaders Armeria headers} does not have one. | |
* the {@linkplain HttpHeaders Armeria headers} has one. |
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.
I actually meant it. 😉 Let me change the word one to {@link HttpHeaderNames#HOST}
because the original sentence is unclear. 😅
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.
Ah I see what you meant 😅
@@ -67,6 +69,12 @@ final class ArmeriaServerHttpRequest extends AbstractServerHttpRequest { | |||
.publishOn(Schedulers.fromExecutor(ctx.eventLoop())); | |||
} | |||
|
|||
private static HttpHeaders springHeaders(RequestHeaders headers) { | |||
final HttpHeaders springHeaders = new HttpHeaders(); | |||
toHttp1Headers(headers, (key, value) -> springHeaders.add(key.toString(), value)); |
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.
How about adding a new functional interface that takes three arguments and injecting the springHeaders
into the method so that the lambda does not capture the object outside of the local scope.
toHttp1Headers(headers, (key, value) -> springHeaders.add(key.toString(), value)); | |
toHttp1Headers(headers, springHeaders, (output, key, value) -> output.add(key.toString(), value)); |
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.
That's brilliant. 👍
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.
Thanks, @minwoox! 🙇♂️
...figure/src/main/java/com/linecorp/armeria/spring/web/reactive/ArmeriaClientHttpResponse.java
Outdated
Show resolved
Hide resolved
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.
Thanks, @minwoox !
Motivation:
We store HTTP/2 pseudo headers to the map in
HttpHeaders
with other headers.Then, the entries of the map are copied to Spring
HttpHeaders
in Spring integration.However, it can be a problem when the copied Spring
HttpHeaders
is converted to Netty Headers later:https://github.com/spring-cloud/spring-cloud-gateway/blob/v3.1.3/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java#L125-L128
The validation is failed when the pseudo headers are set.
Modifications:
HttpHeaders
from ArmeriaHttpHeaders
.:authority
header toHOST
header.ServerHttpRequest.getLocalAddress()
which is added since Spring 5.2.x.Result:
IllegalArgumentException
indicating the prohibited character of a header namein an environment where Spring integration module and Netty client are used together.(e.g. Spring Cloud Gateway)