.NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接

Flurl.Http-3.2.4 升级到 4.0.0 版本后,https请求异常:Call failed. The SSL connection could not be established.

如下图:

Flurl.Http-3.2.4版本绕过https的代码,对于 Flurl.Http-4.0.0 版本来说方法不再适用,3.2.4及4.0.0版本绕过https代码成果在文章最后有展示。

查看了Flurl.Http4.0的文档,地址:Configuration - Flurl

配置相关文档内容简单翻译(翻译可能不够精准,请点击上面链接查看官方文档)如下:

配置

Flurl.Http 包含一组强大的选项和技术,用于在各个级别配置其行为。

设置

Flurl 主要通过 Settings上可用的属性进行配置。以下是可用的设置:IFlurlClient IFlurlRequest IFlurlClientBuilder HttpTest

Setting Type Default Value
Timeout TimeSpan? 100 seconds
HttpVersion string "1.1"
AllowedHttpStatusRange string null ("2xx,3xx", effectively)
Redirects.Enabled bool true
Redirects.AllowSecureToInsecure bool false
Redirects.ForwardHeaders bool false
Redirects.ForwardAuthorizationHeader bool false
Redirects.MaxAutoRedirects int 10
JsonSerializer ISerializer DefaultJsonSerializer
UrlEncodedSerializer ISerializer UrlEncodedSerializer

所有属性都是读/写的,可以直接设置:

// set default on the client:

var client = new FlurlClient("https://some-api.com");

client.Settings.Timeout = TimeSpan.FromSeconds(600);

client.Settings.Redirects.Enabled = false;

// override on the request:

var request = client.Request("endpoint");

request.Settings.Timeout = TimeSpan.FromSeconds(1200);

request.Settings.Redirects.Enabled = true;

您还可以流畅地配置它们:

clientOrRequest.WithSettings(settings => {

settings.Timeout = TimeSpan.FromSeconds(600);

settings.AllowedHttpStatusRange = "*";

settings.Redirects.Enabled = false;

})...

或者在很多情况下甚至更流利:

clientOrRequest

.WithTimeout(600)

.AllowAnyHttpStatus()

.WithAutoRedirect(false)

...

当同时使用客户端、请求和流畅配置时,您需要密切关注正在配置的对象,因为它可以确定对该客户端的后续调用是否受到影响:

await client

.WithSettings(...) // configures the client, affects all subsequent requests

.Request("endpoint") // creates and returns a request

.WithSettings(...) // configures just this request

.GetAsync();

无客户端模式支持所有相同的扩展方法。配置并发送请求而无需显式引用客户端:

var result = await "https://some-api.com/endpoint"

.WithSettings(...) // configures just this request

.WithTimeout(...) // ditto

.GetJsonAsync<T>();

上述所有设置和扩展方法也可以在 上使用IFlurlClientBuilder,这对于在启动时进行配置非常有用:

// clientless pattern, all clients:

FlurlHttp.Clients.WithDefaults(builder =>

builder.WithSettings(...));

// clientless pattern, for a specific site/service:

FlurlHttp.ConfigureClientForUrl("https://some-api.com")

.WtihSettings(...);

// DI pattern:

