Reputation: 2711
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
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