ABP_VNext 動態Api客戶端訪問坑


動態API客戶端訪問過程由於API描述的名稱不一致導致的坑,通過查詢資料學習記錄如下:

1、通過AOP攔截器實現遠端接口代理訪問實現

訪問遠端接口當然還是通過HttpClient直接訪問了,大致訪問過程如下:

  1. 獲取入口處配置的,服務接口類型和服務地址標記KEY
  2. 根據服務地址標記KEY獲取服務地址信息,主要是HOST地址,API版本
  3. 獲取遠端服務的接口描述
  4. 根據接口描述,構造請求路徑,序列化參數、添加認證信息,發送請求
  5. 根據接口描述,獲取請求結果,進行反序列化

實現類使用ABP的動態攔截實現如下: public class DynamicHttpProxyInterceptor : AbpInterceptor, ITransientDependency;主要實現方法在MakeRequestAsync里面,使用HttpClientFactory構造HttpClient來請求,本次訪問坑在於使用RequestPayloadBuilder構建請求時使用HttpActionParameterHelper來獲取匹配參數變量,由於對ParameterApiDescriptionModel不是很了解,導致了名稱錯配的坑,這里一個重點就是如何能知道對方的服務接口描述信息?我們定義了一個C#接口,這個接口是如何被客戶端獲取描述信息的呢?我們分析代碼得到在調用過程中使用ApiDescriptionFinder.FindActionAsync進行API描述的信息的查找。

public static object FindParameterValue(IReadOnlyDictionary<string, object> methodArguments, ParameterApiDescriptionModel apiParameter)
        {
            var value =  methodArguments.GetOrDefault(apiParameter.NameOnMethod);
            if (value == null)
            {
                return null;
            }

            if (apiParameter.Name == apiParameter.NameOnMethod)
            {
                return value;
            }

            return ReflectionHelper.GetValueByPath(value, value.GetType(), apiParameter.Name);
        }

  2、如何查找遠端API描述信息

ApiDescriptionFinder.FindActionAsync看到其內部通過調用對方服務站點下的/api/abp/api-definition接口來獲取的接口描述,遠程接口提供方還提供一個api/abp/api-definition的接口,根據我們本地的接口定義信息能夠找到遠程接口描述,具體這個API元數據接口代碼如下:

// 查找API描述信息
ApiDescriptionFinder.FindActionAsync(remoteServiceConfig.BaseUrl, typeof(TService), invocation.Method)

// 內部又會調用如下接口,調用遠程服務站點下的接口
protected virtual async Task<ApplicationApiDescriptionModel> GetApiDescriptionFromServerAsync(string baseUrl)
{
    using (var client = HttpClientFactory.Create())
    {
        // ABP VNext默認的實現,調用一個固定的地址,如果對方不是采用Abp框架來提供接口,只能模擬適配這個接口的返回了
        var response = await client.GetAsync(
            baseUrl.EnsureEndsWith('/') + "api/abp/api-definition",
            CancellationTokenProvider.Token
        ).ConfigureAwait(false);

       // ...
    }
}

我們使用Abp框架進行開發,自帶了這個接口,通過獲取控制器Action的元數據信息,循環獲取所有的API接口描述都返回,ABP的實現代碼:

[Route("api/abp/api-definition")]
public class AbpApiDefinitionController : AbpController, IRemoteService
{
    private readonly IApiDescriptionModelProvider _modelProvider;

    public AbpApiDefinitionController(IApiDescriptionModelProvider modelProvider)
    {
        _modelProvider = modelProvider;
    }

    [HttpGet]
    public ApplicationApiDescriptionModel Get()
    {
        return _modelProvider.CreateApiModel();
    }
}

public class AspNetCoreApiDescriptionModelProvider : IApiDescriptionModelProvider, ITransientDependency
{
    public ILogger<AspNetCoreApiDescriptionModelProvider> Logger { get; set; }

    private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider;
    private readonly AbpAspNetCoreMvcOptions _options;
    private readonly AbpApiDescriptionModelOptions _modelOptions;

    public AspNetCoreApiDescriptionModelProvider(
        IApiDescriptionGroupCollectionProvider descriptionProvider,
        IOptions<AbpAspNetCoreMvcOptions> options,
        IOptions<AbpApiDescriptionModelOptions> modelOptions)
    {
        _descriptionProvider = descriptionProvider;
        _options = options.Value;
        _modelOptions = modelOptions.Value;

        Logger = NullLogger<AspNetCoreApiDescriptionModelProvider>.Instance;
    }

    public ApplicationApiDescriptionModel CreateApiModel()
    {

        var model = ApplicationApiDescriptionModel.Create();
        // 從 Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupCollectionProvider中獲取描述信息
        foreach (var descriptionGroupItem in _descriptionProvider.ApiDescriptionGroups.Items)
        {
            foreach (var apiDescription in descriptionGroupItem.Items)
            {
                if (!apiDescription.ActionDescriptor.IsControllerAction())
                {
                    continue;
                }

                AddApiDescriptionToModel(apiDescription, model);
            }
        }

        return model;
    }
    
    // ...
}

對於其他語言開發的動態接口代理偽造請求參數的構造RequestPayloadBuilder,未有替換服務,是一種遺憾,通過查看最新的5.0,依然未開放。

另外:對於動態客戶端使用的認證服務是由Abp的認證模塊Volo.Abp.IdentityModel提供的,是通過IdentityModelAuthenticationService,其內部適配OIDC的客戶端認證和密碼兩種模來提供接口認證,如果對方的接口認證不是OIDC實現的兩種規范,則需要去重寫這個認證服務,我們服務未采用IdentityServer4,所以使用客戶端替換服務,替換掉這個服務IRemoteServiceHttpClientAuthenticator;

if (requestContext.Action.AllowAnonymous != true)
            {
                await ClientAuthenticator.Authenticate(
                    new RemoteServiceHttpClientAuthenticateContext(
                        client,
                        requestMessage,
                        remoteServiceConfig,
                        clientConfig.RemoteServiceName
                    )
                );
            }

 

參考:

1、動態API客戶端文檔: https://docs.abp.io/zh-Hans/abp/latest/AspNetCore/Dynamic-CSharp-API-Client

2、dotnet_core_learning:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM