ServiceFabric stateless WebApi automatically retries 503 and 404 responses - asp.net-core-webapi

We have a ServiceFabric stateless WebAPI frontend built on top of dotnet5. I have implemented following exception handling filter for it:
public class OrderServiceRetryFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
var exc = context.Exception;
if (exc is AggregateException ae && (
ae.InnerException is OrdersNotFetchedException onfe))
{
context.HttpContext.Response.Headers.Add("Retry-After", "2");
var result = new ObjectResult(onfe.Message) { StatusCode = 591 };
context.Result = result;
context.ExceptionHandled = true;
}
if (exc is AggregateException ate && (
ate.InnerException is System.TimeoutException toex))
{
context.HttpContext.Response.Headers.Add("Retry-After", "1");
var result = new ObjectResult(toex.Message) { StatusCode = 504 };
context.Result = result;
context.ExceptionHandled = true;
}
if (exc is AggregateException anfe && (
anfe.InnerException is OrderNotFoundException onf))
{
var result = new NotFoundObjectResult(onf.Message);
context.Result = result;
context.ExceptionHandled = true;
}
}
}
If the stateful backend services throw an exception this filter will find the inner exception and return the correct status code (591, 504, 404) for the HTTP query.
Now, if the backend service throws a OrdersNotFetchedException the status code is set to 591 and the client will get it. I am using our own 591 because returning 503 would cause something to retry the call. This retry happens also in the case of 404. If I make a GET call, that will result in 404, from Postman, it will eventually just time out. Debugging the code shows that the code constantly returns to the OnException method that returns 404. If I change the error code to 592 during debug it will return that result code to the calling client without retries.
Something, somewhere, and I think it is the ServiceFabric, is retrying the simple API call if it returns 503 or 404. Where can I disable this kind of behavior or am I doing something against the way public facing Web APIs are designed with ServiceFabric?
This is how I start the Kestrel server:
private IWebHost BuildWebHost(string url, AspNetCoreCommunicationListener listener)
{
ServiceEventSource.Current.ServiceMessage(Context, $"Starting Kestrel on {url}");
var webHost = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(
services => services
.AddSingleton(Context)
.AddSingleton(ServiceFabricRemoting.CreateServiceProxy<IOrderService>(new Uri($"{ServiceFabricRemoting.GetFabricApplicationName()}/MyApp.OrderService"), new MyLogger(Context), 1))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseUniqueServiceUrl)
.UseUrls(url)
.Build();
HandleWebHostBuilt(webHost);
return webHost;
}

According to https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-reverseproxy#special-handling-for-port-sharing-services the correct answer is to add X-ServiceFabric: ResourceNotFound header to the response to bypass the retry in the case of 404.
So, in this case:
context.HttpContext.Response.Headers.Add("X-ServiceFabric", "ResourceNotFound");

Related

SocketError when creating multiple HttpClient Connections with the Load balancer(F5)

