You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决Cloud Run中HTTP请求触发的“Operation not supported”异常

问题描述

我有一个部署在Google Cloud Run上的ASP.NET Core 8 Web API,它通过HttpClient调用由Google Cloud HTTP(S)负载均衡器暴露的内部API。负载均衡器会返回以下响应头:

Alt-Svc: h3=":443"; ma=2592000, h3-29=":443"; ma=2592000

请求会间歇性失败,抛出如下异常:

System.Net.NetworkInformation.NetworkInformationException (95): Operation not supported
   at System.Net.NetworkInformation.NetworkChange.CreateSocket()
   at System.Net.NetworkInformation.NetworkChange.add_NetworkAddressChanged(NetworkAddressChangedEventHandler value)
   at System.Net.Http.HttpConnectionPoolManager.StartMonitoringNetworkChanges()
   at System.Net.Http.HttpConnectionPool.HandleAltSvc(IEnumerable`1 altSvcHeaderValues, Nullable`1 responseAge)
   at System.Net.Http.HttpConnectionPool.ProcessAltSvc(HttpResponseMessage response)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.<SendCoreAsync>g__Core|4_0(HttpRequestMessage request, Boolean useAsync, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.PolicyHttpMessageHandler.SendCoreAsync(HttpRequestMessage request, Context context, CancellationToken cancellationToken)
   at Polly.Retry.AsyncRetryEngine.ImplementationAsync[TResult](Func`3 action, Context context, ExceptionPredicates shouldRetryExceptionPredicates, ResultPredicates`1 shouldRetryResultPredicates, Func`5 onRetryAsync, CancellationToken cancellationToken, Int32 permittedRetryCount, IEnumerable`1 sleepDurationsEnumerable, Func`4 sleepDurationProvider, Boolean continueOnCapturedContext)
   at Polly.AsyncPolicy`1.ExecuteInternalAsync(Func`3 action, Context context, Boolean continueOnCapturedContext, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.PolicyHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Redacted.Signon.Client.Service.SusMessageHandler.<>c__DisplayClass3_0.<<SendAsync>b__2>d.MoveNext() in /workspace/Redacted.Billing/Redacted.Signon.Client/Service/SusMessageHandler.cs:line 51
--- End of stack trace from previous location ---
   at Polly.Retry.AsyncRetryEngine.ImplementationAsync[TResult](Func`3 action, Context context, ExceptionPredicates shouldRetryExceptionPredicates, ResultPredicates`1 shouldRetryResultPredicates, Func`5 onRetryAsync, CancellationToken cancellationToken, Int32 permittedRetryCount, IEnumerable`1 sleepDurationsEnumerable, Func`4 sleepDurationProvider, Boolean continueOnCapturedContext)
   at Polly.AsyncPolicy`1.ExecuteInternalAsync(Func`3 action, Context context, Boolean continueOnCapturedContext, CancellationToken cancellationToken)
   at Redacted.Signon.Client.Service.SusMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /workspace/Redacted.Billing/Redacted.Signon.Client/Service/SusMessageHandler.cs:line 44
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.<SendCoreAsync>g__Core|4_0(HttpRequestMessage request, Boolean useAsync, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Redacted.Signon.Client.Service.SusService.GetUsersByIds(IEnumerable`1 susUserIds, Boolean includeProviderPictureUrls, CancellationToken cancellationToken) in /workspace/Redacted.Billing/Redacted.Signon.Client/Service/SusService.cs:line 114
   at Redacted.Signon.Client.Service.SusService.GetUserById(String userId, Boolean includeProviderPictureUrls, CancellationToken cancellationToken) in /workspace/Redacted.Billing/Redacted.Signon.Client/Service/SusService.cs:line 126
   at Redacted.Billing.Services.Authentication.TokenValidationService.ValidateUserTokenAsync(IServiceProvider services, ClaimsPrincipal principal, ClaimsIdentity claimsIdentity, CancellationToken cancellationToken) in /workspace/Redacted.Billing/Redacted.Billing/Services/Authentication/TokenValidationService.cs:line 101
   at Redacted.Billing.Services.Authentication.TokenValidationService.ValidateTokenAsync(IServiceProvider services, ClaimsPrincipal principal, CancellationToken cancellationToken) in /workspace/Redacted.Billing/Redacted.Billing/Services/Authentication/TokenValidationService.cs:line 35
   at Redacted.Billing.App_Start.Services.Auth.SusConfiguration.<PostConfigure>b__4_0(TokenValidatedContext ctx) in /workspace/Redacted.Billing/Redacted.Billing/App_Start/Services/Auth.cs:line 77
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()

从栈追踪可以看出,异常发生在HttpClient处理Alt-Svc响应头时。

观察结果

  • 仅在Cloud Run环境中出现异常,本地无法复现
  • 使用HTTP/2的请求可以正常执行
  • 失败是间歇性的,推测和HttpClient内部的Alt-Svc缓存/退避逻辑有关
  • 异常触发点是HttpClient启动网络变更监控(通过NetworkChange类)时

当前HttpClient配置

services
    .AddHttpClient(Constants.RedactedApiClient)
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler()
    {
        AllowAutoRedirect = false
    })
    .ConfigureHttpClient(client =>
    {
        client.DefaultRequestVersion = HttpVersion.Version20;
        client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
    });

请求使用方式

var client = factory.CreateClient(Constants.RedactedApiClient);
using var response = await client.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode();

已尝试的解决方案

  • 强制使用HTTP/2(配置如上):
    DefaultRequestVersion = HttpVersion.Version20;
    DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
    
  • 通过环境变量禁用HTTP/3:
    DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP3SUPPORT=false
    
  • 查阅相关问题讨论

以上方案均无法阻止异常发生,目前只能在捕获到NetworkInformationException时添加重试逻辑,请问还有其他解决方法吗?


可行解决方案

1. 完全禁用Alt-Svc处理

通过配置SocketsHttpHandler的EnableAltSvc属性为false,彻底关闭HttpClient对Alt-Svc头的处理逻辑,从根源上避免触发网络变更监控:

services
    .AddHttpClient(Constants.RedactedApiClient)
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler()
    {
        AllowAutoRedirect = false,
        EnableAltSvc = false // 关键配置:禁用Alt-Svc处理
    })
    .ConfigureHttpClient(client =>
    {
        client.DefaultRequestVersion = HttpVersion.Version20;
        client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
    });

2. 禁用网络变更监控

如果需要保留Alt-Svc功能但不想触发网络监控,可以通过环境变量DOTNET_SYSTEM_NET_NETWORKCHANGE_ENABLED设置为false,阻止NetworkChange类的初始化:

DOTNET_SYSTEM_NET_NETWORKCHANGE_ENABLED=false

该配置会让HttpClient跳过网络变更监控的启动流程,从而避免抛出"Operation not supported"异常。

3. 优化重试策略(补充现有方案)

如果以上配置无法生效,可以优化现有Polly重试逻辑,专门针对错误码为95的NetworkInformationException进行重试,同时设置指数退避间隔,避免给下游API造成压力:

// 定义针对目标异常的重试策略
var retryPolicy = Policy
    .Handle<NetworkInformationException>(ex => ex.NativeErrorCode == 95)
    .Or<HttpRequestException>()
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

// 将策略注册到HttpClient
services.AddHttpClient(Constants.RedactedApiClient)
    .AddPolicyHandler(retryPolicy)
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler()
    {
        AllowAutoRedirect = false
    })
    .ConfigureHttpClient(client =>
    {
        client.DefaultRequestVersion = HttpVersion.Version20;
        client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
    });

内容的提问来源于stack exchange,提问作者ProgrammingLlama

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 13:12:38