0

I created a WebSocket server using Java Spring, which worked correctly when connecting via ws://localhost:8080/socket and ws://localhost:30019/socket. However, when I attempt to upload the socket to a host and connect to it via ws://website.com/socket, it cannot connect. The difference in ports (connecting to port 80 on the host despite it running on port 30019) is due to the host using a proxy server and redirecting port 80 of the proxy to port 30019 of the hosting server. Connecting to the static portion of the page is the same between localhost:30019 and website.com, where the webpage displays accurately in both cases.

When I try to connect, I receive a WebSocketHandshakeException due to a 400 response from the server:

Exception in thread "main" java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.net.http.WebSocketHandshakeException
    at com.socketsimpl.Main.main(Main.java:34)
Caused by: java.util.concurrent.ExecutionException: java.net.http.WebSocketHandshakeException
    at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
    at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2073)
    at  com.socketsimpl.Main.main(Main.java:27)
Caused by: java.net.http.WebSocketHandshakeException
    at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.resultFrom(OpeningHandshake.java:226)
    at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
    at java.base/java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:614)
    at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:844)
    at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
Caused by: jdk.internal.net.http.websocket.CheckFailedException: Unexpected HTTP response status code 400
    at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.checkFailed(OpeningHandshake.java:343)
    at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.handleResponse(OpeningHandshake.java:252)
    at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.resultFrom(OpeningHandshake.java:222)
    ... 10 more

When the server returns the 400 code, the error in the server console is "Handshake failed due to invalid Upgrade header: null". When switching the log level to debug by adding logging.level.root=DEBUG to application.properties, it can be seen that the Upgrade header is not in the headers list.

To add the header, I changed my connection code from:

HttpClient client = HttpClient.newHttpClient();
socket = client.newWebSocketBuilder()
    .buildAsync(
        URI.create("ws://website.com/socket"),
        new Listener()
    )
    .get();

to

HttpClient client = HttpClient.newHttpClient();
    .header("Connection", "Upgrade")
    .header("Upgrade", "WebSocket")
    .buildAsync(
        URI.create("ws://website.com/socket"),
        new Listener()
    )
    .get();

However, as Connection and Upgrade are considered restricted headers by Java, this causes an IllegalArgumentException with description restricted header name: "Connection". As widely suggested across StackOverflow and other websites, I added the allowRestrictedHeaders property, making my startup command:

java -"Djdk.httpclient.allowRestrictedHeaders=Upgrade,Connection" -jar .\untitled-1.0-SNAPSHOT-all.jar

However, the headers logged in the server console remain the exact same, the error returns to WebSocketHandshakeException, and the error in the server console also remains the same; it is as if I didn't add the headers. When testing with other headers, they appear in the headers logged in the server console, meaning it is a valid way to add headers.

How can I fix the handshake, allowing the client to successfully connect to the server?

KingsDev
  • 654
  • 5
  • 21
  • Can you use wss instead? The upgrade headers are probably stripped by the proxy. – daniel Oct 04 '22 at 11:35
  • Changing to `wss` has the same result – KingsDev Oct 04 '22 at 11:51
  • Note: In the server's console's listed headers, `Connection` is always listed as `close`, whether I attempt to set it or not. `Upgrade` is never set. – KingsDev Oct 04 '22 at 11:53
  • 1
    I'd suggest enabling the client side logs by specifying `-Djdk.httpclient.HttpClient.log=errors,headers,requests` on the java command line on the client side. This will help you see what headers & requests are sent by the client to establish the websocket connection. https://docs.oracle.com/en/java/javase/17/core/java-networking.html – daniel Oct 04 '22 at 12:50
  • 1
    Another note: no need to try to specify Upgrade or Connection from the calling code. There is a reason why they are restricted: the HttpClient will set them appropriately. – daniel Oct 04 '22 at 13:13
  • @daniel This shows that the `Connection` and `Upgrade` headers are indeed added, being set to `Upgrade` and `websocket` respectively. However, on the server, `Connection` is still `close` and there is still no `Upgrade` header – KingsDev Oct 05 '22 at 05:52
  • 1
    If it doesn't work: blame the proxy ;-) I believe the issue is with the proxy you are using - or the proxy that sits in front of the server - if any. Fix the proxy. – daniel Oct 05 '22 at 11:06
  • As you said, it was an issue with the proxy. It wasn't forwarding headers, so I contacted the host and they fixed it. Thanks! – KingsDev Oct 06 '22 at 10:35

0 Answers0