Uploading file to Web API timeouts on the server - c#

I've been having this problem all day, and i'm pretty lost as to why it happens.
I'm posting a file (a .xls file) from the client side in a React JS component in response to a click on a button like this:
upload: function (e) {
e.preventDefault();
var model = this.state.model;
var xlsFile = this.refs.xlsFile.getDOMNode();
var file = xlsFile.files[0];
var fd = new FormData();
fd.append('file', file);
var request = {
groupId: this.state.vkNumber,
payload: fd
};
model.importRequest = request;
model.setImportProcessing();
MultiOrderActions.updateUsers(request);
this.setState(model.toState());
}
The request as it appears in Chrome (POST-request):
Accept:/
Accept-Encoding:gzip, deflate
Accept-Language:en-US,en;q=0.8,da;q=0.6
Connection:keep-alive
Content-Length:3799243
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryBVYYdSXRza1yRuW4
And the payload of the request:
------WebKitFormBoundaryBVYYdSXRza1yRuW4
Content-Disposition: form-data; name="file"; filename="bruger_caas.xls"
Content-Type: application/vnd.ms-excel
------WebKitFormBoundaryBVYYdSXRza1yRuW4--
Should there not be any binary data in the request? The content length is correct, and so is the content-type and filename.
In WebAPI i'm doing the following, where the ReadAsMultiPartAsync never returns:
[HttpPost]
[Route("bc/s/{*url}")]
public async Task<IHttpActionResult> Post(string url)
{
var service = CreateClient<GenericService>();
if (Request.Content.IsMimeMultipartContent())
{
try
{
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
foreach (var content in provider.Contents)
{
//Do stuff
;
}
}
catch (Exception e)
{
;
}
}
return InternalServerError();
}
When it reaches the await keyword it just dies, and times out after 2 minutes, because Chrome cancels the request.
What am i doing wrong here? I'm really not to sure if the problem is that the file is never really sent from the client side, or if it's my server side code that's the problem.
Any input would be much appreciated.

for those googling...
I was also troubleshooting timeouts for file uploads.
In my case, I was getting a deadlocked thread when using the MultipartMemoryStreamProvider and ReadAsMultipartAsync
In the end this other post had a solution for me:
Request.Content.ReadAsMultipartAsync never returns
which uses "TaskCreationOptions.LongRunning" to ensure a distinct thread and prevent a deadlock. The comments of that issue have a helpful explanation.

Related

Forward multipart/x-mixed-replace live stream ASP.NET Core

