dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.57k stars 10.05k forks source link

Basic implementation of signalR when hosted on azure failing to connect with negotiation #48800

Closed Vikas252 closed 1 year ago

Vikas252 commented 1 year ago

Is there an existing issue for this?

Describe the bug

I have an instance of signalR hosted on azure with the free tier, for development purpose with the help of @microsoft/signalr package i have created a basic implementation to connect to the hub with the instance in typescript that is in the azure but it fails saying the following error when the skipNegotiation is not given The error:

  _nextKeepAlive: 0,
  _freezeEventListener: [Function (anonymous)],
  serverTimeoutInMilliseconds: 30000,
  keepAliveIntervalInMilliseconds: 15000,
  _logger: ConsoleLogger {
    _minLevel: 0,
    out: Object [console] {
      log: [Function: log],
      warn: [Function: warn],
      dir: [Function: dir],
      time: [Function: time],
      timeEnd: [Function: timeEnd],
      timeLog: [Function: timeLog],
      trace: [Function: trace],
      assert: [Function: assert],
      clear: [Function: clear],
      count: [Function: count],
      countReset: [Function: countReset],
      group: [Function: group],
      groupEnd: [Function: groupEnd],
      table: [Function: table],
      debug: [Function: debug],
      info: [Function: info],
      dirxml: [Function: dirxml],
      error: [Function: error],
      groupCollapsed: [Function: groupCollapsed],
      Console: [Function: Console],
      profile: [Function: profile],
      profileEnd: [Function: profileEnd],
      timeStamp: [Function: timeStamp],
      context: [Function: context]
    }
  },
  _protocol: JsonHubProtocol { name: 'json', version: 1, transferFormat: 1 },
  connection: HttpConnection {
    _stopPromiseResolver: [Function (anonymous)],
    features: {},
    _negotiateVersion: 1,
    _logger: ConsoleLogger { _minLevel: 0, out: [Object [console]] },
    baseUrl: 'https://<baseurl>.service.signalr.net',
    _httpClient: AccessTokenHttpClient {
      _innerClient: [DefaultHttpClient],
      _accessTokenFactory: undefined
    },
    _connectionState: 'Disconnected',
    _connectionStarted: false,
    _options: {
      transport: 1,
      logger: [ConsoleLogger],
      logMessageContent: false,
      withCredentials: true,
      timeout: 100000,
      WebSocket: [Function],
      EventSource: [Function]
    },
    onreceive: [Function (anonymous)],
    onclose: [Function (anonymous)]
  },
  _reconnectPolicy: DefaultReconnectPolicy {
    _retryDelays: [ 0, 2000, 10000, 30000, null ]
  },
  _handshakeProtocol: HandshakeProtocol {},
  _callbacks: {},
  _methods: {},
  _closedCallbacks: [],
  _reconnectingCallbacks: [],
  _reconnectedCallbacks: [],
  _invocationId: 0,
  _receivedHandshakeResponse: false,
  _connectionState: 'Disconnected',
  _connectionStarted: false,
  _cachedPingMessage: '{"type":6}\x1E'
}
[2023-06-14T11:00:39.858Z] Debug: Starting HubConnection.
[2023-06-14T11:00:39.859Z] Debug: Starting connection with transfer format 'Text'.
[2023-06-14T11:00:39.860Z] Debug: Sending negotiation request: https://<baseurl>.service.signalr.net/negotiate?negotiateVersion=1.
[2023-06-14T11:00:41.005Z] Error: Failed to complete negotiation with the server: Error: Not Found: Status code '404' Either this is not a SignalR endpoint or there is a proxy blocking the connection.
[2023-06-14T11:00:41.006Z] Error: Failed to start the connection: Error: Failed to complete negotiation with the server: Error: Not Found: Status code '404' Either this is not a SignalR endpoint or there is a proxy blocking the connection.
[2023-06-14T11:00:41.007Z] Debug: HubConnection failed to start successfully because of error 'Error: Failed to complete negotiation with the server: Error: Not Found: Status code '404' Either this is not a SignalR endpoint or there is a proxy blocking the connection.'.
D:\Projects\az-signalr\node_modules\@microsoft\signalr\src\HttpConnection.ts:346
            return Promise.reject(new FailedToNegotiateWithServerError(errorMessage));
                                  ^
