SignalR with IIS 10 and ARR 3.0

2019-06-12 03:51发布

问题:

We can't get SignalR to work when using ARR 3.0 as a reverse proxy in front of our Visual Studio development machine. The connection is successfully established but the initial frame that should be sent from the SignalR server once the connection is established is never sent, as a matter of fact, no frames can be sent, this causes the client to drop the WebSocket connection. To sum up, "the websocket connection can establish, but can't transfer frames."

The MVC app works without the reverse proxy.

We've tried all suggested solutions in the following threads:

  • Websockets reverse proxy in IIS 8
  • http://matthewmanela.com/blog/using-signalr-in-an-arr-cluster/

Screenshot of "negotiate"-request.

Screenshot of "connect"-request.

EDIT 1

It appears that the connected-frame is being sent but fails down the road. It's visible through Wireshark on loopback.

EDIT 2

It appears that SignalR works with ARR when I'm not appending a suffix to the url. In other words:

  • http://websitea.local => http://websiteb.local works great
  • http://websitea.local/portal => http://websiteb.local doesn't work

回答1:

So, we managed to solve the matter in question and we dug down in the underlying cause to be able to reproduce the problem clearly. It has to do with the order in which rewriting is done, what we did was that we had created a helper method in our ASP.NET MVC application that would rewrite the input parameter URL if the HTTP headers from the orginated request contained our rewriting path suffix.

public static class RouteHelper
{
    public static string Url(string url)
    {
        string orginalUrl = HttpContext.Current?.Request.Headers["X-Original-URL"];
        if (!string.IsNullOrEmpty(orginalUrl))
        {
            if (orginalUrl.StartsWith("/" + "portal", true, CultureInfo.InvariantCulture)
                || orginalUrl.StartsWith("portal", true, CultureInfo.InvariantCulture))
            {
                url = "/" + "portal" + url;
            }
        }
        return url;
    }
}

We used this method to the set URL of the SignalR hub/connection as following:

$.connection.hub.url = '@RouteHelper.Url("/signalr")'

The result would be $.connection.hub.url = '@RouteHelper.Url("/portal/signalr")'

It's easy to think that this would give the same result as creating an outbound rewrite rule that would do this changes but that's not correct.

The response that returns to the client from the proxy will be the same but the communication between the proxy and the backend webserver will malfunction.

Instead, we created an outbound rule for this, that looked like this:

<rule name="SignalRReverseProxySignalRHubsUrl">
    <match filterByTags="None" pattern="connection.hub.url.*=.*['\&quot;](.*)['\&quot;]" />
    <action type="Rewrite" value="connection.hub.url = &quot;/portal{R:1}&quot;" />
</rule>

I can't explain what's happening internal between the reverse proxy and backend web servers that causes the SignalR frames not to arrive but I can confirm that all URL rewriting needs to be done at the proxy and logic in the application that simulates the same rewriting will not work properly, at least not in the way described above.