WebClient Request Extremely slow when running from Windows Service - c#

I'm trying to make a HTTP POST request to a WebAPI Controller, I am calling it from my service. Here is the class that I am injecting into my business layer in order to make the calls:
/// <summary>
/// Makes calls to WebAPI
/// </summary>
public class WebApiProxy : IWebApiProxy
{
/// <summary>
/// Sends HTTP POST requests to WebAPI along with post values
/// </summary>
/// <param name="apiControllerName">The name of the controller to call</param>
/// <param name="postValues">The values to post to the controller</param>
public void SendApiRequest(string apiControllerName, NameValueCollection postValues)
{
using (var client = new WebClient())
{
try
{
//
// Breakpoint below this line, it's where it hangs
//
byte[] responsebytes = client.UploadValues(string.Format("http://localhost:8080/Api/{0}", apiControllerName), "POST", postValues);
string responsebody = Encoding.UTF8.GetString(responsebytes);
}
catch (Exception)
{
// TODO: Handle failed requests
}
}
}
}
I realised that it is running extremely slowly, probably around 30 seconds for every request.
As an experiment I tried doing the request from the console in Chrome using the following code and it runs lightening fast - in the blink of an eye:
$.post("http://localhost:8080/Api/FinishedTakersCount", { "QuizOwnerUserId": 1, UrlId: "0mFjBH" }, null);
I also created a quick console app to test the functionality and this also runs in the blink of an eye:
static void Main(string[] args)
{
while(true)
{
Console.WriteLine("Press Enter to make web request, or type exit...");
var typed = Console.ReadLine();
if (typed == "exit")
{
break;
}
using (var client = new WebClient())
{
try
{
var apiControllerName = "LiveTakersCount";
var postValues = new NameValueCollection();
postValues.Add("QuizOwnerUserId", "1");
postValues.Add("UrlId", "0mFjBH");
//TODO: Remove port from localhost
byte[] responsebytes = client.UploadValues(string.Format("http://localhost:8080/Api/{0}", apiControllerName), "POST", postValues);
string responsebody = Encoding.UTF8.GetString(responsebytes);
Console.WriteLine("HTTP Request completed.");
}
catch (Exception ex)
{
// TODO: Handle failed requests
Console.WriteLine("An Exception occurred with the web request: {0}", ex.Message);
}
}
}
}
You can see the values that I am posting in the above code.
I can't for the life of me understand why it would be taking so long, just because I am running the request within a Window Service.
It is the same whether it makes the call when installed on my local machine or if I debug it within Visual Studio - stepping over client.UploadValues(...) takes 30 seconds or so and the result is fine.
Why is it running slow in my Windows Service (installed or debugging in VS), but lightening fast every time when doing an AJAX request or request from a console application?

Related

IdentityServer4: IDX20803: Unable to obtain configuration from 'https://<ids_server_url>/.well-known/openid-configuration'

