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

Handshake failing when connecting to .NET signalr server #142

Closed
roks0n opened this issue Nov 7, 2022 · 6 comments
Closed

Handshake failing when connecting to .NET signalr server #142

roks0n opened this issue Nov 7, 2022 · 6 comments

Comments

@roks0n
Copy link
Contributor

roks0n commented Nov 7, 2022

I'm trying to connect to a .NET server running signalr, but for some reason handshake is failing due to No Connection with that ID error.

I can successfully connect to that particular signalr server using signalr typescript client.

I've put some Println's in the code in order to see if connection id really isn't being passed along but from what I'm able to see it is.

Here are the Print outputs that I've added to httpconnection.go (signalr lib) and dial.go (websocket lib):

reqURL: https://server/path
negotiateURL url: https://server/path/negotiate
Negotiation response: {"connectionId":"XKoZcw-gWaEAz5WGMjigtA","availableTransports":[{"transport":"WebSockets","transferFormats":["Text","Binary"]},{"transport":"ServerSentEvents","transferFormats":["Text"]},{"transport":"LongPolling","transferFormats":["Text","Binary"]}]}
Req url after connection id: https://server/path?id=XKoZcw-gWaEAz5WGMjigtA
wsURL= wss://server/path?id=XKoZcw-gWaEAz5WGMjigtA
Before handshakeRequest: wss://server/path?id=XKoZcw-gWaEAz5WGMjigtA
secWebSocketKey: n/YW1icIRHIljCaKnveplw==
Handshake resp.body: No Connection with that ID
panic: failed to WebSocket dial: expected handshake response status code 101 but got 404

And this is how I instantiated the client:

type receiver struct {
	signalr.Receiver
}

func (r *receiver) Receive(msg string) {
	fmt.Println(msg)
	// The silly client urges the server to end his connection after 10 seconds
	r.Server().Send("abort")
}

func main() {
	address := "https://server/path"

	// Create a Connection (with timeout for the negotiation process)
	ctx := context.Background()
	creationCtx, _ := context.WithTimeout(ctx, 2*time.Second)
	conn, err := signalr.NewHTTPConnection(creationCtx, address)
	if err != nil {
		panic(err)
	}

	receiver := &receiver{}
	// Create the client and set a receiver for callbacks from the server
	client, err := signalr.NewClient(ctx, nil,
		signalr.WithConnection(conn),
		signalr.WithReceiver(receiver))
	if err != nil {
		fmt.Println(err)
		// panic(err)
	}
	// Start the client loop
	client.Start()
}

I didn't notice anything out of the ordinary with the constructed requests (investigated headers as well) hence I'm in the dark if I'm perhaps using the client in a wrong way?

@philippseith
Copy link
Owner

can you share the server code? I guess there might be some confusion between ConnectionToken and ConnectionId, as https://github.com/dotnet/aspnetcore/blob/main/src/SignalR/docs/specs/TransportProtocols.md does not state where the id query parameter comes from.

@roks0n
Copy link
Contributor Author

roks0n commented Nov 7, 2022

I believe I know what's causing the problem. The server uses loadbalancer and the client needs to set a "sticky" cookie in order for the loadbalancer to pass the handshake request to an expected backend server. Currently as far as I've been testing the handshake is done to a different backend server than the initial request that generated the connectionId.

So what I need to do is pass that sticky cookie along so when ws handshakes it does to an expected backend. I'm figuring out how to do that with existing signalr lib functionalities (I don't believe it should work withHeaders?) or if this is something that needs to be implemented in.

@philippseith
Copy link
Owner

philippseith commented Nov 8, 2022

It won't work with WithHeaders. I guess that you need to read the cookies from the negotiate response here

resp, err := httpConn.client.Do(req)
and add them to opts.HTTPHeader as Cookie headers before dialing here
ws, _, err := websocket.Dial(ctx, wsURL.String(), opts)

like

for _, cookie := range resp.Cookies() {
	opts.HTTPHeader.Add("Cookie", cookie.String())
}

ws, _, err := websocket.Dial(ctx, wsURL.String(), opts)

can you please try this, and if it works, submit a PR?

@roks0n
Copy link
Contributor Author

roks0n commented Nov 8, 2022

Appreciate your insight. Of course, I'll open a PR when I get it working.

@iProhor
Copy link

iProhor commented Oct 19, 2023

It is possible for another reason to cause this issue. When a server is stuck with a ConnectionToken while we use negotiateVersion=1 in the negotiation response, this can happen. If we negotiate without a version (as in lib), the negotiate version would be 0, and the post response for negotiation will not contain ConnectionToken. However, if we use version 1, we should use ConnectionToken in the next HTTP GET request after the negotiation.

@philippseith
Copy link
Owner

@iProhor will #185 solve this for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants