Forward Stream using ASP.Net WebAPI - c#

I'm using .Net Core 2.0 to develop my ASP.Net API and I want to make request for external public stream and forward it with my own GET route.
The external stream is an MJPEG content-type.
Since I'm using the latest version of .NET Core, the PushStreamContent is not available anymore.
This is the class that takes care of the Connection and Stream process:
internal class LiveViewStream
{
HttpClient _client = new HttpClient();
WebClient webClient = new WebClient();
public Stream outputStream = new MemoryStream();
public Stream GetVideoTCP()
{
string url = "http://87.139.76.248:8081/cgi-bin/faststream.jpg";
return webClient.OpenRead(url);
}
public void Main()
{
var bytesRead = 0;
var buffer = new byte[65536];
using (Stream stream = GetVideoTCP())
{
do
{
bytesRead = stream.Read(buffer, 0, buffer.Length);
outputStream.Write(buffer, 0, bytesRead);
}
while (stream.Position != stream.Length);
}
}
And this is the GET route:
// GET: api/<controller>
[HttpGet]
public HttpResponseMessage Get()
{
var video = new LiveViewStream();
video.Main();
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(video.outputStream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("video/x-motion-jpeg");
return response;
}
This code gives me error with while method not being supported. I have tried different methods, and they didn't work, I would either get a file when making a GET request or nothing.
Can anyone help me with this please?

I resolved this issue by using Socket TCP server and forwarding the images with a custom MjpegWriter to client-socket.

Related

Get the file postion of each file into a stream

I am sending multiples files from my web api but I want to read each part of the stream to convert him into a byte array , then at the end I have a list of byte[], and I can save each files:
[Route("GetFiles")]
public HttpResponseMessage GetFile([FromUri] List<string> filesNames)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
if (filesNames.Count == 0)
return Request.CreateResponse(HttpStatusCode.BadRequest);
var content = new MultipartContent();
filesNames.ForEach(delegate (string fileName)
{
string filePath = System.Web.Hosting.HostingEnvironment.MapPath("~/Uploads/" + fileName);
byte[] pdf = File.ReadAllBytes(filePath);
content.Add(new ByteArrayContent(pdf));
response.Headers.Add(fileName, fileName);
});
var files = JsonConvert.SerializeObject(content);
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response = Request.CreateResponse(HttpStatusCode.OK, content);
return response;
}
Here is how I get one file into a stream, then convert him into a byte array to report the process percentage :
public static async Task<byte[]> CreateDownloadTaskForFile(string urlToDownload, IProgress<DownloadBytesProgress> progessReporter)
{
int receivedBytes = 0;
int totalBytes = 0;
WebClient client = new WebClient();
using (var stream = await client.OpenReadTaskAsync(urlToDownload))
{
byte[] buffer = new byte[BufferSize];
totalBytes = Int32.Parse(client.ResponseHeaders[HttpResponseHeader.ContentLength]);
using (MemoryStream memoryStream = new MemoryStream())
{
for (; ; )
{
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
memoryStream.Write(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
await Task.Yield();
break;
}
receivedBytes += bytesRead;
if (progessReporter != null)
{
DownloadBytesProgress args = new DownloadBytesProgress(urlToDownload, receivedBytes, totalBytes);
progessReporter.Report(args);
}
}
return memoryStream.ToArray();
}
}
}
How do I get the position of a stream for each files send ?
Update :
I made a HttpResponseMessage like this :
[Route("GetFiles")]
public HttpResponseMessage GetFiles([FromUri] List<string> filesNames)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
if (filesNames.Count == 0)
return Request.CreateResponse(HttpStatusCode.BadRequest);
var content = new MultipartFormDataContent();
filesNames.ForEach(delegate (string fileName)
{
string filePath = System.Web.Hosting.HostingEnvironment.MapPath("~/Uploads/" + fileName);
byte[] pdf = File.ReadAllBytes(filePath);
content.Add(new ByteArrayContent(pdf), fileName);
});
response = Request.CreateResponse(HttpStatusCode.OK, content);
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return response;
}
But from my device side : When I am trying to run the request But there is nothing on the response content :
using (var httpResponseMessage = await httpClient.GetAsync(urlToDownload + filesNamesArg))
{
var streamProvider = new MultipartMemoryStreamProvider();
streamProvider = httpResponseMessage.Content.ReadAsMultipartAsync().Result;
}
Could you show me some docs or advice ?
What?
This answer provides a 100% working example for:
Serving multiple files as a single response from a web API using multipart/mixed content type,
Reading the file contents on the client by parsing the response of the web API implemented in 1
I hope this helps.
Server:
The server application is a .Net 4.7.2 MVC project with web API support.
The following method is implemented in an ApiController and returns all the files under the ~/Uploads folder in a single response.
Please make note of the use of Request.RegisterForDispose extension to register the FileStreams for later disposal.
public async Task<HttpResponseMessage> GetFiles()
{
string filesPath = System.Web.Hosting.HostingEnvironment.MapPath("~/Uploads");
List<string> fileNames = new List<string>(Directory.GetFiles(filesPath));
var content = new MultipartContent();
fileNames.ForEach(delegate(string fileName)
{
var fileContent = new StreamContent(File.OpenRead(fileName));
Request.RegisterForDispose(fileContent);
fileContent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("image/jpeg");
content.Add(fileContent);
});
var response = new HttpResponseMessage();
response.Content = content;
return response;
}
The response's Content-Type header shows as Content-Type: multipart/mixed; boundary="7aeff3b4-2e97-41b2-b06f-29a8c23a7aa7" and each file is packed in different blocks separated by the boundary.
Client:
The client application is a .Net Core 3.0.1 console application.
Please note the synchronous usage of the async methods. This can be easily changed to asynchronous using await, but implemented like this for simplicity:
using System;
using System.IO;
using System.Net.Http;
namespace console
{
class Program
{
static void Main(string[] args)
{
using (HttpClient httpClient = new HttpClient())
{
using (HttpResponseMessage httpResponseMessage = httpClient.GetAsync("http://localhost:60604/api/GetImage/GetFiles").Result)
{
var content = (HttpContent)new StreamContent(httpResponseMessage.Content.ReadAsStreamAsync().Result);
content.Headers.ContentType = httpResponseMessage.Content.Headers.ContentType;
MultipartMemoryStreamProvider multipartResponse = new MultipartMemoryStreamProvider();
content.ReadAsMultipartAsync(multipartResponse);
for(int i = 0; i< multipartResponse.Contents.Count;i++)
{
Stream contentStream = multipartResponse.Contents[i].ReadAsStreamAsync().Result;
Console.WriteLine("Content {0}, length {1}", i, contentStream.Length);
}
}
}
}
}
}

