5.18预检与自定义提取器

在某些情况下,您可能希望只获取 HTTP 请求对象本身,而不实际发送请求。例如:在单元测试中验证生成的请求是否符合预期,或拿到请求构建器后进一步手动修改,亦或是将请求消息传递给另一个系统执行。

获取请求构建器或请求消息(预检请求)#

在某些情况下,您可能希望只获取 HTTP 请求对象本身,而不实际发送请求。例如:在单元测试中验证生成的请求是否符合预期,或拿到请求构建器后进一步手动修改,亦或是将请求消息传递给另一个系统执行。

为此,当发送 HTTP 远程请求的方法的返回类型为 HttpRequestBuilderHttpRequestMessage 时,框架将直接构建并返回该对象,跳过实际的网络传输。示例如下:

cs
public interface IHttpService : IHttpDeclarative{    // HttpRequestBuilder 类型,不发送请求(预检请求)    [Get("https://furion.net/")]    Task<HttpRequestBuilder> GetRequestBuilderAsync();    // HttpRequestMessage 类型,不发送请求(预检请求)    [Get("https://furion.net/")]    Task<HttpRequestMessage> GetRequestMessageAsync();}

调用时直接获取对象:

cs
// 获取构建器,可继续链式配置后手动发送var builder = await httpService.GetRequestBuilderAsync();	// 不发送请求builder.WithHeader("X-Custom", "value");var httpResponseMessage = await httpRemoteService.SendAsync(builder);	// 发起网络请求// 获取 HttpRequestMessage,用于断言或外部传递var httpRequestMessage = await httpService.GetRequestMessageAsync();	// 不发送请求Assert.Equal("https://furion.net/", httpRequestMessage.RequestUri?.ToString());

应用场景#

  • 预检请求(Pre-flight Check):在正式发送前,检查生成的请求对象是否符合预期。确认 URL、请求头、Token 注入等都正确无误后,再手动发送或继续处理。
  • 单元测试:无需模拟网络环境,直接验证生成的 HttpRequestMessage 是否包含了正确的参数、头和认证信息。
  • 请求对象传递:将构造好的 HttpRequestMessage 传递给其他服务、库或进程执行,实现请求构造与请求执行的分离。
  • 混合编程:先通过构建器或声明式完成大部分配置(参数映射、Token 注入等),再拿到构建器进行少量动态修改后手动发送,兼顾声明式的简洁与命令式的灵活。

自定义 HTTP 声明提取器#

19.5 声明式请求 章节中,我们了解到每种特性或参数类型都对应着一种 HTTP 声明式提取器。以下系统预置的特性提取器及其对应的实现:

通过自定义 HTTP 声明式提取器,您可以为 HTTP 声明式接口提供额外的功能。以下是一个自定义 AcceptAttribute 特性及其提取器的示例:

1. 定义 AcceptAttribute 特性

设置 AcceptAttribute 特性作用范围为方法或接口上。

cs
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface)]public sealed class AcceptAttribute : Attribute{    public AcceptAttribute(string accept)    {        ArgumentException.ThrowIfNullOrWhiteSpace(accept);        Accept = accept;    }    public string Accept { get; set; }}

2. 实现 AcceptDeclarativeExtractor 提取器

解析 AcceptAttribute 特性并设置给 HttpRequestBuilder 实例。

cs
public sealed class AcceptDeclarativeExtractor : IHttpDeclarativeExtractor{    // 实现 Extract 方法    public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeParsingContext context)    {        // 获取方法或接口定义的 AcceptAttribute 特性        if (!context.IsMethodDefined<AcceptAttribute>(out var acceptAttribute, true))        {            return;        }        // 设置 Accept 头        httpRequestBuilder.WithHeader("Accept", acceptAttribute.Accept, replace: true);    }}

提取器 Extract 方法的 context 参数的类型为 HttpDeclarativeParsingContext,包含以下属性和方法:

  • 属性

    • Method:被调用方法(MethodInfo 类型)。
    • Args:被调用方法的参数值数组(object[] 类型)。
    • MethodMetadata:被调用方法的元数据(HttpDeclarativeMethodMetadata 类型)。
    • Parameters:被调用方法的参数键值字典(IReadOnlyDictionary<ParameterInfo, object?> 类型)。
    • UnFrozenParameters:被调用方法的非冻结类型参数键值字典(IReadOnlyDictionary<ParameterInfo, object?> 类型)。
  • 方法

