使用Polly RetryAsync调用HttpClient.SendAsync时报请求已发送错误如何解决
异常产生原因
HttpRequestMessage在.NET中被设计为单次使用对象,首次发送后内部会标记为已发送状态,同时关联的请求内容流会被释放,无法复用进行二次发送- 现有代码在重试策略外部创建了
HttpRequestMessage实例,重试时直接复用了已经被发送过的对象,因此触发该错误 - 附加注意:你传入的
StringContent也属于流类型,仅支持一次读取,直接复用同样会导致重试时请求内容为空的问题
修复方案
核心逻辑是把请求对象创建、请求内容生成的逻辑放到重试策略的执行委托内部,每次重试都生成全新的请求实例:
方案1:修改方法签名,传入内容生成委托(推荐)
该方案无额外性能损耗,且灵活性更高:
public static async Task<HttpResponseMessage> Post(string specificUrl, string token, Func<StringContent> inputDataFactory) { var policy = Policy .Handle<Exception>() .OrResult<HttpResponseMessage>(res => !res.IsSuccessStatusCode) .RetryAsync(3); string fullUrl = baseUrl + specificUrl; HttpResponseMessage res = await policy.ExecuteAsync(async () => { using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, fullUrl)) { requestMessage.Content = inputDataFactory(); requestMessage.Headers.Add("access_token", token); return await client.SendAsync(requestMessage).ConfigureAwait(false); } }).ConfigureAwait(false); return res; }
调用示例:
// 原来的调用写法:Post("user/", token, new StringContent(json, Encoding.UTF8, "application/json")) // 修改为传入生成Content的委托即可 await Post("user/", token, () => new StringContent(json, Encoding.UTF8, "application/json"));
方案2:兼容原有调用签名,提前缓存请求内容
如果不想修改现有调用逻辑,可以提前把请求内容缓存为字符串,每次重试重新生成StringContent:
public static async Task<HttpResponseMessage> Post(string specificUrl, string token, StringContent inputData) { // 提前缓存请求内容,避免流被消费后无法读取 string contentStr = await inputData.ReadAsStringAsync(); var contentType = inputData.Headers.ContentType; var policy = Policy .Handle<Exception>() .OrResult<HttpResponseMessage>(res => !res.IsSuccessStatusCode) .RetryAsync(3); string fullUrl = baseUrl + specificUrl; HttpResponseMessage res = await policy.ExecuteAsync(async () => { using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, fullUrl)) { requestMessage.Content = new StringContent(contentStr, contentType.CharSet, contentType.MediaType); requestMessage.Headers.Add("access_token", token); return await client.SendAsync(requestMessage).ConfigureAwait(false); } }).ConfigureAwait(false); return res; }
内容的提问来源于stack exchange,提问作者Show More
相关产品推荐
相关产品推荐

