SignalR with Redis Backplane Behind F5 - StatusCode: 400, ReasonPhrase: "Bad Request"

I am using SignalR version 2.1.2 with SignalR.Redis 2.1.2 on a 2012 R2 server, IIS 8.5 with WebSockets enabled.

Everything works fine in my development environment. I can even stand on different servers (for example, http machine1 / myapp / signalr, http machine2 / myapp / signalr) in a site configured to use the same backplane, and both UIs receive messages that were available to them.

Then I moved "myapp" to our next environment, which is a cluster of two machines behind the F5 load balancer with dns aliases configured to route to F5 and then circular "myapp". The website itself can connect to signalr just fine and can receive the posted messages it subscribes to, BUT when I try to post to the site via an alias (e.g. http myappalias / signalr) I get a 400, Bad Request error response. Here's an example of an error.

  InnerException: Microsoft.AspNet.SignalR.Client.Infrastructure.StartException
       _HResult=-2146233088
       _message=Error during start request. Stopping the connection.
       HResult=-2146233088
       IsTransient=false
       Message=Error during start request. Stopping the connection.
       InnerException: System.AggregateException
            _HResult=-2146233088
            _message=One or more errors occurred.
            HResult=-2146233088
            IsTransient=false
            Message=One or more errors occurred.
            InnerException: Microsoft.AspNet.SignalR.Client.HttpClientException
                 _HResult=-2146233088
                 _message=StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Pragma: no-cache
  Transfer-Encoding: chunked
  X-Content-Type-Options: nosniff
  Persistent-Auth: true
  Cache-Control: no-cache
  Date: Thu, 13 Nov 2014 22:30:22 GMT
  Server: Microsoft-IIS/8.5
  X-AspNet-Version: 4.0.30319
  X-Powered-By: ASP.NET
  Content-Type: text/html
  Expires: -1
}

      

Here are some test codes I use to post test messages to every environment where it fails on "connection.Start (). Wait ()"

class Program
{
    static void Main(string[] args)
    {
        var connection = new HubConnection("http://myappalias/signalr");

        connection.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;

        var proxy = connection.CreateHubProxy("MyAppHub");

        connection.Start().Wait();

        ConsoleKeyInfo key = Console.ReadKey();

        do
        {


            proxy.Invoke("NewMessage", new Message() { Payload = "Hello" });

            Console.WriteLine("Message fired.");

            key = Console.ReadKey();

        } while (key.Key != ConsoleKey.Escape);
    }
}

      

Now if I dont use "myappalias" and click server instead, it works fine. F5 seems to be a problem, this scenario needs to be configured differently, otherwise I have to do something different when configuring the signlar launch class. Here is an example of a runnable class I'm using.

[assembly: OwinStartup(typeof(MyApp.Startup))]
namespace MyApp
{
    public class Startup
    {
        private static readonly ILog log = LogManager.GetLogger
        (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public void Configuration(IAppBuilder app)
        {
            try
            {
                log.Debug(LoggingConstants.Begin);

                string redisServer = ConfigurationManager.AppSettings["redis:server"];

                int redisPort = Convert.ToInt32(ConfigurationManager.AppSettings["redis:port"]);

                HubConfiguration configuration = new HubConfiguration();
                configuration.EnableDetailedErrors = true;
                configuration.EnableJavaScriptProxies = false;
                configuration.Resolver = GlobalHost.DependencyResolver.UseRedis(redisServer, redisPort, string.Empty, "MyApp");

                app.MapSignalR("/signalr", configuration);   

                log.Info("SIGNALR - Startup Complete");
            }
            finally
            {
                log.Debug(LoggingConstants.End);
            }
        }

    }

}

      

I download the client source code and connect to it directly, not the nuget package, so I can go through everything. It seems to negotiate successfully and then tries to "connect" to SSE and then LongPolling transfers, but fails on both.

Question 1.1

Does anyone know of a Signalr alternative for .NET that supports load-balanced scaling to a lesser extent "I want to pull my hair out"?

+3


source to share


2 answers


This issue was addressed by switching the profile for "MyApp" in F5 to use the "source_addr" profile embedded in F5 as the parent profile with a 1 hour timeout. Here's a description of what this profile does:

Transferring the affinity to the original address. Also known as simple persistence, constant-affinity source address supports both TCP and UDP, and routes session requests to the same server based solely on the source IP of the packet.



EDIT

It ended up "working", but if I propagate the publisher (something that just publishes through the signalr client) without republishing the hub, the publisher tries to connect over and over again from time to time. UHG.

+1


source


There is no need to configure the source address affinity to use SignalR behind the load balancer. Surely it is wrong to set up session intimacy, but that does not fix your underlying problem.

If you look closely at the contents of the 400 response, you will probably see a message like "The ConnectionId is in the wrong format."

SignalR uses the server's server key to generate an anti-CSRF token, but this requires all servers in your farm to share the machine key in order for the token to be properly decrypted when SignalR queries the host servers. The request / negotiate you see successfully is a request that retrieves the anti-CSRF token. When the SignalR client then uses the anti-CSRF token to complete the request / connection, it failed because the / connect request was processed by another server that did not generate the token and cannot decrypt it.



This explains why setting the session affinity fixed your problem, but machine key exchange will help you avoid this problem even if something goes wrong with the session affinity.

Here is a GitHub related issue for those having a similar issue: https://github.com/SignalR/SignalR/issues/2292 .

+1


source







All Articles