I've an ASP.NET Core server that, upon client request, start fetching a stream from an AXIS camera and returns it to the client for displaying. If the http request to server is made directly by the browser by means of img src attribute, it works fine. But if I use HttpClient (which I need to do since I need CancellationToken), the httpClient.GetStreamAsync instruction get stuck and I cannot parse the returned data.
Server side Controller (take from here):
[ApiController]
[Route("[controller]")]
public class CameraSystemController : ControllerBase
{
private string _contentTypeStreaming = "multipart/x-mixed-replace;boundary=myboundary";
private HttpClient _httpClient = new HttpClient(new HttpClientHandler { Credentials = ...});
[HttpGet("getStream")]
public async Task<IActionResult> GetStream(CancellationToken token)
{
Stream stream = await _httpClient.GetStreamAsync("http://.../axis-cgi/mjpg/video.cgi?resolution=800x600&compression=50", token);
if (stream != null) {
FileStreamResult result = new FileStreamResult(stream, _contentTypeStreaming) {
EnableRangeProcessing = true
};
return result;
} else {
return new StatusCodeResult((int)HttpStatusCode.ServiceUnavailable);
}
}
}
Now, as I said, as long as I make the browser perform the http request by means of
// LiveCamera.razor
<img src="CameraSystem/getStream" onerror="#OnImgLoadingError" alt="">
Data acquired in live mode by the camera is correctly displayed in browser.
If instead I make the request in the client this way:
//LiveCamera.razor.cs
[Inject] public HttpClient Http { get; private set; }
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private async void StartStreamingRequest(object sender, ElapsedEventArgs e) {
Console.WriteLine("I'm about to request live stream");
Stream responseStream = await Http.GetStreamAsync("CameraSystem/getStream", _cancellationTokenSource.Token);
Console.WriteLine("Header found!");
string boundary = "myboundary";
for (MultipartReader streamReader = new MultipartReader(boundary, responseStream); ;) {
MultipartSection nextFrame = await streamReader.ReadNextSectionAsync(_cancellationTokenSource.Token);
DisplayOneFrameCallback(nextFrame.Body);
}
}
private void DisplayOneFrameCallback(Stream body)
{
StreamReader reader = new StreamReader(body);
string frameData = reader.ReadToEnd();
_imgSrc = "data:image/jpeg;base64," + frameData;
StateHasChanged();
}
In this seconds case the request is performed correctly (server-side I can see the code executed and from task manager I can see the bandwidth usage increasing) but the client got stuck on await instruction, and subsequent code is never executed. Now, in Microsoft documentation under GetStreamAsync it is possible to read
This operation will not block. The returned Task object will complete after the response headers are read. This method does not read nor buffer the response body.
so I would expect it not to block. Thus I suppose there could be a problem in the header I'm producing server-side, even though browser request works just fine.
Just out of curiosity, I've captured with Wireshark the data between the camera and the server. The header and the initial part of the body are like this:
..-...HTTP/1.0 200 OK..Cache-Control: no-cache..Pragma: no-cache..Expires: Thu, 01 Dec 1994 16:00:00 GMT..Connection: close..Content-Type: multipart/x-mixed-replace;boundary=myboundary....--myboundary..Content-Type: image/jpeg..Content-Length: 30146......
I've double-checked with browser developer tools and I can confirm that sending the request from browser or from httpClient yield the same exact request. Furthermore right-clicking on the request url and issueing "open in new tab" opens a tab where I can see the camera live stream in both cases.
Could you please help me in this?

Web API .NET MVC - Http POST - Fiddler request send string variable

