Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning anything but ok in HttpResponseMessage results in serverside webservice call delays

Tags:

c#

asp.net

I'm calling 2 webservices from a .Net 3.5 Framework Client (Server is a more modern .NET Framework Server: 4.6).

Separated I run into no Problems, but if I call the methods in the order shown below I get the Problem that the VerifyFile method on the Server is never entered, and I instead immediately get a The server has committed a protocol Violation Section=ResponseStatusLine error on the Client. To be more exact: The Server Registers in the Events the VerifyFile request but doesn't enter the actual code until up to 6 minutes later (and immediately Returns something instead that causes the above error).

After much testing I could reduce it to the first method "DownloadFile" being the cause of the Problem. And that always when I return anything else than the statuscode ok from it (Content or not Content doesn't matter).

I'm at a complete loss with this phenomenon (I would have expected the Client to have Troubles, but the Server not entering that one code part until MINUTES later Looks like the Server is getting into Troubles itself, which is unexpected, also unexpected is that the SECOND method and not the original one is running into Problems there).

So my question is why is returning anything but HttpStatusCode.OK causing These Problems and what can I do to correct this?

Client:

WebClient webClient = new WebClient();
webClient.QueryString.Add("downloadId", id);
webClient.DownloadFile("localhost/Download/DownloadFile", @"c:\temp\local.txt");
webClient = new WebClient();
webClient.QueryString.Add("downloadId", id);
webClient.QueryString.Add("fileLength", GetFileLength(@"c:\temp\local.txt"));
var i = webClient.DownloadString("localhost/Download/VerifyFile");

Testwise I replaced the DownloadFile with: webClient.DownloadString("localhost/Download/DownloadFile");

Originally I also had only one new WebClient, but added the second one after the first failures.

Server:

[RoutePrefix("Download")]
public class DownloadController : ApiController
{
    [HttpGet]
    [Route("DownloadFile")]
    public IHttpActionResult DownloadFile(int downloadId){
        return ResponseMessage(GetFile(downloadId));
    }

    private HttpResponseMessage GetFile(int downloadId){
        HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);

        string filePath = GetFilePathFromDB(downloadid);

        if (filePath != String.Empty){
            var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite);
            result.Content = new StreamContent(stream);
            result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(filePath);
            result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            result.Content.Headers.ContentLength = stream.Length;   
        }
        else{
            result = new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }       
        return result;
    }

    [HttpGet]
    [Route("VerifyFile")]
    public IHttpActionResult VerifyFile(int downloadId, int fileLength)
    {
        return Content(HttpStatusCode.OK, "OK");
    }


    private string GetFilePathFromDB(int downloadId)
    {
         return @"C:\temp\mytest.txt"; // testcode
    }
}
like image 886
Thomas Avatar asked Jun 08 '18 09:06

Thomas


3 Answers

You can try three things.

  1. Follow the http spec so you don't get this error

  2. Add this to your client web.config

    <system.net>
       <settings>
         <httpWebRequest useUnsafeHeaderParsing="true" />
       </settings>
    </system.net>
    
  3. Set connnection header to close instead of keep alive

    class ConnectionCloseClient : WebClient
    {
        protected override WebRequest GetWebRequest(Uri address)
        {
            WebRequest request = base.GetWebRequest(address);
            if (request is HttpWebRequest)
            {
                (request as HttpWebRequest).KeepAlive = false;
            }
            return request;
        }
    }
    
like image 104
ATerry Avatar answered Nov 15 '22 21:11

ATerry


My theory is that When you Get an error response from the server then the file isn't created. Suppose you are catching the DownloadFile exception that you should receive when you return any other response than OK.

GetFileLength probably returns an invalid number and according to this answer it can result in the error you have mentioned. As to why it takes 6 minutes to reach that server code - I guess it does some inner error handling before calling the method, and returning an error. Sadly ASP.net 4.* isn't open source so I am not really familiar with the inner workings.

like image 44
gilmishal Avatar answered Nov 15 '22 21:11

gilmishal


I think it's a problem with a Keep-Alive header. You can capture the http request and check this value. A value of true will try to mantain a connection opened. Not all proxies are compatible with this header.

Try to use two diferent WebClient instances and dispose them before use the next one. Or force the header to false.

WebClient webClient = new WebClient();
webClient.QueryString.Add("downloadId", id);
webClient.DownloadFile("localhost/Download/DownloadFile", @"c:\temp\local.txt");
webClient.Dispose();

webClient2 = new WebClient();
webClient2.QueryString.Add("downloadId", id);
webClient2.QueryString.Add("fileLength", GetFileLength(@"c:\temp\local.txt"));
var i = webClient2.DownloadString("localhost/Download/VerifyFile");
webClient2.Dispose();

Or wrap them in a using statement:

using (WebClient webClient = new WebClient())
{
    webClient.QueryString.Add("downloadId", id);
    webClient.DownloadFile("localhost/Download/DownloadFile", @"c:\temp\local.txt");
}

using (WebClient webClient = new WebClient())
{
    webClient2 = new WebClient();
    webClient2.QueryString.Add("downloadId", id);
    webClient2.QueryString.Add("fileLength", GetFileLength(@"c:\temp\local.txt"));
    var i = webClient2.DownloadString("localhost/Download/VerifyFile");
}

Check this post: WebClient is opening a new connection each time I download a file and all of them stay established

like image 39
Alpha75 Avatar answered Nov 15 '22 22:11

Alpha75