I'm using ASP.NET Web API 2.2 along with Owin to build a web service and I observed each call to the controller will be served by a separate thread running on the server side, that's nothing surprising and is the behavior I expected.
One issue I'm having now is that because the server side actions are very memory intense so if more than X number of users are calling in at the same time there is a good chance the server code will throw an out-of-memory exception.
Is it possible to set a global "maximum action count" so that Web Api can queue (not reject) the incoming calls and only proceed when there's an empty slot.
I can't run the web service in 64bit because some of the referenced libraries won't support that.
I also looked at libraries like https://github.com/stefanprodan/WebApiThrottle but it can only throttle based on the frequency of calls.
Thanks
You could add a piece of OwinMiddleware along these lines (influenced by the WebApiThrottle you linked to):
public class MaxConccurrentMiddleware : OwinMiddleware
{
private readonly int maxConcurrentRequests;
private int currentRequestCount;
public MaxConccurrentMiddleware(int maxConcurrentRequests)
{
this.maxConcurrentRequests = maxConcurrentRequests;
}
public override async Task Invoke(IOwinContext context)
{
try
{
if (Interlocked.Increment(ref currentRequestCount) > maxConcurrentRequests)
{
var response = context.Response;
response.OnSendingHeaders(state =>
{
var resp = (OwinResponse)state;
resp.StatusCode = 429; // 429 Too Many Requests
}, response);
return Task.FromResult(0);
}
await Next.Invoke(context);
}
finally
{
Interlocked.Decrement(ref currentRequestCount);
}
}
}
Related
I've read a lot of conflicting information about this and it seems people are not 100% clear on what is possible and what is not. I am certain that you cannot host a gRPC server app in IIS due to the HTTP/2 limitations. The documentation is pretty clear. However, I want to use IIS as a reverse proxy, with the internal side communicating using gRPC. So the client would be in IIS, not the server. I assumed that since the communication at this point (i.e. the back end) was not funneled through IIS, there would be no issue with this. However, I keep seeing mixed answers.
I have created a dumb webapp that is hosted in IIS Express and can successfully post to my service running on Kestrel with gRPC.
Client code sample below. The SubmitButton is just a form post on the razor page.
public async void OnPostSubmitButton()
{
// The port number(5001) must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
Server code is the boilerplate template for gRPC but looks like this:
namespace grpcGreeter
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
namespace grpcGreeter
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
}
This works. But, because I keep seeing mixed information saying it that it won't, I am not certain that once I go to deploy the client code (i.e. the reverse proxy), if I will run into problems. I would like to use a host like Azure...but don't know if it's possible or not.
Any clarity on the subject would be greatly appreciated.
As far as I know, we could use asp.net core mvc or razor page application as the client to call the grpc server.
But gRPC client requires the service to have a trusted certificate when you hosted the application on remote server IIS.
If you don't have the permission to install the certificate, you should uses HttpClientHandler.ServerCertificateCustomValidationCallback to allow calls without a trusted certificate.
Notice: this will make the call not security.
Additional configuration is required to call insecure gRPC services with the .NET Core client. The gRPC client must set the System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport switch to true and use http in the server address.
Code as below:
AppContext.SetSwitch(
"System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var httpClientHandler = new HttpClientHandler();
// Return `true` to allow certificates that are untrusted/invalid
httpClientHandler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var httpClient = new HttpClient(httpClientHandler);
var channel = GrpcChannel.ForAddress("https://localhost:5001",
new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
i have a xamarin app that is trying to talk to use SignalR in Azure functions.
i have 2 azure functions as per the documentation.
public static class NegotiateFunction
{
[FunctionName("negotiate")]
public static SignalRConnectionInfo GetSignalRInfo(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
[SignalRConnectionInfo(HubName = "chat")] SignalRConnectionInfo connectionInfo)
//, UserId = "{headers.x-ms-client-principal-id}"
{
return connectionInfo;
}
}
and
public static class SendMessageFunction
{
[FunctionName("Send")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
// var chatObj = (ChatObject)(message);
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to this user ID
// UserId = chatObj.ReciversId,
Target = "Send",
Arguments = new[] { message }
});
}
}
in my xamarin client i am connecting like this.
try
{
_connection = new HubConnectionBuilder()
.WithUrl("http://192.168.1.66:7071/api")
.Build();
_connection.On<string>("Send", (message) =>
{
AppendMessage(message);
});
await _connection.StartAsync();
}
I send message using this code in one of the pages of Xamarin app page.
try
{
await _connection.SendAsync("Send", MessageEntry.Text);
MessageEntry.Text = "";
}
connection code works it hits "negotiate" function properly but when i call SendAsync it does not hit break-point in [FunctionName("Send")] and nothing happens. It doesn't give me any exception as well.
local settings are like this
Update
i also tried Invoke. it didnt worked.
Should i try making a POST call to [FunctionName("Send")] ?
The way SignalR SaaS works in Functions is slightly different to using the NuGet package in a .NET Application.
You can't invoke a function using the SignalR library, as you can see on the attribute in your function, it's expecting a Http trigger so you have to do a POST to this endpoint instead of invoking it as you normally would.
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]
You still want to listen to the Send target as normal.
I am using middleware provided by AspNetCoreRateLimit to rate limit incoming requests to an ASP.NET Core 2.x REST API web application.
Currently this library returns html responses for rejected requests. How can I make it return json responses instead?
You could custom your response in the IpRateLimitMiddleware.
IpRateLimitMiddleware
public class MyIpRateLimitMiddleware : IpRateLimitMiddleware
{
public MyIpRateLimitMiddleware(RequestDelegate next
, IOptions<IpRateLimitOptions> options
, IRateLimitCounterStore counterStore
, IIpPolicyStore policyStore
, IRateLimitConfiguration config
, ILogger<IpRateLimitMiddleware> logger)
: base(next, options, counterStore, policyStore, config, logger)
{
}
public override Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule, string retryAfter)
{
//return base.ReturnQuotaExceededResponse(httpContext, rule, retryAfter);
var message = new { rule.Limit, rule.Period, retryAfter };
httpContext.Response.Headers["Retry-After"] = retryAfter;
httpContext.Response.StatusCode = 200;
httpContext.Response.ContentType = "application/json";
return httpContext.Response.WriteAsync(JsonConvert.SerializeObject(message));
}
}
Configure the middlware in Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//app.UseIpRateLimiting();
app.UseMiddleware<MyIpRateLimitMiddleware>();
//your rest middlware
}
Read the docs
If the request gets blocked then the client receives a text response like this:
Status Code: 429
Retry-After: 58
Content: API calls quota exceeded! maximum admitted 2 per 1m.
You can customize the response by changing these options HttpStatusCode and QuotaExceededMessage, if you want to implement your own response you can override the IpRateLimitMiddleware.ReturnQuotaExceededResponse. The Retry-After header value is expressed in seconds. (emphasis mine)
I have setup a SignalR hub which has the following method:
public void SomeFunction(int SomeID)
{
try
{
Thread.Sleep(600000);
Clients.Caller.sendComplete("Complete");
}
catch (Exception ex)
{
// Exception Handling
}
finally
{
// Some Actions
}
m_Logger.Trace("*****Trying To Exit*****");
}
The issue I am having is that SignalR initiates and defaults to Server Sent Events and then hangs. Even though the function/method exits minutes later (10 minutes) the method is initiated again ( > 3 minutes) even when the sendComplete and hub.stop() methods are initiated/called on the client prior. Should the user stay on the page the initial "/send?" request stays open indefinitely. Any assistance is greatly appreciated.
To avoid blocking the method for so long, you could use a Taskand call the client method asynchronously.
public void SomeFunction(Int32 id)
{
var connectionId = this.Context.ConnectionId;
Task.Delay(600000).ContinueWith(t =>
{
var message = String.Format("The operation has completed. The ID was: {0}.", id);
var context = GlobalHost.ConnectionManager.GetHubContext<SomeHub>();
context.Clients.Client(connectionId).SendComplete(message);
});
}
Hubs are created when request arrives and destroyed after response is sent down the wire, so in the continuation task, you need to create a new context for yourself to be able to work with a client by their connection identifier, since the original hub instance will no longer be around to provide you with the Clients method.
Also note that you can leverage the nicer syntax that uses async and await keywords for describing asynchronous program flow. See examples at The ASP.NET Site's SignalR Hubs API Guide.
I'm using SignalR 1.1.2 and I have problem with async hub method. Everything works fine on my PC with ForeverFrame transport but after deploying on server and switching to web sockets transport I receive following error:
An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%# Page Async="true" %>.
My hub method code:
public async Task<string> getUrl()
{
var url = await MyWebservice.GetMyRoomUrlAsync(Context.User.Identity.Name);
return url;
}
Are async methods supported in SignalR with web-sockets transport?
Update:
GetMyRoomUrlAsync code:
public static Task<string> GetMyRoomUrlAsync(string email)
{
var tcs = new TaskCompletionSource<string>();
var client = new Onif40.VisualStudioGeneratedSoapClient();
client.GetRoomUrlCompleted += (s, e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
client.GetRoomUrlAsync(email);
return tcs.Task;
}
After Stephen Cleary clarified me where the problem was, solving it by rewriting EAP to APM was trivial.
public static Task<string> GetMyRoomUrlAsync(string email)
{
var tcs = new TaskCompletionSource<string>();
var client = new Onif40.VisualStudioGeneratedSoapClient();
client.BeginGetRoomUrl(email, iar =>
{
try
{
tcs.TrySetResult(client.EndGetRoomUrl(iar));
}
catch (Exception e)
{
tcs.TrySetException(e);
}
}, null);
return tcs.Task;
}
async methods are supported. However, you cannot use async void or async wrappers around EAP methods.
One common cause of this is using WebClient instead of the newer HttpClient. If that's not the case here, you would need to post the implementation of GetMyRoomUrlAsync.