HttpClient usage in polly - c#

Wanted to verify if HttpCLient instance should be created outside method passed to polly for ExecuteAsync, or in?
My current usage varies between the two options and I am not sure which is the correct one?
Also, if it incurs some drawbacks, or possible memory leaks, etc. ?
Get:
var client = new HttpClient(new NativeMessageHandler()) { Timeout = new TimeSpan(0, 0, TimeOutSec) };
var httpResponse = await AuthenticationOnUnauthorizePolicy.ExecuteAsync(async () =>
{
UpdateClientHeader(client, correlationId);
return await client.GetAsync(url, token);
});
Post:
var httpResponse = await AuthenticationOnUnauthorizePolicy.ExecuteAsync(async () =>
{
using (var client = new HttpClient(new NativeMessageHandler()) { Timeout = new TimeSpan(0, 0, TimeOutSec) })
{
UpdateClientHeader(client, correlationId);
WriteNetworkAccessStatusToLog();
return await client.PostAsync(url, content);
}
});
The policy used here:
AuthenticationOnUnauthorizePolicy = Policy
.HandleResult<HttpResponseMessage>(reposnse => reposnse.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(1, onRetryAsync:
async (response, count, context) =>
{
_logger.Info("Unauthorized Response! Retrying Authentication...");
await Authenticate();
});
Appreciates any comments on the code above.
Is there a correct way?
Do I need to use the Context to get the client again, or is my usage okay?
Update:
Authenticate method:
public virtual async Task Authenticate()
{
// lock it - only one request can request token
if (Interlocked.CompareExchange(ref _isAuthenticated, 1, 0) == 0)
{
var result = new WebResult();
var loginModel = new LoginModel
{
email = _settingService.Email,
password = _settingService.Password
};
var url = ......
var correlationId = Guid.NewGuid().ToString();
try
{
var stringObj = JsonHelper.SerializeObject(loginModel);
HttpContent content = new StringContent(stringObj, Encoding.UTF8, HttpConsts.JsonMediaType);
using (var client = new HttpClient(new NativeMessageHandler()) { Timeout = new TimeSpan(0, 0, TimeOutSec) }
)
{
UpdateClientHeader(client, correlationId, useToken: false); // not token, we need new one
using (var httpResponse = await client.PostAsync(url, content))
{
var sReader = await httpResponse.Content.ReadAsStringAsync();
await HandleRequest(result, sReader, httpResponse, correlationId, url, "result");
}
}
if (result != null && !result.HasError)
{
_loginToken = result.Token;
}
}
catch (Exception ex)
{
// Log error
}
finally
{
_isAuthenticated = 0;
}
}
}
Update client headr method:
if (_loginToken != null &&
!client.DefaultRequestHeaders.Contains("Token"))
{
client.DefaultRequestHeaders.Add("Token", _loginToken );
}
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(HttpConsts.JsonMediaType));

Related

System.Text.Json Cannot access a dispose jsonDocument