This is the exact exception in the error logs.
I have a requirement where I should be creating a set of service calls and wait till all of them are completed successfully before moving on to do a different set of service calls.
I have two services Service1 and Service2.
There are 2 instances of Service2 in 2 different servers and we have set up an F5(Load Balancer) to distribute the load evenly.
Let's say I have 10 service calls to be made from Service1 to Service2 at a time and F5 will share those 10 calls among the 2 servers. i.e. 5 Calls to Service2 of each server.
But I observed that if any one of those 10calls is taking lot of time to complete the work it should do(The work to be done in Service2 has some heavy lifting) then i get a socket exception thrown and entire process gets stopped.
However when I dont use the F5 load balancer and just use 1 instance of the Service2. Then however long the process takes for any of those 10 calls it doesn't throw any exception.
I am not sure if this is an issue with F5 configuration or with the way connections are made with the F5 from .Net code.
Please go through the below code to get some idea of what i am trying to do and let me know if any code change would help me resolve it.
for (int i=0 ; i< ReqList.Count;i++)
{
maxTasks++;
ClassA reqList = new ClassA();
reqList = ReqList[i];
List<ClassA> recsByReq = recs.Where(x => x.ReqId == reqList.ReqtId).ToList();
ClassC service2Input = new ClassC();
service2Input.DetaiList = listofRecs;
service2Input.RecList = recsByReq;
taskList.Add(_service2.Service2MethodCall(service2Input, service2Resource));
if(maxTasks == 10 || ReqList.Count == 10 || i==ReqList.Count-1)
{
Task.WaitAll(taskList.ToArray(),-1);
maxTasks = 0;
taskList.Clear();
}
}
The Service2MethodCall is where I am creating a HttpClient to make connections with the 2nd service i.e. Service2,
public class Service2: IService2 {
private ServiceClient GetService2(string resource)
{
return new ServiceClient(_service2BaseUrl, TimeSpan.FromMinutes(60))
{
Resource = resource,
};
}
public async Task Service2MethodCall(ClassC service2Input, string resource)
{
try
{
var client = GetService2(resource);
await client.PostAsync(JsonConvert.SerializeObject(service2Input).ToString());
}
catch (Exception ex)
{
throw new Exception("The Service encountered an error during the Service2 call for Req ID : " +
service2Input.RecsList.Find(x => x.ReqId != "").ReqId.ToString(), ex);
}
}
}
The PostAsync() method creates a new HttpClient with HttpClientHandler object for each of the call.
public async Task PostAsync(object data) {
using(var httpClientHandler = new HttpClientHandler()) {
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => {
if (sslPolicyErrors == SslPolicyErrors.None) {
return true; //Is valid
}
if (cert.GetCertHashString().ToUpper() == _acceptedThumbprint.ToUpper()) {
return true;
}
return false;
};
using(var client = new HttpClient(httpClientHandler)) {
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(ResponseFormatter.MediaType);
client.Timeout = Timeout.InfiniteTimeSpan;
Uri uri = BuildUri();
if (!String.IsNullOrWhiteSpace(SignatureKey)) {
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization",
RequestFormatter.GenerateAuthHeaderEncodedUserSignature(uri, AuthUserName, SignatureKey, data));
}
//_logger.WriteUsage(client.BaseAddress.ToString());
HttpResponseMessage response = await RequestFormatter.PostAsync(client, uri, data);
string content = await response.Content.ReadAsStringAsync();
try {
response.EnsureSuccessStatusCode();
}#
pragma warning disable CS0168 // The variable 'rex' is declared but never used
catch (HttpRequestException rex)# pragma warning restore CS0168 // The variable 'rex' is declared but never used
{
//_logger.WriteUsage("Response for POST to: {0} did not yield a successful status code. Message: {1}" + uri.ToString() + content);
throw new ApiHttpException(response.StatusCode, content);
}
}
}
}
Is there some thing I can do within the code to avoid this situation once and for all?

SignalR gives 404 when using WebSockets or ServerSentEvents for some users but not for others

SignalR gives me 404 when trying to connect for some users. URLs are the same except for access_token.
It is stable reproducible per user (I mean that some users are stable OK, some users are stable 404).
access_token parsed jwt diff (left is OK user, right gets 404):
I did a trace level of logs and have next:
For the OK user:
For the user that gets 404:
Note: URLs under black squares are the same.
Front End is Angular 9 with package "#microsoft/signalr": "^3.1.8", and here's the code that builds the connection:
private buildHub(): HubConnection {
console.log(this.authService.accessToken);
let builder = new HubConnectionBuilder()
.withAutomaticReconnect()
.configureLogging(LogLevel.Information)
.withUrl('ws/notificationHub', {
accessTokenFactory: () => this.authService.accessToken
});
if (this.debugMode) {
builder = builder.configureLogging(LogLevel.Trace);
}
return builder.build();
}
Backend is using next code in Startup for configuring signalR hub:
In public void ConfigureServices(IServiceCollection services):
services.AddSignalR()
.AddJsonProtocol(options =>
{
options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
});
In public void Configure(IApplicationBuilder app, IHostingEnvironment env):
app.UseSignalR(route =>
{
route.MapHub<NotificationHub>("/ws/notificationHub");
});
Also we use custom authentication, so we have Authorize attribute for the Hub class:
[Authorize]
public class NotificationHub: Hub<INotificationHubClient>
and this code in public void ConfigureServices(IServiceCollection services):
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = identityServerSettings.Url;
options.Audience = identityServerSettings.ApiScopeName;
options.RequireHttpsMetadata = identityServerSettings.RequireHttpsMetadata;
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/ws"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
Unfortunately, I don't have the full access to the environment where it is reproducible, but I can request to see any settings or try to make some changes.
What else can I try to troubleshoot the issue?
UPDATE: negotiate is fine for both users.
I had this issue recently, after the size of my JWT increased. I found that in my case the 404 error was being thrown by IIS because the query string exceeded the limit of 2048. After increasing the query string max length, my issue was resolved.

HttpClient object does not works second time on Xamarin.iOS

I use HttpClient object for PostAsync. I need to add BackgroundSessionConfiguration for iOS while I am creating HttpClient object. So I changed my code like this:
var configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration ("my.app.identifier");
_client = new HttpClient (new NSUrlSessionHandler (configuration));
This works when I send first request with PostAsync. But when I send request second time, it doesn't work.
I did it for Login Operation like this: (It works first time but if I logout and login again, it doesn't work.)
public class LoginService
{
private HttpClient _client;
public LoginService()
{
if (_client == null)
{
_client = Helper.CreateHttpClientLogin(_client);
}
}
public async Task<LoginResponse<LoginDataResponse>> Login(LoginRequest request)
{
LoginResponse<LoginDataResponse> responseModel = new LoginResponse<LoginDataResponse>();
try
{
string json = JsonConvert.SerializeObject(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var jsonBody = await _client.PostAsync(App.ServiceURL.Login_Url, content);
string jsonstr = await jsonBody.Content.ReadAsStringAsync();
if (jsonstr == null || jsonstr == "")
{
responseModel.Success = false;
responseModel.Status = 0;
responseModel.Message = AppResources.UnknownHostException;
}
else
responseModel = (LoginResponse<LoginDataResponse>)JsonConvert.DeserializeObject(jsonstr, typeof(LoginResponse<LoginDataResponse>));
}
catch (Exception ex)
{
string text = ex.ToString();
responseModel.Status = 0;
AppResources.Culture = CrossMultilingual.Current.CurrentCultureInfo;
responseModel.Message = AppResources.UnknownHostException;
}
return responseModel;
}
}
public class Helper
{
public static HttpClient CreateHttpClientLogin(HttpClient _client)
{
if (Device.RuntimePlatform == Device.iOS)
{
var configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration("my.app.identifier");
_client = new HttpClient(new NSUrlSessionHandler(configuration));
}
else
{
//_client = new HttpClient(new System.Net.Http.HttpClientHandler());
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
_client = new HttpClient(handler);
}
return _client;
}
}
And I have this code on AppDelegate: (I don't know but maybe it causes the bug)
public static Action BackgroundSessionCompletionHandler;
public override void HandleEventsForBackgroundUrl(UIApplication application, string sessionIdentifier, Action completionHandler)
{
// We get a completion handler which we are supposed to call if our transfer is done.
BackgroundSessionCompletionHandler = completionHandler;
}
What must I do for this?
Edit:
I solved the problem I mentioned above by creating the Login Service object once the application was first opened. (After logout previously, I was rebuilding every time I login)
But now I have other error. When I run my app on "iPhone 7 plus - iOS 13.6" device I got this error:
System.Net.Http.HttpRequestException: unknown error ---> Foundation.NSErrorException: Error Domain=NSURLErrorDomain Code=-1 "unknown error" UserInfo={NSErrorFailingURLStringKey=https://mydomain/Api/Login, NSErrorFailingURLKey=https://mydomain/Api/Login, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundDataTask <E69F3EAF-0AE9-4FAE-A01B-988167B7F6BC>.<3>"
), _NSURLErrorFailingURLSessionTaskErrorKey=BackgroundDataTask <E69F3EAF-0AE9-4FAE-A01B-988167B7F6BC>.<3>, NSLocalizedDescription=unknown error}
--- End of inner exception stack trace ---
at System.Net.Http.NSUrlSessionHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x001d4] in /Library/Frameworks/Xamarin.iOS.framework/Versions/13.20.2.2/src/Xamarin.iOS/Foundation/NSUrlSessionHandler.cs:527
at System.Net.Http.HttpClient.FinishSendAsyncBuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) [0x0017e] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/external/corefx/src/System.Net.Http/src/System/Net/Http/HttpClient.cs:506
at App.Services.LoginService.Login (FileOrbis.Models.RequestModels.LoginRequest request) [0x00084] in C:\Users\PcName\Desktop\App\App\Services\LoginService.cs:40
And simulator log file is:
Startup:
arguments: --device=06098E5B-1853-4A83-8434-8071D8973A14 --launchsim=//Users/deytek/Library/Caches/Xamarin/mtbs/builds/App.iOS/b2c75f2acbd4ff91c305dba10ca791b7/bin/iPhoneSimulator/Debug/App.iOS.app -argument=-monodevelop-port -argument=51890 -setenv=__XAMARIN_DEBUG_PORT__=51890 --sdkroot=/Applications/Xcode.app -h=192.168.1.7 -ssh=deytek --launched-by=devenv-16.0
version: 16.7.0.0 (54a29526ef6f853bdd37adbcc3791ce90ca82735)
Connecting to existing client
Exit:
Exit Code: 0
I encounter with this error when I use Background Session Configuration. If I use normal HttpClient object (without Background Session Configuration), it works
NOTE: I also tried iPhone 5s iOS 12.4.8 and iPad Pro (3rd Generation) iOS 13.6.1 It works these devices. But it doesn't work on iPhone 7 Plus 13.6