Using:
Frontend: Angular 14,
API: .NET Core 5, c#, MVC
IDS: .NET Core 5, c#, Razor as per ID standard
For my web app I have an instance of IdentityServer 4 running. This worked perfectly fine and without hick ups for about a year. Since recently when the app starts the login still works flawlessly and provides the token as per usual.
However, any API request thereafter return a 500 error, for about 1 minute or so, after which it works fine and without issue. Until the app is in 'rest' position (i.e. no active users) it starts of with the same error for the same amount of time.
I tried installing serilog to see if I can catch the error on the API side, to no avail.
There are no errors in the logged serilog file.
The only errors I can find are in the ASP.NET logs, which generally llok like the below;
fail: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
Exception occurred while processing message.
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://<ids_server_url>/.well-known/openid-configuration'.
---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'https://<ids_server_url>/.well-known/openid-configuration'.
---> System.Net.Http.HttpRequestException: Only one usage of each socket address (protocol/network address/port) is normally permitted. (<ids_server_url>:443)
---> System.Net.Sockets.SocketException (10048): Only one usage of each socket address (protocol/network address/port) is normally permitted.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
Nor can I catch the error on the IDS side, as that also seems to be working fine.
Accessing the .well-known/openid-configuration directly (i.e. from browser) gives a direct and correct response.
Several posts on SO indicated to add the below;
IdentityModelEventSource.ShowPII = true;
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls12;
// | SecurityProtocolType.Tls13;
This didn't seem to do anything at all to improve the error.
Would anybody be able to point me in the directions of any other possibilities?
Especially the fact that it is only about a minute at the startup of the app seems to be weird?
I thought it might be the startup of IDS instance, but given that the actual login window repsonds directly and without delay, it implies that the IDS instance is active and running?
Any ideas would be helpfull?
update: 19/02/2023
With the help of #Tore Nestenius I have been able to add some logging to the initial process but the behaviour remains erratic and only on the deployed instance. (Likely because of app_pool shutting down)
Last night according to logger, after 6 failed attempts there was a succesfull query of the openid-configuration
JwtBearerBackChannelListener
#### SendASync: https://<ids_server_url>/.well-known/openid-configuration
#### success: True
#### completed: True
#### loadtime: 132
#### url: https://<ids_server_url>/.well-known/openid-configuration
But...
The subsequent process fails (again)
fail: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
What's more is that the initial call that the frontend makes is to a non-authorized endpoint (i.e. a public endpoint) there should not be a need for any token verification on that call?
If I query the backend on that endpoint directly from the browser it responds immediately, hence the backend appears to be working correctly? (i.e. api & database respond as expected when queried from the browser) yet in the API ASP logs it indicates a failed jwtHandler call? Weird...
Could it be a timing issue that, when you deploy your application that the client starts to request the discovery document before IdentityServer is up and running?
In AddOpenIDConnect and JwtBearer, you can define your own BackchannelHttpHandler, like this:
.AddJwtBearer(opt =>
{
opt.BackchannelHttpHandler = new JwtBearerBackChannelListener();
opt.BackchannelTimeout = TimeSpan.FromSeconds(60); //default 60s
...
}
This handler is used when it needs to load and reload the discovery document.
A sample handler can look like this:
public class JwtBearerBackChannelListener : DelegatingHandler
{
public JwtBearerBackChannelListener() : base(new HttpClientHandler())
{
Console.WriteLine();
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("JwtBearerBackChannelListener");
Console.WriteLine("#### SendASync: " + request.RequestUri);
var sw = new Stopwatch();
sw.Start();
var result = base.SendAsync(request, cancellationToken);
result.ContinueWith(t =>
{
sw.Stop();
Console.WriteLine("#### success: " + result.IsFaulted);
Console.WriteLine("#### loadtime: " + sw.ElapsedMilliseconds.ToString());
Console.WriteLine("#### url: " + request.RequestUri);
Serilog.Log.Logger.ForContext("SourceContext", "JwtBearerBackChannelListener")
.ForContext("url", request.RequestUri)
.ForContext("loadtime", sw.ElapsedMilliseconds.ToString() + " ms")
.ForContext("success", result.IsCompletedSuccessfully)
.Information("Loading IdentityServer configuration");
});
return result;
}
}
This allows you to add more extensive logging and also even custom retry logic.
It is important that IdentityServer is up-and-running before the client/api starts.
One approach to solve this is to add a middleware that blocks incoming requests from being processed until IdentityServer is online, like this:
Sample code for a waiting middleware
namespace PaymentAPI.Middleware
{
/// <summary>
/// Extension method to register the middleware
/// </summary>
public static class WaitForIdentityServerMiddlewareExtensions
{
public static IApplicationBuilder UseWaitForIdentityServer(this IApplicationBuilder builder, WaitForIdentityServerOptions options)
{
return builder.UseMiddleware<WaitForIdentityServerMiddleware>(options);
}
}
public class WaitForIdentityServerOptions
{
public string Authority { get; set; }
}
/// <summary>
/// ASP.NET Core middleware that will wait for IdentityServer to respond
///
/// It will return a 503 SERVICE UNAVAILABLE if IdentityServer is not responding
///
/// This middleware is only in use until the first successfull response from IdentityServer.
/// After that this module will not do anything.
///
/// It will add the following response headers to the resonse when we return a 503 error:
///
/// - x-reason: Waiting for IdentityServer
/// - Cache-Control: no-store,no-cache,max-age=0
/// - Retry-After: 5
///
/// The authority URL will be taken from the
///
/// Written by Tore Nestenius to be used in the IdentityServer in production training class.
/// https://www.tn-data.se
///
/// </summary>
public class WaitForIdentityServerMiddleware
{
/// <summary>
/// number of seconds between each attempt to contact IdentityServer
/// </summary>
private int secondsBetweenRetries = 2;
/// <summary>
/// How many seconds should we wait before we give up waiting?
/// </summary>
private int httpRequestTimeout = 3;
/// <summary>
/// True when we have been able to reach IdentityServer
/// </summary>
private bool _identityServerReady = false;
private readonly RequestDelegate _next;
private readonly string _discoveryUrl;
private readonly SemaphoreSlim _refreshLock;
private DateTimeOffset _syncAfter = DateTimeOffset.MinValue;
private readonly DateTime _startTime;
public WaitForIdentityServerMiddleware(RequestDelegate next, IConfiguration configuration, WaitForIdentityServerOptions options)
{
_next = next;
_startTime = DateTime.UtcNow;
_discoveryUrl = buildDiscoveryUrl(options.Authority);
_refreshLock = new SemaphoreSlim(1);
}
public async Task InvokeAsync(HttpContext context)
{
//Has IdentityServer has succesfully responsed yet?
if (_identityServerReady == false)
{
//Fail fast if we should wait a bit or if there is already a request is in progress
if (_syncAfter > DateTimeOffset.UtcNow ||
_refreshLock.CurrentCount == 0)
{
//We are waiting to not overload IdentitytServer with to many requests
//Just terminate the request with a 503 Service Unavailable response
CreateServiceUnavailableResponse(context);
return;
}
//Make sure we only do one request at the time
await _refreshLock.WaitAsync().ConfigureAwait(false);
try
{
//Still not answering?
if (_identityServerReady == false)
{
_identityServerReady = await TryToReachIdentityServer(context);
}
}
catch (Exception exc)
{
Log.Logger.ForContext("SourceContext", "WaitForIdentityServerMiddleware")
.ForContext("DiscoveryUrl", _discoveryUrl)
.ForContext("Exception", exc.Message)
.ForContext("Path", context.Request.Path)
.Fatal("Exception while trying to reach IdentityServer");
}
finally
{
_refreshLock.Release();
_syncAfter = DateTimeOffset.UtcNow.AddSeconds(secondsBetweenRetries);
}
}
if (_identityServerReady)
{
// Call the next delegate/middleware in the pipeline
await _next(context);
}
else
{
//As we did not succeeed, let's terminate return a 503 SERVICE UNAVAILABLE error back to the client
CreateServiceUnavailableResponse(context);
return;
}
}
/// <summary>
/// Create a service unavailable 503 error response
/// </summary>
/// <param name="context"></param>
private void CreateServiceUnavailableResponse(HttpContext context)
{
context.Response.Headers.Add("x-reason", "Waiting for IdentityServer");
context.Response.Headers.Add("Retry-After", "5"); //Add a retry again header, with 5 seconds
context.Response.Headers.Add("Cache-Control", "no-store,no-cache,max-age=0"); //Don't cache this response
context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; //503 status code
}
/// <summary>
/// Try to reach the IdentityServer discovery endpoint
/// </summary>
/// <returns>True if successfull</returns>
private async Task<bool> TryToReachIdentityServer(HttpContext context)
{
var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(httpRequestTimeout);
var response = await client.GetAsync(_discoveryUrl);
//Should we log?
if (response.IsSuccessStatusCode == false)
{
var secondsSinceStart = (int)DateTime.UtcNow.Subtract(_startTime).TotalSeconds;
Log.Logger.ForContext("SourceContext", "WaitForIdentityServerMiddleware")
.ForContext("DiscoveryUrl", _discoveryUrl)
.ForContext("Path", context.Request.Path)
.ForContext("Tried for over", secondsSinceStart.ToString() + " seconds")
.Information("Failed to reach IdentityServer at startup");
}
return response.IsSuccessStatusCode;
}
/// <summary>
/// Construct the discovery endpoint URL
/// </summary>
/// <param name="authority"></param>
/// <returns></returns>
private string buildDiscoveryUrl(string authority)
{
string Url = authority;
if (!Url.EndsWith("/", StringComparison.Ordinal))
{
Url = Url + "/";
}
Url = Url + ".well-known/openid-configuration";
return Url;
}
}
}
Then to use the handler:
//Make sure its placed before app.UseAuthentication();
//Wait for IdentityServer to startup
app.UseWaitForIdentityServer(new WaitForIdentityServerOptions()
{ Authority = _configuration["openid:authority"] });

Calling WebApi from Console Application is Failing

I have a WebApi that is running on Azure and fully tested via Postman
This API takes 2 headers
Content-Type: application/json
AppToken: {{AppToken}}
I want to call this API from a console application and I thought it is a pretty straight forward process. Here is my Main program
static void Main(string[] args)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("BASE_ADDRESS/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("AppToken", "MY_APP_TOKEN_VALUE");
// Call API to create the registration
CreateRegistration(client).Wait();
}
public static async Task CreateRegistration(HttpClient client)
{
using (client)
{
try
{
Registration reg1 = new Registration { email = "test#ssdafsds.com", clientId = 2342342, registrationId = 23423, ProgramId = 13 };
HttpResponseMessage responseMessage = await client.PostAsJsonAsync("api/auth/register", reg1);
responseMessage.EnsureSuccessStatusCode();
// Handle success
}
catch (Exception ex)
{
// Handle failure
Console.WriteLine(ex.StackTrace);
}
}
}
I am getting an internal server error as the "Reason Phrase" with status code 500. I am not sure why I am getting this error. I should be getting 201 Created like postman
Any idea how to solve this issue?
I made a mistake by passing a program id that I don't actually have in my database which failed the FK_Reference and caused this issue. Thank you all for your help.

Validate multiple endpoints using HTML status codes before failing when consuming a asmx web service C#

I am currently using a web service, which offers 2 endpoints, as backups for fall over. I need to test all 2 endpoints before my code completely fails and then will need to log the exception. My thoughts were to be to return the status code of the HTML response using this:
Function1:
public string ValidateHttpRequest(string endpointUrl)
{
try
{
var url = endpointUrl;
HttpClient httpClient = new HttpClient();
var reponse = httpClient.GetAsync(endpointUrl);
return reponse.Result.StatusCode.ToString();
}
catch (Exception ex)
{
Log.Log("exception thrown in ValidateHttpRequest()! " + ex.ToString());
Log.Log(ex);
return null;
}
}
This is called from another function, say function2().
Function 2:
private bool function2()
{
//Specify the binding to be used for the client.
BasicHttpsBinding binding = new BasicHttpsBinding();
var epA = "https://www1.endpoint1.com/endpointService.asmx";
var epB = "https://www2.endpoint1.com/endpointService.asmx";
if (ValidateHttpRequest(epA)== "OK")
{
EndpointAddress address = new EndpointAddress("https://www1.enpoint1.com/endpointService.asmx");
_Client = new WebService.SoapClient(binding, address);
return true;
}
else if ((ValidateHttpRequest(epB))== "OK")
{
EndpointAddress address2 = new EndpointAddress(("https://www2.enpoint2.com/endpointService.asmx"));
else
{
// Now Log error here completely, and only fail here if both above checks return anything apart from 200 status code
LogException(“Only log exception if all endpoints fail”);
return false;
}
}
This is all well and good, however I need this to not fail on the first call, as I will need to check if the other endpoint is valid/active. The issue is that if the response is null, the exception is handled and I will not check the rest of my endpoints, how can I correctly ensure my code is safe with i.e. exceptions are handled correctly, but continuing my code to check all endpoints before completely failing and halting execution. it should fail if i receive any other response apart from 200 OK I have researched about how to check the HTTP response and all that I can come up with is this but it doesn’t completely suit my needs .If anyone could point me in the right direction or help with a solution I would be very grateful.
Thanks in advance

Web API async Task await blocking main thread

We have a three tier infrastructure (front end which is all Web API 2, Middleware which accepts API calls from front end and runs business logic and databases access, then the DB)
I'm trying to find out why our app locks up when I take the middle tier down. We use Memcached for all the reads and the front end serves the cached data just fine, but one of the calls that is made checks to see if the user is logged in. Running on my local machine with one app pool, that call locks the thread (I think) and prevents the rest of the calls from doing anything until the timeout on the autologin call expires.
The code path looks like this:
call to api/autologin --> front end API calls Client.SendAsync (our custom method for passing along data to the middleware), this tries to call the middlewware by using HttpClient.SendAsAsync with a timeout of 3 minutes (Probably should shorten this)
My expectation is that this should release this thread while we are waiting. That does not appear to be the result.
The REALLY weird thing is that when the middleware is down the Client.SendAsync gets ran MANY time, like 10. I thought this was maybe HTTP 2.0 in Chrome, but I switched to Fiddler and it did the same thing. Very weird.
So, two questions.
1. What's with the multiple calls?
2. Why do the threads appear to be getting locked?
Here's the code.
/// <summary>
/// Auto login user if they have the persistent cookies.
/// </summary>
/// <returns>The groups the logged in user has access to in the form of a
LoggedInUserData object.</returns>
[Route("api/cms/autologin/")]
[HttpGet]
public async Task<HttpResponseMessage> AutoLogin()
{
HttpResponseMessage response = await Client.SendAsync(this.Request);
return this.LoginCacheHelper(response);
}
That calls
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return await Client.SendAsync<string>(request, null, null, false);
}
Which calls
public static async Task<HttpResponseMessage> SendAsync<T>(HttpRequestMessage request, T content = null, string route = null, bool isFile = false, TimeSpan? timeout = null) where T : class
{
// Validate all internal certs.
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// Determine the route and make sure route has a starting forward slash.
if (!string.IsNullOrWhiteSpace(route) && route.StartsWith("http"))
{
// Check to make sure this is a selinc.com domain for security purposes.
if (Sel.Utils.Validation.UriValidation.IsSelincDomain(route))
{
request.RequestUri = new Uri(route);
}
else
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
else
{
string middlewareRoute = GetRoute(route, request);
// Change Uri to middle ware.
request.RequestUri = new Uri(Config.MwareSiteUrl + middlewareRoute);
}
// Remove host header
request.Headers.Host = string.Empty;
// Set content of request.
// File content will be kept on the request as is.
if (content != null && !isFile)
{
request.Content = new ObjectContent<T>(content, new JsonMediaTypeFormatter());
}
else if (!isFile)
{
request.Content = null;
}
// Client handler set use cookies to false which will pass along the current cookies
HttpClientHandler clientHandler = new HttpClientHandler() { UseCookies = false };
// The HttpClient object
HttpClient client = new HttpClient(clientHandler);
client.Timeout = timeout ?? new TimeSpan(0, 3, 0);
// Send the request
return await client.SendAsync(request);
}
Adding image of the Network log in Chrome to illustrate the behavior.
Note that if I remove the API call to the autologin, everything works fine. It's the only call in this stack that hits the back end.
Also note: If I modify the SendAsync method to just return a new HttpResponseMessage (and thus do no work) then the autologin basically does nothing, returns quickly and site loads as it should, with the middleware server down. This is just to prove that it is the autologin API call causing the problem. The autologin API call is the only method calling SendAsync at this time so it's a valid test.
// Send the request
////return await client.SendAsync(request);
return new HttpResponseMessage();

How do I check to see if an remote API is available when using ASP.Net Core and HttpClient

I am working on an ASP.Net Core API that calls a 3rd party API using an HttpClient GetAsync method. If the 3rd party API is off line, the call times out but in the response that is returned, I do not see any info related to not being able to connect. The response object's StatusCode and ResponsePhrase properties say "Not Found", which is a bit misleading. Is there a way for me to know if the 3rd party API is actually running?
For example, listed below is my code for making a cal to the 3rd party API;
public async Task<SAPIAddAlertSubscriberResponse> AddAlertSubscriberAsync(SAPIAddAlertSubscriberRequest a_request, CancellationToken a_cancellationToken)
{
try
{
SAPIAddAlertSubscriberResponse sapiResponse = new SAPIAddAlertSubscriberResponse();
using (HttpClient client = new HttpClient())
{
var policy = SetRetryPolicy();
HttpResponseMessage response = null;
var uri = $#"{m_appSettings.Value.SAPIUrl88}SAPIAddAlertSubscriber";
string jsonInString = JsonConvert.SerializeObject(a_request);
SetHttpClientHeader(client);
await policy.ExecuteAsync(async token =>
{
response = await client.PostAsync(uri, new StringContent(jsonInString, Encoding.UTF8, "application/json"));
}, a_cancellationToken);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
result = TrimWBResponse(result, "SAPIAddAlertSubscriber");
sapiResponse = JsonConvert.DeserializeObject<SAPIAddAlertSubscriberResponse>(result);
}
else
{
sapiResponse.queue.Add(SetSapiError(response, uri, $"add an AlertSubscriber for CarrierLoginId {a_request.CarrierLoginPrimaryID}"));
}
}
return sapiResponse;
}
catch (Exception ex)
{
m_logger.LogError(1, ex, "An exception has occurred in the AddAlertSubscriberAsync method call.");
return null;
}
}
When I make the call at a time that the 3rd party API is down, the call to the 3rd party API holds until the timeout is reached and then returns response.IsSuccessStatusCode = false. But when I examine the values in the response object's StatusCode and ResponsePhrase properties, they both just say "Not Found".
Is there a way for me to return more information that could indicate that the reason was that the API was not available?
EDIT: Added code for SetRetryPolicy
/// <summary>
/// Creates a retry policy for a Polly instance.
/// </summary>
/// <returns>The Polly.Retry.RetryPolicy object</returns>
/// <remarks>
/// Supports implementation of a Polly (https://github.com/App-vNext/Polly) transient exception handler to apply exponential backoff solution for timeout errors.
/// If a timeout transient error occurs, the call will retry after an exponentially increasing time period (starting at 1/10 second) , up to the
/// RetryAttempts value in the settings database
/// <remarks>
private Polly.Retry.RetryPolicy SetRetryPolicy()
{
int retries = 0;
var policy = Policy.Handle<Exception>()
.WaitAndRetryAsync((int)m_appSettings.Value.SAPIRetryAttempts, attempts => TimeSpan.FromSeconds(0.1 * Math.Pow(2, attempts)),
(exception, calculateWaitDuration) =>
{
m_logger.LogError(1, exception, "A transient exception has occurred{NewLine} ... automatically delaying for [{WaitDuration}] ms. This is retry [{Retries}].", Environment.NewLine, calculateWaitDuration, retries);
retries++;
});
return policy;
}
Thanks in advance for any help you can provide.

Categories