How to receive a byte array and header content in a ASP.NET Core Web API Controller

I need to receive a content together with a byte array in a c# ASP.NET Core Web API application.
**--HttpClient (console Application)**
Dictionary<int, byte[]> fileparts = new Dictionary<int, byte[]>();
int bufferSize = 1000 * 1024;
byte[] buffer;
string filePath = #"D:\Extra\Songs\test.mp3";
using (FileStream fileData = File.OpenRead(filePath))
{
int index = 0;
int i = 1;
while (fileData.Position < fileData.Length)
{
if (index + bufferSize > fileData.Length)
{
buffer = new byte[(int)fileData.Length - index];
fileData.Read(buffer, 0, ((int)fileData.Length - index));
}
else
{
buffer = new byte[bufferSize];
fileData.Read(buffer, 0, bufferSize);
}
fileparts.Add(i, buffer);
index = (int)fileData.Position;
i++;
}
}
while (fileparts.Count() != 0)
{
var data = fileparts.First();
var fileContent = new ByteArrayContent(data);//byte content
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "test" };
fileContent.Headers.Add("id", id);
fileContent.Headers.Add("name", name);
fileContent.Headers.Add("length", length);
if (fileparts.Count() == 1)
fileContent.Headers.Add("IsCompleted", "true");
else
fileContent.Headers.Add("IsCompleted", "false");
using (var content = new MultipartFormDataContent())
{
content.Add(fileContent);
// Make a call to Web API
var result = client.PostAsync("/api/file", fileContent).Result;
if (result.StatusCode == System.Net.HttpStatusCode.OK)
fileparts.Remove(data.Key);
}
**--WebApi Core ApiController**
public class FileController : Controller
{
[HttpPost]
public async Task<IActionResult> Post()
{
/*i need to get the content here , for some reason existing .NET libraries does not work here
like
MultipartMemoryStreamProvider provider = new MultipartMemoryStreamProvider();
FilePart part = null;
await Request.Content.ReadAsMultipartAsync(provider); -- request.content not available
using (Stream fileStream = await provider.Contents[0].ReadAsStreamAsync())
{
part = provider.Contents[0].Headers.GetData(); //public static FilePart GetData name,length,id
part.Data = fileStream.ReadFully();
*/
}
the back end I have working but can't find a way of the new asp.net core controller parsing fileobject and data from client post through to the service! Any ideas would be greatly appreciated as always...
You can modify your controller action method like below to accept ByteArrayContent OR MultipartFormDataContent as required. I have used [FromBody] attribute for the model binding in POST.
Here is more info on [FromBody]
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding
public class FileController : Controller
{
[HttpPost]
public async Task<IActionResult> Post([FromBody] MultipartFormDataContent content)
{
MultipartMemoryStreamProvider provider = new MultipartMemoryStreamProvider();
FilePart part = null;
// access the content here
await content.ReadAsMultipartAsync(provider);
// rest of the code
}
}
as discussed before you should be using content to post to this API like below
var result = client.PostAsync("/api/file", content).Result;
I've used this sucessfully in the past:
[HttpPost]
public async Task<IActionResult> Post(IFormFile formFile)
{
using (Stream stream = formFile.OpenReadStream())
{
//Perform necessary actions with file stream
}
}
I believe you'll also need to change your client code to match the parameter name:
fileContent.Headers.Add("name", "formFile");

How to create an unlimited stream endpoint in asp.net core

I'd like to create:
an ASP.NET Core 1.1 Web Application in VS2017 with one HttpGet Endpoint
The endpoint should stream random Values between 0 and 255
Every 20ms the value should change.
The Client should never stop reading the stream and as fast as possible get the new values.
So far this is my Code:
Client
public async Task SubscribeToUpdates()
{
this.subscribe = false;
try
{
var client = new HttpClient();
var stream = await client.GetStreamAsync(Constants.SubscribeEndpoint);
using (var rdr = new StreamReader(stream))
{
while (!rdr.EndOfStream && !subscribe)
{
var result = rdr.ReadLine();
var json = JObject.Parse(result);
this.HandleUpdateResult(json); // todo
}
}
}
catch (Exception e)
{
// TO do: log exception
}
}
Server, not working
[HttpGet]
public Task PushStreamContent()
{
HttpContext.Response.ContentType = "text/event-stream";
var sourceStream = randomStream();
return sourceStream.CopyToAsync(HttpContext.Response.Body);
}
public static Stream randomStream()
{
Random rnd = new Random();
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(JsonConvert.SerializeObject(rnd.News(0,255));
writer.Flush();
stream.Position = 0;
return stream;
}
Question:
How to rewrite Server function to create the unlimited stream?
Other solutions are also welcome, as long as they keep in mind that they are .net core conform and enable the client to get as fast as possible the latest information.
Working Full .Net Version
I've managed to write the Code for .net Standard, but not for .net core. Reason for this is that PushStreamContent does not exist in .net core :/.
[HttpGet]
public HttpResponseMessage PushStreamContent()
{
var response = Request.CreateResponse(HttpStatusCode.Accepted);
response.Content =
new PushStreamContent((stream, content, context) =>
{
var plotter = new Plotter();
while (true)
{
using (var writer = new StreamWriter(stream))
{
Random rnd = new Random()
writer.WriteLine(rnd.Next(0,255));
stream.Flush();
Thread.Sleep(20);
}
}
});
return response;
}
Thanks to the previous Answer from "Mike McCaughan" and "Joel Harkes", i've rethinked the communcation process and switched from REST to Websockets.
You can find a good Example how to use WebSockets in .net Core (and Xamarin.Forms) here. You will have to use the Nuget Package "Websockets.PCL".

Using Telegram API via HTTP

I'm trying to use the Telegram API via http (documentation on their site says this is possible) to authorize, following these instructions:
https://core.telegram.org/mtproto/auth_key#dh-exchange-initiation
https://core.telegram.org/mtproto/description#unencrypted-message
However, I cannot get any response from the server except a 404 page. Here is the code I'm using:
async Task<String> SendAuthorizeRequestTEST()
{
HttpClient client = new HttpClient();
String message = "req_pq#60469778 3761821:int128 = ResPQ";
HttpContent content = new ByteArrayContent(Packetify(message));
HttpResponseMessage msg = await client.PostAsync(new Uri("http://149.154.167.40:443"), content);
byte[] bytes = await msg.Content.ReadAsByteArrayAsync();
return Encoding.UTF8.GetString(bytes);
}
public byte[] Packetify(String message)
{
var memoryStream = new MemoryStream();
var binaryWriter = new BinaryWriter(memoryStream);
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
binaryWriter.Write(0); //auth_key_id
binaryWriter.Write(1234567); //message_id
binaryWriter.Write(messageBytes.Length); //message_data_length
binaryWriter.Write(messageBytes); //message_data
byte[] packet = memoryStream.ToArray();
memoryStream.Dispose();
binaryWriter.Dispose();
return packet;
}
What am I doing wrong?
You could study what webogram does. It uses the HTTP protocol to speak to telegram.
Further more here are some steps you can use to move along quickly
https://stackoverflow.com/a/34929980/44080
cheers.

ASP.Net Web API doesn't read all bytes from StreamContent

I have an ASP.Net Web API set up on my website that is used to communicated with a WPF desktop application. I have an action setup on the API to receive binary files from the client application. However in some (seemingly random) cases when I get all the bytes from the request not all the bytes are read. Hopefully you can give me an idea of how to do this in a way that will work all of the time. Here's the code:
Client Side:
public static SubmitTurnResult SubmitTurn(int turnId, Stream fileStream)
{
HttpClient client = CreateHttpClient();
HttpContent content = new StreamContent(fileStream);
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
content.Headers.ContentDisposition.FileName = "new-turn.Civ5Save";
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Headers.ContentLength = fileStream.Length;
HttpResponseMessage response = client.PostAsync(
string.Format("SubmitTurn?authKey={0}&turnId={1}",
LocalSettings.Instance.AuthenticationKey,
turnId
),
content
).Result;
response.EnsureSuccessStatusCode();
return response.Content.ReadAsAsync<SubmitTurnResult>().Result;
}
SubmitTurnResult is an enum that defines the result on the server, turnId is the ID for the entity this file is attached to, and fileStream is an actual FileStream reading the bytes of disk.
Server Side:
[HttpGet, HttpPost]
public SubmitTurnResult SubmitTurn(string authKey, int turnId)
{
try
{
bool worked = false;
int gameId = 0;
using (GmrEntities gmrDb = new GmrEntities())
{
var player = gmrDb.Users.FirstOrDefault(u => u.AuthKey == authKey);
if (player != null)
{
var turn = player.Turns.FirstOrDefault(t => t.TurnID == turnId);
if (turn != null)
{
byte[] saveFileBytes = null;
using (MemoryStream tempStream = new MemoryStream())
{
var task = this.Request.Content.CopyToAsync(tempStream);
task.Wait();
saveFileBytes = tempStream.ToArray();
tempStream.Close();
}
if (saveFileBytes.Length != this.Request.Content.Headers.ContentLength.Value)
{
throw new Exception(string.Format("Byte array length ({0}) not equal to HTTP content-length header ({1}). This is not good!",
saveFileBytes.Length, this.Request.Content.Headers.ContentLength.Value));
}
worked = GameManager.SubmitTurn(turn, saveFileBytes, gmrDb);
if (worked)
{
gameId = turn.Game.GameID;
gmrDb.SaveChanges();
}
}
}
}
return SubmitTurnResult.OK;
}
catch (Exception exc)
{
DebugLogger.WriteExceptionWithComments(exc, string.Format("Diplomacy: Sumbitting turn for turnId: {0}", turnId));
return SubmitTurnResult.UnexpectedError;
}
}
As noted in my previous comment, we ran into this same behavior with StreamContent, but when streaming a response from a Windows Server 2003 Web API service. It doesn't repro on 2008. Actually, it also repros on Windows Server 2008 if I configure the VM with a small amount of RAM (712 MB), but with 4 GB of RAM it doesn't repro. Also, we found that this only repros with a FileStream. Converting the FileStream to a MemoryStream bypasses the issue (at the expense of memory of course). We found that when the response stream terminates early, it's always on a 4096-byte boundary, and it hits a cap at around 3.5MB.
Here's the workaround that fixed things for me, tailored to your code example:
public static SubmitTurnResult SubmitTurn(int turnId, Stream fileStream)
{
HttpClient client = CreateHttpClient();
var memoryStream = new MemoryStream((int)fileStream.Length);
fileStream.CopyTo(memoryStream);
fileStream.Close();
memoryStream.Seek(0, SeekOrigin.Begin);
HttpContent content = new StreamContent(memoryStream);
If desired, you can conditionally do the MemoryStream copy only when Stream is a FileStream.

Categories

Resources