動態API客戶端訪問過程由於API描述的名稱不一致導致的坑,通過查詢資料學習記錄如下:
1、通過AOP攔截器實現遠端接口代理訪問實現
訪問遠端接口當然還是通過HttpClient直接訪問了,大致訪問過程如下:
- 獲取入口處配置的,服務接口類型和服務地址標記KEY
- 根據服務地址標記KEY獲取服務地址信息,主要是HOST地址,API版本
- 獲取遠端服務的接口描述
- 根據接口描述,構造請求路徑,序列化參數、添加認證信息,發送請求
- 根據接口描述,獲取請求結果,進行反序列化
實現類使用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: