I am trying to keep track of connected users to my hub.
The way I tried to do this was by creating a custom Authorize attribute for my hub, and checking for the user that is trying to connect. If the user is already connected then the hub does not authorize the connection
public class SingleHubConnectionPerUserAttribute : Microsoft.AspNet.SignalR.AuthorizeAttribute
{
private static readonly HashSet<UserKey> connections = new HashSet<UserKey>();
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
Type hubType = hubDescriptor.HubType;
string userId = request.User.Identity.GetUserId();
UserKey userKey = new UserKey(hubType, userId);
if (connections.Contains(userKey) || !base.AuthorizeHubConnection(hubDescriptor, request))
{
return false;
}
connections.Add(userKey);
return true;
}
}
This would work fine if the method AuthorizeHubConnection was called only once per connection, but that is not what is happening.
When I load the page that tries to connect with the hub, AuthorizeHubConnection oddly runs multiple times, and the number of times it runs is not always the same, sometimes it's 5, some it's 3, I really have no clue of what could possibly be causing it.
Do you know what could cause AuthorizeHubConnection to get called more than once?
Authorization is invoked each time SignalR server receives an HTTP request before it does anything else (See: https://github.com/SignalR/SignalR/blob/dev/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs#L161). While SignalR maintains a logically persistent connection it makes multiple HTTP Requests behind the scenes. When using Websockets transport you will typically see only 3 of these when starting the connection (for the negotiate, connect and start requests) and one for each reconnect. longPolling and serverSentEvents transport create an HTTP request each time to send data (send). In addition longPolling creates a polling HTTP request to receive data (poll). Each of these requests has to be authorized so this is the reason why you see multiple calls to the AuthorizeHubConnection method.
Related
I'm working on a .NET web app using SignalR with the hub class similar to the example class below:
public class ContosoChatHub : Hub
{
public override Task OnConnected()
{
// Add your own code here.
// For example: in a chat application, record the association between
// the current connection ID and user name, and mark the user as online.
// After the code in this method completes, the client is informed that
// the connection is established; for example, in a JavaScript client,
// the start().done callback is executed.
return base.OnConnected();
}
public override Task OnDisconnected()
{
// Add your own code here.
// For example: in a chat application, mark the user as offline,
// delete the association between the current connection id and user name.
return base.OnDisconnected();
}
public override Task OnReconnected()
{
// Add your own code here.
// For example: in a chat application, you might have marked the
// user as offline after a period of inactivity; in that case
// mark the user as online again.
return base.OnReconnected();
}
}
More specific, my web app serves as hub for connecting tablets. when i close the app on the tablet it does not trigger instantly the OnDisconnected task, taking up to 20 seconds or more (server tries to reconnect with the client).
My question is, which method should I use in order to detect the connection loss as soon as it happens or, is there a connection state handler that triggers when the connection is lost?
In order to prevent the data loss (considering a tablet online when in fact it's not) I really need to handle the disconnecting event.
Any help is much appreciated!
Later edit:
I've also included the following lines in the Global.asax file
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(6);
GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(2);
in the Application Start method. The values seem to be saved, as seen in debug and actually reduce the time by half, from 20-30 to 12-14 seconds, but it's still not as close to 2-3 seconds.
You can detect the server disconnection from SignalR client:
$.connection.hub.disconnected(function () {
alert('Server has disconnected');
});
This is the official documentation when each method called:
When OnConnected, OnDisconnected, and OnReconnected are called
Each time a browser navigates to a new page, a new connection has to
be established, which means SignalR will execute the OnDisconnected
method followed by the OnConnected method. SignalR always creates a
new connection ID when a new connection is established.
The OnReconnected method is called when there has been a temporary
break in connectivity that SignalR can automatically recover from,
such as when a cable is temporarily disconnected and reconnected
before the connection times out. The OnDisconnected method is called
when the client is disconnected and SignalR can't automatically
reconnect, such as when a browser navigates to a new page. Therefore,
a possible sequence of events for a given client is OnConnected,
OnReconnected, OnDisconnected; or OnConnected, OnDisconnected. You
won't see the sequence OnConnected, OnDisconnected, OnReconnected for
a given connection.
The OnDisconnected method doesn't get called in some scenarios, such
as when a server goes down or the App Domain gets recycled. When
another server comes on line or the App Domain completes its recycle,
some clients may be able to reconnect and fire the OnReconnected
event.
I host the signalR client in IIS as a web site.
And I tested send message from server to this client, it works ok.
Then I shutdown the client site, and restart it after 10 mins.
Again, I send a message to this client, it still works.
My question is why the connection state didn't change?
BTW, I have state change event callback method
connection.StateChanged += (x) => {
OnStateChange(connectionId, x);
};
private void OnStateChange(string connectionId, StateChange stateChange)
{
StringBuilder text = new StringBuilder();
text.Append("ConnectiId:").Append(connectionId).Append(Environment.NewLine);
text.Append("OldState:").Append(stateChange.OldState.ToString()).Append(Environment.NewLine);
text.Append("NewState:").Append(stateChange.NewState.ToString()).Append(Environment.NewLine);
if(stateChange.NewState == ConnectionState.Disconnected)
{
//reconnect
}
Util.Log("signalR_statechange", text.ToString(), false);
}
There are no logs when I shutdown or restart the site.
go to this thread read...the below Continuation ...also there is no need onstatechange event... read msdn link it will more help for you
How to handle connection lifetime events in the Hub class
Typical reasons for handling connection lifetime events are to keep track of whether a user is connected or not, and to keep track of the association between user names and connection IDs. To run your own code when clients connect or disconnect, override the OnConnected, OnDisconnected, and OnReconnected virtual methods of the Hub class, as shown in the following example.
public class ContosoChatHub : Hub
{
public override Task OnConnected()
{
// Add your own code here.
// For example: in a chat application, record the association between
// the current connection ID and user name, and mark the user as online.
// After the code in this method completes, the client is informed that
// the connection is established; for example, in a JavaScript client,
// the start().done callback is executed.
return base.OnConnected();
}
public override Task OnDisconnected()
{
// Add your own code here.
// For example: in a chat application, mark the user as offline,
// delete the association between the current connection id and user name.
return base.OnDisconnected();
}
public override Task OnReconnected()
{
// Add your own code here.
// For example: in a chat application, you might have marked the
// user as offline after a period of inactivity; in that case
// mark the user as online again.
return base.OnReconnected();
}
}
When OnConnected, OnDisconnected, and OnReconnected are called
Each time a browser navigates to a new page, a new connection has to be established, which means SignalR will execute the OnDisconnected method followed by the OnConnected method. SignalR always creates a new connection ID when a new connection is established.
The OnReconnected method is called when there has been a temporary break in connectivity that SignalR can automatically recover from, such as when a cable is temporarily disconnected and reconnected before the connection times out. The OnDisconnected method is called when the client is disconnected and SignalR can't automatically reconnect, such as when a browser navigates to a new page. Therefore, a possible sequence of events for a given client is OnConnected, OnReconnected, OnDisconnected; or OnConnected, OnDisconnected. You won't see the sequence OnConnected, OnDisconnected, OnReconnected for a given connection.
The OnDisconnected method doesn't get called in some scenarios, such as when a server goes down or the App Domain gets recycled. When another server comes on line or the App Domain completes its recycle, some clients may be able to reconnect and fire the OnReconnected event.
For more information, see Understanding and Handling Connection Lifetime Events in SignalR.
Caller state not populated
The connection lifetime event handler methods are called from the server, which means that any state that you put in the state object on the client will not be populated in the Caller property on the server. For information about the state object and the Caller property, see How to pass state between clients and the Hub class later in this topic.
How to get information about the client from the Context property
To get information about the client, use the Context property of the Hub class. The Context property returns a HubCallerContext object which provides access to the following information:
The connection ID of the calling client.
string connectionID = Context.ConnectionId;
The connection ID is a GUID that is assigned by SignalR (you can't specify the value in your own code). There is one connection ID for each connection, and the same connection ID is used by all Hubs if you have multiple Hubs in your application.
the reason is:
did you close the client browser after shut down the iis?
I can tell you that the client is still connecting even if you shut down the iis.
because the signalr client html file not necessary hosted in iis, it can connect even it is hosted in the client side.
I'm writing a test application with signal r server and a web client and I wanted to know if there is a way to determine or have the server know which transport method the client is establishing with the server.
In regards to websockets which has a persistent two-way connection between the client and server or long polling which keeps polling the server until the server responds and then closes up the connection would there be any downside that I have to be aware of regarding the transport method not being web sockets outside of the persistent two-way connection especially if there are going to be many long running requests being made one after another?
I've noticed that making multiple requests from a client will be handled by the hub and returned when done, example I send a request to wait 10 seconds then a another request to wait 1 second. The Hub will respond to the 1 second wait request first then the 10 second delay, I am curious as to whether there is a thread per request created which is attached to the client via the same persistent duplex connection.
here is my example code.
class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
public class RunningHub : Hub
{
public void SendLongRunning(string name, string waitFor)
{
Clients.All.addMessage(name, "just requested a long running request I'll get back to you when im done");
LongRunning(waitFor);
Clients.All.addMessage(name, "I'm done with the long running request. which took " + waitFor + " ms");
}
private void LongRunning(string waitFor)
{
int waitTime = int.Parse(waitFor);
Thread.Sleep(waitTime);
}
}
JQuery Sample.
$(function () {
//Set the hubs URL for the connection
$.connection.hub.url = "http://localhost:9090/signalr";
// Declare a proxy to reference the hub.
var signalHub = $.connection.runningHub;
$('#url').append('<strong> Working With Port: ' + $.connection.hub.url + '</strong>');
// Create a function that the hub can call to broadcast messages.
signalHub.client.addMessage = function (name, message) {
//handles the response the message here
};
// Start the connection.
$.connection.hub.start().done(function () {
$('#sendlongrequest').click(function() {
signalHub.server.sendLongRunning($('#displayname').val(), $('#waitTime').val());
});
});
});
For ASP.NET Core;
var transportType = Context.Features.Get<IHttpTransportFeature>()?.TransportType;
Regarding the transport method:
You can inspect HubCallerContext.QueryString param transport:
public void SendLongRunning(string name, string waitFor)
{
var transport = Context.QueryString.First(p => p.Key == "transport").Value;
}
Regarding threading & long-running tasks:
Each request will be handled on a separate thread and the hub pipeline resolves the client-side promise when the hub method completes. This means that you can easily block your connection because of the connection limit in browsers (typically 6 connections at a time).
E.g.: if you use long-polling and you make six requests to the server, each triggering (or directly executing) a long-running operation, then you'll have six pending AJAX requests which only get resolved once the hub method is done, and you won't be able to make any further requests to the server until then. So you should use separate tasks for the long-running code and you should also not await those so the hub dispatcher can send its response without a delay.
If the client needs to know when the long-running task is done, then you should do a push notification from the server instead of relying on the .done() callback.
To make this easier to understand: We are using a database that does not have connection pooling built in. We are implementing our own connection pooler.
Ok so the title probably did not give the best description. Let me first Describe what I am trying to do. We have a WCF Service (hosted in a windows service) that needs to be able to take/process multiple requests at once. The WCF service will take the request and try to talk to (say) 10 available database connections. These database connections are all tracked by the WCF service and when processing are set to busy. If a request comes in and the WCF tries to talk to one of the 10 database connections and all of them are set to busy we would like the WCF service to wait for and return the response when it becomes available.
We have tried a few different things. For example we could have while loop (yuck)
[OperationContract(AsyncPattern=true)]
ExecuteProgram(string clientId, string program, string[] args)
{
string requestId = DbManager.RegisterRequest(clientId, program, args);
string response = null;
while(response == null)
{
response = DbManager.GetResponseForRequestId(requestId);
}
return response;
}
Basically the DbManager would track requests and responses. Each request would call the DbManager which would assign a request id. When a database connection is available it would assign (say) Responses[requestId] = [the database reponse]. The request would constantly ask the DbManager if it had a response and when it did the request could return it.
This has problems all over the place. We could possibly have multiple threads stuck in while loops for who knows how long. That would be terrible for performance and CPU usage. (To say the least)
We have also looked into trying this with events / listeners. I don't know how this would be accomplished so the code below is more of how we envisioned it working.
[OperationContract(AsyncPattern=true)]
ExecuteProgram(string clientId, string program, string[] args)
{
// register an event
// listen for that event
// when that event is called return its value
}
We have also looked into the DbManager having a queue or using things like Pulse/Monitor.Wait (which we are unfamiliar with).
So, the question is: How can we have an async WCF Operation that returns when it is able to?
WCF supports the async/await keywords in .net 4.5 http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx. You would need to do a bit of refactoring to make your ExecuteProgram async and make your DbManager request operation awaitable.
If you need your DbManager to manage the completion of these tasks as results become available for given clientIds, you can map each clientId to a TaskCompletionSource. The TaskCompletionSource can be used to create a Task and the DbManager can use the TaskCompletionSource to set the results.
This should work, with a properly-implemented async method to call:
[OperationContract]
string ExecuteProgram(string clientId, string program, string[] args)
{
Task<string> task = DbManager.DoRequestAsync(clientId, program, args);
return task.Result;
}
Are you manually managing the 10 DB connections? It sounds like you've re-implemented database connection pooling. Perhaps you should be using the connection pooling built-in to your DB server or driver.
If you only have a single database server (which I suspect is likely), then just use a BlockingCollection for your pool.
Given an async controller:
public class MyController : AsyncController
{
[NoAsyncTimeout]
public void MyActionAsync() { ... }
public void MyActionCompleted() { ... }
}
Assume MyActionAsync kicks off a process that takes several minutes. If the user now goes to the MyAction action, the browser will wait with the connection open. If the user closes his browser, the connection is closed. Is it possible to detect when that happens on the server (preferably inside the controller)? If so, how? I've tried overriding OnException but that never fires in this scenario.
Note: I do appreciate the helpful answers below, but the key aspect of this question is that I'm using an AsyncController. This means that the HTTP requests are still open (they are long-lived like COMET or BOSH) which means it's a live socket connection. Why can't the server be notified when this live connection is terminated (i.e. "connection reset by peer", the TCP RST packet)?
I realise this question is old, but it turned up frequently in my search for the same answer.
The details below only apply to .Net 4.5
HttpContext.Response.ClientDisconnectedToken is what you want. That will give you a CancellationToken you can pass to your async/await calls.
public async Task<ActionResult> Index()
{
//The Connected Client 'manages' this token.
//HttpContext.Response.ClientDisconnectedToken.IsCancellationRequested will be set to true if the client disconnects
try
{
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://google.com";
var html = await client.GetAsync(url, HttpContext.Response.ClientDisconnectedToken);
}
}
catch (TaskCanceledException e)
{
//The Client has gone
//you can handle this and the request will keep on being processed, but no one is there to see the resonse
}
return View();
}
You can test the snippet above by putting a breakpoint at the start of the function then closing your browser window.
And another snippet, not directly related to your question but useful all the same...
You can also put a hard limit on the amount of time an action can execute for by using the AsyncTimeout attribute. To use this use add an additional parameter of type CancellationToken. This token will allow ASP.Net to time-out the request if execution takes too long.
[AsyncTimeout(500)] //500ms
public async Task<ActionResult> Index(CancellationToken cancel)
{
//ASP.Net manages the cancel token.
//cancel.IsCancellationRequested will be set to true after 500ms
try
{
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://google.com";
var html = await client.GetAsync(url, cancel);
}
}
catch (TaskCanceledException e)
{
//ASP.Net has killed the request
//Yellow Screen Of Death with System.TimeoutException
//the return View() below wont render
}
return View();
}
You can test this one by putting a breakpoint at the start of the function (thus making the request take more than 500ms when the breakpoint is hit) then letting it run out.
Does not Response.IsClientConnected work fairly well for this? I have just now tried out to in my case cancel large file uploads. By that I mean if a client abort their (in my case Ajax) requests I can see that in my Action. I am not saying it is 100% accurate but my small scale testing shows that the client browser aborts the request, and that the Action gets the correct response from IsClientConnected.
It's just as #Darin says. HTTP is a stateless protocol which means that there are no way (by using HTTP) to detect if the client is still there or not. HTTP 1.0 closes the socket after each request, while HTTP/1.1 can keep it open for a while (a keep alive timeout can be set as a header). That a HTTP/1.1 client closes the socket (or the server for that matter) doesn't mean that the client has gone away, just that the socket hasn't been used for a while.
There are something called COMET servers which are used to let client/server continue to "chat" over HTTP. Search for comet here at SO or on the net, there are several implementations available.
For obvious reasons the server cannot be notified that the client has closed his browser. Or that he went to the toilet :-) What you could do is have the client continuously poll the server with AJAX requests at regular interval (window.setInterval) and if the server detects that it is no longer polled it means the client is no longer there.