services.AddSingleton<IFlurlClientCache>(_ => new FlurlClientCache()

// all clients:

.WithDefaults(builder =>

builder.WithSettings(...))

// specific named client:

.Add("MyClient", "https://some-api.com", builder =>

builder.WithSettings(...))

Settings支持(以及所有相关的流畅优点)的第四个也是最后一个对象是HttpTest,并且它优先于所有内容:

using var test = new HttpTest.AllowAnyHttpStatus();

await sut.DoThingAsync(); // no matter how things are configured in the SUT,

// test settings always win

序列化器

和设置值得特别注意JsonSerializer。UrlEncodedSerializer正如您所料,它们分别控制(反)序列化 JSON 请求和响应以及 URL 编码的表单帖子的详细信息。两者都实现了ISerializer,定义了 3 个方法:

string Serialize(object obj);

T Deserialize<T>(string s);

T Deserialize<T>(Stream stream);

Flurl 为两者提供了默认实现。您不太可能需要使用不同的UrlEncodedSerializer,但您可能出于以下几个原因想要更换JsonSerializer:

  1. 您更喜欢3.x 及更早版本的基于Newtonsoft的版本。(4.0 将其替换为基于System.Text.Json的版本。)这可以通过 Flurl.Http.Newtsonsoft配套包获得。

  2. 您想要使用默认实现,但使用自定义JsonSerializerOptions。这可以通过提供您自己的实例来完成:

cs 复制代码
clientOrRequest.Settings.JsonSerializer = new DefaultJsonSerializer(new JsonSerializerOptions {
    PropertyNameCaseInsensitive = true,
    IgnoreReadOnlyProperties = true
});

事件处理程序

将日志记录和错误处理等横切关注点与正常逻辑流程分开通常会产生更清晰的代码。Flurl.Http 定义了 4 个事件 - BeforeCall、AfterCall、OnError和OnRedirect- 以及、和EventHandlers上的集合。(与 不同,事件处理程序在 上不可用。)IFlurlClient IFlurlRequest IFlurlClientBuilder Settings HttpTest

与 类似Settings,所有具有EventHandlers属性的东西都会带来一些流畅的快捷方式:

cs 复制代码
clientOrRequest
    .BeforeCall(call => DoSomething(call)) // attach a synchronous handler
    .OnError(call => LogErrorAsync(call))  // attach an async handler

在上面的示例中,call是 的一个实例FlurlCall,其中包含与请求和响应的各个方面相关的一组可靠的信息和选项:

IFlurlRequest Request

HttpRequestMessage HttpRequestMessage

string RequestBody

IFlurlResponse Response

HttpResponseMessage HttpResponseMessage

FlurlRedirect Redirect

Exception Exception

bool ExceptionHandled

DateTime StartedUtc

DateTime? EndedUtc

TimeSpan? Duration

bool Completed

bool Succeeded

OnError在 之前触发AfterCall,并让您有机会决定是否允许异常冒泡:

cs 复制代码
clientOrRequest.OnError(async call => {
    await LogTheErrorAsync(call.Exception);
    call.ExceptionHandled = true; // otherwise, the exeption will bubble up
});

OnRedirect允许精确处理 3xx 响应:

cs 复制代码
clientOrRequest.OnRedirect(call => {
    if (call.Redirect.Count > 5) {
        call.Redirect.Follow = false;
    }
    else {
        log.WriteInfo($"redirecting from {call.Request.Url} to {call.Redirect.Url}");
        call.Redirect.ChangeVerbToGet = (call.Response.Status == 301);
        call.Redirect.Follow = true;
    }
});

在较低级别,事件处理程序是实现 的对象IFlurlEventHandler,它定义了 2 个方法:

void Handle(FlurlEventType eventType, FlurlCall call);

Task HandleAsync(FlurlEventType eventType, FlurlCall call);

通常,您只需要实现一个或另一个,因此 Flurl 提供了一个默认实现 ,FlurlEventHanler它构成了一个方便的基类 - 两种方法都是虚拟无操作的,因此只需重写您需要的方法即可。处理程序可以这样分配:

clientOrRequest.EventHandlers.Add((FlurlEventType.BeforeCall, new MyEventHandler()));

请注意,EventHanlers项目的类型为Tuple<EventType, IFlurlEventHandler>。保持处理程序与偶数类型分离意味着给定的处理程序可以重用于不同的事件类型。

您可能更喜欢基于对象的方法而不是前面描述的更简单的基于 lambda 的方法,原因之一是如果您使用 DI 并且您的处理程序需要注入某些依赖项:

cs 复制代码
public interface IFlurlErrorLogger : IFlurlEventHandler { }

public class FlurlErrorLogger : FlurlEventHandler, IFlurlErrorLogger
{
    private readonly ILogger _logger;

    public FlurlErrorLogger(ILogger logger) {
        _logger = logger;
    }
}

以下是如何使用 Microsoft 的 DI 框架进行连接:

cs 复制代码
// register ILogger:
services.AddLogging();
// register service that implements IFlurlEventHander and has dependency on ILogger
services.AddSingleton<IFlurlErrorLogger, FlurlErrorLogger>();

// register event hanlder with Flurl, using IServiceProvider to wire up dependencies:
services.AddSingleton<IFlurlClientCache>(sp => new FlurlClientCache()
    .WithDefaults(builder =>
        builder.EventHandlers.Add((FlurlEventType.OnError, sp.GetService<IFlurlErrorLogger>()))

消息处理程序

Flurl.Http 构建于 之上HttpClient,它(默认情况下)使用它HttpClientHandler来完成大部分繁重的工作。IFlurlClientBuilder公开配置两者的方法:

cs 复制代码
// clientless pattern:
FlurlHttp.Clients.WithDefaults(builder => builder
    .ConfigureHttpClient(hc => ...)
    .ConfigureInnerHandler(hch => {
        hch.Proxy = new WebProxy("https://my-proxy.com");
        hch.UseProxy = true;
    }));

// DI pattern: 
services.AddSingleton<IFlurlClientCache>(_ => new FlurlClientCache()
    .WithDefaults(builder => builder.
        .ConfigureHttpClient(hc => ...)
        .ConfigureInnerHandler(hch => ...)));

温馨提示:

1、Flurl 禁用AllowAutoRedirect和UseCookies以便重新实现自己的这些功能概念。如果您将 via 设置为 true ConfigureInnerHander,则可能会破坏 Flurl 中的这些功能。

2、DefaultRequestHeaders Flurl 还实现了自己的和概念Timeout,因此您可能不需要配置HttpClient.

也许您已经阅读过SocketsHttpHandler并且想知道如何在 Flurl 中使用它。您可能会惊讶地发现您可能已经是这样了。如前所述,FlurlClient将其大部分工作委托给HttpClient,而后者又将其大部分工作委托给HttpClientHandler。但鲜为人知的是,自 .NET Core 2.1 以来,HttpClientHandler几乎将所有工作委托给SocketsHttpHandler所有支持它的平台,这基本上是除基于浏览器(即 Blazor)平台之外的所有平台。(如果需要说服力,请浏览源代码。)

FlurlClient → HttpClient → HttpClientHandler → SocketsHttpHandler (on all supported platforms)

HttpClientHandler尽管如此,您可能还是想绕过并直接使用,有一个原因SocketsHttpHander:它的某些可配置性HttpClientHandler在. 只要您不需要支持 Blazor,您就可以这样做:

cs 复制代码
// clientless pattern:
FlurlHttp.Clients.WithDefaults(builder =>
    builder.UseSocketsHttpHandler(shh => {
        shh.PooledConnectionLifetime = TimeSpan.FromMinutes(10);
        ...
    }));

// DI pattern: 
services.AddSingleton<IFlurlClientCache>(_ => new FlurlClientCache()
    .WithDefaults(builder => builder.UseSocketsHttpHandler(shh => {
        ...
    })));

注意:在同一构建器上调用ConfigureInnerHandler和UseSocketsHttpHandler会导致运行时异常。使用其中之一,切勿同时使用。

Flurl 直接支持的最后一种消息处理程序类型是DelegatingHandler,这是一种可链接的处理程序类型,通常称为中间件,通常由第三方库实现。

cs 复制代码
// clientless pattern:
FlurlHttp.Clients.WithDefaults(builder => builder
    .AddMiddleare(new MyDelegatingHandler()));

// DI pattern: 
services.AddSingleton<IFlurlClientCache>(sp => new FlurlClientCache()
    .WithDefaults(builder => builder
        .AddMiddleware(sp.GetService<IMyDelegatingHandler>())

此示例使用流行的弹性库Polly以及在 DI 场景中配置 Flurl:

cs 复制代码
using Microsoft.Extensions.Http;

var policy = Policy
    .Handle<HttpRequestException>()
    ...

services.AddSingleton<IFlurlClientCache>(_ => new FlurlClientCache()
    .WithDefaults(builder => builder
        .AddMiddleware(() => new PolicyHttpMessageHandler(policy))));

上面的示例需要安装Microsoft.Extensions.Http.Polly

以上是配置相关的文档,我并没有在文档中发现关于https的相关配置。

Flurl.Http-3.2.4版本绕过https证书代码:

cs 复制代码
//Startup的ConfigureServices方法中配置
Flurl.Http.FlurlHttp.ConfigureClient(AppSettings.SFGZAPI.serversUrl, cli =>
                        cli.Settings.HttpClientFactory = new UntrustedCertClientFactory()); 


//此类建立你觉得方便的地方即可
public class UntrustedCertClientFactory : DefaultHttpClientFactory
{
    public override HttpMessageHandler CreateMessageHandler()
    {
        return new HttpClientHandler
        {
            ServerCertificateCustomValidationCallback = (a, b, c, d) => true
        };
    }
}

对于4.0.0版本,经过一番阅读源代码【https://github.com/tmenier/Flurl】并测试,测试如下图:

上面截图代码均测试,依然还是:无法建立SSL连接。

又一次阅读文档无客户端使用,发现FlurlHttp.UseClientCachingStrategy说明:

看到可以在这个方法中设置FlurlHttp,一直以为是配置,原来我自己进入了坑,请阅读管理客户端相关文档:Managing Clients - Flurl 或阅读译文:.NetCore Flurl.Http 4.0.0 以上管理客户端-CSDN博客,于是开始了尝试并可以正常访问:

cs 复制代码
//无客户端使用
FlurlHttp.UseClientCachingStrategy(request =>
{
    FlurlHttp.Clients.GetOrAdd("https://xxx", "https://xxx", builder =>
    builder
    .ConfigureInnerHandler(hch =>
    {
        hch.ServerCertificateCustomValidationCallback = (a, b, c, d) => true;

    }));

    return "https://xxx";
});

测试:

cs 复制代码
/// <summary>
/// 无客户端模式
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("FlurlHttp")]
public async Task<ActionResult> FlurlHttp()
{
    try
    {
        var a = await "https://xxx"
        .WithHeader("xx", "xxx")
        .PostAsync()
        .ReceiveJson<dynamic>();

        return Ok(a);
    }
    catch (Exception ex)
    {
        return BadRequest(ex.Message);
    }
}

还有依赖注入模式经过测试也是可以的:

希望本文对你有帮助。

相关推荐
时光追逐者1 天前
C#/.NET/.NET Core学习路线集合,学习不迷路!
开发语言·学习·c#·asp.net·.net·.netcore·微软技术
Jeffrey侠客1 天前
.Net Core 6.0 WebApi在Centos中部署
linux·centos·.netcore
技术拾荒者2 天前
.net core mvc 控制器中页面跳转
后端·c#·asp.net·mvc·.netcore
时光追逐者2 天前
Visual Studio 2022:一个功能全面且强大的IDE
ide·c#·.net·.netcore·visual studio
.Net Core 爱好者4 天前
ASP .NET CORE 6 在项目中集成WatchDog开源项目
c#·.net·.netcore
想起你的日子5 天前
.net core 接口,动态接收各类型请求的参数
.netcore
qq_383139845 天前
Quartz实现定时调用接口(.net core2.0)
.netcore
时光追逐者5 天前
一个.NET开源、轻量级的运行耗时统计库 - MethodTimer
开源·c#·asp.net·.net·.netcore·微软技术
小兜全糖(xdqt)6 天前
.net core NPOI以及NOPI mapper
.netcore
小兜全糖(xdqt)6 天前
.net Core 使用Panda.DynamicWebApi动态构造路由
.netcore