SignalR с объединительной панелью Redis за F5 — код состояния: 400, ReasonPhrase: «Неверный запрос»

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

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

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

  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, которая поддерживает масштабирование с балансировкой нагрузки менее «я хочу рвать на себе волосы»?


person wakurth    schedule 13.11.2014    source источник


Ответы (2)


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

Если вы внимательно посмотрите на содержимое ответа 400, вы, вероятно, увидите сообщение, похожее на «ConnectionId имеет неверный формат».

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

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

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

person halter73    schedule 14.11.2014
comment
Интересно, спасибо за ссылку и объяснение. Я не видел. Тогда ConnectionId имеет неправильный формат где-либо в ответе или в его вложенных ответах, но, безусловно, имеет смысл, что ваш пример может быть тем, что происходит на самом деле. - person wakurth; 15.11.2014
comment
Может быть немного сложно прочитать тело ответа при проверке HttpClientException/WebException, поскольку тело ответа по-прежнему является потоком. Вероятно, самый простой способ получить его — открыть ex.GetError().ResponseBody, где GetError() — это метод расширения для Exception, определенный SignalR в пространстве имен Microsoft.AspNet.SignalR.Client. - person halter73; 15.11.2014

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

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

ИЗМЕНИТЬ

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

person wakurth    schedule 14.11.2014
comment
да, я столкнулся с чем-то похожим на эту проблему в прошлом году, просто реализовав асинхронные запросы на получение на обычных страницах ASP.NET. Проблема заключалась в том, что устройство F5 запускало сеанс, и пользователь нажимал на асинхронную публикацию в ответ, сервер, который получил запрос, не был тем, который изначально обслуживал эту страницу, так что... это не так. т работать. В то время на улице ходили слухи, что мы не поддерживаем липкие сессии... Но поймите это, вместо того, чтобы исправить, они откатили всю поддержку асинхронности в приложениях! Вперед 4 Назад 6.... - person JWP; 14.11.2014
comment
@ user1522548 - да, я тебя слышу. - person wakurth; 15.11.2014