4

I have a windows service which is uploading files to the other website which is processing them. The problem is that with small files it's working fine and it's getting response from there, but with large files (about 6 minute to process) it leaves forever in a waiting mode.

Here is the part of external website post method code:

try
{
  ...
  LogResults();
  return string.Empty;
}
catch (Exception e)
{
  return e.Message;
}

The problem is that I can see logs even for large files, so it means that website always returning value, but for large files my windows service doesn't wait for them.

And here is the code from windows service

var valuesp = new NameValueCollection
{
     { "AccountId", datafeed.AccountId }
};

byte[] resultp = UploadHelper.UploadFiles(url, uploadFiles, valuesp);
response = Encoding.Default.GetString(resultp);

UploadFiles method returns value for small files, but waiting forever for large ones.

Here is complete code of UploadFiles

public static byte[] UploadFiles(string address, IEnumerable<UploadFile> files, NameValueCollection values)
{
    var request = WebRequest.Create(address);
    request.Timeout = System.Threading.Timeout.Infinite; //3600000; // 60 minutes
    request.Method = "POST";
    var boundary = "---------------------------" +
                   DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo);
    request.ContentType = "multipart/form-data; boundary=" + boundary;
    boundary = "--" + boundary;

    using (var requestStream = request.GetRequestStream())
    {
        // Write the values
        if (values != null)
        {
            foreach (string name in values.Keys)
            {
                var buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
                requestStream.Write(buffer, 0, buffer.Length);
                buffer =
                    Encoding.ASCII.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"{1}{1}", name,
                                                          Environment.NewLine));
                requestStream.Write(buffer, 0, buffer.Length);
                buffer = Encoding.UTF8.GetBytes(values[name] + Environment.NewLine);
                requestStream.Write(buffer, 0, buffer.Length);
            }
        }

        // Write the files
        if (files != null)
        {
            foreach (var file in files)
            {
                var buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
                requestStream.Write(buffer, 0, buffer.Length);
                buffer =
                    Encoding.UTF8.GetBytes(
                        string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"{2}", file.Name,
                                      file.Filename, Environment.NewLine));
                requestStream.Write(buffer, 0, buffer.Length);
                buffer =
                    Encoding.ASCII.GetBytes(string.Format("Content-Type: {0}{1}{1}", file.ContentType,
                                                          Environment.NewLine));
                requestStream.Write(buffer, 0, buffer.Length);
                requestStream.Write(file.Stream, 0, file.Stream.Length);
                buffer = Encoding.ASCII.GetBytes(Environment.NewLine);
                requestStream.Write(buffer, 0, buffer.Length);
            }
        }

        var boundaryBuffer = Encoding.ASCII.GetBytes(boundary + "--");
        requestStream.Write(boundaryBuffer, 0, boundaryBuffer.Length);
    }

    using (var response = request.GetResponse())
    using (var responseStream = response.GetResponseStream())
    using (var stream = new MemoryStream())
    {
        responseStream.CopyTo(stream);
        return stream.ToArray();
    }
}

What I'm doing wrong here?

EDIT: Locally it's working even for 7-8 minutes processing. But in live environment doesn't. Can it be related with main app IIS settings? Can it be related with windows service server settings?

EDIT 2: Remote server web.config httpRuntime settings

<httpRuntime enableVersionHeader="false" maxRequestLength="300000" executionTimeout="12000" targetFramework="4.5" />
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Gab
  • 471
  • 3
  • 10
  • 25
  • 3
    I cant see anything obvious but its worth noting that, just because your web tier receives the request, it doesnt mean your client gets the response. If it were me, Id get a copy of Fiddler and just watch the HTTP traffic go backwards and forwards and see if you can idenfify where either the response is returned from the web server (and is not spotted by .net) or that the response is never returned from the web server. Hopefully keener eyes will spot a code problem, but Fiddler would be my first diagnostic. – PhillipH Aug 21 '14 at 10:43
  • What happens if you use ```StreamReader``` instead of copying stream to memory stream? like this: ```StreamReader reader = new StreamReader(responseStream); string responseFromServer = reader.ReadToEnd();``` – Alexandr Nikitin Aug 25 '14 at 11:02
  • Same result... Actually response is very small string, so I don't think that MemoryStream is the problem. – Gab Aug 25 '14 at 12:27
  • Small edit was added to question. – Gab Aug 25 '14 at 13:03
  • Could you expand what is happening in external website method? Is it an MVC controller? How do you handle exceptions there? More code please :) – Alexandr Nikitin Aug 26 '14 at 06:12
  • The code is written first in the question. Log() method works and with try/catch block I'm always returning some value. – Gab Aug 26 '14 at 06:31
  • What happens with the failing request in Fiddler? – Alexandr Nikitin Aug 28 '14 at 09:47
  • 2
    I don't see any error handling on the posting service side. What happens if the `using (var response = request.GetResponse())` throws an exception (like a timeout for instance)? Would you see it somewhere? The thing I'm after is, are you **really** sure that the service is actually stuck waiting for a response? – user1429080 Aug 28 '14 at 12:13
  • One thing that is different when streaming web data locally and remotely is the stream chunking. Remote streaming will cut the transmission into several pieces of arbitrary length + the final stream size may not be know ahead. So any code that reads such data (both server side and the client side) must take it into account – xmojmr Aug 30 '14 at 04:47
  • Have you tried using WebClient? WebClient has method called UploadFile. That does everything as expected without much of hassle. – Akash Kava Aug 30 '14 at 12:00
  • @AkashKava, does webclient's method allows to pass additional name-value parameters? I don't think so. – Chuck Norris Aug 30 '14 at 13:35

