I make ws server on nestjs
I use #nestjs/websockets library
#WebSocketGateway({
path: '/websocket',
})
export class EventsGateway implements OnGatewayConnection {
constructor(
private jwtService: JwtService,
) {}
#WebSocketServer()
server!: Server;
private static clients: Map<string, WebSocket> = new Map();
async handleConnection(socket: WebSocket, args) {
try {
const { email } = this.jwtService.verify(
args.headers.authorization.split('Bearer ')[1],
);
socket.onclose = () => {
EventsGateway.clients.delete(email);
};
} catch (e) {
socket.close(1000, 'No authorization');
}
}
}
All parameters are default, but the connection lasts only one minute. Can't figure out how to fix this so it lasts a long time.
As far as I understand, the connection should be automatically maintained with the help of a ping, or do I need to do it myself?
On the localhost, the connection takes a very long time.
Related
download the example here:https://github.com/apache/ignite/tree/master/modules/platforms/dotnet/examples
Adjust the code in ServerNode.Program.cs
namespace Apache.Ignite.Examples.ServerNode
{
public static class Program
{
public static void Main()
{
using (var ignite = Ignition.Start(Utils.GetServerNodeConfiguration()))
{
ignite.GetCluster().SetActive(true); **//add one line code here**
Utils.DeployDefaultServices(ignite);
.....
}
}
}
}
Adjust the code in Util.cs
public static IgniteConfiguration GetServerNodeConfiguration()
{
return new IgniteConfiguration
{
Localhost = "127.0.0.1",
.......
PeerAssemblyLoadingMode = PeerAssemblyLoadingMode.CurrentAppDomain,
**//add some code here to enable persistence**
DataStorageConfiguration = new DataStorageConfiguration
{
DefaultDataRegionConfiguration = new DataRegionConfiguration
{
Name = "Default_Region",
PersistenceEnabled = true
}
}
};
}
Add the destroycache method into Apache.Ignite.Examples.Thin.Sql.LinqThin.Program Main method like this:
public static void Main()
{
using (var ignite = Ignition.StartClient(Utils.GetThinClientConfiguration()))
{
Console.WriteLine();
Console.WriteLine(">>> Cache LINQ example started.");
........
var sw = Stopwatch.StartNew();
ignite.DestroyCache(OrganizationCacheName);
sw.Stop();
Console.WriteLine($"DESTROY COST {sw.ElapsedMilliseconds}");
.....
}
....
}
When execute the detroyCache method, it throws socket timeout exception sometimes, or it cost long time sometimes if destroy successfully.
But all is OK when destroyCache if donot enable persistence.
Creating and destroying caches can be slower with persistence, especially on HDD (as opposed to SSD).
You can increase socket timeout like this:
var cfg = new IgniteClientConfiguration
{
...
SocketTimeout = TimeSpan.FromSeconds(15)
};
That being said, I've tried the steps above and it prints DESTROY COST 140 on my machine (Core i7-9700, SSD).
I created MyListener which will start listening (using TcpListener) on his own thread upon creation. the TcpListener should handle multiple clients so i am running inside infinte while and handle each client in special task.
this is my code:
public class MyListener
{
public event EventHandler<MessageEventArgs> MessageReceived;
public MyListener()
{
var thread = new Thread(Listen);
thread.Start();
}
private void Listen()
{
TcpListener server = null;
try
{
server = new TcpListener(IPAddress.Any, 8977);
server.Start();
while (true)
{
var client = server.AcceptTcpClient();
Task.Run(() =>
{
try
{
var msg = GetMessageFromClient(client);
MessageReceived?.Invoke(this, new MessageEventArgs { Message = msg });
}
catch (Exception)
{
}
finally
{
client.Close();
}
});
}
}
catch (Exception)
{
}
finally
{
if (server != null)
server.Stop();
}
}
private string GetMessageFromClient(TcpClient client)
{
var bytes = new byte[client.ReceiveBufferSize];
var stream = client.GetStream();
var i = stream.Read(bytes, 0, bytes.Length);
var message = Encoding.UTF8.GetString(bytes, 0, i);
return message;
}
}
here are my questions:
how can i ensure that the task handle the client will use the client i pass to it when i start the task and not different client (becuase after the task start we return to the AcceptTcpClient method and may get new client)
in my example and with multiple clients handled by the same method ("GetMessageFromClient") do i need to put some kind of locking on this
method?
Hi All (Especially the Aurelia core team hanging about round here)
I have an aurelia app using the "aurelia-http-client" to make requests to my back end API.
My back end API is a C# based service running on Nancy.
In my front end Iv'e abstracted the http client out to my own network lib as follows:
import { inject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
import { HttpClient } from 'aurelia-http-client';
import environment from './environment';
#inject(HttpClient, Router)
export default class httpservice {
private http: HttpClient = null;
private router: Router = null;
private authService: any = null;
private authToken: string = "";
constructor(HttpClient, Router) {
this.http = HttpClient;
this.router = Router;
HttpClient.configure(http => {
http.withBaseUrl(environment.servicebase);
});
}
public setAuthService(authService: any) {
this.authService = authService;
}
public get(url: string, authObject?: any): any {
let myAuth = this.authService ? this.authService : authObject;
let myToken = "";
if (myAuth) {
myToken = myAuth.getAuthToken();
}
let self = this;
let client = this.http
.createRequest(url)
.asGet()
.withHeader("AuthenticationToken", myToken)
.withInterceptor({
responseError(responseError) {
console.log(responseError);
if (responseError.statusCode === 401) {
if (myAuth) {
myAuth.destroySession();
}
}
if (responseError.statusCode === 404) {
self.router.navigateToRoute("missing");
}
return responseError;
}
});
return client;
}
public post(url: string, postData: any, authObject?: any): any {
let myAuth = this.authService ? this.authService : authObject;
let myToken = "";
if (myAuth) {
myToken = myAuth.getAuthToken();
}
let self = this;
let client = this.http
.createRequest(url)
.asPost().withContent(postData)
.withHeader("AuthenticationToken", myToken)
.withInterceptor({
responseError(responseError) {
console.log(responseError);
if (responseError.statusCode === 401) {
if (myAuth) {
myAuth.destroySession();
}
}
if (responseError.statusCode === 404) {
self.router.navigateToRoute("missing");
}
return responseError;
}
});
return client;
}
}
and I then use this in my other modules/classes as follows:
import { Aurelia, inject } from 'aurelia-framework';
import HttpService from './httpservice';
import environment from './environment';
import { EventAggregator } from 'aurelia-event-aggregator';
#inject(EventAggregator, Aurelia, HttpService)
export default class Authservice {
public http: HttpService = null;
public app: Aurelia = null;
public ea: EventAggregator = null;
public authToken: any = null;
private loginUrl: string = "";
private logoutUrl: string = "";
private checkUrl: string = "";
constructor(eventAggregator, aurelia, httpService) {
this.http = httpService;
this.app = aurelia;
this.ea = eventAggregator;
this.loginUrl = "/login";
}
public getAuthToken() {
if (!sessionStorage[environment.tokenname] ||
(sessionStorage[environment.tokenname] == null)) {
return null;
}
return sessionStorage[environment.tokenname];
}
public login(loginName, password) {
let postData = {
loginName: loginName,
password: password
};
let client = this.http.post(this.loginUrl, postData);
client.send()
.then((response) => response.content)
.then((data) => {
if (data.error) {
this.ea.publish("loginMessage", { message: data.errorMessage });
return;
}
if (data.authenticationFailed) {
this.ea.publish("loginMessage", { message: "Invalid user name and/or password supplied." });
return;
}
if (data.accountSuspended) {
this.ea.publish("loginMessage", { message: "Your account has been suspended, please contact support." });
return;
}
sessionStorage[environment.tokenname] = data.token;
sessionStorage["displayedLoginName"] = data.displayName;
location.assign('#/');
this.app.setRoot('app');
})
.catch(() =>
{
debugger;
alert("Something bad happened trying to connect to server.");
});
}
public isAuthenticated() {
// TODO: hook this up to check auth token validity via rest call???
let token = this.getAuthToken();
return token !== null;
}
}
enum LoginStates {
LoginValid = 0,
BadUserNameOrPassword,
AccountSuspended
}
Please note I've stripped some of the code out of the auth library to reduce confusion
In general ALL of this works well. The interceptors get triggered when 401s and 404s occur, and if I add a 500 that get's handled too, so where all good there.
The problem I have is handling communication failures.
As you can see in the login routine, I have a catch following the then.
I expected that if the server couldn't be reached or some other base communications failure occurred, that this catch would trigger rather than the "then" and thus allow me to handle the error, but instead it does not.
What I get instead is this in the console:
Worse still, my login routine doesn't abort, it actually succeeds and allows the logged in page to be shown.
It seems that while the library is making the OPTIONS call (Which is when this error occurs) none of my user code is taken into account.
The OPTIONS call is required for successful pre-flight/ajax requests, so stopping this happening is not an option, and I feel that if the OPTIONS call did not abort, but made it to the POST call,t hat my error handling would then be taken into consideration.
It seems silly to be not able to trap errors like this, especially in today's mobile world where a device may be out of coverage or temporarily offline.
If anyone has any thoughts on how this can be solved, I'd love to hear them.
Update 1
My problem seems to be related to this one:
aurelia-fetch-client - a promise was rejected with a non-error: [object Response]
However, I'm not using "useStandardConfiguration()" which is apparently the cause for that case. I'm also not using the fetch client, however I do note that the API in both clients is practically the same, so I wonder if the underlying code is also similar.
Ok.... so, after a long hard afternoon of head scratching and hair pulling, it turns out, the whole thing is actually linked to a reported issue with the "BlueBird promises library" which is what aurelia uses to manage it's promises.
The link to the issue with BlueBird can be found here:
https://github.com/petkaantonov/bluebird/issues/990
It's not specifically an issue according to the BB dev's but to many folks encountering it, it sure looks like one.
The bottom line is that the library is not designed to throw the errors generated directly by it (As the example on the issue page shows)
The correct way according to the BB team, is to either throw a new error completely, or derive a new instance from the one passed to the promise, and alter the parameters to it before then re-throwing it.
Of course, because of the abstraction in Aurelia, this is not an option for most of us, unless we want to go about changing the http client library code.
Some of the marks for this need to go to "TheBlueFox" for His/Her comments above.
The solution ended up being something like the following:
import { inject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
import { HttpClient, Interceptor } from 'aurelia-http-client';
import environment from './environment';
import Debugger = require("_debugger");
#inject(HttpClient, Router)
export default class httpservice {
private http: HttpClient = null;
private router: Router = null;
private authService: any = null;
private authToken: string = "";
private myInterceptors: Interceptor;
constructor(HttpClient, Router) {
this.http = HttpClient;
this.router = Router;
HttpClient.configure(http => {
http.withBaseUrl(environment.servicebase);
http.withInterceptor(new HttpInterceptors());
});
}
public setAuthService(authService: any) {
this.authService = authService;
}
public get(url: string, authObject?: any): any {
let myAuth = this.authService ? this.authService : authObject;
let myToken = "";
if (myAuth) {
myToken = myAuth.getAuthToken();
}
let client = this.http
.createRequest(url)
.asGet()
.withHeader("AuthenticationToken", myToken);
return client;
}
public post(url: string, postData: any, authObject?: any): any {
let myAuth = this.authService ? this.authService : authObject;
let myToken = "";
if (myAuth) {
myToken = myAuth.getAuthToken();
}
let self = this;
let client = this.http
.createRequest(url)
.asPost().withContent(postData)
.withHeader("AuthenticationToken", myToken);
return client;
}
}
class HttpInterceptors implements Interceptor {
responceError(error)
{
if (error.statusCode === 0) {
throw new Error("Could not contact server");
}
if (error.statusCode === 401) {
// do auth handling here
}
if (error.statusCode === 404) {
// do 404 handling here
}
return error;
}
}
The magic is in the HttpInterceptors class attached to the bottom of my HttpService. You should be able to see a check for a status code of 0, and that the actual action performed here is to throw a new error.
It's the action of this new error being thrown that then causes the "catch" in the actual call to the http client to be caught.
If you don't throw at that point, then everything just falls apart and you get the scenario seen in my original question post, throw and you get to catch it and deal with it in user code.
This way of doing things is also apparent in the aurelia-fetch-client too, as that works in a broadly similar way, using the BlueBird promise library.
I am working on a Spring-MVC application in which I have implemented chat functionality using Cometd. As a feature, I would like to know if there is any way Cometd has support or some way I can show which user is typing. Ofcourse the user information I can retrieve. Here is my chat code. Thanks.
ChatServiceImpl :
#Named
#Singleton
#Service
public class ChatServiceImpl {
#Inject
private BayeuxServer bayeux;
#Session
private ServerSession serverSession;
#Listener(value = "/service/person/{id}")
public void privateChat(ServerSession remote, ServerMessage.Mutable message,#Param("id")String id) {
System.out.println("wassup");
Person sender = this.personService.getCurrentlyAuthenticatedUser();
String senderName = sender.getFirstName();
Map<String, Object> input = message.getDataAsMap();
String data = (String) input.get("name");
String timestamp = (String) input.get("timestamp");
String temp = message.getChannel();
String temp1 = temp;
temp = temp.replace("/service/person/", "");
String channelName = temp1.replace("/service","");
final int conversationId = Integer.valueOf(temp);
Replies replies = new Replies();
replies.setReplyingPersonName(senderName);
replies.setReplyText(data);
replies.setReplyTimeStamp(timestamp);
replies.setReplyingPersonId(sender.getId());
replies.setRead(false);
Long replyId = this.repliesService.addReply(replies, conversationId, sender);
Map<String, Object> output = new HashMap<String, Object>();
output.put("text", data);
output.put("firstname", senderName);
output.put("channelname", channelName);
output.put("timestamp", timestamp);
output.put("id",sender.getId());
output.put("read","true");
output.put("replyid",replyId);
ServerChannel serverChannel = bayeux.createChannelIfAbsent("/person/" + id).getReference();
serverChannel.setPersistent(true);
serverChannel.publish(serverSession, output);
}
Application.js : Please note, I am using parts of this file in other JS file.
(function($)
{
var cometd = $.cometd;
$(document).ready(function()
{
function _connectionEstablished()
{
$('#body').append('<div>CometD Connection Established</div>');
}
function _connectionBroken()
{
$('#body').append('<div>CometD Connection Broken</div>');
}
function _connectionClosed()
{
$('#body').append('<div>CometD Connection Closed</div>');
}
var _connected = false;
function _metaConnect(message)
{
if (cometd.isDisconnected())
{
_connected = false;
_connectionClosed();
return;
}
var wasConnected = _connected;
_connected = message.successful === true;
if (!wasConnected && _connected)
{
_connectionEstablished();
}
else if (wasConnected && !_connected)
{
_connectionBroken();
}
}
// Function invoked when first contacting the server and
// when the server has lost the state of this client
function _metaHandshake(handshake)
{
if (handshake.successful === true)
{
cometd.batch(function()
{
cometd.subscribe('/chat/1306', function(message)
{
var data = message.data;
$('#body').append('<div>Server Says: ' + data.firstname + '/' + data.accountid + data.time1+'</div>');
});
});
}
}
// Disconnect when the page unloads
$(window).unload(function()
{
cometd.disconnect(true);
});
$(document).on('click', '#sender', function()
{
cometd.publish('/service/chat/1306', { name: 'hello_' + Date.now() });
});
var cometURL = location.protocol + "//" + location.host + config.contextPath + "/cometd";
cometd.configure({
url: cometURL,
logLevel: 'debug'
});
cometd.websocketEnabled = false;
cometd.addListener('/meta/handshake', _metaHandshake);
cometd.addListener('/meta/connect', _metaConnect);
cometd.handshake();
});
})(jQuery);
Kindly let me know how I can achieve this, as I cannot find many references for this. Thanks a lot. :-)
This is easily achieved by detecting on the client side the typing start/stop (in a smart way to avoid to send too many messages to the server), then send a CometD service message to the server.
The server can then just broadcast a message to a special channel (say /chat/typing) with the nickname of the user that is typing.
The client application will subscribe to /chat/typing and receive these messages, then display in the UI who is typing, possibly coalescing multiple users into a single UI notification.
The CometD part is trivial, the detection of the start/stop of the typing in a smart way is probably most of the work.
I am using the following class to accept incoming connections from client applications - using the send function I want to write the same UTFBytes to each client at the same time - is this possible? or if not, what would be the fastest way to write to them sequentially.
public class ProjectorClients
{
private var _serverSocket:ServerSocket;
private var _clients:Vector.<Socket> = new Vector.<Socket>;
private function ProjectorClients():void
{
_serverSocket = new ServerSocket();
_serverSocket.addEventListener(ServerSocketConnectEvent.CONNECT, onConnect)
_serverSocket.bind(888);
_serverSocket.listen();
}
private function onConnect(e:ServerSocketConnectEvent):void
{
trace("Client is connected");
e.socket.addEventListener(ProgressEvent.SOCKET_DATA, onData);
e.socket.addEventListener(Event.CLOSE, onConnectionClosed);
_clients.push(e.socket);
trace("Number of connected clients: " + _clients.length);
}
public function send(command:String):void
{
for each(var clientSocket:Socket in _clients)
{
if (clientSocket.connected)
{
clientSocket.writeUTFBytes(command);
clientSocket.flush();
}
}
}
private function onData(e:ProgressEvent):void
{
trace("data received");
}
private function onConnectionClosed(e:Event):void
{
trace("Client Socket is Closed");
for (var i:int = 0; i < _clients.length; i++)
{
if (_clients[i] == e.currentTarget)
{
_clients.splice(i,1);
break;
}
}
trace("Number of connected clients: " + _clients.length);
}
}
As mentioned by #eSniff, you need a publish subscribe module here. Redis would be a better option as it requires bare minimal steps to set up. The incoming connections will subscribe to the queue and you can publish the data, so that all the client receive it the same time. Please refer to the link below for better understanding.
http://redis.io/topics/pubsub