So I'm doing an API where I have to send a huge json file and a name for a variable to the database. Currently am sending and reading the file with my code, and it's working fine.
Then thing is that I have to send a string for a service name (may have "/" characters, that's why I didn't put it as the file name, or query string.
I tried sending this request (HTTP POST) :
---------------------------32r23rfewfwfaedef
Content-Disposition: form-data; name="fieldNameHere"; filename="XXXXXX.json"
Content-Type: application/json
{"name":"test"}
<#INCLUDE *C:\....\XXXXXX.json*#>
---------------------------32r23rfewfwfaedef--
The thing is that I can't find that variable anywhere in my controller... My code is like this:
public HttpResponseMessage Post()
{
if (HttpContext.Current.Request.Files.Count != 1)
throw new HttpResponseException(new HttpResponseMessage()
{
ReasonPhrase = "One file is required, a json in order to create the Swagger.",
StatusCode = HttpStatusCode.BadRequest
});
SwaggerSaveModel model = new SwaggerSaveModel();
HttpPostedFile postedFile = HttpContext.Current.Request.Files[0];
using (System.IO.StreamReader myFile = new System.IO.StreamReader(postedFile.InputStream))
{
var XmlObj = new StreamReader(postedFile.InputStream).ReadToEnd();
model.SwaggerJson = XmlObj.ToString();
}
return Request.CreateResponse(HttpStatusCode.Created, "blabla");
}
The file reading is fine, but I can't find the "name" variable... I also canĀ“t change that json file, because it's a swagger and it has to be used exactly as it is being sent.
Please help..
For future doubts like mine, I figured out how to do this. I'm sending my request like this:
parameter: FileName
And accessing on my controller like this:
HttpContext.Current.Request.Params["HTTP_PARAMETER"]
Then it returns "FileName"

Upload files Asp.NET MVC 4 - Web API - Http Put verb/method

So I have to make a method in asp.net for an API that accepts 2 files via PUT (1 json and 1 xml for processing data, not saving- because I have to, OK? :) ), sending the request via fiddler..
Right now I'm sending the request like this on fiddler (PUT METHOD):
Content-Type: multipart/form-data
Authorization: XXXXX
Host: XXXX
Request body:
<#INCLUDE *C:\Users\ZZZZ.json*#>
<#INCLUDE *C:\Users\XXXX.xml*#>
Here's what I've tried inside the controller so far:
[HttpPut, Route("api/Student/{studentId}/Classes/{classId}")]
public async Task<string> Put(int studentId, int classId)
{
var file = HttpContext.Current.Request.Files.Count > 0 ?
HttpContext.Current.Request.Files[0] : null;
Stream fileContent = await this.Request.Content.ReadAsStreamAsync();
MediaTypeHeaderValue contentTypeHeader = this.Request.Content.Headers.ContentType;
if (fileContent != null)
return "ok";
return "not ok";
}
So far the file is not being uploaded nor appears within the request (everything's null). I've also tried the "Request" variable and HttpContext.
Tried the exact same thing but with a POST Method (including the boundaries) and the same happens.
What would you do in order to make this work? I really have to send a json object and another in xml, I really can't change languages or send everything in json ('cause that I could make it work)...
PS: The files don't have a defined structure, it has to be dynamic
PS2 : How would you then attempt to read those files without actually saving them?
You don't have to use a stream to read the file content. You can just try using the HttpPostedFile.
[HttpPut, Route("api/student/{studentId}/classes/{classId}")]
public async Task<string> Put(int studentId, int classId)
{
if (HttpContext.Current.Request.Files.Count == 0)
throw new HttpResponseException(new HttpResponseMessage()
{
ReasonPhrase = "Files are required",
StatusCode = HttpStatusCode.BadRequest
});
foreach (string file in HttpContext.Current.Request.Files)
{
var postedFile = HttpContext.Current.Request.Files[file];
if (!(postedFile.ContentType == "application/json" || postedFile.ContentType == "application/xml"))
{
throw new System.Web.Http.HttpResponseException(new HttpResponseMessage()
{
ReasonPhrase = "Wrong content type",
StatusCode = HttpStatusCode.BadRequest
});
}
}
return "OK";
}
POSTMAN
My POSTMAN:
enter image description here
Fiddler

C# StreamContent and File.OpenRead() Not Producing HTTP'able multipart content