7 Answers7

6

The problem was with Azure Load Balancer, which has Idle Timeout set to 4 minutes by default. The new post of Windows Azure blog says that this timeout is now configurable to the value between 4-30 minutes

http://azure.microsoft.com/blog/2014/08/14/new-configurable-idle-timeout-for-azure-load-balancer/

However, the problem was solved with sending additional KeepAlive bytes via TCP, which told Load Balancer to not kill requests.

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(address);
...
request.Proxy = null;
request.ServicePoint.SetTcpKeepAlive(true, 30000, 5000); //after 30 seconds, each 5 second

Setting Proxy to null (not using proxy) is mandatory action here, because otherwise proxy will not pass tcp bytes to the server.

Chuck Norris
  • 15,207
  • 15
  • 92
  • 123
  • Finally! This solved my long-standing issue. I had a long-running request which kept waiting forever when using HttpClient.GetAsync or WebRequest.GetResponseAsync. Even though the server indicated it had completed its request the request never finished. Turns out this happens when the firewall kills the connection. The confusion in searchterms between Http Keepalive and Tcp keepalive didn't help either. https://en.wikipedia.org/wiki/Keepalive#HTTP_keepalive – Robert Sirre Jan 07 '15 at 11:35
2

Upload your files with a method that comply with RFC1867

See this

And then :

 UploadFile[] files = new UploadFile[]
{
new UploadFile(fileName1),
new UploadFile(fileName2)
};

NameValueCollection form = new NameValueCollection();

form["name1"] = "value1";
form["name2"] = "xyzzy";

string response = UploadHelper.Upload(url, files, form);

That's all folks!

EDIT :

I use the method above to upload files with over 100MB in size, I don't use ASP at all, it works just perfect!

Rafik Bari
  • 4,867
  • 18
  • 73
  • 123
2

I had a similar issue where upload would work locally but timeout on server. There are 2 things at play here - I'm assuming you're talking about IIS7+. If not, let me know and I'll gladly delete my answer.

So first, there's ASP.NET which looks at this setting (under system.web):

<!-- maxRequestLength is in KB - 10 MB here. This is needed by ASP.NET. Must match with maxAllowedContentLength under system.webserver/security/requestLimits -->
<httpRuntime targetFramework="4.5" maxRequestLength="1048576" />

Then there's IIS:

<system.webServer>
    <security>
        <requestFiltering>
            <!-- maxAllowedContentLength in bytes - 10MB -->
            <requestLimits maxAllowedContentLength="10485760" />
        </requestFiltering>
    </security>
<system.webServer>

You need both of these settings to be present and the limit to match for things to work. Notice that one is in KB and the other one is in bytes.

Mrchief
  • 75,126
  • 20
  • 142
  • 189
1

Maybe it is caused by the IIS upload limit? In IIS7 the standard value is 30MB. See MSDN

EDIT1: Please be aware that if you are uploading multiple files in 1 request the size adds up.

EDIT2: In all MS examples there is always only one Stream.Write(). In MSDN it is stated: After the Stream object has been returned, you can send data with the HttpWebRequest by using the Stream.Write method. My interpretation of this sentence would be that you should call Write() only once. Put all data you want to send into the buffer and call the Write() afterwards.

EvilFonti
  • 331
  • 1
  • 7
  • No, file is only 1.5mb. Also, execution timeout and max request length was increased from web.config. – Gab Aug 25 '14 at 13:31
  • Is this the size of all combined files of one upload (I think there are more as one as you are working with an IEnumerable) or the size of one file? How many files are you uploading at once? – EvilFonti Aug 25 '14 at 13:45
  • No, UploadHelper is allowing to upload multiple files, but I have done always with single one. – Gab Aug 26 '14 at 10:57
1

Have you checked the IIS file size limit on the remote server? It defaults to 30MB, so if the files you are trying to upload are larger than that, it will fail. here's how to change the upload limit (IIS7): http://www.web-site-scripts.com/knowledge-base/article/AA-00696/0/Increasing-maximum-allowed-size-for-uploads-on-IIS7.html

solidau
  • 4,021
  • 3
  • 24
  • 45
1

You can achieve this task with AsyncCallback technique.

Whats AsyncCallback?

When the async method finish the processing, AsyncCallback method is automatically called, where post processing stmts can be executed. With this technique there is no need to poll or wait for the async thread to complete.

you can find more details from this link

AsynCallback

Community
  • 1
  • 1
Sarvaratchagan
  • 95
  • 1
  • 10
1

you should change this executionTimeout="12000" to executionTimeout="360000" wich means changing execution timeout from 2 minutes to 6 minutes.

Abdessabour Mtk
  • 3,895
  • 2
  • 14
  • 21
  • 2
    12000 seconds is 200 minutes, which is equal to 3 hours 20 minutes. – Gab Aug 28 '14 at 06:53
  • I think that there using miliseconds based time. – Abdessabour Mtk Aug 28 '14 at 19:38
  • [...executionTimeout...Specifies the maximum number of seconds that a request is allowed to execute before being automatically shut down by ASP.NET...The default is 110 seconds...](http://msdn.microsoft.com/en-us/library/vstudio/e1f13641(v=vs.100).aspx) – xmojmr Aug 30 '14 at 04:33