I have written an API endpoint and created a simple .net core console app to send off multiple requests to the API endpoint simultaneously to test how the API endpoint works
The code looks as below
static async Task Main(string[] args)
{
// ARRANGE
int request_number = 200;
Task<HttpResponseMessage>[] tasks = new Task<HttpResponseMessage>[request_number];
Action[] actions = new Action[request_number];
for (int i = 0; i < request_number; i++)
{
int temp = i;
actions[temp] = () =>
{
tasks[temp] = CallMyAPI();
};
}
// ACT
Parallel.Invoke(actions);
await Task.WhenAll(tasks);
// ASSERT
string sample1 = await tasks[0].Content.ReadAsStringAsync();
for (int i = 1; i < request_number; i++)
{
string toBeTested = await tasks[i].Content.ReadAsStringAsync();
if (toBeTested != sample1)
{
Console.WriteLine("Wrong! i = " + i);
}
}
Console.WriteLine("finished");
Console.WriteLine("Press any key to complete...");
Console.Read();
}
static async Task<HttpResponseMessage> CallMyAPI()
{
var request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
string contentString = "some json string as http body";
request.Content = new StringContent(contentString, System.Text.Encoding.UTF8, "application/json");
request.RequestUri = new Uri("http://myAPIendpoint.com");
HttpResponseMessage response;
using (HttpClient httpClient = new HttpClient())
{
response = await httpClient.SendAsync(request);
}
return response;
}
So basically what I have been trying to do by the code is to send off multiple requests once, and wait and record all the responses. Then I compare them to verify that they all return the same response.
Initially, when I set the variable request_number as small numbers, like 50, 100, the test app runs well. However, as the request_numbergoes up and reaches around 200, it starts to throw an exception that looks like:
Inner Exception 1:
IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request.
Inner Exception 2:
SocketException: The I/O operation has been aborted because of either a thread exit or an application request
What is this kind of exception supposed to mean?
Your problem is this:
using (HttpClient httpClient = new HttpClient())
{..
}
Each of these uses a new socket.
Use a single httpclient, e.g. a static one
Related
I'm trying to mesure duration process on my API.
For that, I'm using a simple code which call n time the API at the same time.
private HttpClient _httpClient { get; } = new HttpClient();
[TestCase]
public async Task Perf_ExecuteAuthThreads()
{
await Parallel.ForEachAsync(Enumerable.Range(0, NB_THREADS).ToList(), new ParallelOptions
{
MaxDegreeOfParallelism = NB_THREADS
}, async (item, cancellationToken) => await CreateAuthThread(item));
}
private async Task CreateAuthThread(int i)
{
string tokenAuth = string.Empty;
DateTime startDate = DateTime.Now;
var stopWatchGlobal = new Stopwatch();
var stopWatchDetails = new Stopwatch();
stopWatchGlobal.Restart();
try
{
stopWatchDetails.Restart();
using var request = new HttpRequestMessage(HttpMethod.Post, RequestFactory.GetRoute(RequestFactory.RequestType.AUTH));
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Content = JsonContent.Create(RequestFactory.CreateRequest(RequestFactory.RequestType.AUTH));
// Authenticate Method
using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
stopWatchDetails.Stop();
if (response.IsSuccessStatusCode)
{
tokenAuth = (await response.Content.ReadFromJsonAsync<Token>())!.JwtToken;
}
else
{
string responseAsString = await response.Content.ReadAsStringAsync();
//Assert.Fail("Internal server Error : {0}", responseAsString);
}
}
catch { /* DO NOTHING */ }
stopWatchGlobal.Stop();
// Get business transaction Guid
var transactionGuid = new JwtSecurityToken(jwtEncodedString: tokenAuth).Claims.FirstOrDefault(x => x.Type == Constants.JWT_CLAIMS_TRANSACTION_GUID)?.Value ?? null;
// Thread Id;TransactionGuid;Begin Date;End Date;Auth Duration;Business Duration;Result Business;Total Duration;
Console.WriteLine($"\"{i}\";\"{transactionGuid}\";\"{startDate.ToString("MM/dd/yyyy hh:mm:ss.fff tt")}\";\"{DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt")}\";\"{stopWatchDetails.ElapsedMilliseconds}\";\"{(tokenAuth != string.Empty ? true : false)}\";\"{stopWatchGlobal.ElapsedMilliseconds}\"");
}
Assuming my local computer and the server have synchronous clock, I notice that a difference about 3 seconds appears between send request to API and receive request by API. This difference don't exist between the response by API and the end of the call on my computer.
I know transport layers cost time, but 3 seconds appears at expensive.
Do you know if is it possible to reduce this time ? I don't think it's "burst time" (meaning : initialization time of communication)...
My API (and my test code) are on .Net 6 and using TLS connection (HTTPS). Request and Response are classic Json, without a lot of datas.
I've a .NET Core web app that, once a web method (i.e. Test()) is invoked, call another remote api.
Basically, I do it this way (here's a POST example, called within a web method Test()):
public T PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
T returnValue = default(T);
succeded = false;
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
HttpResponseMessage res = null;
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var task = Task.Run(() => client.PostAsync($"{baseAddress}/{url}", body));
task.Wait();
res = task.Result;
succeded = res.IsSuccessStatusCode;
if (succeded)
{
returnValue = JsonConvert.DeserializeObject<T>(res.Content.ReadAsStringAsync().Result);
}
else
{
Log($"PostRead failed, error: {JsonConvert.SerializeObject(res)}");
}
}
}
catch (Exception ex)
{
Log($"PostRead error: {baseAddress}/{url} entity: {JsonConvert.SerializeObject(entity)}");
}
return returnValue;
}
Question is: if Test() in my Web App is called 10 times, in parallel, do the POST requests to the remote server are be called in parallel or in serial?
Because if they will be called in serial, the last one will take the time of the previous ones, and that's not what I want.
In fact, when I have huge list of requests, I often receive the message A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
Question is: if Test() in my Web App is called 10 times, in parallel, do the POST requests to the remote server are be called in parallel or in serial?
They will be called in parallel. Nothing in the code would make it in a blocking way.
In fact, when I have huge list of requests, I often receive the message A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
There are at least 2 things that need to be fixed here:
Properly using async/await
Properly using HttpClient
The first one is easy:
public async Task<T> PostRead<T>(string baseAddress, string url, out bool succeded, object entity = null)
{
succeded = false;
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var responseMessage = await client.PostAsync($"{baseAddress}/{url}", body);
var responseContent = await responseMessage.Content.ReadAsStringAsync();
if (responseMessage.IsSuccessStatusCode)
{
succeeded = true;
return JsonConvert.DeserializeObject<T>();
}
else
{
// log...
}
}
}
catch (Exception ex)
{
// log...
}
return default; // or default(T) depending on the c# version
}
The second one is a bit more tricky and can quite possibly be the cause of the problems you're seeing. Generally, unless you have some very specific scenario, new HttpClient() is plain wrong. It wastes resources, hides dependencies and prevents mocking. The correct way to do this is by using IHttpClientFactory, which can be very easily abstracted by using the AddHttpClient extension methods when registering the service.
Basically, this would look like this:
services.AddHttpClient<YourClassName>(x =>
{
x.BaseAddress = new Uri(baseAddress);
x.DefaultRequestHeaders.Add("Authorization", MyApiKey);
}
Then it's a matter of getting rid of all using HttpClient in the class and just:
private readonly HttpClient _client;
public MyClassName(HttpClient client) { _client = client; }
public async Task<T> PostRead<T>(string url, out bool succeded, object entity = null)
{
succeded = false;
try
{
string json = JsonConvert.SerializeObject(entity);
var responseMessage = await _client.PostAsync($"{baseAddress}/{url}", body);
//...
}
catch (Exception ex)
{
// log...
}
return default; // or default(T) depending on the c# version
}
[HttpClient call using parallel and solve JSON Parse error]
You can use task. when for parallel call . but when you use N number of calls and try to parse as JSON this may throw an error because result concatenation change the Json format so we can use Jarray. merge for combining the result .
Code
public async Task < string > (string baseAddress, string url, out bool succeded, object entity = null) {
using(HttpClient client = new HttpClient()) {
string responseContent = string.Empty;
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", MyApiKey);
string json = JsonConvert.SerializeObject(entity);
var body = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var tasks = new List < Task < string >> ();
var jArrayResponse = new JArray();
int count = 10; // number of call required
for (int i = 0; i <= count; i++) {
async Task < string > RemoteCall() {
var response = await client.PostAsync($"{baseAddress}/{url}", body);
return await response.Content.ReadAsStringAsync();
}
tasks.Add(RemoteCall());
}
await Task.WhenAll(tasks);
foreach(var task in tasks) {
string postResponse = await task;
if (postResponse != "[]") {
var userJArray = JArray.Parse(postResponse);
jArrayResponse.Merge(userJArray);
}
}
responseContent = jArrayResponse.ToString();
return responseContent;
}
}
Based on this article I decided to create single instance for my HttpClient in my project, where are sent two requests to my WebAPI from its consumer. Also to kill the connection aftrewards I decided to use solution from this article. After sending two requests using the singleton I was receiving this exception:
System.InvalidOperationException: This instance has already started
one or more requests.
So I decided to use the interface and configuration (similar) from this SO answer. Everything seems to work fine, BUT after running netstat.exe I noticed, that for my API consumer are opened two (2) different connection with various port numbers:
Note that [::1]:49153 is my WebAPI port, so I assume that [::1]:57612 and [::1]:57614 are opened for my consumer. Why? If they supposed to use the same client?
Should not it be like in the first mentioned article?
My HttpClientFactory:
public interface IHttpClientFactory
{
HttpClient CreateClient();
}
public class HttpClientFactory : IHttpClientFactory
{
static AppConfig config = new AppConfig();
static string baseAddress = config.ConnectionAPI;
public HttpClient CreateClient()
{
var client = new HttpClient();
SetupClientDefaults(client);
return client;
}
protected virtual void SetupClientDefaults(HttpClient client)
{
//client.Timeout = TimeSpan.FromSeconds(30);
client.BaseAddress = new Uri(baseAddress);
client.DefaultRequestHeaders.ConnectionClose = true;
}
}
And my two request sending methods:
public async Task<bool> SendPostRequestAsync(string serializedData)
{
CallerName = _messageService.GetCallerName();
HttpResponseMessage response = new HttpResponseMessage();
Console.Write(MessagesInfo[CallerName]);
try
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, Config.ApiPostUri);
request.Content = new StringContent(serializedData, Encoding.UTF8, "application/json");
response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
Console.WriteLine(MessagesResult[CallerName]); //success status
return true;
}
else
{
throw new Exception("Status code: " + response.StatusCode.ToString());
}
}
catch (Exception e)
{
_logger.Error(e.Message.ToString() + ", " + MessagesError[CallerName]);
CloseConnection();
return false;
}
}
and
public async Task<bool> SendGetRequestAsync()
{
CallerName = _messageService.GetCallerName();
HttpResponseMessage response = new HttpResponseMessage();
Console.Write(MessagesInfo[CallerName]);
try
{
response = await _httpClient.GetAsync(Config.ApiGetUri, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
Console.WriteLine(MessagesResult[CallerName]); //success status
return true;
}
else
{
throw new Exception("Status code: " + response.StatusCode.ToString());
}
}
catch (Exception e)
{
_logger.Error(e.Message.ToString() + ", " + MessagesError[CallerName]);
CloseConnection();
return false;
}
}
Connection is closed with:
public void CloseConnection()
{
CallerName = _messageService.GetCallerName();
var sp = ServicePointManager.FindServicePoint(new Uri(Config.ConnectionAPI));
sp.ConnectionLeaseTimeout = 1 * 1000;
Console.WriteLine();
Console.WriteLine(MessagesResult[CallerName]);
}
You need to Dispose of response, as per this discussion. You may wish to use using.
Additionally (from that link):
It's expected that the connections stay open. You don't want them to
stay open at all? By default connections are kept alive in a
connection pool for several minutes in order to avoid the expense of
creating and tearing them down for every request.
In other words, connections are pooled for future performance (for a short window of time).
Additionally, your:
var sp = ServicePointManager.FindServicePoint(new Uri(Config.ConnectionAPI));
sp.ConnectionLeaseTimeout = 1 * 1000;
should be run once at startup, not repeatedly. After doing this, you could thus remove the CloseConnection method.
My HttpClient uses digest authentication to connect to the server and expects search queries in response. These search queries can come in any time so the client is expected to leave the connection open at all times.
The connection is made using the following code:
public static async void ListenForSearchQueries(int resourceId)
{
var url = $"xxx/yyy/{resourceId}/waitForSearchRequest?token=abc";
var httpHandler = new HttpClientHandler { PreAuthenticate = true };
using (var digestAuthMessageHandler = new DigestAuthMessageHandler(httpHandler, "user", "password"))
using (var client = new HttpClient(digestAuthMessageHandler))
{
client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
var request = new HttpRequestMessage(HttpMethod.Get, url);
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout.Infinite));
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, tokenSource.Token))
{
Console.WriteLine("\nResponse code: " + response.StatusCode);
using (var body = await response.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(body))
while (!reader.EndOfStream)
Console.WriteLine(reader.ReadLine());
}
}
}
This is how the method is being used in the Main method of a console application.
private static void Main(string[] args)
{
const int serviceId = 128;
.
.
.
ListenForSearchQueries(resourceId);
Console.ReadKey();
}
This is what the output on the console window looks like:
Response code: OK
--searchRequestBoundary
Even though the timeout for the client is set to infinity, the connection times out after roughly five minutes (which is not the default timeout of the HttpClient) after the first output, throwing the following exception.
System.IO.IOException occurred
HResult=0x80131620
Message=The read operation failed, see inner exception.
Source=System.Net.Http
StackTrace:
at System.Net.Http.HttpClientHandler.WebExceptionWrapperStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.Net.Http.DelegatingStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.StreamReader.ReadBuffer()
at System.IO.StreamReader.get_EndOfStream()
at ConsoleTester.Program.<ListenSearchQueriesDigestAuthMessageHandler>d__10.MoveNext() in C:\Users\xyz\ProjName\ConsoleTester\Program.cs:line 270
Inner Exception 1:
WebException: The operation has timed out.
The DelegateHandler used for the authentication is a a rough adaption of this code (see the source section).
Why is the client timing out and how can I prevent this?
My ultimate goal is to make a call and wait indefinitely for a response. When a response does come, I don't want the connection to close because more responses might come in the future. Unfortunately, I can't change anything at the server end.
Although the default value for Stream.CanTimeout is false, returning a stream via the response.Content.ReadAsStreamAsync() gives a stream where the CanTimeout property returns true.
The default read and write time out for this stream is 5 minutes. That is after five minutes of inactivity, the stream will throw an exception. Much similar to the exception shown in the question.
To change this behavior, ReadTimeout and/or the WriteTimeout property of the stream can be adjusted.
Below is the modified version of the ListenForSearchQueries method that changes the ReadTimeout to Infinite.
public static async void ListenForSearchQueries(int resourceId)
{
var url = $"xxx/yyy/{resourceId}/waitForSearchRequest?token=abc";
var httpHandler = new HttpClientHandler { PreAuthenticate = true };
using (var digestAuthMessageHandler = new DigestAuthMessageHandler(httpHandler, "user", "password"))
using (var client = new HttpClient(digestAuthMessageHandler))
{
client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
var request = new HttpRequestMessage(HttpMethod.Get, url);
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout.Infinite));
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, tokenSource.Token))
{
Console.WriteLine("\nResponse code: " + response.StatusCode);
using (var body = await response.Content.ReadAsStreamAsync())
{
body.ReadTimeout = Timeout.Infinite;
using (var reader = new StreamReader(body))
while (!reader.EndOfStream)
Console.WriteLine(reader.ReadLine());
}
}
}
}
This fixed the exception which was actually being thrown by the stream but seemed like was being thrown by the HttpClient.
Make the method return a Task
public static async Task ListenForSearchQueries(int resourceId) {
//...code removed for brevity
}
Update the console's main method to Wait on the Task to complete.
public static void Main(string[] args) {
const int serviceId = 128;
.
.
.
ListenForSearchQueries(resourceId).Wait();
Console.ReadKey();
}
I solved this problem in the following way:
var stream = await response.Content.ReadAsStreamAsync();
while (b == 1)
{
var bytes = new byte[1];
try
{
var bytesread = await stream.ReadAsync(bytes, 0, 1);
if (bytesread > 0)
{
text = Encoding.UTF8.GetString(bytes);
Console.WriteLine(text);
using (System.IO.StreamWriter escritor = new System.IO.StreamWriter(#"C:\orden\ConSegu.txt", true))
{
if (ctext == 100)
{
escritor.WriteLine(text);
ctext = 0;
}
escritor.Write(text);
}
}
}
catch (Exception ex)
{
Console.WriteLine("error");
Console.WriteLine(ex.Message);
}
}
in this way I get byte to byte the answer and I save it in a txt
later I read the txt and I'm erasing it again. for the moment it is the solution I found to receive the notifications sent to me by the server from the persistent HTTP connection.
Hopefully, someone can enlighten me on the following.
I have a client that will do a request to a controller endpoint (there is no view, c# to c# or even C++ later). That controller will have to send responses as it fetches them asynchronously as json (sends json1 to client, then json2, then json3 until it closes the connection or send a null terminated text or similar). The purpose is to stream the results back to the client so it can start processing while the server still works.
My controller endpoint looks like this:
[HttpGet("testStream")]
public async Task testStream()
{
var response = HttpContext.Response;
response.Headers[HeaderNames.TransferEncoding] = "chunked";
for (var i = 0; i < 10; ++i)
{
await response.WriteAsync($"6\r\ntest {i}\r\n");
await response.Body.FlushAsync();
await Task.Delay(1 * 1000);
}
await response.WriteAsync("0\r\n\r\n");
await response.Body.FlushAsync();
}
My test looks like this:
static async void DownloadPageAsync()
{
// ... Target page.
string page = "http://localhost:8080/api/Stream/testStream";
Console.WriteLine("test");
while (!Debugger.IsAttached) Thread.Sleep(500);
// ... Use HttpClient.
using (HttpClient client = new HttpClient())
using (var response = await client.GetAsync(page, HttpCompletionOption.ResponseHeadersRead))
using (HttpContent content = response.Content)
{
string result = await content.ReadAsStringAsync();
do
{
Console.WriteLine(result);
result = await content.ReadAsStringAsync();
}
while (result != "null");
}
Console.WriteLine("END");
}
[Fact]
public void Test1()
{
TestSurvey.DownloadPageAsync();
}
I am getting exception when I call content.ReadAsStringAsync();
System.Net.Http.HttpRequestException : Error while copying content to a stream.
[xUnit.net 00:01:14.5836121] ---- System.IO.IOException : The read operation failed, see inner exception.
[xUnit.net 00:01:14.5836496] -------- System.Net.Http.CurlException : Failure when receiving data from the peer
[xUnit.net 00:01:14.5846837] Stack Trace:
[xUnit.net 00:01:14.5857807] at System.Net.Http.HttpContent.<LoadIntoBufferAsyncCore>d__48.MoveNext()
EDIT: Exception was due to not sending the size of the chunk
await response.WriteAsync($"6\r\ntest {i}\r\n");
but now on the test/client side, I get all the chunks at once...
To solve this, I made use of SSE or Server Side Events.
here is the server side in asp.net core:
[HttpGet("testStream")]
public async Task testStream()
{
var response = HttpContext.Response;
response.StatusCode = 200;
response.ContentType = "text/event-stream";
for (var i = 0; i < 10; ++i)
{
//the tags are either 'events:' or 'data:' and two \n indicates ends of the msg
//event: xyz \n\n
//data: xyz \n\n
await response.WriteAsync($"data: test {i}\n\n");
response.Body.Flush();
await Task.Delay(5 * 1000);
}
await response.WriteAsync("data:\n\n");
await response.Body.FlushAsync();
}
and here is the client side:
string page = "http://localhost:8080/api/Stream/testStream";
//while (!Debugger.IsAttached) Thread.Sleep(500);
using (HttpClient client = new HttpClient())
using (var s = await client.GetStreamAsync(page))
{
using (StreamReader r = new StreamReader(s))
{
string line = null;
while (null != (line = r.ReadLine()))
{
Console.WriteLine(line);
}
}
}
Usage of ReadAsStringAsync forced wait of all the message in order to proceed.