1、 自定义请求处理

先说一下目标,由于和九识无人车对接需要调用九识的接口,九识接口需要先获取 token ,所以我的想法是:

  1. 先从缓存获取 token,如果有就将 token 放到请求头中然后发送请求,没有就调用获取 token 接口
  2. 如果 token 过期了(接口返回 401)就重新调用获取 token 接口,然后重发这次失败的请求

今天对接九识 API 发现了一个使用 HttpClient 的技巧

1
builder.Services.AddHttpClient("JiuShi").AddHttpMessageHandler<JiuShiApiDelegatingHandler>();// 添加http消息处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
using Microsoft.Extensions.Options;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using UnmannedVehicle.Configs;
using UnmannedVehicle.DTO.Fetch;
using UnmannedVehicle.Interfaces;

namespace UnmannedVehicle.Utils {
public class JiuShiApiDelegatingHandler : DelegatingHandler {
private readonly ICacheService _cacheService;
private readonly IHttpClientFactory _httpClientFactory;
private const string TokenKey = "JIUSHI_API_TOKEN";
private readonly IOptionsSnapshot<JiuShiSetting> _jiuShiSetting;
public JiuShiApiDelegatingHandler(
ICacheService cacheService,
IHttpClientFactory httpClientFactory,
IOptionsSnapshot<JiuShiSetting> jiuShiSetting
) {
_cacheService = cacheService;
_httpClientFactory = httpClientFactory;
_jiuShiSetting = jiuShiSetting;
}
/// <summary>
/// 获取token
/// </summary>
/// <returns>token</returns>
public async Task<string> GetTokenAsync() {
var oldToken = await _cacheService.GetAsync<string>(TokenKey);
if (!string.IsNullOrEmpty(oldToken)) return oldToken;
var result = await FetchTokenAsync();
var token = result.data.token;
await _cacheService.SetAsync(TokenKey, token, TimeSpan.FromMinutes(result.data.expiresAfter - 2));
return token;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken) {
// 请求前:注入 token
var token = await GetTokenAsync();
request.Headers.Add("token", token);

// 先正常发送一次
HttpResponseMessage response;
try {
response = await base.SendAsync(request, cancellationToken);
} catch {
throw;
}

// 如果不是 401,直接返回
if (response.StatusCode != HttpStatusCode.Unauthorized)
return response;

// 401:清 token + 重新获取
await _cacheService.RemoveAsync(TokenKey);
var newToken = await GetTokenAsync();

// 重新构建 request(HttpRequestMessage 不能重复使用)
var retryRequest = await CloneRequestAsync(request);
retryRequest.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", newToken);

return await base.SendAsync(retryRequest, cancellationToken);
}

/// <summary>
/// 复制 HttpRequestMessage(必须)
/// </summary>
private static async Task<HttpRequestMessage> CloneRequestAsync(HttpRequestMessage request) {
var clone = new HttpRequestMessage(request.Method, request.RequestUri);

// 复制 headers
foreach (var header in request.Headers)
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);

// 复制 content
if (request.Content != null) {
var ms = new MemoryStream();
await request.Content.CopyToAsync(ms);
ms.Position = 0;
clone.Content = new StreamContent(ms);

foreach (var header in request.Content.Headers)
clone.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
}

return clone;
}
/// <summary>
/// 获取token
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<JiuShiApiRes<FetchTokenRes>> FetchTokenAsync() {
var url = "https://auth.zelostech.com.cn/app/accessToken";
var httpClient = _httpClientFactory.CreateClient();
var body = new FetchTokenReq(_jiuShiSetting.Value.AppId, _jiuShiSetting.Value.AppKey);
HttpResponseMessage response = await httpClient.PostAsJsonAsync(url, body);
if (!response.IsSuccessStatusCode) {
throw new Exception($"获取九识 token 失败,HTTP {(int)response.StatusCode}");
}
var data = await response.Content.ReadFromJsonAsync<JiuShiApiRes<FetchTokenRes>>();
if (data == null || !data.success) {
throw new Exception("九识返回 token 失败:" + data?.message);
}
return data;
}
}
}

注意在 JiuShiApiDelegatingHandler里面不能使用 JiuShiHttpClient 会导致循环引用,所以我用 HttpClientFactory 创建了一个新的

需要覆写 protected override async Task<HttpResponseMessage> SendAsync()方法,可以调用 base.sendAsync来发送这次请求

需要复制 HttpRequestMessage