重新发送 HttpRequestMessage - 异常
Posted
技术标签:
【中文标题】重新发送 HttpRequestMessage - 异常【英文标题】:Re-Send HttpRequestMessage - Exception 【发布时间】:2013-08-01 17:21:13 【问题描述】:我想多次发送完全相同的请求,例如:
HttpClient client = new HttpClient();
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, "http://example.com");
await client.SendAsync(req, HttpCompletionOption.ResponseContentRead);
await client.SendAsync(req, HttpCompletionOption.ResponseContentRead);
第二次发送请求会抛出异常消息:
请求消息已发送。无法发送相同的请求 多次发送消息。
他们是否可以“克隆”请求以便我再次发送?
我的真实代码在HttpRequestMessage
上设置的变量比上面示例中的变量多,例如标头和请求方法等变量。
【问题讨论】:
【参考方案1】:我编写了以下扩展方法来克隆请求。
public static HttpRequestMessage Clone(this HttpRequestMessage req)
HttpRequestMessage clone = new HttpRequestMessage(req.Method, req.RequestUri);
clone.Content = req.Content;
clone.Version = req.Version;
foreach (KeyValuePair<string, object> prop in req.Properties)
clone.Properties.Add(prop);
foreach (KeyValuePair<string, IEnumerable<string>> header in req.Headers)
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
return clone;
【讨论】:
这并不总是有效。如果您有一个没有任何内容的请求,它可以正常工作。但是,如果您尝试使用已使用的内容克隆请求,则会失败并显示“无法访问已处置的对象”。错误信息。 @G0tPwned 你是对的,当有内容时它不起作用。知道如何克隆内容吗? @Prabhu 如果对内容调用 LoadIntoBufferAsync,则可以保证内容缓冲在 HttpContent 对象内部。剩下的唯一问题是读取流并不会重置位置,所以需要ReadAsStreamAsync并设置流Position = 0。 @Skadoosh 我改进了 drahcir 的解决方案以解决请求有内容的情况。请参阅下面的答案。 @Skadoosh 您需要在发送请求之前克隆请求,因为 SendAsync 将处理请求的内容【参考方案2】:这是对@drahcir 提出的扩展方法的改进。改进是确保请求的内容以及请求本身被克隆:
public static HttpRequestMessage Clone(this HttpRequestMessage request)
var clone = new HttpRequestMessage(request.Method, request.RequestUri)
Content = request.Content.Clone(),
Version = request.Version
;
foreach (KeyValuePair<string, object> prop in request.Properties)
clone.Properties.Add(prop);
foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers)
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
return clone;
public static HttpContent Clone(this HttpContent content)
if (content == null) return null;
var ms = new MemoryStream();
content.CopyToAsync(ms).Wait();
ms.Position = 0;
var clone = new StreamContent(ms);
foreach (KeyValuePair<string, IEnumerable<string>> header in content.Headers)
clone.Headers.Add(header.Key, header.Value);
return clone;
编辑 05/02/18:这里是异步版本
public static async Task<HttpRequestMessage> CloneAsync(this HttpRequestMessage request)
var clone = new HttpRequestMessage(request.Method, request.RequestUri)
Content = await request.Content.CloneAsync().ConfigureAwait(false),
Version = request.Version
;
foreach (KeyValuePair<string, object> prop in request.Properties)
clone.Properties.Add(prop);
foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers)
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
return clone;
public static async Task<HttpContent> CloneAsync(this HttpContent content)
if (content == null) return null;
var ms = new MemoryStream();
await content.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
var clone = new StreamContent(ms);
foreach (KeyValuePair<string, IEnumerable<string>> header in content.Headers)
clone.Headers.Add(header.Key, header.Value);
return clone;
【讨论】:
在异步块中使用此代码时的警告词,您可以按原样使用代码导致线程死锁。一种更安全的模式是同时使用两种扩展方法async
。第一种方法将调用Content = await request.Content.Clone()
。第二种方法将调用await content.CopyToAsync(ms);
。 Wait()
方法使此执行同步并在创建流时短暂锁定整个等待调用链。
仅供参考,根据下面的文档,StreamContent
确实 在提供的流上调用 Dispose
,因此这是安全处置的。 docs.microsoft.com/en-us/dotnet/api/…
另请注意,异步方法定义实际上并不包含 async
关键字。【参考方案3】:
我正在传递Func<HttpRequestMessage>
的实例而不是HttpRequestMessage
的实例。 func 指向一个工厂方法,因此每次调用它时我都会收到一条全新的消息,而不是重复使用。
【讨论】:
@G0tPwned mediaingenuity.github.io/2013/09/25/… 尝试在没有委托处理程序包装器的情况下使用 Polly 实现这一点浪费了半个小时。如果没有处理程序,不建议使用此方法。【参考方案4】:我有类似的问题,并以一种 hack 的方式解决了它,反射。
感谢开源!通过阅读源代码,原来HttpRequestMessage
类中有一个私有字段_sendStatus
,我所做的是在重用请求消息之前将其重置为0
。它适用于 .NET Core,我希望微软不会永远重命名或删除它。 :p
// using System.Reflection;
// using System.Net.Http;
// private const string SEND_STATUS_FIELD_NAME = "_sendStatus";
private void ResetSendStatus(HttpRequestMessage request)
TypeInfo requestType = request.GetType().GetTypeInfo();
FieldInfo sendStatusField = requestType.GetField(SEND_STATUS_FIELD_NAME, BindingFlags.Instance | BindingFlags.NonPublic);
if (sendStatusField != null)
sendStatusField.SetValue(request, 0);
else
throw new Exception($"Failed to hack HttpRequestMessage, SEND_STATUS_FIELD_NAME doesn't exist.");
【讨论】:
Mono 有bool HttpRequestMessage.is_used
标志 :-)【参考方案5】:
AFAIK,HttpClient 只是 'HttpWebRequest' 的包装器,它使用流来发送/接收数据,因此无法重用请求,尽管克隆它/使其循环运行应该非常简单。
【讨论】:
以上是关于重新发送 HttpRequestMessage - 异常的主要内容,如果未能解决你的问题,请参考以下文章
将 JSON 字符串加载到 HttpRequestMessage
c# 如何正确传递 HttpRequestMessage 并在函数外返回 HttpRequestMessage 而不会泄漏
HttpRequestMessage.Content 带有无效和不可读的字符
HttpClient 标头与 HttpRequestMessage 标头