FailedToNegotiateWithServerError: Failed to complete negotiation with the server: Error: Not Found: Status code '404' Either this is not a SignalR endpoint or there is a proxy blocking the connection.
    at HttpConnection._getNegotiationResponse (D:\Projects\az-signalr\node_modules\@microsoft\signalr\src\HttpConnection.ts:346:35)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async HttpConnection._startInternal (D:\Projects\az-signalr\node_modules\@microsoft\signalr\src\HttpConnection.ts:247:41)
    at async HttpConnection.start (D:\Projects\az-signalr\node_modules\@microsoft\signalr\src\HttpConnection.ts:137:9)
    at async HubConnection._startInternal (D:\Projects\az-signalr\node_modules\@microsoft\signalr\src\HubConnection.ts:207:9)
    at async HubConnection._startWithStateTransitions (D:\Projects\az-signalr\node_modules\@microsoft\signalr\src\HubConnection.ts:181:13)
    at async start (D:\Projects\az-signalr\index.ts:18:3) {
  errorType: 'FailedToNegotiateWithServerError' 

If i add the flag of skipNegotation the error:

  Debug: Starting HubConnection.
[2023-06-14T11:17:37.677Z] Debug: Starting connection with transfer format 'Text'.
[2023-06-14T11:17:37.678Z] Trace: (WebSockets transport) Connecting.
[2023-06-14T11:17:38.739Z] Information: (WebSockets transport) There was an error with the transport.
[2023-06-14T11:17:38.746Z] Error: Failed to start the connection: Error: WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.
[2023-06-14T11:17:38.750Z] Debug: HubConnection failed to start successfully because of error 'Error: WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.'.  
D:\Projects\az-signalr\node_modules\@microsoft\signalr\src\WebSocketTransport.ts:136
                    reject(new Error(error));
                           ^
Error: WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled. 
    at WebSocket.webSocket.onclose (D:\Projects\az-signalr\node_modules\@microsoft\signalr\src\WebSocketTransport.ts:136:28)
    at WebSocket.onClose (D:\Projects\az-signalr\node_modules\ws\lib\event-target.js:136:16)      
    at WebSocket.emit (node:events:527:28)
    at WebSocket.emit (node:domain:475:12)
    at WebSocket.emitClose (D:\Projects\az-signalr\node_modules\ws\lib\websocket.js:236:12)       
    at Object.onceWrapper (node:events:641:28)
    at ClientRequest.emit (node:events:527:28)
    at ClientRequest.emit (node:domain:475:12)
    at emitAbortNT (node:_http_client:389:7)
    at processTicksAndRejections (node:internal/process/task_queues:82:21)

Expected Behavior

Should connect to the endpoint of signalR provided in the serverless websocket mode.

Steps To Reproduce

basic code

import { HubConnectionBuilder, HttpTransportType, LogLevel } from '@microsoft/signalr'

const start = async () => {
  let connection = new HubConnectionBuilder()
    .withUrl("https://<baseURL>", {
      skipNegotiation: true,
      transport: HttpTransportType.WebSockets
    }).configureLogging(LogLevel.Trace).withAutomaticReconnect()
    .build();

  console.log(connection);

  connection.on("send", data => {
    console.log(data);
  });

  await connection.start()
    .then(() => connection.invoke("send", "Hello"));
}

start()

package.json:

{
  "name": "az-signalr",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@microsoft/signalr": "^7.0.7"
  }
}

Exceptions (if any)

I think i might be missing something very basic. Thank you in advance.

.NET Version

6.0.408

Anything else?

No response

BrennanConroy commented 1 year ago

https://.service.signalr.net/negotiate?negotiateVersion=1

It looks like you're trying to connect directly to the Azure SignalR Service which generally doesn't work. In serverless mode you usually setup an Azure Functions endpoint to connect your client to: https://learn.microsoft.com/azure/azure-signalr/signalr-quickstart-azure-functions-csharp?tabs=in-process

Vikas252 commented 1 year ago

@BrennanConroy thank you for the quick reply i followed the steps mentioned on the above tutorial the connection string is giving and error now

The error:

Executed 'Functions.negotiate' (Failed, Id=<invocationID_Function_App>, Duration=41ms)
[2023-06-15T11:51:14.860Z] System.Private.CoreLib: Exception while executing function: Functions.negotiate. Microsoft.Azure.SignalR.Common: Connection string missing required properties endpoint. (Parameter 'connectionString').

fucntion.json:

{
  "disabled": false,
  "bindings": [
    {
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "route": "negotiate",
      "authLevel": "function",
      "methods": ["post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "signalRConnectionInfo",
      "name": "connectionInfo",
      "hubName": "serverless",
      "direction": "in",
      "connectionStringSetting": "AzureSignalRConnectionString",
    }
  ],
  "entryPoint": "negotiate",
  "scriptFile": "../dist/server.js"
}

The environment is serverless framework with the yml file

negotiate:
    handler: dist/server.negotiate
    events:
      - http: true
        route: negotiate{any}
        methods: ANY
    outputs:
      connectionInfo:
         type: signalRConnectionInfo
         name: connectionInfo
         hubName: serverless
         connectionStringSetting: AzureSignalRConnectionString
         direction: in

local.settings.json

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureSignalRConnectionString": "Endpoint=https://<base_url>;AccessKey=<access_key_in_azure>=;Version=1.0;"
  },
  "Host": {
    "CORS": "http://localhost:4200",
    "CORSCredentials": true
  }
}
BrennanConroy commented 1 year ago

Please file an issue at https://github.com/Azure/azure-signalr/issues.