Koa SSE connection reconnecting - server-sent-events

I have set up an SSE connection using Koa like so:
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// Sets up the HTTP header and sends a message via SSE
function writeSSE(ctx, message) {
ctx.res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
'Access-Control-Allow-Origin': '*',
});
ctx.res.write(`id: 01\n`);
ctx.res.write(`data: ${message}\n\n`);
}
// Router Middleware
router.get('/stream', (ctx, next) => {
writeSSE(ctx, 'Stream reached.');
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(8080);
Where my React components starts the connection like so:
new EventSource("http://localhost:8080/stream")
The component then receives the answer sent by the writeSSE method on the backend.
But for some reason the /stream endpoint is reached every 3 seconds or so, as if the connection was being reestablished.
And my error listener on the front-end catches a CONNECTING event every time.
this.state.source.onerror = (e) => {
if (e.target.readyState == EventSource.CONNECTING) {
console.log("Connecting...");
}
};
And on the back-end, ctx.response equals { status: 404, message: 'Not Found', header: {} }.
Would anyone know the cause of this issue? Is it linked to the way I use Koa?

this is a bit too late, but I will write my experience with sse using Koa.
First of all using ctx.res directly is not much appreciated by Koa, if you still want to use it make sure to put ctx.respond = false to bypass koa response mecanism.
In my experience a stream is the best way to use SSE with Koa you can do something like :
const stream = require('stream');
const koa = require('koa');
const app = new koa();
app.use(async ctx => {
ctx.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
ctx.status = 200;
const stream = new stream.PassThrough()
ctx.body = stream; // here koa will pipe the ctx.res to stream and end the ctx.res when ever the stream ends.
let counter = 5;
const t = setInterval(() => {
stream.write(`data: hi from koa sse ${counter}`);
counter--;
if (counter === 0) {
stream.end();
clearInterval(t);
}
}, 1000);
});
Hope this help anyone will play with SSE on koa.
PS: I wrote this on hurry if there is anything wrong with code tell me and I will correct it.

I'm in the process of implementing a Koa-based server for SSE. I've been running into the same problem, and here are my thoughts / working solution:
As far as I can tell, the reason why onmessage and onerror keep getting called is because the EventSource object on the client side is emitting an error event. This is causing the connection to be disconnected, which causes the client to send another request to initialize the stream to the server. From here, the process repeats itself indefinitely.
Based on my own testing, EventSource is emitting an error due to the data that is being sent back from the server. Per the docs, a 200 response that has as Content-Type other than 'text/event-stream' will cause a failure.
In your example, you have declared your response as 'text/event-stream' and are passing a string into the ctx.res.write method. While this looks correct, and in fact works when using comparable code and Express, it seems that it doesn't work in Koa. However, if you change the 'data' you are writing to your response to a stream, such as this example here, you'll find that the connection establishes correctly.
Maybe try the following:
//require Passthrough
const PassThrough = require('stream').PassThrough;
//then, in your writeSSE function, try this:
let stream = new PassThrough();
stream.write(`data: ${message}\n\n`);
ctx.res.write(stream);
I'm not 100% sure why this change works. My best guess is that there is something about Koa's ctx object that prevents a plain string or template literal from being viewed as valid text/event-stream data, but this is entirely supposition (this begs the question as to why it works in Express, but hopefully someone more knowledgeable can answer this for both of us). From what I've seen of other snippets published online, the stream approach is the one to take in Koa.
I'm not sure what your results will be, as it looks like you may be using a different version of Koa than I am, but I'd give it a shot. I was able to get my connection established correctly making this small change.

Related

How to make a gRPC firestore listen request in Rust?

Using gRPC bindings from https://github.com/gkkachi/firestore-grpc I was able to puzzle together something that is seemingly working but does not receive any content:
Creating the request:
let req = ListenRequest {
database: format!("projects/{}/databases/(default)", project_id),
labels: HashMap::new(),
target_change: Some(TargetChange::AddTarget(Target {
// "Rust" in hex: https://github.com/googleapis/python-firestore/issues/51
target_id: 0x52757374,
once: false,
target_type: Some(TargetType::Documents(DocumentsTarget {
documents: vec![users_collection],
})),
resume_type: None,
})),
};
Sending it:
let mut req = Request::new(stream::iter(vec![req]));
let metadata = req.metadata_mut();
metadata.insert(
"google-cloud-resource-prefix",
MetadataValue::from_str(&db).unwrap(),
);
println!("sending request");
let res = get_client(&token).await?.listen(req).await?;
let mut res = res.into_inner();
while let Some(msg) = res.next().await {
println!("getting response");
dbg!(msg);
}
(full code in this repo).
The request can be made but the stream does not contain any actual content. The only hint I get from the debug logs is
[2021-10-27T14:54:39Z DEBUG h2::codec::framed_write] send frame=GoAway { error_code: NO_ERROR, last_stream_id: StreamId(0) }
[2021-10-27T14:54:39Z DEBUG h2::proto::connection] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
Any idea what is missing?
The crucial thing I was missing as pointed out in the rust users forum was that the request stream was immediately ending which caused the connection to close. The send frame=GoAway was actually send by the client (facepalm).
To keep the connection open and receive responses we can keep the input stream pending: Request::new(stream::iter(vec![req]).chain(stream::pending())). There will be a better way to set things up and keep control over subsequent input requests but this is enough to fix the example.

angular5 how to make asyc request?

In my project i made multiple request to server to get data for single page. I want to make all request asyc. Right now until i get the response from first request,the response of second request is not load.
So basically i just want to achive asyc request and response so one request will not wait for other request to finish.
Right now it's like first come first serve fashion.
But i want from multiple request which request get first response should load first.
this is code of my component
constructor(private _dashboardService: DashboardService) {
this.getLineChart();
this.todayPaymentDetails();
this.todayPaymentMethod();
this.rewardCustomers();
this.getAverageBill();
this.getItemByVolumn();
this.getItemBySales();
}
todayPaymentMethod(id=null){
this.paymentMethodsLoader=0;
this._dashboardService.getTodayPaymentMethod(id).subscribe(res =>{
if(null != res.data && '' != res.data){
this.location = res.data.location;
this.payment_methods = res.data.payment_methods;
}
this.paymentMethodsLoader=1;
});
}
this is my service code:
getTodayTotalPayment(id) : Observable<any> {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this._http.get(environment.apiUrl + constants.API_V1 + 'today-total-payment/'+id, options)
.map(res => res.json())
.catch((error: any) => Observable.throw(error.json().error || error));
}
Here shows code for only one request but as shown in constructor i send multiple request at a time.
FIrst it is not a good practise to call function like this in constructor.
For running multiple request or observable together use operators i.e. switchMap etc.
Follow this video link related to event loop in JavaScript this will improve your JavaScript execution , event loop concepts . also explains how asynchronous code executed.
Hope it will help.

SignalR: getting error: WebSocket closed

I am working on project of Ionic with angular and AspNet with SignalR that have chat module.
I use SignalR for Chat.It's working smoothly but some time i am getting error as per below screen shot and because of that it's get stop working at all.
I have hosted my service on IIS and creating proxy and communicating with client and server. Here is sample
(function () {
angular
.module('app')
.factory('SignalRFactory', SignalRFactory);
SignalRFactory.$inject = ['$rootScope', 'Hub', 'ionicToast'];
function SignalRFactory($rootScope, Hub, ionicToast) {
var signalRLocal = this;
var serverURL = 'https://serivcerURL.com/signalr';
//Hub setup
var hub = new Hub('CommunicationHub', {
rootPath: serverURL,
listeners: {
'send': function (data) {
console.log("send " + data);
}
},
errorHandler: function (error) {
//Here i am getting that websocket closed error
console.error(error);
}
});
signalRLocal.Connect = function (user) {
console.log("SignalR Connecting as :" + user.UserName);
hub.invoke('connect', user);
};
return signalRLocal;
}
})();
I have hosted service on IIS. I search for the solution and find something like this link
I also try with above link solution by using "long Polling" as per below
Hub.connection.start({ transport: 'longPolling' });
But i don't want to use "long Polling" at all.
So can someone help me to figure out this issue without use of 'long Polling'.
Can someone tell me what configuration i have to do at client side or at IIS level.
As we said in comments, SignalR client will try to reconnect after the connection is lost. Besides, many factors (such as physical network interruption, client browser failure, server offline etc) can cause the connection lost, this article explains some disconnection scenarios, you can refer to it and find the possible causes of the issue.
Besides, as I mentioned in comment, you can call the Start method from your Closed event handler (disconnected event handler on JavaScript clients) to start a new connection to make client automatically re-establish a connection after it has been lost.
edit:
The connection to ws://localhost:3156/signalr/signalr/connect?transport=webSoc‌​kets&clientProtocol=‌​1.5&connectionToken=‌​g8vpRv9ncVDjPIYB9UuE‌​pAAILEaOcTMTG9p46IA2‌​4 was interrupted while the page was loading.
Under "Client disconnection scenarios" section in the article, you can find:
In a browser client, the SignalR client code that maintains a SignalR connection runs in the JavaScript context of a web page. That's why the SignalR connection has to end when you navigate from one page to another, and that's why you have multiple connections with multiple connection IDs if you connect from multiple browser windows or tabs. When the user closes a browser window or tab, or navigates to a new page or refreshes the page, the SignalR connection immediately ends because SignalR client code handles that browser event for you and calls the Stop method.
I am trying same and my client is in vue.js. I have changed below in vue.config.js
module.exports = {
devServer: {
proxy: {
'/hub': {
target: 'https://localhost:5001',
changeOrigin: false,
secure: false,
headers: {
'x-forwarded-proto': 'http',
},
},
},
},
}
Previously i am trying 'x-forwarded-proto': 'https', when I changed to http its work.
https://localhost:5001 is my .net endpoint and http://localhost:8080 is my vue enpoint
Changed in Startup.csbelow,
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<CardsHub>("/hub/cardsHub");
});
Added below code in vue component
this.connection = new signalR.HubConnectionBuilder()
.withUrl('/hub/cardsHub')
.build()
In my case the culprit was the wrong version of Microsoft.AspNetCore.SignalR.Common
the default one installed was 5.0.3
but I was targeting 3.1.0
Downgrading to 3.1.12 fixed the issue with the connection.

