SignalR с объединительной панелью Redis позади F5 - StatusCode: 400, ReasonPhrase: "Bad Request"

Я использую SignalR версии 2.1.2 с SignalR.Redis 2.1.2 на Server 2012 R2, IIS 8.5 с включенными WebSockets.

Все отлично работает в моей среде разработки. Я даже могу хранить копии на разных серверах (например, http machine1/myapp/signalr, http machine2/myapp/signalr) сайта, настроенного на использование одной и той же объединительной панели, и оба интерфейса пользователя получают сообщения, опубликованные на них идеально.

Затем я переместил "myapp" в нашу следующую среду, которая представляет собой кластер из 2 машин, расположенных за балансировщиком нагрузки F5, с настройкой псевдонима dns для маршрутизации к F5, а затем с циклическим перебором "myapp". Сам веб-сайт может нормально подключаться к сигнализатору и может получать опубликованные сообщения, на которые он подписан, НО, когда я пытаюсь опубликовать сайт через псевдоним (например, http myappalias/signalr), я получаю 400 ошибок Bad Request 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
}

Вот некоторый тестовый код, который я использую для публикации тестовых сообщений в каждой среде, где происходит сбой на "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);
    }
}

Теперь, если я не использую "myappalias" и вместо этого включаю сервер, он работает отлично. Похоже, либо F5 является проблемой, клиент должен быть настроен по-другому для этого сценария, либо я должен сделать что-то другое при настройке класса запуска signlar. Вот пример класса запуска, который я использую.

[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);
            }
        }

    }

}

Я загружаю исходный код клиента и подключаю его напрямую, а не к пакету nuget, чтобы можно было пройти через все. Кажется, он успешно согласовывает, а затем пытается "соединиться" с SSE, а затем с транспортами LongPolling, но не в обоих случаях.

Вопрос 1.1

Кто-нибудь знает альтернативу Signalr для.NET, которая поддерживает масштабирование с балансировкой нагрузки с меньшими затратами?

2 ответа

Не должно быть необходимости настраивать привязку адреса источника для использования SignalR за балансировщиком нагрузки. Это, конечно, не неправильно устанавливать сессионную привязку, но это не решает основную проблему.

Если вы внимательно посмотрите на содержание ответа 400, вы, вероятно, увидите сообщение, похожее на "Идентификатор подключения имеет неправильный формат".

SignalR использует машинный ключ сервера для создания токена анти-CSRF, но для этого необходимо, чтобы все серверы в вашей ферме совместно использовали машинный ключ для правильной расшифровки токена, когда SignalR запрашивает серверы прыжков. Успешный запрос /gotiate - это запрос, который извлекает токен anti-CSRF. Когда клиент SignalR затем использует токен anti-CSRF для выполнения запроса /connect, он завершился неудачно, поскольку запрос /connect был обработан другим сервером, который не создал токен и не может его расшифровать.

Это объясняет, почему настройка соответствия сеанса устранила вашу проблему, но совместное использование ключа машины поможет вам избежать этой проблемы, даже если что-то пойдет не так с привязкой сеанса.

Вот проблема, поданная на GitHub кем-то, кто столкнулся с подобной проблемой: https://github.com/SignalR/SignalR/issues/2292.

Проблема была исправлена ​​путем переключения профиля для "MyApp" в F5 на использование профиля "source_addr", встроенного в F5, в качестве родительского профиля с таймаутом в 1 час. Вот описание того, что делает этот профиль:

Постоянство соответствия адреса источника Также известное как простое сохранение, сохранение соответствия адреса источника поддерживает протоколы TCP и UDP и направляет запросы сеанса на один и тот же сервер исключительно на основе IP-адреса источника пакета.

РЕДАКТИРОВАТЬ

Это закончилось тем, что оно "Работало" некоторое время, но если я разверну издателя (что-то, что просто публикуется через клиент-сигнализатор) без повторной публикации Хаба, издатель прекратит попытки подключения снова и снова. UHG.

Другие вопросы по тегам