it-roy-ru.com

Повторно отправить HttpRequestMessage - исключение

Я хочу отправить один и тот же запрос более одного раза, например:

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);

Отправка запроса во второй раз вызовет исключение с сообщением:

Сообщение с запросом уже отправлено. Невозможно отправить тот же запрос сообщение несколько раз.

Это способ "clone" запрос, чтобы я мог отправить снова?

В моем реальном коде больше переменных, установленных в HttpRequestMessage, чем в примере выше, таких переменных, как заголовки и метод запроса.

20
Drahcir

Я написал следующий метод расширения для клонирования запроса.

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;
}
15
Drahcir

Вот усовершенствование метода расширения, предложенного @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 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 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;
}
13
desautelsj

Я передаю экземпляр Func<HttpRequestMessage> вместо экземпляра HttpRequestMessage. Функция указывает на фабричный метод, поэтому я получаю новое сообщение каждый раз, когда он вызывается вместо повторного использования.

10
Nick

У меня была похожая проблема, и я решил ее, взломав, рефлексия.

Спасибо за открытый исходный код! Читая исходный код, оказывается, что есть закрытое поле _sendStatus в классе HttpRequestMessage, что я сделал, чтобы сбросить его на 0 перед повторным использованием сообщения запроса. Он работает в .NET Core, и я бы хотел, чтобы Microsoft не переименовывала и не удаляла его навсегда. :п

// 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.");
}
2
Ricky

На самом деле, HttpClient - это просто оболочка для «HttpWebRequest», который использует потоки для отправки/получения данных, что делает невозможным повторное использование запроса, хотя это должно быть довольно просто, чтобы оправдать клонирование/создание этого в цикле.

0
Fred Deschenes