I have a .Net Core 2.0 application that is sending files to a Web API endpoint, using multipart content. Everywhere I've looked, such as C# HttpClient 4.5 multipart/form-data upload, makes it seem that it should be as easy as passing a FileStream to a StreamContent. However, when I make the post, it looks like the file is attaching as text, not bits.
Actual code:
var request = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri( "http://localhost:10442/filetest" )
};
var multiContent = new MultipartFormDataContent();
var filestream = File.OpenRead( path );
var filename = Path.GetFileName( path );
var streamContent = new StreamContent( filestream );
streamContent.Headers.Add( "Content-Type", "application/octet-stream" );
streamContent.Headers.Add( "Content-Disposition", $"form-data; name=\"file1\"; filename=\"{filename}\"" );
multiContent.Add( streamContent, "file", filename );
request.Content = multiContent;
var response = await new HttpClient().SendAsync( request );
The request looks like this which, as you may notice, is not all on one line (which I think is a/THE problem):
POST http://localhost:10442/filetest HTTP/1.1
Content-Type: multipart/form-data; boundary="c5295887-425d-4ec7-8638-20c6254f9e4b"
Content-Length: 88699
Host: localhost:10442
--c5295887-425d-4ec7-8638-20c6254f9e4b
Content-Type: application/octet-stream
Content-Disposition: form-data; name="file1"; filename="somepdf.pdf"
%PDF-1.7
%
1 0 obj
<</Type/Catalog/Version/1.7/Pages 3 0 R/Outlines 2 0 R/Names 8 0 R/Metadata 31 0 R>>
endobj
Fiddler shows the entire post all the way down to the end boundary, but await Request.Content.ReadAsStringAsync() in the endpoint only shows the first couple dozen bytes (it looks as if the stream wasn't finished, but if Fiddler got it all, shouldn't my endpoint have too?).
I was having similar trouble trying to hit a remote endpoint; I built this endpoint to test locally.
The exception I'm getting is:"Unexpected end of MIME multipart stream. MIME multipart message is not complete." To me, this makes sense both if I'm really only getting part of my stream, or if the line breaks are throwing something off.
I have also tried throwing some of the Idisposables into Usings but, as expected, that closes the streams and I get exceptions that way.
And for completeness's sake, here's the endpoint I'm calling:
public async void ReceiveFiles()
{
// exception happens here:
var mpData = await Request.Content.ReadAsMultipartAsync();
await Task.FromResult( 0 );
}
Try something like this:
static int Main(string[] args)
{
var request = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri("http://localhost:10442/filetest")
};
var path = "c:\\temp\\foo.bak";
using (var filestream = File.OpenRead(path))
{
var length = filestream.Length.ToString();
var streamContent = new StreamContent(filestream);
streamContent.Headers.Add("Content-Type", "application/octet-stream");
streamContent.Headers.Add("Content-Length", length);
request.Content = streamContent;
Console.WriteLine($"Sending {length} bytes");
var response = new HttpClient().SendAsync(request).Result;
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
return 0;
}
and
[HttpPost]
public async Task<IActionResult> Upload()
{
var buf = new byte[1024 * 64];
long totalBytes = 0;
using (var rs = Request.Body)
{
while (1 == 1)
{
int bytesRead = await rs.ReadAsync(buf, 0, buf.Length);
if (bytesRead == 0) break;
totalBytes += bytesRead;
}
}
var uploadedData = new
{
BytesRead = totalBytes
};
return new JsonResult(uploadedData) ;
}
I'm trying to solve a similar issue, and I'm not 100% to a solution yet, but maybe some of my research can help you.
It was helpful to me to read through the microsoft docs for .NET core file uploads, specifically for large files that use streams and multipart form data:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1#uploading-large-files-with-streaming
You already referenced it, but there's some relevant useful information in this answer:
C# HttpClient 4.5 multipart/form-data upload
This explains the details of the content-disposition header and how it is used with multipart form data requests: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#As_a_header_for_a_multipart_body
As to your specific problem of the file being sent as text instead of bits, since http is text-based, it can only be sent as text, but that text can be encoded as you see fit. Perhaps your StreamContent needs a specific encoding to be used, like base64 encoding or similar? I do believe the newlines are significant in the multipart request, so hopefully setting the encoding for the file content as needed would be enough.
Another possibility: could it be that you need to set additional information on the file section's headers or in the definition of the StreamContent to indicate that it should expect to continue, or that the boundary information isn't put in correctly? See Multipart forms from C# client
I use this lib : https://github.com/jgiacomini/Tiny.RestClient
It's make easier to send multiplart file to send multipart files.
Here a sample :
await client.PostRequest("MultiPart/Test").
AsMultiPartFromDataRequest().
AddStream(stream1, "request", "request2.bin").
AddStream(stream2, "request", "request2.bin")
ExecuteAsync();

WebAPI cannot parse multipart/form-data post