An asynchronous operation exceeded the page timeout (trying to use HttpRequestMessage with ASP.NET WebForms page)

I have created a web API in AWS that I am trying to get some JSON back from using a web page built in ASP.NET Webforms (most up-to-date version). I can't get the asynchronous part to work. Either the GET method hangs seemingly forever, or - following the best practice approach from the Microsoft documentation - I get this error after a little while:
[TimeoutException: An asynchronous operation exceeded the page
timeout.] System.Web.UI.d__554.MoveNext() +984
I know this is something to do with the wait/async portion of the code and being in ASP.NET because of the following.
If I use very similar code in a console application it works fine.
If i call the web API using POSTMAN it works fine.
I have made async = true in the page directive. Here is my page load
protected void Page_Load(object sender, EventArgs e)
{
try
{
RegisterAsyncTask(new PageAsyncTask(GetStuffAsync));
}
catch (Exception ex)
{
renderStoreCards.Text = ex.Message;
}
}
Here is my method
private async Task GetStuffAsync()
{
string testHtml = string.Empty;
try
{
var signer = new AWS4RequestSigner("AccessKey", "SecretKey");
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://some-aws-address-changed-for-stack-overflow.execute-api.ap-southeast-2.amazonaws.com/Prod/tables/InSiteStoreInformation/ServerName")
};
request = await signer.Sign(request, "execute-api", "ap-southeast-2");
var client = new HttpClient();
var response = await client.SendAsync(request).ConfigureAwait(false);
string responseString = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
renderStoreCards.Text = ex.Message;
}
}
The above example produces a TimeoutException. Previous to the above, I was trying the following code. This works fine in a console app, but not in the ASP.NET page.
class Program
{
static void Main(string[] args)
{
try
{
MainAsync().Wait();
}
catch (Exception ex)
{
Console.WriteLine($"Exception occured {ex.Message}");
Console.ReadKey();
}
}
static async Task MainAsync()
{
try
{
var signer = new AWS4RequestSigner("AccessKey", "SecretKey");
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://<Hiddenforstackoverflowpost>.execute-api.ap-southeast-2.amazonaws.com/Prod/tables/InSiteStoreInformation/ServerName")
};
request = await signer.Sign(request, "execute-api", "ap-southeast-2");
var client = new HttpClient();
var response = await client.SendAsync(request);
var responseStr = await response.Content.ReadAsStringAsync();
dynamic sales = Newtonsoft.Json.JsonConvert.DeserializeObject(responseStr);
Console.WriteLine($"Server = {sales[0].ServerName}");
Console.ReadKey();
Console.Write(responseStr);
Console.ReadKey();
}
catch (Exception ex)
{
throw (ex);
}
}
}
I am by no means an expert in async/wait combinations, but it appears that the HttpClient I'm using has no synchronous alternative, so I have to figure this out.
I know this is an old question, but I just came across.
The timeouts are most likely caused by ReadAsStringAsync as you neglected to use '.ConfigureAwait(false)' on it. Running async tasks inside ASP.net can be very finicky especially around execution contexts. It most likely is some kind of dead-lock on the async method when it tries to restore the execution context on its return. This usually fails due to the nature of IIS hosting. I am not sure if it actually is a dead-lock or some other issue under the hood. Just make sure to always use .ConfigureAwait(false).
I know this is old, but maybe it helps someone else coming across this issue.

