I'm using Signalr 2.2.1 with a successful websocket connection.
Here are the events for different states : ( simplified for brevity)
var hub = $.connection.moveShapeHub;
$.connection.hub.start().done(function ()
{
console.log("hub started successfully");
}).fail(function () { console.log('Could not Connect!'); });
$.connection.hub.disconnected(function ()
{
$.connection.hub.start();
console.log('Connection disconnected')
});
My app is working fine as expected.
But look what happen when I disable the network card ( I access my computer not via localhost but via dynamic dns which goes to the world and then comes back to my computer)
At first you can see websocket connection error (I see it multiple times)
WebSocket connection to
'ws://xxxxxx.ddns.net/signalr/reconnect?transport=webSockets&messageId=d-C68A95E5-g%2C1&clientProtocol=1.5&connectionToken=%2FDJL8eAtVtSA3XKeap4Js3IrbkCm56C%2FWKCQtApGiMroWAgnzNoRHmJ0Y2LpIdWWWL%2BfY3dXvJqYHFfby1XYii0ibPpKM55PQuZyf9aH4k9JHIT79lWoMWBasIpa9Gjk&connectionData=%5B%5D&tid=2'
failed: Error in connection establishment:
net::ERR_INTERNET_DISCONNECTED
And then you see endless calls(!!!) to the negotiate
http://xxxx.ddns.net/signalr/negotiate?clientProtocol=1.5&connectionToken=%2FDJL8eAtVtSA3XKeap4Js3IrbkCm56C%2FWKCQtApGiMroWAgnzNoRHmJ0Y2LpIdWWWL%2BfY3dXvJqYHFfby1XYii0ibPpKM55PQuZyf9aH4k9JHIT79lWoMWBasIpa9Gjk&connectionData=%5B%7B%22name%22%3A%22moveshapehub%22%7D%5D&_=1485811277855
Wait ~15 seconds to see the endless loop :
Question
How can I fix those endless calls ? Or alternatvly - increase delay in those "negotiate calls" -say every 2 seconds ( instead of blazing fast endlessly 0.1 seconds)
Edit
I've changed this code :
$.connection.hub.disconnected(function ()
{
$.connection.hub.start();
console.log('Connection disconnected')
});
to this (remove hub start):
$.connection.hub.disconnected(function ()
{
console.log('Connection disconnected')
});
And now I see only this message :
But now I'm losing all the basic idea of "trying restart connecting" in case of disconnect. So I ask again is there any reasonable solution or at least trying "restart the connection every 2 seconds" ?
negotiate is the first request a SignalR client sends to establish a connection. You are trying to start the connection as soon as it gets disconnected in the disconnected event handler. Because the network is down negotiate fails and the disconnected event is invoked and you try to start the connection again.
The documentation shows how to do it with the timeout:
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 5000); // Restart connection after 5 seconds.
});
The answer to all issues of SR connections is check and persist connection status on each server request. I have created a method that takes the proxy method as a parameter and fires the proxy method after establishing the hub connection is available.
call the method using SR_Connection.execute.
When it has established the connection is commits the execution of the request.
function cancel(){
SR_Connection.execute('SRProxy.server.Cancel', uniqueID);
}
var SR_Connection = (function () {
//Start of the Return Statement
return {
execute: function (method, params) {
if ($.connection.hub && $.connection.hub.state === $.signalR.connectionState.disconnected) {
$.connection.hub.start().done(function () {
SR_Connection.commit(method, params);
});
}
else {
SR_Connection.commit(method, params);
}
},
commit: function (method, params) {
var namespaces = method.split("."),
context;
if (typeof (window) == "undefined") {
context = global;
} else {
context = window;
}
var functionToExecute = namespaces.pop();
for (var i = 0; i < namespaces.length; i++) {
context = context[namespaces[i]];
}
context[functionToExecute](params);
}
};//End of the Return Statement
})();
Related
I have a SignalR hub written in my MVC solution, with a Javascript client connecting from the view.
The point of the connection is to receive changes for a wallboard from the server. This has to happen almost instantly and requires a lifetime connection, since the webpage is running on a screen without direct pc access.
So far the SignalR connection works for a couple of hours before it gives error.
The error I get is
Error: Connection disconnected with error 'Error: Server timeout elapsed without receiving a message form the server.'.
Failed to load resource: net::ERR_CONNECTION_TIMED_OUT
Warning: Error from HTTP request. 0:
Error: Failed to complete negotiation with the server: Error
Error: Failed to start the connection: Error
Uncaught (in promise) Error
at new HttpError (singlar.js:1436)
at XMLHttpRequest.xhr.onerror (singalr.js:1583)
My client code
let connection = new signalR.HubConnectionBuilder()
.withUrl("/wbHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.start().then(function () {
connection.invoke("GetAllWallboards").then(function (wallboard) {
for (var i = 0; i < wallboard.length; i++) {
displayWallboard(wallboard[i]);
}
startStreaming();
})
})
connection.onclose(function () {
connection.start().then(function () {
startStreaming();
})
})
function startStreaming() {
connection.stream("StreamWallboards").subscribe({
close: false,
next: displayWallboard
});
}
Hub Code:
public class WallboardHub : Hub
{
private readonly WallboardTicker _WallboardTicker;
public WallboardHub(WallboardTicker wallboardTicker)
{
_WallboardTicker = wallboardTicker;
}
public IEnumerable<Wallboard> GetAllWallboards()
{
return _WallboardTicker.GetAllWallboards();
}
public ChannelReader<Wallboard> StreamWallboards()
{
return _WallboardTicker.StreamWallboards().AsChannelReader(10);
}
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnDisconnectedAsync(exception);
}
}
Question 1: Is the way I handle reconnecting correct? From the error it feels like the .onclose works, but that it only tries one time? Is there anyway to try for x min before showing error?
Question 2: Reloading the website makes the connection work again, is there potential anyway to refresh the browser on signalR connection error?
I have the same issue (Question 1), and i resolve with this:
const connection = new SignalR.HubConnectionBuilder()
.withUrl("/hub")
.configureLogging(SignalR.LogLevel.Information)
.build();
connect(connection);
async function connect(conn){
conn.start().catch( e => {
sleep(5000);
console.log("Reconnecting Socket");
connect(conn);
}
)
}
connection.onclose(function (e) {
connect(connection);
});
async function sleep(msec) {
return new Promise(resolve => setTimeout(resolve, msec));
}
Every 5 seconds tries to reconnect, but i don't know if this is the right way to do this.
ASP.NET Core 2.1 (current LTS release) with the corresponding SignalR release doesn't seem to have some integrated reconnecting method avaliable. The code from #Shidarg doesn't work for me, it calls the reconnect method in a infinitive loop crashiny my browser. I also like the async/await syntax from C# more, so I updated it:
let reconnectWaitTime = 5000
let paramStr = '?myCustomArg=true'
let client = new signalR.HubConnectionBuilder()
.withUrl("/overviewHub" + paramStr)
.build();
client.onclose(async () => {
console.warn(`WS connection closed, try reconnecting with loop interval ${reconnectWaitTime}`)
tryReconnect(client)
})
await tryReconnect(client)
async function tryReconnect(client) {
try {
let started = await client.start()
console.log('WS client connected!')
// Here i'm initializing my services, e.g. fetch history of a chat when connection got established
return started;
} catch (e) {
await new Promise(resolve => setTimeout(resolve, reconnectWaitTime));
return await tryReconnect(client)
}
}
But for ASP.NET Core 3 they included a reconnecting method:
let client = new signalR.HubConnectionBuilder()
.withUrl("/myHub")
.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Information)
.build();
Per default it try three reconnects: First after 2 seconds, second after 10 seconds and the last about 30 seconds. This could be modificated by passing the intervalls as array parameter:
.withAutomaticReconnect([5000, 1500, 50000, null])
This example re-trys after 5s, 15s and 50s. The last null param tell SignalR to stop re-trying. More information could be found here: https://www.jerriepelser.com/blog/automatic-reconnects-signalr/
Configuring automatic reconnects only requires a call to withAutomaticReconnect on the HubConnectionBuilder. Here is what my JavaScript code looks like for configuring my connection:
connection = new signalR.HubConnectionBuilder()
.withUrl("/publish-document-job-progress")
.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Information)
.build();
You can configure the backoff period by passing an array of retry delays to the call to withAutomaticReconnect(). The default for this is [0, 2000, 10000, 30000, null]. The null value tells SignalR to stop trying. So, for example, if I wanted it to retry at 0, 1 second and 5 seconds, I can configure my HubConnectionBuilder as follows:
connection = new signalR.HubConnectionBuilder()
.withUrl("/publish-document-job-progress")
.withAutomaticReconnect([0, 1000, 5000, null])
.configureLogging(signalR.LogLevel.Information)
.build();
is there a way to simulate lag with Meteor? Perhaps something that would delay all calls by say, 300ms?
You can do it in publish using:
Meteor._sleepForMs(5000); // sleeps for 5 seconds
I guess I'm a bit late for the party, but here's a better solution:
There are basically two parts to this question. One is, how to delay Meteor WebSocket (SockJS) writes and one is how to delay HTTP traffic (connect). You'll need to add both of the following snippets to your server-side code in order to delay all traffic sent from the Meteor server.
WebSocket
The hard part was overwriting the WebSocket write to delay it with a setTimeout:
(function () {
// Set the delay you want
var timeout = 3000
// stream_server abstracts sockJS the DDP-Server talks to it.
var streamServer = Meteor.server.stream_server
// The connect event listener
var standardConnect = streamServer.server._events.connection
// Overwrite the default event-handler
streamServer.server._events.connection = function (socket) {
// Overwrite the writes to the socket
var write = socket.write
socket.write = function () {
var self = this
var args = arguments
// Add a delay
setTimeout(function () {
// Call the normal write methods with the arguments passed to this call
write.apply(self, args)
}, timeout)
}
// Call the normal handler after overwritting the socket.write function
standardConnect.apply(this, arguments)
}
})()
HTTP
With connect it's pretty straight forward:
// Add a simple connect handler, wich calls the next handler after a delay
WebApp.rawConnectHandlers.use(function (req, res, next) {
return setTimeout(next, timeout)
})
Not sure about all calls, but you can use Futures to add a lag on the server, that way you can see latency compensation in action.
In a meteor method for example, you can
Meteor.methods({
post: function(post) {
post.title = post.title + (this.isSimulation ? '(client)' : '(server)');
// wait for 5 seconds
if (! this.isSimulation) {
var Future = Npm.require('fibers/future');
var future = new Future();
Meteor.setTimeout(function() {
future.ret();
}, 5 * 1000); // 5 seconds
future.wait();
}
var postId = Posts.insert(post);
return postId;
}
});
This will show the post being inserted with (client) appended to the end, and then 5 seconds later will get the update from the server and post's title will end with (server)
If you want simulate the lag in the subscriptions you can do the next:
Meteor.publish('collection', function(params) {
Meteor._sleepForMs(2000); // Sleep for 2 seconds
return CollectionX.find(params.query,params.projection);
});
I am using node-http-proxy. However, in addition to relaying HTTP requests, I also need to listen to the incoming and outgoing data.
Intercepting the response data is where I'm struggling. Node's ServerResponse object (and more generically the WritableStream interface) doesn't broadcast a 'data' event. http-proxy seems to create it's own internal request, which produces a ClientResponse object (which does broadcast the 'data' event) however this object is not exposed publically outside the proxy.
Any ideas how to solve this without monkey-patching node-http-proxy or creating a wrapper around the response object?
Related issue in issues of node-http-proxy on Github seems to imply this is not possible. For future attempts by others, here is how I hacked the issue:
you'll quickly find out that the proxy is only calling writeHead(), write() and end() methods of the res object
since res is already an EventEmitter, you can start emitting new custom events
listen for these new events to assemble the response data and then use it
var eventifyResponse = function(res) {
var methods = ['writeHead', 'write', 'end'];
methods.forEach(function(method){
var oldMethod = res[method]; // remember original method
res[method] = function() { // replace with a wrapper
oldMethod.apply(this, arguments); // call original method
arguments = Array.prototype.slice.call(arguments, 0);
arguments.unshift("method_" + method);
this.emit.apply(this, arguments); // broadcast the event
};
});
};
res = eventifyResponse(res), outputData = '';
res.on('method_writeHead', function(statusCode, headers) { saveHeaders(); });
res.on('method_write', function(data) { outputData += data; });
res.on('method_end', function(data) { use_data(outputData + data); });
proxy.proxyRequest(req, res, options)
This is a simple proxy server sniffing the traffic and writing it to console:
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create a proxy server with custom application logic
//
var proxy = httpProxy.createProxyServer({});
// assign events
proxy.on('proxyRes', function (proxyRes, req, res) {
// collect response data
var proxyResData='';
proxyRes.on('data', function (chunk) {
proxyResData +=chunk;
});
proxyRes.on('end',function () {
var snifferData =
{
request:{
data:req.body,
headers:req.headers,
url:req.url,
method:req.method},
response:{
data:proxyResData,
headers:proxyRes.headers,
statusCode:proxyRes.statusCode}
};
console.log(snifferData);
});
// console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2));
});
proxy.on('proxyReq', function(proxyReq, req, res, options) {
// collect request data
req.body='';
req.on('data', function (chunk) {
req.body +=chunk;
});
req.on('end', function () {
});
});
proxy.on('error',
function(err)
{
console.error(err);
});
// run the proxy server
var server = http.createServer(function(req, res) {
// every time a request comes proxy it:
proxy.web(req, res, {
target: 'http://localhost:4444'
});
});
console.log("listening on port 5556")
server.listen(5556);
I tried your hack but it didn't work for me. My use case is simple: I want to log the in- and outgoing traffic from an Android app to our staging server which is secured by basic auth.
https://github.com/greim/hoxy/
was the solution for me. My node-http-proxy always returned 500 (while the direct request to stage did not). Maybe the authorization headers would not be forwarded correctly or whatever.
Hoxy worked fine right from the start.
npm install hoxy [-g]
hoxy --port=<local-port> --stage=<your stage host>:<port>
As rules for logging I specified:
request: $aurl.log()
request: #log-headers()
request: $method.log()
request: $request-body.log()
response: $url.log()
response: $status-code.log()
response: $response-body.log()
Beware, this prints any binary content.
This error happens whenever the node process make a http request to get user's information from a web API.
The scenario is :
I'm running a TCP server using node, and when it get "login" request from a client, it will send a http GET request to another web API to retrieve the user's information.
While users increasing, the node process starts to throw the "ETIMEOUT" error when retrieving user's info. And once if the error happened, all the request after that will throw the same error.
I've tried to perform the same request with wget but everything is fine, so I think maybe it's not a network problem.
And strangely, after increasing the open file limit to 10,0000 using ulimit -n, it goes well until the next level user increment.
The fetch function is here:
fetchUserInfo = function(callback) {
var http = require('http');
var opt = {
agent: false,
host: 'www.someapi.net',
port: 80,
path: '/userInfo.php'
}
var body = '';
var req = http.request(opt, function(res) {
res.setEncoding('utf8');
res.on('data', function(chunk){
body += chunk;
});
res.on('end', function() {
if(callback) {
callback(body);
}
});
});
req.on('error', function(e) {
sys.log("User info fetch error: " + e.message);
if(callback) {
callback();
}
});
req.end();
}
My environment is Debian GNU/Linux 6.0 with node v0.4.10.
Finding it hard to describe this issue - so please edit if you know more relevant terms.
I'm building a web application which essentially uses Redis (PubSub) + Node.js + Socket.IO as a distribution server.
I have two-way communication working with no issues - but I need to be able to make a request to the server from the client (asynchronously) and deal with the response while still processing other irrelevant responses that might come in before it.
This is what I have so far, but I'm not particularly happy with this approach:
Server
// Lots of other code
redis.psubscribe('*');
redis.on("pmessage", function(pattern, channel, message) {
// broadcast
});
io.on('connection', function(client) {
client.on('message', function(message) {
switch(message.method) {
// call relevant function
}
});
});
function object_exists(object_id) {
// do stuff to check object exists
client.send({method: 'object_exists', value: object_exists});
}
Client
var call = Array();
$(document).ready(function() {
socket.connect();
socket.on("message", function(obj){
console.log(obj);
call[obj.method](obj.value);
});
});
function object_exists(object_id) {
socket.send({method: 'object_exists', value: object_id});
// Set a function to be called when the next server message with the 'object_exists' method is received.
call['object_exists'] = function(value) {
if(value) {
// object does exist
}
}
}
tl;dr: I need to 'ask' the server something and then deal with the response using Socket.IO.
You don't specifically say why you are unhappy with your approach, but it looks to me like you are almost there. I am not really sure what you are trying to do with the call array, so I just took it out for clarity.
Basically, you just need to set up a switch statement to act as a message router on each side of the socket connection and fire off the appropriate methods based in incoming messages. Send enough state with the message itself so you can handle the work without any additional context. In your reworked code, I send the object_id to the server and back again to the client.
///SERVER
// Lots of other code
redis.psubscribe('*');
redis.on("pmessage", function(pattern, channel, message) {
// broadcast
});
io.on('connection', function(client) {
client.on('message', function(message) {
switch(message.method) {
case 'object_exists':
object_exists(message.objectId);
break;
}
});
});
//Takes an id an returns true if the object exists
function object_exists(object_id) {
// do stuff to check object exists
client.send({method: 'object_exists', objectId: object_id, value: object_exists});
}
///CLIENT
$(document).ready(function() {
//setup the message event handler for any messages coming back from the server
//This won't fire right away
socket.on("message", function(message){
switch(message.method) {
case 'object_exists':
object_exists(message.objectId, message.value);
break;
}
});
//When we connect, send the server the message asking if object_exists
socket.on("connect", function() {
socket.send({method: 'object_exists', objectId: object_id});
});
//Initiate the connection
socket.connect();
});
//Get's called with with objectId and a true if it exists, false if it does not
function object_exists(objectId, value) {
if(value) {
// object does exist, do something with objectId
}
else {
// object does not exist
}
}
If you want to see a bunch more code in the same stack doing work similar to what you are trying to accomplish, check out my nodechat.js project.