    • IsFrozenParameter(parameter):判断参数是否是冻结参数类型。
    • IsMethodDefined<TAttribute>(out var attribute, inherit):检查被调用方法是否定义了指定特性。
    • GetMethodDefinedCustomAttributes(inherit, methodScanFirst):获取被调用方法指定特性的所有实例。

3. 在配置中注册自定义提取器

Startup.csProgram.cs 文件中,配置并注册 HttpRemote 服务,以启用自定义 HTTP 声明式提取器功能。

cs
services.AddHttpRemote(builder =>{    builder.AddHttpDeclarativeExtractors(() => [ new AcceptDeclarativeExtractor() ]);});

4. 在 HTTP 声明式接口中使用自定义特性

cs
[Accept("text/html")]public interface IHttpService : IHttpDeclarative{    // 在方法上应用    [Accept("text/xml")]    [Get("https://furion.net/")]    Task<string> GetStringAsync();}

通过上述步骤,您已经成功创建了一个自定义的 HTTP 声明式提取器。这不仅可以增强 HTTP 声明式接口的功能,还可以使代码更加简洁和易于维护。您可以根据自己的需求继续扩展和自定义其他 HTTP 声明式提取器。

如需更多自定义的 HTTP 声明式提取器,请参考框架内置的 HTTP 声明式提取器代码实现。

自定义 HTTP 声明提取器(授权)#

以下是一个示例,展示了如何通过自定义 AuthenticationAttributeAllowAnonymousAttribute 特性,并添加相应的提取器,以实现自动授权和匿名访问功能。

1. 定义 AuthenticationAttribute 特性

AuthenticationAttribute 特性应用于方法或接口上。

cs
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface)]public class AuthenticationAttribute : Attribute;

2. 实现 AuthenticationDeclarativeExtractorAllowAnonymousDeclarativeExtractor 提取器

cs
/// <summary>///     [Authentication] 特性提取器/// </summary>public class AuthenticationDeclarativeExtractor : IHttpDeclarativeExtractor{    /// <inheritdoc />    public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeParsingContext context)    {        // 如果贴了 [AllowAnonymous] 特性则跳过        if (context.IsMethodDefined<AllowAnonymousAttribute>(out _, true)) return;        // 检查是否已经设置了授权信息        if (httpRequestBuilder.AuthenticationHeader is not null) return;        // 添加授权标头(这里可以实现任何授权的逻辑,比如从参数获取 token 等等)        httpRequestBuilder.AddBearerAuthentication(            "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");    }}/// <summary>///     [AllowAnonymous] 特性提取器/// </summary>public class AllowAnonymousDeclarativeExtractor : IHttpDeclarativeExtractor{    /// <inheritdoc />    public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeParsingContext context)    {        // 如果没有贴 [AllowAnonymous] 特性则跳过        if (!context.IsMethodDefined<AllowAnonymousAttribute>(out _, true)) return;        // 移除授权标头        httpRequestBuilder.RemoveHeaders("Authorization");    }}

3. 在配置中注册自定义提取器

Startup.csProgram.cs 文件中,配置并注册 HttpRemote 服务,以启用自定义 HTTP 声明式提取器功能。

cs
services.AddHttpRemote(builder =>{    // 添加自定义 HTTP 声明式提取器    builder.AddHttpDeclarativeExtractors(() => [ new AuthenticationDeclarativeExtractor(), new AllowAnonymousDeclarativeExtractor() ]);    // 扫描程序集批量添加 HTTP 声明式提取器(推荐)    // builder.AddHttpDeclarativeExtractorsFromAssemblies([ assembly1, assembly2, ... ]); // 若使用 Furion 框架可直接设置 App.Assemblies});

4. 在 HTTP 声明式接口中使用自定义特性

cs
[Authentication]    // 添加全局授权public interface IAuthService : IHttpDeclarative{    [Get("https://furion.net/")]    Task<string> GetDataAsync();    // 访问这个接口需要授权    [AllowAnonymous]    // 匿名访问    [Get("https://furion.net/")]    Task<string> LoginAsync(string username, string password);}

当调用 GetDataAsync 方法时,将自动添加授权标头(实现授权)。调用 LoginAsync 方法时,将自动移除授权请求标头(实现匿名访问)。

通过这个示例可以看出,自定义 HTTP 声明提取器为实现复杂的授权逻辑提供了极大的灵活性。