Skip to content
Bitzsoft.Integrationsbitzsoft.integrations

Concept

Rest 通用传输

通用多租户 REST/Webhook 传输层——端点解析、凭据、多种鉴权、HMAC 签名与流式响应。

Last updated

Rest 把多租户 REST 调用的通用模式抽象成可复用管道。它不是某个功能域的抽象层,而是位于基础设施层(与 RequestLogging 平行),被需要复杂 OAuth 流程、HMAC 签名或 mTLS 的域(如 Jira Cloud)直接引用。有了它,这类域不必各自实现端点解析、凭据管理、鉴权和签名,只描述端点策略和请求即可。

一次调用的内部流程

RestClient.SendAsync

解析端点
IRestEndpointResolver

解析凭据
IIntegrationCredentialResolver

检查凭据过期
IRestClock

打开 HttpClient 租约
IRestHttpClientLeaseFactory

构建请求
鉴权 · 签名

SendAsync
ResponseHeadersRead

RestResponse 流式

RestClientsealed)按固定顺序处理:解析端点 → 解析凭据 → 检查过期 → 打开租约 → 构建消息(鉴权 + 签名)→ 流式发送。响应以 ResponseHeadersRead 读取,正文由调用方按需物化。

协议接口

接口职责
IRestClient执行 SendAsync(context, request),返回 RestResponse
IRestClock可测试的时间来源,提供 UtcNow
IRestEndpointResolver按上下文解析 RestEndpoint(固定端点或多租户)
IRestHttpClientLeaseFactory打开 IRestHttpClientLease,管理 HttpClient 与 mTLS
IRestOAuthTokenProviderOAuth2 access token 获取(client-credentials / JWT Bearer)
IRestPaginator分页器,内置 RestLinkPaginator 跟随 RFC Link rel=next

请求与响应模型

类型说明
RestRequest请求描述:方法、相对路径、查询串、正文、头部、幂等键
RestRequestContext租户上下文,含 TenantIdCorrelationId,用于凭据解析
RestResponse流式响应,拥有底层 HttpResponseMessage 和 lease,调用方必须释放
RestEndpoint固定 origin 的端点策略:鉴权模式、OAuth、mTLS、签名、超时
RestConstantsProviderId = "standard"ProviderKey = "Rest:standard"HttpClientName
RestProviderDescriptor通用 REST/Webhook Provider 的能力描述

鉴权与签名选项

类型说明
RestHmacSigningOptionsHMAC 请求签名:时间戳头、签名头、签名编码、密钥字段名
RestOAuthJwtBearerOptionsJWT Bearer 断言:issuer、private key、key id、subject

RestResponse 的几个关键行为:以 ResponseHeadersRead 流式读取,正文不隐式截断;EnsureSuccessStatusCode 映射状态码到 IntegrationFailureKind;实现 IAsyncDisposable,持有 lease 必须释放。

RestClient 的鉴权模式

RestEndpoint.AuthenticationMode 决定 ApplyAuthenticationAsync 如何注入凭据:

模式行为
None不注入鉴权
ApiKeyHeader写入指定 header(默认由端点配置 header 名)
BasicBase64 编码 username:password,写入 Authorization: Basic
BearerToken直接写入 Authorization: Bearer <token>
OAuth2ClientCredentials走 client-credentials grant,由 IRestOAuthTokenProvider 获取 token
OAuth2JwtBearer走 JWT Bearer assertion 换 token

OAuth2 两种模式都最终拿 Bearer token 写入 Authorization。内置 OAuth 支持仅限 client-credentials grant。

// ① 端点声明用 API Key 鉴权 + HMAC 签名
var endpoint = new RestEndpoint(
baseAddress: new Uri("https://api.example.com/"),
authenticationMode: RestAuthenticationMode.ApiKeyHeader,
apiKeyHeaderName: "X-Api-Key",
requestSigning: new RestHmacSigningOptions
{
SignatureHeaderName = "X-Signature",
TimestampHeaderName = "X-Timestamp",
SecretFieldName = "RequestSigningSecret",
SignatureEncoding = WebhookSignatureEncoding.Hex,
});

HMAC 签名

端点配置了 RequestSigning 时,RestClient.ApplyHmacSignatureAsync 会计算请求签名并写入头部。规范串由时间戳、HTTP 方法、PathAndQuery 和正文 SHA-256 摘要拼接:

// ② 规范串:timestamp\nMETHOD\npathAndQuery\nbodyHash
var canonicalText = string.Join("\n",
timestamp,
request.Method.Method.ToUpperInvariant(),
message.RequestUri!.PathAndQuery,
bodyHash);

签名用凭据的 UseSecretchar[] 有效期内计算 HMAC-SHA256,避免中间 string。计算完成后签名可选 Hex(转小写)或 Base64 编码,写入时间戳头、签名头(可带前缀)和正文哈希头。

错误处理

SendAsync 用一套 try/catch 分层处理异常,全部归一为 RestException

捕获条件处理
OperationCanceledExceptioncancellationToken.IsCancellationRequested原样重抛(用户主动取消)
OperationCanceledException(超时)包装为 RestExceptionfailureKind = TimeoutisTransient = true
HttpRequestException / IOException / SocketException / AuthenticationException包装为 RestExceptionfailureKind = NetworkisTransient = true
RestException原样重抛
其他不捕获,向上传播

HTTP 状态码到 IntegrationFailureKind 的映射发生在 RestResponse.CreateStatusException

状态码FailureKind
401Authentication
403Authorization
404NotFound
408、504Timeout
409Conflict
429RateLimited
其他 4xxValidation
其他Provider

isTransient 对 408、425、429 和所有 5xx 标记为 trueRetryAfter 从响应头的 Retry-After 解析。

RestException

RestException 继承 IntegrationException,额外增加 Operation 字段标识失败操作(如 "send""authenticate""sign"),Domain 固定为 "Rest"

// ③ RestException 比 IntegrationException 多了 Operation
public sealed class RestException : IntegrationException
{
public string? Operation { get; }
}

加密卫生

RestClient 对所有承载密钥的 byte[]finally 块中清零,避免密钥残留在托管堆上:

var bytes = Encoding.UTF8.GetBytes(username + ":" + password);
try
{
message.Headers.Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(bytes));
}
finally
{
Array.Clear(bytes, 0, bytes.Length); // ④ Basic 凭据立即清零
}

HMAC 签名的规范串、签名、密钥 buffer、读取 buffer 都遵循同样的 finally { Array.Clear(...) } 模式。这与 IntegrationCredential 的可清零 char[] 缓冲区一致,贯穿整个密钥处理路径。

DI 注册

Rest 的 DI 扩展位于 Microsoft.Extensions.DependencyInjection,提供三种入口:

// ① 固定端点:凭据仍按每次调用的租户上下文解析
services.AddBitzsoftRest(endpoint);
// ② 多租户端点解析器:宿主提供 IRestEndpointResolver 实现
services.AddBitzsoftRest<MultiTenantEndpointResolver>();
// ③ 仅注册可复用的 HTTP/mTLS/OAuth/时钟基础设施,不注册端点解析器
// 供 typed protocol/Provider 自行组合
services.AddBitzsoftRestTransport();

AddBitzsoftRest / AddBitzsoftRest<T> 内部调用 AddBitzsoftRestTransport,再注册 IRestClientRestClient)、IRestPaginatorRestLinkPaginator)和 IWebhookVerifierRestWebhookVerifier)三项能力,并把验签器注册到 Webhooks 的 verifier 体系。

AddBitzsoftRestTransport 配置命名 HttpClient Bitzsoft.Integrations.Rest,超时设为 InfiniteTimeSpan(由端点级 RestEndpoint.Timeout 控制,默认 2 分钟),并注册默认 handler、IRestClockIRestHttpClientLeaseFactoryIRestOAuthTokenProvider

消费示例

public sealed class JiraIssueService(IRestClient rest, IRestClock clock)
{
public async Task<RestResponse> GetIssueAsync(
RestRequestContext context, string issueKey, CancellationToken ct)
{
var request = new RestRequest
{
Method = HttpMethod.Get,
EscapedRelativePath = $"rest/api/3/issue/{issueKey}",
};
var response = await rest.SendAsync(context, request, cancellationToken: ct);
response.EnsureSuccessStatusCode("get_issue"); // ⑤ 状态码映射为 RestException
return response;
}
}

RestResponse 是流式的——正文只在调用 ReadBodyAsStringAsync / OpenBodyStreamAsync 时才物化,且不施加应用层截断。调用方控制物化时机和保留策略。

位置与边界

相关

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%