web-dev-qa-db-ja.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に設定されています。これは、ヘッダーやリクエストメソッドなどの変数です。

27
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;
}
17
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;
}
15
desautelsj

HttpRequestMessageのインスタンスの代わりにFunc<HttpRequestMessage>のインスタンスを渡しています。 funcはファクトリメソッドを指しているので、再利用する代わりに、呼び出されるたびに新しいメッセージが表示されます。

12
Nick

私は同様の問題を抱えており、ハッキングの方法、反射でそれを解決しました。

オープンソースをありがとう!ソースコードを読むと、HttpRequestMessageクラスにプライベートフィールド_sendStatusがあることがわかります。私が行ったのは、リクエストメッセージを再利用する前に0にリセットすることです。 .NET Coreで動作し、Microsoftが名前を変更したり、永久に削除したりしないことを願っています。 :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.");
}
4
Ricky

申し訳ありませんが、HttpClientは 'HttpWebRequest'の単なるラッパーであり、データを送受信するためにストリームを使用するため、要求を再利用することは不可能ですが、それを複製してループで作成するのは非常に簡単です。

0
Fred Deschenes