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

feat: Fix maybeWebSocketUpgrade to return true when Connection/Upgrade header has multiple… #5958

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

blue-hope
Copy link

@blue-hope blue-hope commented Oct 24, 2024

Motivation:

  1. I requested websocket request for my armeria server with those headers:
  • Connection: keep-alive, websocket
  • Upgrade: websocket
  1. It returns:
The upgrade header must contain:
  Upgrade: websocket
  Connection: Upgrade
  1. I trouble-shooted and found:
    Fix that WebSocketService rejects headers that have multiple values #5230

  2. So I upgraded my armeria server's dependency from 1.23.1 to 1.26.0

  3. But it still occurs even though I upgraded deps properly.

  4. So I debugged, and found that there are one more conditional statement in toArmeria in ArmeriaHttpUtil.java

private static boolean maybeWebSocketUpgrade(AsciiString header, CharSequence value) {
    if (HttpHeaderNames.CONNECTION.contentEqualsIgnoreCase(header) &&
        HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(value)) {
        return true;
    }
    return HttpHeaderNames.UPGRADE.contentEqualsIgnoreCase(header) &&
           HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(value);
}

=> I wrote this PR because It is confusing that the multivalue header accepted by WebSocketUtil is not accepted by the websocket conditional statement of ArmeriaHttpUtil. If the two utils perform different roles in different scopes, and the strictness of websocket checks should be different, this PR can be closed. (But the question still remains as to whether multi-value headers should be supported.)

Modifications:

  • Like above PR's WebSocketUtil.java, change the conditional statement from equals to contains

Result:

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@blue-hope
Copy link
Author

As for the test, I tried writing it, but it seems to be written only for abstract headers like {"a": "b"}, so I was not sure where to start, so I stopped. However, if you give me a guide, I think I can write it.

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Hi! Thanks for the PR. 😉
Would you mind adding a unit test, please?

@blue-hope
Copy link
Author

@minwoox I wrote one, please check it 😄

Copy link
Contributor

github-actions bot commented Oct 24, 2024

🔍 Build Scan® (commit: b544f41)

Job name Status Build Scan®

@minwoox
Copy link
Member

minwoox commented Oct 24, 2024

I wrote one, please check it

It seems like the test case doesn't call maybeWebSocketUpgrade internally which means we can't verify if the fix is correct or not. Could you check it, please?

@blue-hope
Copy link
Author

Oops, I'll check it

@blue-hope
Copy link
Author

blue-hope commented Oct 24, 2024

@minwoox Sorry for the confusion. I wrote toArmeria test again and checked that it calls maybeWebSocketUpgrade by debugger.

@blue-hope
Copy link
Author

blue-hope commented Oct 24, 2024

Github checks occur some errors but I don't know how I can deal with them. (I think it may be irrelevant to my PR, I ran ./gradlew :core:lint in local to lint my modified code and it finished successfully).

@minwoox
Copy link
Member

minwoox commented Oct 28, 2024

Github checks occur some errors but I don't know how I can deal with them.

Let me see what happened. In the meantime, would you add e2e tests to see if the fix works on HTTP/1.1 and HTTP/2?
You can probably add an additional header to these test cases:
https://github.com/line/armeria/blob/main/core/src/test/java/com/linecorp/armeria/server/websocket/WebSocketServiceHandshakeTest.java

@minwoox minwoox added the defect label Oct 28, 2024
@minwoox minwoox added this to the 1.31.0 milestone Oct 28, 2024
@blue-hope
Copy link
Author

@minwoox Can you please check my last commit? I added e2e test.
By the way, I found that similar test exists on WebSocketServiceCorsTest, which includes CONNECTION / UPGRADE header.
Do we need to add additional header on there, too?

@minwoox
Copy link
Member

minwoox commented Oct 29, 2024

Do we need to add additional header on there, too?

I don't think so. 😉

Comment on lines 195 to 200
.add(HttpHeaderNames.PROTOCOL, HttpHeaderValues.WEBSOCKET.toString());
.add(HttpHeaderNames.PROTOCOL, HttpHeaderValues.WEBSOCKET.toString())
// It works even if the header contains multiple values
.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE.toString())
.add(HttpHeaderNames.UPGRADE, "additional_value");
Copy link
Member

Choose a reason for hiding this comment

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

Let's revert this change. I realized that HTTP/2 uses the different method so we probably don't need to check for HTTP/2.

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Thanks, @blue-hope! 👍 👍 👍

@@ -102,7 +102,9 @@ void http1Handshake(SessionProtocol sessionProtocol, boolean useThreadRescheduli
.build();
final RequestHeadersBuilder headersBuilder =
RequestHeaders.builder(HttpMethod.GET, "/chat")
.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE.toString());
// It works even if the header contains multiple values
Copy link
Contributor

Choose a reason for hiding this comment

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

Question) When I checked, it seems like this test passes with the main branch without the changes in this PR.

I'm curious, were you able to verify the same bug with the HEAD of the main branch? It seems a little odd since ArmeriaHttpUtil#maybeWebSocketUpgrade is only called for trailers in the server-side which doesn't seem to make sense to me.

Copy link
Author

@blue-hope blue-hope Nov 7, 2024

Choose a reason for hiding this comment

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

Thanks, @jrhee17. In toArmeria released in version 1.28.0, I found that the purgeHttp1OnlyHeaders process was dropping useless additional headers, and that is what I intended to. I'll do it again on my server and figure out what was the issue.

return true;
}
return HttpHeaderNames.UPGRADE.contentEqualsIgnoreCase(header) &&
HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(value);
AsciiString.containsIgnoreCase(value, HttpHeaderValues.WEBSOCKET);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think either websocketxxx or xxxwebsocket is a valid header value. How about splitting the value with a comma , before checking the equality?

Copy link
Member

Choose a reason for hiding this comment

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

Can we also add a test case that makes sure it doesn't pick up headers like connection: not-upgrade-lol and upgrade: madam-websocket?

@jrhee17
Copy link
Contributor

jrhee17 commented Nov 7, 2024

gentle ping @blue-hope

@jrhee17 jrhee17 modified the milestones: 1.31.0, 1.32.0 Nov 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

My websocket request returns 400 with header Connection: keep-alive, websocket
6 participants