http.get don't make a call, Angular 2

I am playing with angular 2 and I have problem with sending http get request.
I created method like this:
test(){
console.log("call test");
let header = new Headers();
header.append("authorization",'change9ziKuJH8wnVbNES3AMleYGPKzZ');
this._http.get('http://localhost:42055/api/Question',{headers:header}).do(res => console.log("Result: " + JSON.stringify(res)));
}
The main problem is that, this http request never was send. I look at the Fiddler and there is no request to my localhost:42055.
Unfortunately Angular don't display any errors, so I don't have any clue what is going one.
Observables are lazy so you need to subscribe them to actually execute corresponding processing (HTTP requests in your case):
this._http.get('http://localhost:42055/api/Question',{headers:header})
.do(res => console.log("Result: " + JSON.stringify(res)))
.subscribe((res) => { // <-------------
// handle result
});

Meteor http calls limitations

Currently, I use the built-in meteor http method (see http://docs.meteor.com/#http) for issuing http calls, on both my client and my server.
However, I'm experiencing two issues:
is it possible to cancel a request?
is it possible to have multiple query parameters which share the same key?
Are these just Meteor limitations, or are there ways to get both to work using Meteor?
I know I could you jquery on the clientside, and there must be a server-side solution which supports both as wel, but I'd prefer sticking with meteor code here.
"is it possible to cancel a request?"
HTTP.call() does not appear to return an object on which we could call something like a stop() method. Perhaps a solution would be to prevent execution of your callback based on a Session variable?
HTTP.call("GET", url, function(error, result) {
if (!Session.get("stopHTTP")) {
// Callback code here
}
});
Then when you reach a point where you want to cancel the request, do this:
Session.set("stopHTTP", true);
On the server, instead of Session perhaps you could use an environment variable?
Note that the HTTP.call() options object does accept a timeout key, so if you're just worried about the request never timing out, you can set this to whatever millisecond integer you want.
"is it possible to have multiple query parameters which share the same key?"
Yes, this appears to be possible. Here's a simple test I used:
Meteor code:
HTTP.call("GET", "http://localhost:1337", {
query: "id=foo&id=bar"
}, function(error, result) {
// ...
});
Separate Node.js server: (just the basic example on the Node.js homepage, with a console.log line to output the request URL with query string)
var http = require('http');
http.createServer(function(req, res) {
console.log(req.url); // Here I log the request URL, with the query string
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
When the Meteor server is run, the Node.js server logged:
/?id=foo&id=bar
Of course, this is only for GET URL query parameters. If you need to do this for POST params, perhaps you could store the separate values as a serialized array string with EJSON.stringify?

Resources