Koa websocket to websocket - websocket

I have a node.js server that takes data from a "sleepy" client over a websocket, then tries to push that data to the web client via its websocket. Sleepy means that it sleeps for a few minutes then wakes up to take a measurement and push the data, then back to sleep. See below:
sleepy client --> websocket--> node.js server --> websocket --> web clients
There is only one sleepy client but there can be multiple web clients. So when new data arrives, I need to push it to all web client web sockets. To keep track of this, I have an array that track web clients and stores their ctx info for later.
But, and this is the problem, when I try to push data to the web client websocket, I get a context error and node.js crashes.
Here is my code:
var globalCtx = [];
app.ws.use( function( ctx, next ) {
ctx.websocket.on('message', function ( message )
{
if( "telemetry" in jsonMsg )
{
console.log( "Got Telemetry: " );
console.log( message.toString() );
// we just got a telemetry message,
// so push it to the all the web client websockets
globalCtx.forEach( (myCtx, next) => {
console.log( "DEBUG: ", util.inspect(myCtx) ); <<<-----crashes here
myCtx.send( jsonMsg ); <<<----- or crashes here when not debugging
});
}
else
if( "webClient" in jsonMsg )
{
console.log( "Got WS Client: " );
console.log( message.toString() );
// we just got a web client connection message
// so store its context for pushing to later
if( Array.isArray( globalCtx ) && globalCtx.length )
{
// search for an existing entry
for( let idx = 0; idx < globalCtx.length; idx++ )
{
if( globalCtx[ idx ].ip == ctx.ip )
{
// we already have this IP stored, do nothing
console.log("IP already found: ", ctx.ip );
//return next( ctx );
return;
}
}
// we made it here, this means that the IP wasn't found
console.log("not found, adding IP: ", ctx.ip );
globalCtx.push( ctx );
}
else
{
// the array is empty, so just add it
console.log("empty array adding IP: ", ctx.ip );
globalCtx.push( ctx );
}
}
});
return next(ctx);
});
Here is the error:
/home/pi/node_modules/koa/lib/response.js:73
return this.res.statusCode;
^
TypeError: Cannot read property 'statusCode' of undefined
at Object.get status [as status] (/home/pi/node_modules/koa/lib/response.js:73:21)
at /home/pi/node_modules/only/index.js:6:20
at Array.reduce (<anonymous>)
at module.exports (/home/pi/node_modules/only/index.js:5:15)
at Object.toJSON (/home/pi/node_modules/koa/lib/response.js: 562:12)
at Object.toJSON (/home/pi/node_modules/koa/lib/context.js:5 1:31)
at Object.inspect (/home/pi/node_modules/koa/lib/context.js: 33:17)
at formatValue (internal/util/inspect.js:745:19)
at Object.inspect (internal/util/inspect.js:319:10)
at /home/pi/koaThermostat/index2.js:46:46
Here is the error from the myCtx.send( jsonMsg ); line
/home/pi/koaThermostat/index2.js:47
myCtx.send( jsonMsg );
^
TypeError: myCtx.send is not a function
at /home/pi/koaThermostat/index2.js:47:23

Well I feel silly! When using the Koa context with websockets, you have to use the websocket object. So in my case it should have been myCtx.websocket.send() not myCtx.send().
Now I'm on to my next bug....

Related

how to use client context to start child spans at server side

from an angular client i have injected context and sending through headers
try {
const ctx = api.trace.setSpan(
api.context.active(),
parentSpan
);
const tracer = trace.getTracerProvider().getTracer('Ui-Service');
let span = tracer.startSpan('start_Client_spans', undefined, ctx);
const carrier:{traceparent:string} = {
traceparent:""
};
const propagator = new W3CTraceContextPropagator();
propagator.inject(
api.trace.setSpanContext(api.context.active(), span.spanContext()),
carrier,
api.defaultTextMapSetter,
)
const clone = req.clone({
headers: req.headers
.set('ot-tracer-traceid' ,carrier.traceparent)
});
span.end()
return clone;
} catch (e) {
console.log(e)
throw e
}
}
server side code below here the context is extracted from headers and trying to be injected to start further spans at server side so that client request is parent and server requests are child spans of the same trace id but currently both server and client are having separate trace ids
const carrier = req.headers["ot-tracer-traceid"]
const propagator = new W3CTraceContextPropagator();
if(carrier?.length>0){
const clientContext = propagator.extract(
api.context.active(),
carrier,
api.defaultTextMapGetter
);
const newTracer = api.trace.getTracer("test","1.0.0")
// create server span using the client context
//below code snippet does create spans but individual spans , but i am expecting them to use the client context
const serverSpan = newTracer.startSpan('operationName', undefined, clientContext);
console.log(newTracer);
}
can some one hint me where am i getting wrong ?

