Использование Firebase .net Admin Sdk с прокси-сервером для отправки push-уведомлений

Я пытаюсь отправить PushNotifications со своего сервера на мобильные устройства с помощью Firebase Cloud Messaging. Сервер находится за прокси, и по какой-то причине я не могу пройти через этот прокси.

(При локальном тестировании без прокси все работает как положено)

Я получаю следующую ошибку:

Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"Server response does not contain a JSON
object. Status code is: ProxyAuthenticationRequired", Description:"", Uri:""
at Google.Apis.Auth.OAuth2.Responses.TokenResponse.FromHttpResponseAsync(HttpResponseMessage response, Clock clock, ILogger logger)                                                                                                                                                                                                                                        

Полная трассировка стека:

Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"Server response does not contain a JSON object. Status code is: ProxyAuthenticationRequired", Description:"", Uri:""
at Google.Apis.Auth.OAuth2.Responses.TokenResponse.FromHttpResponseAsync(HttpResponseMessage response, IClock clock, ILogger logger)
at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.ExecuteAsync(TokenRequest request, HttpClient httpClient, String tokenServerUrl, CancellationToken taskCancellationToken, IClock clock, ILogger logger)
at Google.Apis.Auth.OAuth2.ServiceAccountCredential.RequestAccessTokenAsync(CancellationToken taskCancellationToken)
at Google.Apis.Auth.OAuth2.TokenRefreshManager.RefreshTokenAsync()
at Google.Apis.Auth.OAuth2.TokenRefreshManager.ResultWithUnwrappedExceptions[T](Task`1 task)
at Google.Apis.Auth.OAuth2.TokenRefreshManager.<>c.<GetAccessTokenForRequestAsync>b__10_0(Task`1 task)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__274_0(Object obj)
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown --- 
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location where exception was thrown --- 
at Google.Apis.Auth.OAuth2.TokenRefreshManager.GetAccessTokenForRequestAsync(CancellationToken cancellationToken)
at Google.Apis.Auth.OAuth2.ServiceAccountCredential.GetAccessTokenForRequestAsync(String authUri, CancellationToken cancellationToken)
at Google.Apis.Auth.OAuth2.ServiceCredential.GetAccessTokenWithHeadersForRequestAsync(String authUri, CancellationToken cancellationToken)
at Google.Apis.Auth.OAuth2.ServiceCredential.InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at Google.Apis.Http.ConfigurableMessageHandler.CredentialInterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at Google.Apis.Http.ConfigurableMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at FirebaseAdmin.Util.ErrorHandlingHttpClient`1.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at FirebaseAdmin.Util.ErrorHandlingHttpClient`1.SendAndReadAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at FirebaseAdmin.Util.ErrorHandlingHttpClient`1.SendAndDeserializeAsync[TResult](HttpRequestMessage request, CancellationToken cancellationToken)
at FirebaseAdmin.Messaging.FirebaseMessagingClient.SendAsync(Message message, Boolean dryRun, CancellationToken cancellationToken)
at FirebaseAdmin.Messaging.FirebaseMessaging.SendAsync(Message message, Boolean dryRun, CancellationToken cancellationToken)
at FirebaseAdmin.Messaging.FirebaseMessaging.SendAsync(Message message, Boolean dryRun)
at FirebaseAdmin.Messaging.FirebaseMessaging.SendAsync(Message message)
at Proxy_Test.Program.SendTestMessage() in C:[...]\Program.cs:line 23                                                                                                                   

Это минимальный код, чтобы воссоздать проблему. В основном я создаю FirebaseApp с учетными данными и настройками прокси. Затем я отправляю тестовое сообщение на конкретное устройство. Когда я комментирую строку 7, где я устанавливаю HttpClientFactory, она работает локально.

Использование HttpClientHandler для выполнения обычного запроса Http-Get приводит к успеху, поэтому учетные данные и адрес прокси-сервера верны. Я также успешно попытался связаться с googleapis.com вручную через прокси-сервер, поэтому прокси-сервер каким-то образом не блокирует адрес, но, возможно, я что-то упускаю.

class Program
{
    static void Main(string[] args)
    {
        AppOptions options = new AppOptions(){
            Credential = GoogleCredential.FromFile("path-to-file-with-key"),
            HttpClientFactory = new ProxyAwareHttpClientFactory()
        };
        FirebaseApp.Create(options);
        SendTestMessage();
    }
    public static async Task SendTestMessage()
    {
        try
        {
            string token = "device-token";
            var notification = new FirebaseAdmin.Messaging.Message()
            {
                Token = token,
                Notification = new FirebaseAdmin.Messaging.Notification()
                {Title = "Notification Title",Body = "Notification Body",}
            };
            var response = await FirebaseAdmin.Messaging.FirebaseMessaging.DefaultInstance.SendAsync(notification);
        } catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

public class ProxyAwareHttpClientFactory : HttpClientFactory
{
    protected override HttpMessageHandler CreateHandler(CreateHttpClientArgs args)
    {
        ICredentials credentials = new NetworkCredential("proxy-user", "proxy-pw");
        var httpClientHandler = new HttpClientHandler()
        {
            Proxy = new WebProxy(Address: "proxy-uri", BypassOnLocal: false, BypassList: null, Credentials: credentials),
            UseProxy = true,
        };
        return httpClientHandler;
    }
}

Любая помощь будет оценена по достоинству! Спасибо

Обновлять: Я установил локальный прокси-сервер со squid для дальнейшего тестирования. Я получаю эти ошибки только в производственной среде. Так что я предполагаю, что это проблема с конфигурацией прокси. Но, как я уже упоминал, когда я пытаюсь получить доступ к службам googleapis.com вручную через прокси-сервер, все работает нормально. Я действительно не знаю, чего мне здесь не хватает...

«забавный» факт: это работало в моей локальной среде, потому что я не ограничивал свою сеть, чтобы разрешить трафик только через этот прокси-сервер squid. Таким образом, первый вызов аутентификации прошел через обычную сеть, а сама отправка прошла через прокси-маршрут... Я обнаружил это только потому, что проследил пакеты с помощью wireshark и обнаружил, что заголовки аутентификации отсутствовали в первых пакетах...

Verwey 27.04.2023 13:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я починил это. В случае, если у кого-то такая же проблема, я опубликую ответ:

Проблема заключалась в том, что FirebaseApp должен был отделить сетевой запрос. Во-первых, для аутентификации. Во-вторых, чтобы отправить сообщение службе.

Передачи ProxyData только в AppOptions в HttpClientFactory недостаточно, вы также должны предоставить настройки прокси в credential части. Поэтому вы можете создать объект учетных данных следующим образом:

var factory = new ProxyAwareHttpClientFactory();
GoogleCredential credential = GoogleCredential.FromFile("path-to-file-with-key");
credential = credential.CreateWithHttpClientFactory(factory);

AppOptions proxyOptions = new AppOptions(){
    Credential = credential,
    HttpClientFactory = factory
};

FirebaseApp.Create(proxyOptions);

имейте в виду, что эта функция была представлена ​​​​в Google.Apis.Auth v 1.52.0 (см. этот выпуск) Однако FirebaseAdmin 2.3.0 (последняя версия Nuget) требует только Google.Apis.Auth v 1.49.0. Поэтому в моем случае мне нужно было вручную обновить Google.Apis и Google.Apis.Auth до v 1.60.0 (текущая версия), чтобы они были умеют пользоваться этим методом.

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

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