could someone help me how to fix this error?
I can't resolve this until now. I can't figure out where the problem is.
"Cannot access a disposed object. Object name: 'JsonDocument'"
I just started to use "Sytem.Text.Json" that's why I'm still learning and want to to know how to use it properly.
Thank you.
public static async Task<JsonElement> ParseJsonData(string api, CancellationToken ct)
{
clientHandler = new HttpClientHandler()
{
UseProxy = Proxy.IsUseProxy ? true : false,
Proxy = Proxy.IsUseProxy ? new WebProxy($"{Proxy.ProxyHost}:{Proxy.ProxyPort}") : null,
//ServerCertificateCustomValidationCallback = (sender, certificate, chain, errors) => { return true; },
// SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};
var uri = new Uri(api, UriKind.Absolute);
utils.SetConnection(uri);
client = new HttpClient(clientHandler);
using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
{
AddRequestHeaders(request, uri);
return await ResponseMessage(request, ct);
}
}
private static async Task<JsonElement> ResponseMessage(HttpRequestMessage request, CancellationToken ct)
{
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false))
{
ct.ThrowIfCancellationRequested();
using (var content = response.Content)
{
var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
var json = await ParseStream(stream, response);
return json.RootElement;
}
}
}
private static async Task<JsonDocument> ParseStream(Stream stream, HttpResponseMessage response)
{
if (stream == null || stream.CanRead == false)
{
return default;
}
HttpStatusCode status = response.StatusCode;
StatusCode.status = status.ToString();
StatusCode.value = (int)status;
using (var json = await JsonDocument.ParseAsync(stream).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
throw new ApiException()
{
Content = json.RootElement.ToString(),
StatusCode = status.ToString(),
value = (int)status,
};
}
return json;
}
}
UPDATE: (Here's what I've tried)
public static async Task<JsonDocument> ParseJsonData(string api, CancellationToken ct)
{
clientHandler = new HttpClientHandler()
{
UseProxy = Proxy.IsUseProxy ? true : false,
Proxy = Proxy.IsUseProxy ? new WebProxy($"{Proxy.ProxyHost}:{Proxy.ProxyPort}") : null,
ServerCertificateCustomValidationCallback = (sender, certificate, chain, errors) => { return true; }
// SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};
var uri = new Uri(api, UriKind.Absolute);
utils.SetConnection(uri);
client = new HttpClient(clientHandler);
using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
{
AddRequestHeaders(request, uri);
return await ResponseMessage(request, ct);
}
}
private static async Task<JsonDocument> ResponseMessage(HttpRequestMessage request, CancellationToken ct)
{
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false))
{
ct.ThrowIfCancellationRequested();
HttpStatusCode status = response.StatusCode;
using (var content = response.Content)
{
var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
if (stream == null || stream.CanRead == false) { return default; }
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
var json = await JsonDocument.ParseAsync(stream, options).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
throw new ApiException()
{
Content = json.RootElement.ToString(),
StatusCode = status.ToString(),
value = (int)status,
};
}
return json;
}
}
}
public static async Task<test> GetData(string id, CancellationToken ct)
{
string API = $"https://www.test.com/api/videos/{id}";
using (var root = await MyClientHelper.ParseJsonData(API, ct))
{
var json = root.RootElement;
//here i can access the root and dispose after
return new test()
{
/////
}
}
}
It's the way using works. When you leave a using clause, the object is disposed. That's on purpose.
So consider your code:
using (var json = await JsonDocument.ParseAsync(stream).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
throw new ApiException()
{
Content = json.RootElement.ToString(),
StatusCode = status.ToString(),
value = (int)status,
};
}
return json; <------ the moment you return it you also dispose it
}
So when you try to access it outside, you are getting the error:
var json = await ParseStream(stream, response);
// here your object is already disposed
return json.RootElement;
Solution: before existing the parse function, return your json. The JsonDocument object should not be used outside the using clause.
You should NOT omit to dispose of the object as a workaround: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsondocument?view=netcore-3.1
Failure to properly dispose this object will result in the memory not being returned to the pool, which will increase GC impact across various parts of the framework.
Luckily, there's the Clone() method. So instead of:
using JsonDocument doc = JsonDocument.Parse(jsonString);
return doc; // or return doc.RootElement;`
You can do this:
using JsonDocument doc = JsonDocument.Parse(jsonString);
var root = doc.RootElement.Clone();
return root;
"Gets a JsonElement that can be safely stored beyond the lifetime of the original JsonDocument."
https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.clone?view=net-5.0

HttpClient with dynamic proxy