Custom google cast receiver stuck in "Load is in progress"

My custom v3 CAF receiver app is successfully playing the first few live & vod assets. After that, it gets into a state were media commands are being queued because "Load is in progress". It is still (successfully) fetching manifests, but MEDIA_STATUS remains "buffering". The log then shows:
[ 4.537s] [cast.receiver.MediaManager] Load is in progress, media command is being queued.
[ 5.893s] [cast.receiver.MediaManager] Buffering state changed, isPlayerBuffering: true old time: 0 current time: 0
[ 5.897s] [cast.receiver.MediaManager] Sending broadcast status message
CastContext Core event: {"type":"MEDIA_STATUS","mediaStatus":{"mediaSessionId":1,"playbackRate":1,"playerState":"BUFFERING","currentTime":0,"supportedMediaCommands":12303,"volume":{"level":1,"muted":false},"currentItemId":1,"repeatMode":"REPEAT_OFF","liveSeekableRange":{"start":0,"end":20.000999927520752,"isMovingWindow":true,"isLiveDone":false}}}
CastContext MEDIA_STATUS event: {"type":"MEDIA_STATUS","mediaStatus":{"mediaSessionId":1,"playbackRate":1,"playerState":"BUFFERING","currentTime":0,"supportedMediaCommands":12303,"volume":{"level":1,"muted":false},"currentItemId":1,"repeatMode":"REPEAT_OFF","liveSeekableRange":{"start":0,"end":20.000999927520752,"isMovingWindow":true,"isLiveDone":false}}}
Fetch finished loading: GET "(manifest url)".
No errors are shown.
Even after closing and restarting the cast session, the issue remains. The cast device itself has to be rebooted to resolve it. It looks like data is kept between sessions.
It could be important to note that the cast receiver app is not published yet. It is hosted on a local network.
My questions are:
What could be the cause of this stuck behavior?
Is there any session data kept between session?
How to fully reset the cast receiver app, without the necessity to restart the cast device.
The receiver app itself is very basic. Other than license wrapping it resembles the vanilla example app:
const { cast } = window;
const TAG = "CastContext";
class CastStore {
static instance = null;
error = observable.box();
framerate = observable.box();
static getInstance() {
if (!CastStore.instance) {
CastStore.instance = new CastStore();
}
return CastStore.instance;
}
get debugLog() {
return this.framerate.get();
}
get errorLog() {
return this.error.get();
}
init() {
const context = cast.framework.CastReceiverContext.getInstance();
const playerManager = context.getPlayerManager();
playerManager.addEventListener(
cast.framework.events.category.CORE,
event => {
console.log(TAG, "Core event: " + JSON.stringify(event));
}
);
playerManager.addEventListener(
cast.framework.events.EventType.MEDIA_STATUS,
event => {
console.log(TAG, "MEDIA_STATUS event: " + JSON.stringify(event));
}
);
playerManager.addEventListener(
cast.framework.events.EventType.BITRATE_CHANGED,
event => {
console.log(TAG, "BITRATE_CHANGED event: " + JSON.stringify(event));
runInAction(() => {
this.framerate.set(`bitrate: ${event.totalBitrate}`);
});
}
);
playerManager.addEventListener(
cast.framework.events.EventType.ERROR,
event => {
console.log(TAG, "ERROR event: " + JSON.stringify(event));
runInAction(() => {
this.error.set(`Error detailedErrorCode: ${event.detailedErrorCode}`);
});
}
);
// intercept the LOAD request to be able to read in a contentId and get data.
this.loadHandler = new LoadHandler();
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD,
loadRequestData => {
this.framerate.set(null);
this.error.set(null);
console.log(TAG, "LOAD message: " + JSON.stringify(loadRequestData));
if (!loadRequestData.media) {
const error = new cast.framework.messages.ErrorData(
cast.framework.messages.ErrorType.LOAD_CANCELLED
);
error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
return error;
}
if (!loadRequestData.media.entity) {
// Copy the value from contentId for legacy reasons if needed
loadRequestData.media.entity = loadRequestData.media.contentId;
}
// notify loadMedia
this.loadHandler.onLoadMedia(loadRequestData, playerManager);
return loadRequestData;
}
);
const playbackConfig = new cast.framework.PlaybackConfig();
// intercept license requests & responses
playbackConfig.licenseRequestHandler = requestInfo => {
const challenge = requestInfo.content;
const { castToken } = this.loadHandler;
const wrappedRequest = DrmLicenseHelper.wrapLicenseRequest(
challenge,
castToken
);
requestInfo.content = wrappedRequest;
return requestInfo;
};
playbackConfig.licenseHandler = license => {
const unwrappedLicense = DrmLicenseHelper.unwrapLicenseResponse(license);
return unwrappedLicense;
};
// Duration of buffered media in seconds to start/resume playback after auto-paused due to buffering; default is 10.
playbackConfig.autoResumeDuration = 4;
// Minimum number of buffered segments to start/resume playback.
playbackConfig.initialBandwidth = 1200000;
context.start({
touchScreenOptimizedApp: true,
playbackConfig: playbackConfig,
supportedCommands: cast.framework.messages.Command.ALL_BASIC_MEDIA
});
}
}
The LoadHandler optionally adds a proxy (I'm using a cors-anywhere proxy to remove the origin header), and stores the castToken for licenseRequests:
class LoadHandler {
CORS_USE_PROXY = true;
CORS_PROXY = "http://192.168.0.127:8003";
castToken = null;
onLoadMedia(loadRequestData, playerManager) {
if (!loadRequestData) {
return;
}
const { media } = loadRequestData;
// disable cors for local testing
if (this.CORS_USE_PROXY) {
media.contentId = `${this.CORS_PROXY}/${media.contentId}`;
}
const { customData } = media;
if (customData) {
const { licenseUrl, castToken } = customData;
// install cast token
this.castToken = castToken;
// handle license URL
if (licenseUrl) {
const playbackConfig = playerManager.getPlaybackConfig();
playbackConfig.licenseUrl = licenseUrl;
const { contentType } = loadRequestData.media;
// Dash: "application/dash+xml"
playbackConfig.protectionSystem = cast.framework.ContentProtection.WIDEVINE;
// disable cors for local testing
if (this.CORS_USE_PROXY) {
playbackConfig.licenseUrl = `${this.CORS_PROXY}/${licenseUrl}`;
}
}
}
}
}
The DrmHelper wraps the license request to add the castToken and base64-encodes the whole. The license response is base64-decoded and unwrapped lateron:
export default class DrmLicenseHelper {
static wrapLicenseRequest(challenge, castToken) {
const wrapped = {};
wrapped.AuthToken = castToken;
wrapped.Payload = fromByteArray(new Uint8Array(challenge));
const wrappedJson = JSON.stringify(wrapped);
const wrappedLicenseRequest = fromByteArray(
new TextEncoder().encode(wrappedJson)
);
return wrappedLicenseRequest;
}
static unwrapLicenseResponse(license) {
try {
const responseString = String.fromCharCode.apply(String, license);
const responseJson = JSON.parse(responseString);
const rawLicenseBase64 = responseJson.license;
const decodedLicense = toByteArray(rawLicenseBase64);
return decodedLicense;
} catch (e) {
return license;
}
}
}
The handler for cast.framework.messages.MessageType.LOAD should always return:
the (possibly modified) loadRequestData, or
a promise for the (possibly modified) loadRequestData
null to discard the load request (I'm not 100% sure this works for load requests)
If you do not do this, the load request stays in the queue and any new request is queued after the initial one.
In your handler, you return an error if !loadRequestData.media, which will get you into that state. Another possibility is an exception in the load request handler, which will also get you in that state.
I guess we have a different approach and send everything possible through sendMessage, when we loading stuff we create a new cast.framework.messages.LoadRequestData() which we load with playerManager.load(loadRequest).
But I guess that you might be testing this on an integrated Chromecast, we see this problems as well!?
I suggest that you do one or more
Enable gzip compression on all responses!!!
Stop playback playerManager.stop() (maybe in the interseptor?)
Change how the licenseUrl is set
How we set licenseUrl
playerManager.setMediaPlaybackInfoHandler((loadRequestData, playbackConfig) => {
playbackConfig.licenseUrl = loadRequestData.customData.licenseUrl;
return playbackConfig;
}
);

Dart - WebSocket.readyState always returns WebSocket.OPEN

Basically, I'm trying to check the status of my WebSocket Server.ws. However, when I query Server.ws.readyState, the only response I ever get is WebSocket.OPEN. How do I check if a WebSocket is disconnected if it always returns WebSocket.OPEN?
For example, I've tried to turn off the WiFi of the device used to test the Flutter app. Normally, after one second, the WebSocket is assumed disconnected and the connection is closed with a WebSocketStatus.GOING_AWAY close code. I assumed it would also change the WebSocket.readyState, but that doesn't seems to be the case.
So, how do I properly check the status of my WebSocket?
How I'm currently checking :
/// Connection status
IconButton _status() {
IconData iconData;
switch (Server.ws?.readyState) {
case WebSocket.CONNECTING:
print("readyState : CONNECTING");
iconData = Icons.wifi;
break;
case WebSocket.OPEN:
print("readyState : OPEN");
iconData = Icons.signal_wifi_4_bar;
break;
case WebSocket.CLOSING:
print("readyState : CLOSING");
iconData = Icons.signal_wifi_4_bar_lock;
break;
case WebSocket.CLOSED:
print("readyState : CLOSED");
iconData = Icons.warning;
break;
default:
print("readyState : " + Server.ws.readyState.toString());
break;
}
return new IconButton(
icon: new Icon(iconData),
tooltip: 'Connection Status', // TODO:Localize
onPressed: () {
setState(() {
Server.ws.close();
});
},
);
}
Additional info about the WebSocket :
/// Should be called when the IP is validated
void startSocket() {
try {
WebSocket.connect(Server.qr).then((socket) {
// Build WebSocket
Server.ws = socket;
Server.ws.listen(
handleData,
onError: handleError,
onDone: handleDone,
cancelOnError: true,
);
Server.ws.pingInterval = new Duration(
seconds: Globals.map["PingInterval"],
);
send(
"CONNECTION",
{
"deviceID": Globals.map["UUID"],
},
);
});
} catch (e) {
print("Error opening a WebSocket : $e");
}
}
/// Handles the closing of the connection.
void handleDone() {
print("WebSocket closed.");
new Timer(new Duration(seconds: Globals.map["PingInterval"]), startSocket);
}
/// Handles the WebSocket's errors.
void handleError(Error e) {
print("WebSocket error.");
print(e);
Server.ws.close();
}
I've gone ahead and taken a look at the source code for the WebSocket implementation. It appears that when the WebSocket is being closed with the status GOING_AWAY, the internal socket stream is being closed. However, it is possible that this event does not propagate to the transformed stream which handles the readyState of the instance. I would recommend filing a bug report at dartbug.com.
try setting the pingInterval, this checks for connection status every said interval, then the closeCode will update

Restify debugging in WebStorm

I'm trying to set up a very basic restify server so I can debug it in WebStorm. I can run the process normally, but when using the debug function in WebStorm the process exits immediately.
My restify server is located in the server.js folder:
var restify = require('restify');
function respond(req, res, next) {
res.send('hello ' + req.params.name);
next();
}
var server = restify.createServer();
server.get('/hello/:name', respond);
server.head('/hello/:name', respond);
server.listen(8080, function() {
console.log('%s listening at %s', server.name, server.url);
});
I would like to hit a breakpoint set inside the respond function.
When I run the solution it starts up normally and I can hit the hello endpoint:
"C:\Program Files (x86)\JetBrains\WebStorm 2016.1.1\bin\runnerw.exe" "C:\Program Files\nodejs\node.exe" server.js
restify listening at http://[::]:8080
However, hitting debug I get :
"C:\Program Files (x86)\JetBrains\WebStorm 2016.1.1\bin\runnerw.exe" "C:\Program Files\nodejs\node.exe" --debug-brk=60036 server.js
Debugger listening on port 60036
Process finished with exit code -1073741819 (0xC0000005)
My local config file looks like this (nothing special):
What am I doing wrong?
EDIT:
I looked at the template which WebStorm generates with express to try and find some inspiration to get debugging to work with restify. Basically I am mimicing the WebStorm template now, so I have an additional file in bin/wwww:
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('test2:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
//app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
Instead of the server file, I use app.js:
var restify = require('restify');
var restifyApp = function () {
var server = restify.createServer({
name: 'myapp',
version: '1.0.0'
});
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.get('/echo/:name', function (req, res, next) {
res.send(req.params);
return next();
});
};
module.exports = restifyApp;
With this setup I am actually able to start in debug mode. However, as soon as I try http://localhost:3000/echo/hey (as an example), the request times out and once again I get feedback in the terminal with the message Process finished with exit code -1073741819 (0xC0000005)
Looks like Node V8 issue (as debugging also fails when using node-inspector). I was able to debug your code after downgrading to Node.js 0.10.31. Node 4.3 breaks with AccessViolation, Node 5.5 hangs.

boost deadline_timer not kicking off

We have written a single threaded client based on the boost asio. The client needs to implement a timeout, so that the connection is broken if the read or write from server was not completed in a stipulated time period. But the async_wait for the deadline_timer does not kick off untill I don't call run for the io_service. Now if I call run on the io_service then my reading and writing to the server is not possible.
Please see the excerpts from my current code:
typedef boost::scoped_ptr<boost::asio::ip::tcp::socket> SocketPtr;
typedef boost::shared_ptr<boost::asio::deadline_timer> DLTPtr;
SocketPtr m_SocketPtrClient;
DLPtr m_ClientTimeoutDLTPtr;
boost::asio::io_service ios;
m_SocketPtrClient.reset( new boost::asio::ip::tcp::socket( ios));
m_ClientTimeoutDLTPtr.reset( new deadline_timer( ios));
m_ClientTimeoutDLTPtr->expires_from_now( boost::posix_time::seconds( m_uiCommTimeout));
m_ClientTimeoutDLTPtr->async_wait( boost::bind( &MyClass::clientTimeoutHandler,
this,
boost::asio::placeholders::error
)
);
m_SocketPtrClient->connect
(
boost::asio::ip::tcp::endpoint
(
boost::asio::ip::address::from_string
(
m_sCommAddress == "localhost" ? "127.0.0.1" : m_sCommAddress
), m_usCommPort
), ec
);
if( !ec && m_SocketPtrClient->is_open())
{
m_ClientTimeoutDLTPtr->cancel();
}
else
{
m_ClientTimeoutDLTPtr->cancel();
m_SocketPtrClient->close();
return eStateError;
}
//install a timeout handler
m_ClientTimeoutDLTPtr->expires_from_now( boost::posix_time::seconds( m_uiCommTimeout));
m_ClientTimeoutDLTPtr->async_wait( boost::bind( &MyClass::clientTimeoutHandler,
this,
boost::asio::placeholders::error
)
);
ec = writeToServer( *m_SocketPtrClient);
if( ec)
{
// do error handling and throw an exception
}
m_ClientTimeoutDLTPtr->cancel();
//install a timeout handler
m_ClientTimeoutDLTPtr->expires_from_now( boost::posix_time::seconds( m_uiCommTimeout));
m_ClientTimeoutDLTPtr->async_wait( boost::bind( &MyClass::clientTimeoutHandler,
this,
boost::asio::placeholders::error
)
);
ec = readFromServer( *m_SocketPtrClient);
if( ec)
{
// do error handling and throw an exception
}
m_ClientTimeoutDLTPtr->cancel();
void MyClass::clientTimeoutHandler( const boost::system::error_code& ec)
{
if( ec)
{
m_ClientTimeoutDLTPtr->cancel();
m_SocketPtrClient->close();
m_ssMsg << std::endl << "break all handling because of timeout on io_service of Client!";
}
else
{
m_ClientTimeoutDLTPtr->expires_from_now( boost::posix_time::seconds( m_uiCommTimeout));
m_ClientTimeoutDLTPtr->async_wait( boost::bind( &MyClass::clientTimeoutHandler,
this,
boost::asio::placeholders::error
)
);
}
}
I need to connect, write to the server and then get response from the server and for each operation I need the timeout to kickoff. If I call the run from io_service then I can't make my these three calls.
I need to connect, write to the server and then get response from the
server and for each operation I need the timeout to kickoff. If I call
the run from io_service then I can't make my these three calls.
When using deadline_timer::async_wait() you will need to use the corresponding asynchronous socket methods such as socket::async_connect() instead of socket::connect().

Resources