I'm trying to accept a post from a client (iOS app) and my code keeps failing on reading the stream. Says the message is not complete. I've been trying to get this working for hours it seems like something is wrong in my message format or something. All I'm trying to do is read a string but the developer I'm working with who is doing the iOS part only knows how to send multipart/form-data not content-type json.
Here is exact error:
Unexpected end of MIME multipart stream. MIME multipart message is not complete."
It fails here: await Request.Content.ReadAsMultipartAsync(provider);
Headers:
POST http://localhost:8603/api/login HTTP/1.1
Host: localhost:8603
Accept-Encoding: gzip,deflate
Content-Type: multipart/form-data; boundary=------------nx-oauth216807
Content-Length: 364
Accept-Language: en-us
Accept: */*
Connection: keep-alive
Body:
--------------nx-oauth216807
Content-Disposition: form-data; name="token"
CAAH5su8bZC1IBAC3Qk4aztKzisZCd2Muc3no4BqVUycnZAFSKuleRU7V9uZCbc8DZCedYQTIFKwJbVZCANJCs4ZCZA654PgA22Nei9KiIMLsGbZBaNQugouuLNafNqIOTs9wDvD61ZA6WSTd73AVtFp9tQ1PmFGz601apUGHSimYZCjLfGBo40EBQ5z6eSMNiFeSylym1pK4PCvI17fXCmOcRix4cs96EBl8ZA1opGKVuWizOsS0WZCMiVGvT
--------------nx-oauth216807--
Here is the WebAPI code:
public async Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
try
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
My application was experiencing this error periodically too. Upgrading to WEB API 2.1 did nothing and the exception message is completely useless.
I think what was actually happening though is it was choking on large files. Increasing my max request limits in web.config seemed to fix it right up.
<system.web>
<httpRuntime maxRequestLength="30000" />
</system.web>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="30000" />
</requestFiltering>
</security>
</system.webServer>
(This sets the ceiling to 30 megs. Set it to whatever you need. More info here)
I encountered this error too. The InnerException is Cannot access a disposed object. This means that something is reading your stream before your call to ReadAsMultipartAsync.
Somewhere before this call Request.Content.ReadAsMultipartAsync(provider), you can call
Request.Content.LoadIntoBufferAsync().Wait(), which will load this tream into a buffer and allow you to read it more than once.
This is not an optimal solution, but it works.
I am leaving this here since it took me quite some time trying other workarounds until I bumped onto the following helpful answer and some people having this issue may end up on this post.
A \r\n needs to be appended at the end of request content stream.
Instead of using this line to read the data:
await Request.Content.ReadAsMultipartAsync(provider);
You will need to:
load the request stream to memory
append the \r\n string that is required
create a stream content from the memory content
manually add the request headers to the stream content
Finally use this instead:
streamContent.ReadAsMultipartAsync(provider);
Check the answer of Landuber Kassa here for the complete code: ASP.NET Web API, unexpected end of MIME multi-part stream when uploading from Flex FileReference
Just a modification for Shaw Levin answer in case anyone wants to use it.
boundary = value.Substring(0, value.IndexOf("\r\n")); will find the first occurance of the CRLF, you should change it to boundary = value.Substring(0, value.LastIndexOf("\r\n")); so it looks for the last occurance. Otherwise if the content includes a CRLF somewhere in the middle you will lose part of the data in the request.
There were similar error posts, for some, the solution worked is: to mention Id="", name="" attribute to file upload html control, thanks to WebAPI upload error. Expected end of MIME multipart stream. MIME multipart message is not complete
But in my case, it did not resolve with above simple tweak :(
I would not recommend this answer - hopefully there is a better way available now.
Someone asked so here is my custom parser which has been working fine:
Boundary comes from here:
string value;
using (var reader = new StreamReader(tempStream, Encoding.UTF8))
{
value = reader.ReadToEnd();
// Do something with the value
}
boundary = value.Substring(0, value.IndexOf("\r\n"));
And then we parse the content of the request here:
public Dictionary<string, BodyContent> ParseContent(string content)
{
string[] list = content.Split(new string[] { boundary }, StringSplitOptions.RemoveEmptyEntries);
string name="", val="";
Dictionary<string, BodyContent> temp = new Dictionary<string, BodyContent>();
foreach (String s in list)
{
if (s == "--" || s == "--\r\n")
{
//Do nothing.
}
else
{
string[] token = s.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
val = "";
name = "";
foreach (string x in token)
{
if(x.StartsWith("Content-Disposition"))
{
//Name
name = x.Substring(x.IndexOf("name=")+5, x.Length - x.IndexOf("name=")-5);
name = name.Replace(#"\","");
name = name.Replace("\"","");
}
if (x.StartsWith("--"))
{
break;
}
if (!x.StartsWith("--") && !x.StartsWith("Content-Disposition"))
{
val = x;
}
}
if (name.Length > 0)
{
BodyContent b = new BodyContent();
b.content = name;
if (val.Length == 0)
{
b.value = "";
}
else
{
b.value = val;
}
temp.Add(name, b);
}
}
}
return temp;
}

Categories

Resources