spring-cloud / spring-cloud/spring-cloud-gateway

Websocket handshake response missing added headers

Open
#2,436 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

waiting-for-triage
Dominant language
Java
Stars
4.9k
Forks
3.5k
Avg merge
20h 57m
Merged PRs (30d)
8

Description

Describe the bug
spring-boot-dependencies:2.5.2
spring-cloud-dependencies:2020.0.4 (spring-cloud-gateway-server:3.0.4)

We are using SCG to front a set of web applications implemented in Streamlit. Streamlit applications establish a websocket connection which is used to dynamically return data to the page. When the websocket connection is estabished, the streamlit server returns a Set-Cookie header that contains an XSRF token. This token is then used for operations such as file upload.

Screen Shot 2021-11-09 at 10 16 59 AM

After placing this app behind SCG, we noticed that we are no longer receiving the "additional" response headers. They are being lost as the response transits through the gateway.

I have put together a basic setup that replicates the issue using a simple Node.js websocket server, a client HTML page, and the SCG setup.

Steps to Reproduce
  1. Run the node server.mjs file:
// install the one dependency:
// npm install ws

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 5678 });

wss.on('headers', (headers) => {
    headers.push('Set-Cookie: _xsrf=1234599');
    headers.push('X-Other-Header: hello');
});
wss.on('connection', function connection(ws) {
    console.log('Connection')
    ws.send('hi from the server');
});
  1. Open the client.html in your browser. It should establish a connection to the websocket server and display hi from the server on the page.
<!DOCTYPE html>
<html lang="en">
<head>
    <title>WebSocket demo</title>
</head>
<body>
<script>
    port = "5678" // Go direct to server
    //port = "6600" // Go through gateway

    let ws = new WebSocket("ws://127.0.0.1:" + port + "/"), messages = document.createElement('ul');

    ws.onmessage = function (event) {
        let messages = document.getElementsByTagName('ul')[0],
            message = document.createElement('li'),
            content = document.createTextNode(event.data);
        message.appendChild(content);
        messages.appendChild(message);
    };
    document.body.appendChild(messages);
</script>
</body>
</html>
  1. Open up the network tab, and you should also observe that the browser received two response headers (Set-Cookie and X-Other-Header).
Screen Shot 2021-11-09 at 10 33 20 AM
  1. Now configure and start up a gateway to front this server:
# Spring Cloud Gateway Config
spring:
  cloud:
    gateway:
      routes:
        - id: websocket
          uri: http://localhost:5678
          predicates:
            - Host=**
server:
  port: 6600
logging:
  level:
    org:
      springframework:
        cloud:
          gateway: TRACE
  1. The client.html page needs to be updated to go through the gateway. This is done by changing the port number from 5678 to 6600.
<script>
    //port = "5678" // Go direct to server
    port = "6600" // Go through gateway
  1. Go back to the browser and refresh the client.html page. You should one again see hi from the server on the page. Now examine the network tab and notice our two added headers are absent from the server response.
Screen Shot 2021-11-09 at 10 38 39 AM

Setting a breakpoint in WebsocketRoutingFilter.java, I can see the missing response headers in the proxySession.handshakeinfo.headers map.

But how to get them into the response back to the browser... 🤔. I would appreciate any tips and/or hacks to get these headers through the gateway! Thanks.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with WebsocketRoutingFilter.java at the linked breakpoint and inspect proxySession.handshakeinfo.headers. Reproduce the behavior with server.mjs, client.html, and the Spring Cloud Gateway configuration, comparing direct and proxied websocket responses. Done means the proxied response exposes the added Set-Cookie and X-Other-Header headers in the browser network tab.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, node.js, spring-boot
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.