MobileServiceClient MobileServiceInvalidOperationException Response Content is null

I'm using the following code in a Xamarin Forms app:
HttpResponseMessage response = null;
try
{
HttpContent content = new StringContent(JsonConvert.SerializeObject(register), Encoding.UTF8, "application/json");
response = await client.InvokeApiAsync("register", content, HttpMethod.Post, null, null);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
var def = new { Message = "" };
var errorMessage = JsonConvert.DeserializeAnonymousType(error, def);
return KloverResult.BuildError(true, errorMessage.Message);
}
}
catch (MobileServiceInvalidOperationException e)
{
if (e.Response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{
string error = await e.Response.Content.ReadAsStringAsync();
var def = new { Message = "" };
var errorMessage = JsonConvert.DeserializeAnonymousType(error, def);
return KloverResult.BuildError(true, errorMessage.Message);
}
else
{
return KloverResult.BuildError(false, "Invalid username or password");
}
}
The issue that I'm having is when a MobileServiceInvalidOperationException is thrown as a result of a 500. When I try to read the content of the response (e.Response.Content) it's null. When I call the same API using Restlet I get the following response:
{
"Message": "Name jblogs is already taken."
}
This is what I expect to be in my error variable, however it's null.
My question is, should I be able to read the Content of the Response? If so, do I need to do some more setup on the client/server? The API being called is returning the error form a webapi using:
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Name jblogs is already taken.");
Any help would be appreciated.
A 500 response means that the server crashed. It's likely that there was no content in that case.
If your API is returning status=500, then it is doing the wrong thing. What you should be doing is returning a status in the 400 series - 409 (conflict) seems appropriate to me.
If your API is not returning status=500 deliberately, then the server crashed and you don't get content.
According to your description, I built my Mobile App application with a custom WebApi endpoint to test this issue. Based on my test, I leverage Microsoft.Azure.Mobile.Client 3.1.0 to invoke custom WebApi, I could retrieve the content by Response.Content.ReadAsStringAsync() when the response status is 409 or 500 and so on. Here are my code snippet, you could refer to them:
WebApi
[MobileAppController]
public class ValuesController : ApiController
{
public async Task<HttpResponseMessage> Get()
{
await Task.Delay(TimeSpan.FromSeconds(2));
return Request.CreateErrorResponse(HttpStatusCode.Conflict, "Name jblogs is already taken.");
}
}
Client App
try
{
MobileServiceClient client = new MobileServiceClient("https://bruce-chen-002.azurewebsites.net/");
var response = await client.InvokeApiAsync("/api/values", HttpMethod.Get, null);
}
catch (MobileServiceInvalidOperationException e)
{
if (e.Response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{
string error = await e.Response.Content.ReadAsStringAsync();
}
}
Result

Resources