I have a method MultiLikeAsync(username, password, proxy) which can be executed multiple times and each time with different username, password, proxy or no proxy at all.
for (int i = 0; i < 15; ++i)
{
Class class = new Class();
await class.MultiLikeAsync(random_username, random_password, random_proxy);
}
This is my current setup:
public class Class
{
private static readonly HttpClient _httpClient;
static Class()
{
if (_httpClient == null)
{
var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
_httpClient = new HttpClient(handler);
}
}
public async Task MultiLikeAsync(string username, string password, string proxy = null)
{
//_httpClient.Proxy = ??? // Can't do that
// first request
HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, "url");
// Polly
var timeoutPolicy = Policy.TimeoutAsync(1);
HttpResponseMessage httpResponse = await timeoutPolicy.ExecuteAsync(async ct => await _httpClient.SendAsync(httpRequest, ct), CancellationToken.None);
// second request
HttpRequestMessage httpRequest2 = new HttpRequestMessage(HttpMethod.Post, "different_url");
// Polly
HttpResponseMessage httpResponse2 = await timeoutPolicy.ExecuteAsync(async ct => await _httpClient.SendAsync(httpRequest2, ct), CancellationToken.None);
}
}
The problem with my solution is that I can't dynamically set _httpClient.Proxy for each MultiLikeAsync call. It has to be set along with HttpClientHandler during HttpClient instantiation or with multiple HttpClient instances.
It's a bad idea to reinstantiate HttpClient more than once, because it creates socket exhaustion and that's the reason they introduced IHttpClientFactory in .NET Core 2.1. Even if I used IHttpClientFactory, I would have had same problem, because it doesn't allow me to dynamically change proxies for each request. More info about it.
I need a solution immune to socket exhaustion (just like IHttpClientFactory) that allows me use a different proxy for each MultiLikeAsync call. Additionally, I want to keep my Polly Timeout.
Edit:
After your example given below, it's kinda the same. It doesn't close the TIME_WAIT requests. They are disposed after 240 seconds.
public class Class
{
private const int Timeout = 6;
public async Task MultiLikeAsync(string username, string password, string proxy = null)
{
using HttpClientHandler httpHandler = new HttpClientHandler();
if (proxy != null)
{
httpHandler.Proxy = new WebProxy(proxy, true);
}
if (httpHandler.SupportsAutomaticDecompression)
{
httpHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
using HttpClient httpClient = new HttpClient(httpHandler);
try
{
using HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, "url");
var timeoutPolicy = Policy.TimeoutAsync(Timeout);
HttpResponseMessage httpResponse = await timeoutPolicy.ExecuteAsync(async ct => await httpClient.SendAsync(httpRequest, ct), CancellationToken.None);
if (httpResponse.IsSuccessStatusCode)
{
string content = await httpResponse.Content.ReadAsStringAsync();
...
}
using HttpRequestMessage httpRequest2 = new HttpRequestMessage(HttpMethod.Post, "different_url");
HttpResponseMessage httpResponse2 = await timeoutPolicy.ExecuteAsync(async ct => await httpClient.SendAsync(httpRequest2, ct), CancellationToken.None);
if (httpResponse2.IsSuccessStatusCode)
{
string content = await httpResponse2.Content.ReadAsStringAsync();
...
}
}
catch (TimeoutRejectedException ex)
{
if (ex.InnerException is TaskCanceledException)
{
}
}
catch (HttpRequestException ex)
{
if (ex.InnerException is SocketException)
{
}
}
catch (Exception)
{
}
}
}

waiting for an exception from an Action

