28

I'm trying to clone a request using the method outlined in this answer: https://stackoverflow.com/a/18014515/406322

However, I get an ObjectDisposedException, if the original request has content.

How can you reliably clone a HttpRequestMessage?

Community
  • 1
  • 1
Prabhu
  • 12,995
  • 33
  • 127
  • 210
  • I don't know if you can. If you open a stream and use that as the content for instance, the stream can only be used once. Where is the original being created? – Jason Goemaat Jul 30 '14 at 18:27
  • My original request is a PUT with an authentication header and some parameters as the content. I need to do retries (but change the header for each retry), so what are my options? – Prabhu Jul 30 '14 at 18:40

3 Answers3

48

This should do the trick:

    public static async Task<HttpRequestMessage> CloneHttpRequestMessageAsync(HttpRequestMessage req)
    {
        HttpRequestMessage clone = new HttpRequestMessage(req.Method, req.RequestUri);

        // Copy the request's content (via a MemoryStream) into the cloned object
        var ms = new MemoryStream();
        if (req.Content != null)
        {
            await req.Content.CopyToAsync(ms).ConfigureAwait(false);
            ms.Position = 0;
            clone.Content = new StreamContent(ms);

            // Copy the content headers
            foreach (var h in req.Content.Headers)
                clone.Content.Headers.Add(h.Key, h.Value);
        }


        clone.Version = req.Version;

        foreach (KeyValuePair<string, object?> option in req.Options)
            clone.Options.Set(new HttpRequestOptionsKey<object?>(option.Key), option.Value);

        foreach (KeyValuePair<string, IEnumerable<string>> header in req.Headers)
            clone.Headers.TryAddWithoutValidation(header.Key, header.Value);

        return clone;
    }
Carlos P
  • 3,928
  • 2
  • 34
  • 50
  • I just used this snippet and so far it works fine. @Prahbu: could your mark it as right answer? – Mauricio Aviles Feb 04 '16 at 08:55
  • Use "using" key word to avoid the memory leaks: using var ms = new MemoryStream(); – Ivan Kukushkin Feb 01 '21 at 08:55
  • 1
    as of .net 5 `req.Content.Headers` is never `null`, so there's no point in checking for null-ness. I just run the loop without the condition. Also `req.Properties` is obsolete and `req.Options` should be used instead. – Yos Feb 18 '21 at 08:28
  • 3
    so in .NET 5, it should change to something like `foreach (KeyValuePair option in req.Options) clone.Options.Set(new HttpRequestOptionsKey(option.Key), option.Value);` – sferencik Mar 04 '21 at 12:50
  • why not just `clone.Content = req.Content`? – MatteoSp Jul 08 '21 at 13:59
  • 1
    Because Content as well can be separately Disposed, example is `StringContent` – Code Name Jack Jun 02 '22 at 06:38
7

If you call LoadIntoBufferAsync on the content, you can guarantee that the content is buffered inside the HttpContent object. The only problem remaining is that reading the stream does not reset the position, so you need to ReadAsStreamAsync and set the stream Position = 0.

My example is very similar to the one Carlos showed...

 private async Task<HttpResponseMessage> CloneResponseAsync(HttpResponseMessage response)
        {
            var newResponse = new HttpResponseMessage(response.StatusCode);
            var ms = new MemoryStream();

            foreach (var v in response.Headers) newResponse.Headers.TryAddWithoutValidation(v.Key, v.Value);
            if (response.Content != null)
            {
                await response.Content.CopyToAsync(ms).ConfigureAwait(false);
                ms.Position = 0;
                newResponse.Content = new StreamContent(ms);

                foreach (var v in response.Content.Headers) newResponse.Content.Headers.TryAddWithoutValidation(v.Key, v.Value);

            }
            return newResponse;
        }

```

Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • Link is now dead, perhaps you can add the relevant snippet to your answer? – NStuke Jul 27 '16 at 23:24
  • The file was moved it seems and the LoadIntoBufferAsync was removed by a newer commit: https://github.com/tavis-software/Tavis.HttpCache/commit/a85bd009d477b423ce07336c7e93f7f32a5224a1 – Eleasar Oct 14 '16 at 07:36
  • 1
    One note: this answer regards HttpResponseMessage cloning. The question was about cloning the request, not the response. The approach is fairly similar. – petko Jul 23 '19 at 07:25
2

Carlos's answer with some linq shortcuts:

public static async Task<HttpRequestMessage> Clone(this HttpRequestMessage httpRequestMessage)
{
    HttpRequestMessage httpRequestMessageClone = new HttpRequestMessage(httpRequestMessage.Method, httpRequestMessage.RequestUri);

    if (httpRequestMessage.Content != null)
    {
        var ms = new MemoryStream();
        await httpRequestMessage.Content.CopyToAsync(ms);
        ms.Position = 0;
        httpRequestMessageClone.Content = new StreamContent(ms);

        httpRequestMessage.Content.Headers?.ToList().ForEach(header => httpRequestMessageClone.Content.Headers.Add(header.Key, header.Value));
    }

    httpRequestMessageClone.Version = httpRequestMessage.Version;

    httpRequestMessage.Properties.ToList().ForEach(props => httpRequestMessageClone.Properties.Add(props));
    httpRequestMessage.Headers.ToList().ForEach(header => httpRequestMessageClone.Headers.TryAddWithoutValidation(header.Key, header.Value));

    return httpRequestMessageClone;
}

UPDATED VERSION FOR .NET VERSION > .NET 5

public static async Task<HttpRequestMessage> Clone(this HttpRequestMessage request)
{
    var clone = new HttpRequestMessage(request.Method, request.RequestUri)
    {
        Version = request.Version
    };

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

        request.Content.Headers.ToList().ForEach(header => clone.Content.Headers.TryAddWithoutValidation(header.Key, header.Value));
    }

    request.Options.ForEach(option => clone.Options.TryAdd(option.Key, option.Value));
    request.Headers
        .ForEach(header => clone.Headers.TryAddWithoutValidation(header.Key, header.Value));

    return clone;
}
Amichai
  • 174
  • 2
  • 11