Search code examples
javascriptc#signalrautodesk-forgengrok

SignalR unable to connect from client to server with any available transport


I'm trying to connect to a websocket established with SignalR on my server (.NET).

My client (JavaScript) starts the negotiation, gets a response with the connectionId, connectionToken, etc., but afterwards is not able to connect with any of the available transport methods.

The last debug-trace I get is this:

[2022-11-17T10:21:02.093Z] Debug: HubConnection failed to start successfully because of error 'Error: Unable to connect to the server with any of the available transports. WebSockets failed: Error: There was an error with the transport. ServerSentEvents failed: Error: Error occurred LongPolling failed: Error'.

My server code:

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace Sample
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options => options.EnableEndpointRouting = false).SetCompatibilityVersion(CompatibilityVersion.Version_3_0).AddNewtonsoftJson();
            services.AddSignalR().AddNewtonsoftJsonProtocol(opt => {
                opt.PayloadSerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            });
            services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
            {
                builder.WithOrigins("http://localhost:8080", "http://127.0.0.1:8080")
                       .AllowAnyMethod()
                       .AllowAnyHeader()
                       .AllowCredentials();
            }));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseFileServer();
            app.UseDefaultFiles();
            app.UseStaticFiles();
            app.UseCors("MyPolicy");
            app.UseMvc();
            app.UseRouting();
            app.UseEndpoints(routes =>
            {
                routes.MapHub<Controllers.DesignAutomationHub>("/api/signalr/designautomation");
            });

        }
    }
}

Controller:

using Autodesk.Forge;
using Autodesk.Forge.DesignAutomation;
using Autodesk.Forge.DesignAutomation.Model;
using Autodesk.Forge.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace Sample.Controllers
{
    [ApiController]
    public class ServiceController : Controller
    {
        // Design Automation v3 API
        DesignAutomationClient _designAutomation;
        // Used to access the application folder (temp location for files & bundles)
        private IWebHostEnvironment _env;
        // used to access the SignalR Hub
        private IHubContext<DesignAutomationHub> _hubContext;

        public ServiceController(IWebHostEnvironment env, IHubContext<DesignAutomationHub> hubContext, DesignAutomationClient api)
        {
            _designAutomation = api;
            _env = env;
            _hubContext = hubContext;
        }
    }

    /// <summary>
    /// Class uses for SignalR
    /// </summary>
    public class DesignAutomationHub : Microsoft.AspNetCore.SignalR.Hub
    {
        public string GetConnectionId() { return Context.ConnectionId; }
    }
}

Client:

var connection;
var connectionId;

function startConnection(onReady) {
  if (connection && connection.connectionState) {
    if (onReady) onReady();
    return;
  }
  connection = new signalR.HubConnectionBuilder()
    .withUrl(
      "http://<SERVERADRESS>/api/signalr/designautomation"
    )
    .configureLogging(signalR.LogLevel.Trace)
    .build();
  connection.start().then(function () {
    connection.invoke("getConnectionId").then(function (id) {
      connectionId = id; 
      if (onReady) onReady();
    });
  });

  connection.on("downloadResult", function (url) {
    console.log('<a href="' + url + '">Download result file here</a>');
  });

  connection.on("onComplete", function (message) {
    console.log(message);
  });
}

I tested it locally with server and client on one machine, and all is working fine. But since the deployment test, I get the errors. (Websockets are activated on the server.)

Also Postman can establish a connection to the websocket, just my client fails.

I would appreciate any kind of help. Thanks in advance!

EDIT: I also tried connecting to SignalR via the (here) described alternative to the SignalR client-side.

async function connectToWebsocket(negotiations) {
  let token = encodeURIComponent(negotiations.connectionToken);
  let wssPath = `ws://<SERVERADRESS>/api/signalr/designautomation?id=${token}`;
  let ws = new WebSocket(wssPath);

  console.log(ws);
}

async function connectToSignalR() {
  $.ajax({
    url: "<SERVERADRESS>/api/signalr/designautomation/negotiate?negotiateVersion=1",
    contentType: "application/json",
    dataType: "json",
    headers: { "Access-Control-Allow-Origin": "*" },
    type: "POST",
    success: function (res) {
      console.log(res);
      connectToWebsocket(res);
    },
    error: function (error) {
      console.log(error);
    },
  });
}

Still with the same outcome. I get the response from the negotiation but cant connect to the websocket.

As an additional Information. My "server" is an iis-express on a Azure-VM with a via ngrok forwarded adress.

ANOTHER EDIT: My whole case about the use of SignalR is to get a websocket connection running, which the autodesk-forge servers can callback, when they finished my submitted task, to let them know what the next tasks are. I added the Tag, maybe someone from this direction encountered same problems and could give a hint.

AND ANOTHER EDIT: I now also tried the connection to the remote server with the simplest example I could find, the chatapp example from microsoft. Still the same problems. I added the whole console output here: console_output Also I'm curious if theres maybe something wrong with my CORS. But it's defined as stated in every working example...


Solution

  • The problem resulted from ngrok and probably CORS (as stated in the console output).

    After a push in the right direction, I tried another tool (Conveyor - VS Extension) forwarding localhost to the net. There was absolutely no issue with connecting to the websocket or any other transport method from a remote client.

    So for anybody with the same problem, trying to debug websocket connections forwarded with ngrok in iis-express and getting CORS errors, use Conveyor instead. As long as you don't know how to configure ngroks CORS handling ("http --host-header=rewrite" wasn't doing the trick).

    If you know how to configure ngroks CORS handling, I would be glad to read it in the comments.