I have this function:
public static async Task<T> Relay<T>(string uriController, T jsonObject)
{
var json = string.Empty;
try
{
LogHelpers.LogVerbose( "Enter " + uriController );
using ( var client = new HttpClient() )
{
HttpResponseMessage response;
client.BaseAddress = Uri;
client.Timeout = new TimeSpan( 0, 0, 0, 20, 0 );
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue( "application/json" ) );
if ( jsonObject != null )
{
var jsonInput = JsonConvert.SerializeObject( jsonObject );
var contentPost = new StringContent( jsonInput, Encoding.UTF8, "application/json" );
response = await client.PostAsync( uriController, contentPost ).ConfigureAwait( false );
}
else
{
response = await client.PostAsync( uriController, null ).ConfigureAwait( false );
}
response.EnsureSuccessStatusCode();
if (response.StatusCode != HttpStatusCode.OK)
{
return default(T);
}
json = await response.Content.ReadAsStringAsync().ConfigureAwait( false );
LogHelpers.LogVerbose( "Exit " + uriController );
SendNotification?.Invoke();
}
}
catch (Exception e)
{
throw e;
}
return JsonConvert.DeserializeObject<T>(json);
}
and this is called by:
private async void GetPiSystemInfo()
{
SystemPiInfo = await RaspberryPi.Relay<SystemPiInfo>("Api/SystemPiInfo/IsConnected", null);
}
which in turn is run from this:
private async Task<bool> RunTheMethod(Action myMethodName)
{
await Task.Run(
() =>
{
while (true)
{
try
{
myMethodName();
return true;
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
});
return false;
}
Which finally is called from this:
Action[] Actions = new Action[10];
Actions[0] = GetPiSystemInfo;
await RunTheMethod(Actions[0]);
The reason for this architecture is that when my app starts it will connect to my Raspberry Pi box. Several api calls are made to get the data from that box.
If for any reason connection is not made or an error is returned I wish to rerun the action until it does so (obviously there would be a cut off).
Task<T> Relay<T>()
Method is generic as it will be called from many different places. So, whenever a server error occurs I would catch and throw back up the stack.
What is happening though is that
return true;
is being called as soon as the action is called.

A task was cancelled error

I am using REST call, the code is like:
public bool restfunc(string id)
{
var result = Task.Factory.StartNew(() => RunAsync(id)).Result; //RunAsync(id).Wait();
return true;
}
public static Task RunAsync(string id)
{
try
{
dynamic result = null;
string pathValue = WebConfigurationManager.AppSettings["R2G2APIUrl"];
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(pathValue);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromMinutes(5);
Task responsetask = null;
var jobid = id.Split('_')[1];
client.GetAsync("OnTranscriptionStarted/" + jobid).ContinueWith((requesttask) =>
{
responsetask = requesttask;
HttpResponseMessage resp = requesttask.Result; //HERE ERR OCCUR
resp.EnsureSuccessStatusCode();
resp.Content.ReadAsStringAsync().ContinueWith((readtask) =>
{
result = JsonConvert.DeserializeObject(readtask.Result);
});
});
}
return result;
Doubt:
I have API in pathvalue..I have to write pathvalue in
GetAsync() or function name and id which I want to call?
Help me out from this problem please..
You can use UriBuilder for that:
var builder = new UriBuilder(
"http", pathValue, 80, string.Format("OnTranscriptionStarted/{0}", jobId));
client.GetAsync(builder.Uri.ToString());

How to return the Task<T> from HttpResponseMessage.Content.ReadAsStringAsync().ContinueWith() method?

I am trying to Get/Post using the HttpClient Class and facing the following issues
Do not know how to return the Task from HttpResponseMessage.Content.ReadAsStringAsync().ContinueWith() method.
For some reason, it is keep on cancelling automatically
private static Task<T> HttpClientSendAsync<T>(string url, object data, HttpMethod method, string contentType, CancellationToken token)
{
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(method, url);
RetryDelegatingHandler retryDelegatingHandler = new RetryDelegatingHandler();
retryDelegatingHandler.PreAuthenticate = true;
retryDelegatingHandler.Credentials = Credential;
retryDelegatingHandler.Proxy = null;
HttpClient httpClient = new HttpClient(retryDelegatingHandler);
httpClient.Timeout = new TimeSpan(Constants.TimeOut);
if (data != null)
{
byte[] byteArray = Encoding.ASCII.GetBytes(Helper.ToJSON(data));
MemoryStream memoryStream = new MemoryStream(byteArray);
httpRequestMessage.Content = new StringContent(new StreamReader(memoryStream).ReadToEnd(), Encoding.UTF8, contentType);
}
Task<HttpResponseMessage> httpResponseMessage = httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.ContinueWith((task) =>
{
if (!task.IsFaulted)
{
HttpResponseMessage response = task.Result;
response.Content.ReadAsStringAsync().ContinueWith(
(stringTask) =>
{
if (!stringTask.IsFaulted)
{
return Helper.FromJSON<T>(stringTask.Result);
}
else
{
Logger.Log(string.Format("SendAsyncRequest Task IsFaulted: {0} \nException: {1}", typeof(T), task.Exception));
UpdateError(typeof(T).ToString());
return default(T);
}
});
}
else
{
Logger.Log(string.Format("SendAsyncRequest Task IsFaulted: {0} \nException: {1}", typeof(T), task.Exception));
UpdateError(typeof(T).ToString());
return default(T);
}
});
}
Update: It does work however still it does not work while trying to handle the Fault.
return httpClient.SendAsync(httpRequestMessage).ContinueWith(task =>
{
var response = task.Result;
return response.Content.ReadAsStringAsync().ContinueWith(stringTask =>
{
var json = stringTask.Result;
return Helper.FromJSON<T>(json);
});
}).Unwrap();
Task.ContinueWith returns the continuation task: a Task or Task<T>. If I'm understanding the question, in your case here you could do something like this:
var continuation = httpResponseMessage.ContinueWith((task) =>
{
if (!task.IsFaulted)
{
HttpResponseMessage response = task.Result;
return response.Content.ReadAsStringAsync().ContinueWith(
(stringTask) =>
{
...
and continuation will end up being a Task<Task<T>> which you can call .Unwrap() on to turn that into a proxy task Task<T>.

Categories