I am using 'rxjs-websockets' to connect with websockets. But after certain time (approx 2min)
the connection gets closed. How can I hold the connection until it is manually closed.
Here is the code snippet I use
constructor(private socketService: WebSocketService) {}
this.socketService.connect('/endpoint');
this.socketSubscription = this.socketService.messages
.subscribe(result => {
// perform operation
});
This is the WebSocketService
import {Injectable} from '#angular/core';
import {QueueingSubject} from 'queueing-subject';
import {Observable} from 'rxjs/Observable';
import websocketConnect from 'rxjs-websockets';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/retryWhen';
import 'rxjs/add/operator/delay';
#Injectable()
export class WebSocketService {
private inputStream: QueueingSubject<string>;
public messages: Observable<string>;
constructor() {
}
public connect(socketUrl) {
this.messages = websocketConnect(
socketUrl,
this.inputStream = new QueueingSubject<string>()
).messages.retryWhen(errors => errors.delay(1000))
.map(message => JSON.parse(message))
.share();
}
public send(message: string): void {
this.inputStream.next(message);
}
}
Websockets usually holds the connection for a long time interval with the help of some message exchange.
In our case we can call it as 'ping => pong', client sends message 'ping' and server may respond with message 'pong'.
You can send ping every 30 seconds as follows.
setInterval(() => {
this.socketService.send('ping');
}, 30000);
As you are converting every message received at WebSocketService into JSON, you have to make these change
to avaoid JSON Parsing error.
export class WebSocketService {
.
.
.
public connect(socketUrl) {
this.messages = websocketConnect(
socketUrl,
this.inputStream = new QueueingSubject<string>()
).messages.retryWhen(errors => errors.delay(1000))
//parse messages except pong to avoid JSON parsing error
.map(message => message === 'pong' ? message : JSON.parse(message))
.share();
}
.
.
.
}
Related
I have a problem with Nestjs, with starting a microservice that listens to a messagebroker, while simultaneously starting a websocket server with which we can communicate with front-end clients.
So I am aware of hybrid nest applications, where the websocket gateway can be injected into the microservice. However I get errors about Nest not being able to resolve the Gateway import.
I configured the microservice in a ResultsModule, the websocket in UsersModule.
The error I get is:
ERROR [ExceptionHandler] Nest can't resolve dependencies of the ResultsController (ResultsService, ?). Please make sure that the argument UsersGateway at index [1] is available in the ResultsModule context.
These are the relevant pieces of code:
main.js
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.RMQ,
options: {
urls: [
`amqp://${config.rabbitmq.username}:${config.rabbitmq.password}#${config.rabbitmq.host}:${config.rabbitmq.port}`,
],
queue: config.rabbitmq.channelListen,
noAck: false,
queueOptions: {
durable: false,
},
},
});
await app.startAllMicroservices();
await app.listen(82);
}
bootstrap();
app.module.ts
#Module({
imports: [UsersModule, ResultsModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
results.module.ts
#Module({
imports: [UsersModule],
controllers: [ResultsController],
providers: [ResultsService],
})
export class ResultsModule {}
users.module.ts
#Module({
providers: [UsersGateway, UsersService],
})
export class UsersModule {}
users.gateway.ts
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
OnGatewayInit,
OnGatewayConnection,
OnGatewayDisconnect,
WebSocketServer,
ConnectedSocket,
} from '#nestjs/websockets';
import { Socket, Server } from 'socket.io';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
#WebSocketGateway(82)
export class UsersGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
constructor(private readonly usersService: UsersService) {}
#WebSocketServer() server: Server;
afterInit(server: Server) {
console.log('init');
}
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
console.log(client.id);
}
#SubscribeMessage('resource')
create(
#MessageBody() createUserDto: CreateUserDto,
#ConnectedSocket() client: Socket,
) {
console.log(client.id);
console.log(createUserDto.resourceId)
}
}
I tried to move around the imports, not wrapping the gateway in a module.
Tried moving the results module out of the app.module. Which removes the error, however then I do not understand where to let nestjs know to use the resultsmodule for the microservice.
------------------------ANSWER-------------------------------------
Alright, we fixed it with the help of #yomateo , we were missing the export of the websocket gateway in the module. Thanks for the help!
You're close (you have it backwards).
Have your UsersGateway or UsersService call the ResultsService.
I'm a new in Kotlin's coroutines.
Here code with classic Thread:
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import okhttp3.*
import okio.ByteString
import org.slf4j.LoggerFactory
import java.util.concurrent.atomic.AtomicInteger
object BithumbSocketListener : WebSocketListener() {
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
super.onFailure(webSocket, t, response)
Thread {
оkHttpClient.newWebSocket(wsRequest, BithumbSocketListener)
}.start()
}
override fun onMessage(webSocket: WebSocket, text: String) {
super.onMessage(webSocket, text)
logger.debug("ws_onMessage: text = $text")
}
}
fun main(args: Array<String>) {
currenciesList = currencies.split(",")
currenciesList.forEach {
OkHttpClient().newWebSocket(wsRequest, BithumbSocketListener)
}
}
As you can see I have list of currencies (currenciesList). I iterate it and call newWebSocket for every item of list. As you can see BithumbSocketListener is a singleton.
If has some problem with web socket then call callback method onFailure and I create new web socket in separate java thread:
Thread {
оkHttpClient.newWebSocket(wsRequest, BithumbSocketListener)
}.start()
Nice. It's work fine.
But I want replace this code by Kotlin coroutines.
How I can do this?
Thanks.
Since you're processing an async stream of messages, you should port it to coroutines by implementing an actor, such as
val wsActor: SendChannel<String> = actor {
for (msg in channel) {
logger.info("Another message is in: ${msg}")
}
}
From the type of wsActor you can see you're supposed to send messages to it. This is where the bridging code comes in:
class BithumbSocketListener(
private val chan: Channel<String>
) : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
chan.send(text)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
оkHttpClient.newWebSocket(wsRequest, this)
}
}
Note that, compared to your code, I don't start any new threads for retrying. This has nothing to do with porting to coroutines, your code doesn't need it either. newWebSocket is an async call that returns immediately.
Finally, start the websockets for each currency:
currenciesList.forEach {
OkHttpClient().newWebSocket(wsRequest, BithumbSocketListener(wsActor)
}
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I'm building an ASP .NET Core App with Angular-CLI using this tutorial.
App works great, I got pre-build event to ng build before starting the app in VS, it works OK. I also can successfully get to my API over localhost:port/api/[controller] (in this case it would be Contacts as it's supposed to be a contact book).
Now, I'm trying to have Angular get the data directly from API. For this reason, I created a IContact interface, a ContactService and I pass it onto ContactComponent which is supposed to display it.
I might be making a silly mistake here (my skills are very basic), but for some reason I don't even see the object .json coming in through the network logs when I run the app (before trying to pass it to the view I'm trying to ensure I'm getting the data from the API first).
I might be doing something wrong (so I encourage you to reply even if you think you might be saying silly-obvious stuff) but here's my questions:
Should my code below work (not attaching imports though, I think I got all of them but check me ;-))? I'm not talking super-efficient or stuff, just basic to get the job done.
What is the best way to see if service to getAPI is working? Would that be network logging in your browser if you just import the service into the component and try to call the get method? Or is there another way?
Is my logic and approach towards the general architecture of the app OK or am I getting something wrong? :-)
contact.service.ts
const API_URL = environment.apiUrl;
#Injectable()
export class ContactService {
constructor(private http: Http) { }
public getContacts(): Observable<IContact[]> {
return this.http.get(API_URL)
.map((response: Response) => <IContact[]>response.json())
.catch(this.handleError);
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error')
}
}
icontact.ts
export interface IContact {
id: number;
firstName: string,
lastName: string,
address: string,
telephone: string
}
contact.component.ts
#Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrls: ['./contact.component.css'],
providers: [ContactService]
})
export class ContactComponent implements OnInit {
private _contactService: ContactService;
private contactlist: IContact[];
constructor() {
}
public ngOnInit() {
this._contactService.getContacts()
.subscribe((contacts) => { this.contactlist = contacts });
}
}
Any other code requirements or anything - let me know. All feedback is appreciated. Thanks!
I have slightly changed your code.
File icontact.ts. I have made IContact a simple type. You can make it an interface back to support your object shape.
export type IContact = string;
File contact.service.ts. We call the /api/values API that is created by default in a new ASP.NET Core Web API project if you follow the tutorial you mentioned.
import { Injectable } from "#angular/core";
import { Http } from "#angular/http";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/map'
import { IContact } from "./icontact";
const API_URL = '/api/values'; // environment.apiUrl;
#Injectable()
export class ContactService {
constructor(private http: Http) { }
public getContacts(): Observable<IContact[]> {
return this.http.get(API_URL)
.map(response => <IContact[]>response.json());
}
}
File contact.component.ts. The template simply displays the list.
import { Component, OnInit } from "#angular/core";
import { ContactService } from "./contact.service";
import { IContact } from "./icontact";
#Component({
selector: 'app-contact',
template: '<div *ngFor="let contact of contactList">{{contact}}</div>',
providers: [ContactService]
})
export class ContactComponent implements OnInit {
private contactList: IContact[];
constructor(private contactService: ContactService) { }
public ngOnInit() {
this.contactService.getContacts().subscribe(
(contacts) => { this.contactList = contacts; },
(error) => { console.log(error); }
);
}
}
File app.component.html. Display your component on the application's page.
<app-contact>Wait...</app-contact>
File app.module.ts. Import HttpModule. That fixes one of the error messages on your screenshot.
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { HttpModule } from '#angular/http';
import { AppComponent } from './app.component';
import { ContactComponent } from "./contact.component";
#NgModule({
declarations: [AppComponent, ContactComponent],
imports: [BrowserModule, HttpModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
In the Configure method in Startup.cs, make sure you put the app.Use(...) call before app.UseStaticFiles().
Since you serve your Angular app from wwwroot by a ASP.NET Core middleware, the host is the same for the Angular app and the Web API, so you don't need to configure CORS for that setup.
By the way, you may be interested in taking a look at the Angular CLI Templates on Visual Studio Marketplace. (Disclaimer: I'm the author.) The Item Template supports your project setup out-of-the-box.
I want to be able to click a button on a website, have it represent a command, issue that command to my program via a websocket, have my program process that command (which will produce a side effect), and then return the results of that command to the website to be rendered.
The websocket would be responsible for updating state changes applied by different actors that are within the users view.
Example: Changing AI instructions via the website. This modifies some values, which would get reported back to the website. Other users might change other AI instructions, or the AI would react to current conditions changing position, requiring the client to update the screen.
I was thinking I could have an actor responsible for updating the client with changed information, and just have the receiving stream update the state with the changes?
Is this the right library to use? Is there a better method to achieve what I want?
You can use akka-streams and akka-http for this just fine. An example when using an actor as a handler:
package test
import akka.actor.{Actor, ActorRef, ActorSystem, Props, Stash, Status}
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{Message, TextMessage}
import akka.http.scaladsl.server.Directives._
import akka.stream.scaladsl.{Flow, Sink, Source, SourceQueueWithComplete}
import akka.stream.{ActorMaterializer, OverflowStrategy, QueueOfferResult}
import akka.pattern.pipe
import scala.concurrent.{ExecutionContext, Future}
import scala.io.StdIn
object Test extends App {
implicit val actorSystem = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit def executionContext: ExecutionContext = actorSystem.dispatcher
val routes =
path("talk") {
get {
val handler = actorSystem.actorOf(Props[Handler])
val flow = Flow.fromSinkAndSource(
Flow[Message]
.filter(_.isText)
.mapAsync(4) {
case TextMessage.Strict(text) => Future.successful(text)
case TextMessage.Streamed(textStream) => textStream.runReduce(_ + _)
}
.to(Sink.actorRefWithAck[String](handler, Handler.Started, Handler.Ack, Handler.Completed)),
Source.queue[String](16, OverflowStrategy.backpressure)
.map(TextMessage.Strict)
.mapMaterializedValue { queue =>
handler ! Handler.OutputQueue(queue)
queue
}
)
handleWebSocketMessages(flow)
}
}
val bindingFuture = Http().bindAndHandle(routes, "localhost", 8080)
println("Started the server, press enter to shutdown")
StdIn.readLine()
bindingFuture
.flatMap(_.unbind())
.onComplete(_ => actorSystem.terminate())
}
object Handler {
case object Started
case object Completed
case object Ack
case class OutputQueue(queue: SourceQueueWithComplete[String])
}
class Handler extends Actor with Stash {
import context.dispatcher
override def receive: Receive = initialReceive
def initialReceive: Receive = {
case Handler.Started =>
println("Client has connected, waiting for queue")
context.become(waitQueue)
sender() ! Handler.Ack
case Handler.OutputQueue(queue) =>
println("Queue received, waiting for client")
context.become(waitClient(queue))
}
def waitQueue: Receive = {
case Handler.OutputQueue(queue) =>
println("Queue received, starting")
context.become(running(queue))
unstashAll()
case _ =>
stash()
}
def waitClient(queue: SourceQueueWithComplete[String]): Receive = {
case Handler.Started =>
println("Client has connected, starting")
context.become(running(queue))
sender() ! Handler.Ack
unstashAll()
case _ =>
stash()
}
case class ResultWithSender(originalSender: ActorRef, result: QueueOfferResult)
def running(queue: SourceQueueWithComplete[String]): Receive = {
case s: String =>
// do whatever you want here with the received message
println(s"Received text: $s")
val originalSender = sender()
queue
.offer("some response to the client")
.map(ResultWithSender(originalSender, _))
.pipeTo(self)
case ResultWithSender(originalSender, result) =>
result match {
case QueueOfferResult.Enqueued => // okay
originalSender ! Handler.Ack
case QueueOfferResult.Dropped => // due to the OverflowStrategy.backpressure this should not happen
println("Could not send the response to the client")
originalSender ! Handler.Ack
case QueueOfferResult.Failure(e) =>
println(s"Could not send the response to the client: $e")
context.stop(self)
case QueueOfferResult.QueueClosed =>
println("Outgoing connection to the client has closed")
context.stop(self)
}
case Handler.Completed =>
println("Client has disconnected")
queue.complete()
context.stop(self)
case Status.Failure(e) =>
println(s"Client connection has failed: $e")
e.printStackTrace()
queue.fail(new RuntimeException("Upstream has failed", e))
context.stop(self)
}
}
There are lots of places here which could be tweaked, but the basic idea remains the same. Alternatively, you could implement the Flow[Message, Message, _] required by the handleWebSocketMessages() method by using GraphStage. Everything used above is also described in detail in akka-streams documentation.
I'm trying to setup a basic connection between an akka-http websocket server and simple javascript.
1 out of roughly 20 connections succeeds, the rest fails. I have no idea why the setup of the connection is so unreliable.
Application.scala:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import services.WebService
import scala.concurrent.Await
import scala.concurrent.duration._
import java.util.concurrent.TimeoutException
object Application extends App {
implicit val system = ActorSystem("api")
implicit val materializer = ActorMaterializer()
import system.dispatcher
val config = system.settings.config
val interface = config.getString("app.interface")
val port = config.getInt("app.port")
val service = new WebService
val binding = Http().bindAndHandle(service.route, interface, port)
try {
Await.result(binding, 1 second)
println(s"server online at http://$interface:$port/")
} catch {
case exc: TimeoutException =>
println("Server took to long to startup, shutting down")
system.shutdown()
}
}
WebService.scala:
import actors.{PublisherActor, SubscriberActor}
import akka.actor.{Props, ActorSystem}
import akka.http.scaladsl.model.ws.{Message, TextMessage}
import akka.http.scaladsl.server.Directives
import akka.stream.Materializer
import akka.stream.scaladsl.{Source, Flow}
import scala.concurrent.duration._
class WebService(implicit fm: Materializer, system: ActorSystem) extends Directives {
import system.dispatcher
system.scheduler.schedule(15 second, 15 second) {
println("Timer message!")
}
def route =
get {
pathSingleSlash {
getFromResource("web/index.html")
} ~
path("helloworld") {
handleWebsocketMessages(websocketActorFlow)
}
}
def websocketActorFlow: Flow[Message, Message, Unit] =
Flow[Message].collect({
case TextMessage.Strict(msg) =>
println(msg)
TextMessage.Strict(msg.reverse)
})
}
client side:
<input type="text" id="inputMessage"/><br/>
<input type="button" value="Send!" onClick="sendMessage()"/><br/>
<span id="response"></span>
<script type="application/javascript">
var connection;
function sendMessage() {
connection.send(document.getElementById("inputMessage").value);
}
document.addEventListener("DOMContentLoaded", function (event) {
connection = new WebSocket("ws://localhost:8080/helloworld");
connection.onopen = function (event) {
connection.send("connection established");
};
connection.onmessage = function (event) {
console.log(event.data);
document.getElementById("response").innerHTML = event.data;
}
});
</script>
if the connection to the server fails I get a timeout message after 5 seconds which says the following:
[DEBUG] [07/23/2015 07:59:54.517] [api-akka.actor.default-dispatcher-27] [akka://api/user/$a/flow-76-3-publisherSource-prefixAndTail] Cancelling akka.stream.impl.MultiStreamOutputProcessor$SubstreamOutput#a54778 (after: 5000 ms)
No matter if the connection fails or succeeds, I always get the following log message:
[DEBUG] [07/23/2015 07:59:23.849] [api-akka.actor.default-dispatcher-4] [akka://api/system/IO-TCP/selectors/$a/0] New connection accepted
Look at that error message carefully... it is coming from a source I would not have expected, some "MultiStreamOutputProcessor" when I only expect to handle a single stream.
That tells me - along with the webSocketActorFlow - that maybe you are getting messages and they aren't being caught by the flow, and so you're ending up with substreams you never expected.
So instead of it "only working some of the time," maybe it is "working most of the time but unable to handle all of the input as you have demanded in the flow, and you are left with un-selectable streams that have to die first.
See if you can either a) make sure you get a grip on the streams so you don't end up with stragglers, b) bandaid adjust timeouts, and c) detect such occurences and cancel processing the downstream
https://groups.google.com/forum/#!topic/akka-user/x-tARRaJ0LQ
akka {
stream {
materializer {
subscription-timeout {
timeout=30s
}
}
}
}
http://grokbase.com/t/gg/akka-user/1561gr0jgt/debug-message-cancelling-akka-stream-impl-multistreamoutputprocessor-after-5000-ms