Stephen Ellis
Stephen Ellis

Reputation: 2711

How to forward a response from a remote server in .net core?

I think I've fallen down a rabbit hole, and suspect there is a far easier solution than the one I'm trying. Basically within my .net core MVC app, I want to pull a streaming video file from an external API.

So that means, for any requests to my action, I want to call an external API function across the wire. I need to add a header to the request, but pass on all other headers that have been requested, and then once I've heard back from the API I will forward the response in its entirety to the consumer. It's basically like a Redirect without the StatusCode, and adding a header to the redirected source.

The way I'm doing it is as follows. In my controller, I call a ForwardedResponseResult:

var client = await this.FileAssetsApiContext.GetHttpClient();
//Copy the Headers
foreach (var header in Request.Headers)
{
    header.Value.ToList().ForEach(p => {
        if (client.DefaultRequestHeaders.Contains(header.Key))
        {
            client.DefaultRequestHeaders.Remove(header.Key);
            client.DefaultRequestHeaders.Add(header.Key, p);
         }
         else
         {
             client.DefaultRequestHeaders.Add(header.Key, p);
         }});
    }

    var fullPath = Url.Combine(VideosRootPath, path);
    var response = await client.GetAsync(url);
    return new ForwardedResponseResult(response);     

And then I have a forwarding class:

  public class ForwardedResponseResult : ActionResult
{
    public override void ExecuteResult(ActionContext context)
    {
        throw new NotImplementedException();
    }
    public ForwardedResponseResult(HttpResponseMessage httpResponseMessage)
    {
        this.HttpResponseMessage = httpResponseMessage;
    }

    public override async Task ExecuteResultAsync(ActionContext context)
    {
        var responseToCopy = this.HttpResponseMessage;
        var responseToCopyHeaders = responseToCopy.Headers.ToArray();
        var responseToCopyContentHeaders = responseToCopy.Content.Headers.ToArray();
        var streamToCopy = await responseToCopy.Content.ReadAsStreamAsync();
        var responseToReturnTo = context.HttpContext.Response;
        var streamToCopyTo = responseToReturnTo.Body;

        var headers = responseToCopy.Headers.ToArray();
        var contentHeaders = responseToCopy.Content.Headers.ToArray();
        var headersTo = responseToReturnTo.Headers.ToArray();

        foreach (var header in responseToCopy.Headers)
        {
                header.Value.ToList().ForEach(p => responseToReturnTo.Headers.Add(header.Key, p));
        }
        responseToReturnTo.StatusCode = (int) responseToCopy.StatusCode;

        foreach (var header in responseToCopy.Content.Headers)
        {
            header.Value.ToList().ForEach(p => responseToReturnTo.Headers.Add(header.Key, p));
        }

        //Copy to the output buffer
        var remainingBytes = streamToCopy.Length;

        while (remainingBytes > 0)
        {
            //var bufferSize = 1024;
            var bufferSize = 8096;
            var buffLen = remainingBytes > bufferSize ? bufferSize : remainingBytes;
            var buffer = new byte[buffLen];
            streamToCopy.Read(buffer, 0, (int)buffLen);
            streamToCopyTo.Write(buffer, 0, (int)buffLen);
            remainingBytes -= buffLen;
        }
        //streamToCopy.CopyTo(streamToCopyTo);
        //await streamToCopyTo.FlushAsync();

    }
    public HttpResponseMessage HttpResponseMessage { get; }

}

This seems like massive overkill, and the performance for range requests is dire. Am I missing something obvious? Is there a way of doing this more simply?

Upvotes: 0

Views: 1194

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456887

I want to pull a streaming video file from an external API.

In the case of large downloads, you want to be sure to use HttpCompletionOption.ResponseHeadersRead. Otherwise, the GetAsync call is actually loading the entire response content into memory (and the .Content.ReadAsStreamAsync is not actually asynchronous).

Also, you can make use of the existing FileStreamResult type. Something like this should work:

var client = await this.FileAssetsApiContext.GetHttpClient();
... // Copy Request.Headers to client.DefaultRequestHeaders.

var fullPath = Url.Combine(VideosRootPath, path);
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
var stream = await client.Content.ReadAsStreamAsync();

... // Copy response.Headers / response.Content.Headers to Response.Headers
return File(stream, "application/octet-stream");

I don't know of a clean way to copy request/response headers, but it seems like a good place for a couple of utility methods.

Upvotes: 1

Related Questions