We are seeing an issue where a customer is hitting our API and cancelling the request before it completes. We believe that they are also closing the connection so the requests are not getting written to the IIS logs. I'm trying to create a runner to duplicate this behavior. What I can't figure out is how can I force close the connection so that the server cannot send data back?
Here is my current code:
private static async Task RunMe()
{
var cts = new CancellationTokenSource();
cts.CancelAfter(5000);
using (var client = new HttpClient())
{
try
{
client.DefaultRequestHeaders.ConnectionClose = true;
client.DefaultRequestHeaders.Add("x-apikey", "some key");
Console.WriteLine("making request");
using (var response = await client.GetAsync("https://foo.com/api/apitest/testcancel", cts.Token))
{
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Request completed: {result}");
}
}
finally
{
client.CancelPendingRequests();
Console.WriteLine("about to dispose the client");
client.Dispose();
}
}
}
}
The code correctly cancels the request after 5 seconds but IIS still seem to send back data after the API call completes (set to 10 seconds).
How can I close the connection so that IIS cannot send back any data after my 5s timeout?
I was finally able to duplicate the problem by using raw sockets, which is a lot of work. The way to get IIS to log the cancelled requests is to do the following in the code below:
Connection: Close
Socket.Dispose()
Here is the final working code, hopefully someone else will find this useful:
private static void SocketTime(int readTimeout, Random random)
{
var ip = IPAddress.Parse("127.0.0.1");
const int hostPort = 80;
var hostep = new IPEndPoint(ip, hostPort);
var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.Connect(hostep);
var uniqueID = random.Next().ToString();
var get = $"GET http://localhost/api/apitest/testcancel/{uniqueID} HTTP/1.1\r\n" +
"x-apikey: somekey\r\n" +
"Host: foo.com\r\n" +
"Connection: Close\r\n" +
"\r\n";
var getBytes = Encoding.UTF8.GetBytes(get);
sock.ReceiveTimeout = readTimeout;
sock.Send(getBytes);
var responseData = new byte[1024];
try
{
var bytesRead = sock.Receive(responseData);
var responseString = new StringBuilder();
while (bytesRead != 0)
{
responseString.Append(Encoding.ASCII.GetChars(responseData), 0, bytesRead);
bytesRead = sock.Receive(responseData);
}
Console.WriteLine($"SUCCESS => Unique ID = {uniqueID}, Timeout = {readTimeout}");
}
catch (SocketException e)
{
if (e.SocketErrorCode == SocketError.TimedOut)
{
Console.WriteLine($"FAIL => Unique ID = {uniqueID}, Timeout = {readTimeout}");
}
else
{
Console.WriteLine("UNHNALDED ERROR: " + e.Message);
}
}
finally
{
sock.Dispose();
}
}
You are probably hit by a very low timeout setting. If you set it to a sufficiently short amount of time, the client will close the connection although the server still tries to return data.
If this doesn't reproduce the problem you could also look into concurrency and the property 'UseNagleAlgorithm' on the servicepoint manager. This property being true attempts to pigiback several small http requests in the same TCP package. It is a band with saver, but causes client timeouts as the os delays sending data through the socket until it has gathered enough data.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With