4

I'm using HttpWebRequest to connect to my in-house built HTTP server. My problem is that it is a lot slower than connecting to the server via for instance PostMan (https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en), which is probably using the built-in functions in Chrome to request data.

The server is built using this example on MSDN (http://msdn.microsoft.com/en-us/library/dxkwh6zw.aspx) and uses a buffer size of 64. The request is a HTTP request with some data in the body.

When connecting via PostMan, the request is split into a bunch of chunks and BeginRecieve() is called multiple times, each time receiving 64B and taking about 2 milliseconds. Except the last one, which receives less than 64B.

But when connecting with my client using HttpWebRequest, the first BeginRecieve() callback receives 64B and takes about 1 ms, the following receives only 47B and takes almost 200 ms, and finally the third receives about 58B and takes 2ms.

What is up with the second BeginRecieve? I note that the connection is established as soon as I start to write data to the HttpWebRequest input stream, but the data reception does not start until I call GetResponse().

Here is my HttpWebRequest code:

var request = (HttpWebRequest)WebRequest.Create(url);

request.Method = verb;
request.Timeout = timeout;
request.Proxy = null;
request.KeepAlive = false;
request.Headers.Add("Content-Encoding", "UTF-8");
System.Net.ServicePointManager.Expect100Continue = false;
request.ServicePoint.Expect100Continue = false;

if ((verb == "POST" || verb == "PUT") && !String.IsNullOrEmpty(data))
{
    var dataBytes = Encoding.UTF8.GetBytes(data);

    try
    {
        var dataStream = request.GetRequestStream();
        dataStream.Write(dataBytes, 0, dataBytes.Length);
        dataStream.Close();
    }
    catch (Exception ex)
    {
        throw;
    }

}

WebResponse response = null;
try
{
    response = request.GetResponse();
}
catch (Exception ex)
{
    throw;
}

var responseReader = new StreamReader(rStream, Encoding.UTF8);
var responseStr = responseReader.ReadToEnd();

responseReader.Close();
response.Close();

What am I doing wrong? Why is it behaving so much differently than a HTTP request from a web browser? This is effectively adding 200ms of lag to my application.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
DukeOf1Cat
  • 1,087
  • 15
  • 34
  • AFAIK the buildin .Net WebRequest is really slow. Maybe this would explain the difference http://stackoverflow.com/questions/2519655/httpwebrequest-is-extremely-slow?rq=1 – CodingBarfield Aug 27 '13 at 12:36
  • @CodingBarfield Yeah, I saw that thread and I've tried the suggestions but to no succes :( – DukeOf1Cat Aug 27 '13 at 12:42
  • Have you tried setting `request.SendChunked = true;`? See [HttpRequest.SendChunked](http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.sendchunked.aspx) – Jim Mischel Aug 27 '13 at 13:46
  • @JimMischel I tried it now, and it does not seem to help. From what I can understand, it only splits the request up into multiple HTTP requests, right? I still get problems with the packets within a single request. – DukeOf1Cat Aug 27 '13 at 14:15
  • Another possibility is to set `request.ContentLength = dataBytes.Length` before you get the request stream. That *might* have an effect on how data is buffered. – Jim Mischel Aug 27 '13 at 15:18
  • You should remove those try/catch blocks; they do nothing for you. – John Saunders Aug 27 '13 at 15:22
  • @JohnSaunders There is some error handling there, I just removed it before pasting for the sake of simplicity. – DukeOf1Cat Aug 27 '13 at 16:06
  • @JimMischel I've tried with and without setting the ContentLength explicitly, it does not seem to make any difference. – DukeOf1Cat Aug 27 '13 at 16:07
  • what is the variable 'data'? –  May 02 '14 at 09:18

4 Answers4

4

This looks like a typical case of the Nagle algorithm clashing with TCP Delayed Acknowledgement. In your case you are sending a small Http Request (~170 bytes according to your numbers). This is likely less than the MSS (Maximum Segment Size) meaning that the Nagle Algorithm will kick in. The server is probably delaying the ACK resulting in a delay of up to 500 ms. See links for details.

You can disable Nagle via ServicePointManager.UseNagleAlgorithm = false (before issuing the first request), see MSDN.

Also see Nagle’s Algorithm is Not Friendly towards Small Requests for a detailed discussion including a Wireshark analysis.

Note: In your answer you are running into the same situation when you do write-write-read. When you switch to write-read you overcome this problem. However I do not believe you can instruct the HttpWebRequest (or HttpClient for that matter) to send small requests as a single TCP write operation. That would probably be a good optimization in some cases. Althought it may lead to some additional array copying, affecting performance negatively.

Jakob Möllås
  • 4,239
  • 3
  • 33
  • 61
  • Very good answer but be careful because ServicePointManager.UseNagleAlgorithm = false; is a global settings which means it will turn off this algorithm for all of your endpoint and for all of your requests in the entire App Domain. When you call more than one service endpoints (usually that is the case) with mixed sized of Request it will bite back. So you should consider setting this only for one specific ServicePoint, you can acquire it by: ServicePointManager.FindServicePoint(); and not set it globally. – Major Sep 14 '17 at 08:12
3

200ms is the typical latency of the Nagle algorithm. This gives rise to the suspicion that the server or the client is using Nagling. You say you are using a sample from MSDN as the server... Well there you go. Use a proper server or disable Nagling.

Assuming that the built-in HttpWebRequest class has an unnecessary 200ms latency is very unlikely. Look elsewhere. Look at your code to find the problem.

usr
  • 168,620
  • 35
  • 240
  • 369
2

It seems like HttpWebRequest is just really slow.

Funny thing: I implemented my own HTTP client using Sockets, and I found a clue to why HttpWebRequest is so slow. If I encoded my ASCII headers into its own byte array and sent them on the stream, followed by the byte array encoded from my data, my Sockets-based HTTP client behaved exactly like HttpWebRequest: first it fills one buffer with data (part of the header), then it uses another buffer partially (the rest of the header), waits 200 ms and then sends the rest of the data.

The code:

TcpClient client = new TcpClient(server, port);
NetworkStream stream = client.GetStream();

// Send this out
stream.Write(headerData, 0, headerData.Length);
stream.Write(bodyData, 0, bodyData.Length);
stream.Flush();

The solution was of course to append the two byte arrays before sending them out on the stream. My application is now behaving as espected.

The code with a single stream write:

TcpClient client = new TcpClient(server, port);
NetworkStream stream = client.GetStream();

var totalData = new byte[headerBytes.Length + bodyData.Length];
Array.Copy(headerBytes,totalData,headerBytes.Length);
Array.Copy(bodyData,0,totalData,headerBytes.Length,bodyData.Length);

// Send this out
stream.Write(totalData, 0, totalData.Length);
stream.Flush();

And HttpWebRequest seems to send the header before I write to the request stream, so it might be implemented somewhat like my first code sample. Does this make sense at all?

Hope this is helpful for anyone with the same problem!

DukeOf1Cat
  • 1,087
  • 15
  • 34
  • I thought I should comment here - later on we discovered that using HttpWebRequest works fine as long as you disable nagling and set up the client service point correctly. – DukeOf1Cat Sep 17 '14 at 13:06
-1

Try this: you need to dispose of your IDisposables:

var request = (HttpWebRequest)WebRequest.Create(url);

request.Method = verb;
request.Timeout = timeout;
request.Proxy = null;
request.KeepAlive = false;
request.Headers.Add("Content-Encoding", "UTF-8");
System.Net.ServicePointManager.Expect100Continue = false;
request.ServicePoint.Expect100Continue = false;

if ((verb == "POST" || verb == "PUT") && !String.IsNullOrEmpty(data))
{
    var dataBytes = Encoding.UTF8.GetBytes(data);

    using (var dataStream = request.GetRequestStream())
    {
        dataStream.Write(dataBytes, 0, dataBytes.Length);
    }
}

string responseStr;
using (var response = request.GetResponse())
{
    using (var responseReader = new StreamReader(rStream, Encoding.UTF8))
    {
        responseStr = responseReader.ReadToEnd();
    }
}
John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • You should either setup static property once, or setup instance one for your requests. But not both – Grigory Nov 01 '20 at 09:04