10

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
    }
}
Thomas
  • 2,886
  • 3
  • 34
  • 78
  • Does https://stackoverflow.com/questions/23747973/the-server-committed-a-protocol-violation-section-responsestatusline-in-c-sharp help? – mjwills Jun 08 '18 at 10:21
  • @Thomas My suspicion is that the second action is trying to access the file stream before it is available from the previous call. – Nkosi Jun 11 '18 at 14:53
  • @Nkosi I found out today what is going on and how to correct it but in all honesty I'm out of my league in figuring the why out. When I replace the ERROR return with return Request.CreateErrorResponse(HttpStatusCode.ExpectationFailed, "Some message here"); the problems stop and it all works as intended. – Thomas Jun 11 '18 at 16:30
  • @Thomas does the same happen for Not Found? – Nkosi Jun 11 '18 at 16:51
  • @Thomas, you are creating a response with no content as such and no content type, may be that is the problem here? – Tarun Lalwani Jun 12 '18 at 08:43
  • @TarunLalwani That is very quite possible. Although tbh it confuses the hell out of me why it causes lagspikes (on the Server) instead of throwing exceptions (on the Server or the Client or both). – Thomas Jun 12 '18 at 09:12
  • @Nkosi didn't test it out. I already put a try catch block around the whole part before I tried to make tests there. Although I guess you mean unhandled exceptions in General not only a not found one correct? In that case I didn't notice any unusual behaviour (only expected one instead of the strange lagspikes / delays as I have observed in my case). – Thomas Jun 12 '18 at 09:14
  • @Thomas, can you test that theory at least? – Tarun Lalwani Jun 12 '18 at 10:21
  • what does c:\temp\local.txt actually contain after you run downloadFile? does that part actually run successfully? – gilmishal Jun 12 '18 at 10:28
  • @gilmishal whenever the file was written all went okay and the ok Response was generated which. The file was there (fully with the expected Content) and no Problem. the Problem only started whenever I tried to return an error Response. – Thomas Jun 12 '18 at 10:53
  • What does GetFileLength return when you return an error from the server? – gilmishal Jun 12 '18 at 11:06
  • Use downloadstringasync to verify you aren’t stuck on connection and do not reinit wbclient but download a new url instead. –  Jun 16 '18 at 15:16
  • @gilmishal getfilelength? – Thomas Jun 17 '18 at 17:57
  • @Thomas You are adding a query parameter, named fileLength to the VerifyFile endpoint, what does it contain when the download returns an error? – gilmishal Jun 17 '18 at 18:02
  • @gilmishal I always let it default to 0 on the client if the file was not found, else it has the size of the file even if that size is 3 bytes less than hit should be – Thomas Jun 17 '18 at 18:26

3 Answers3

1

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;
        }
    }
    
Alex Terry
  • 1,992
  • 1
  • 11
  • 17
1

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.

gilmishal
  • 1,884
  • 1
  • 22
  • 37
1

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

Alpha75
  • 2,140
  • 1
  • 26
  • 49