So I had the following code on Python that created a websocket connection:
channels_dict = {}
channels_dict['Authorization'] = 'true'
for channel in channels: #adds some extra headers with info
channels_dict["Channel" + str(channel)] = '1'
ws = websocket.WebSocketApp("ws://localhost:8888/ws",
on_message = on_message,
on_error = on_error,
on_close = on_close,
header = channels_dict)
And I could easily add extra headers that I could access later in the server upon connection. Is there a way I could do the same in Javascript? I haven't found much information about setting custom headers in websocket creation. I have the following code in JS:
var webSocket;
function openWebsocket() {
webSocket = new WebSocket("ws://localhost:8888/ws")
}
I've heard of adding query parameters in the url, is that the only way of adding extra headers/information to a websocket connection in javascript?
Try this:
const webSocket = new WebSocket ("ws://localhost:8888/ws" + "?param1=" + param1); // here you can add other params
webSocket.on("open", function open(e) {
console.log("*** websocket opened");
});
Related
I'm trying to make a direct websocket connection to appsync, but right after connecting I get keep getting an NoProtocolError error (
{"payload":{"errors":[{"message":"NoProtocolError","errorCode":400}]},"type":"connection_error"}')
This is my code
let ws = undefined;
const startWebsocket = () => {
const url = 'wss://XXXX.appsync-realtime-api.YYYY.amazonaws.com/graphql';
ws = new WebSocket(url);
// just log everything
ws.onopen = (e) => {console.log('Socket opened', e);};
ws.onmessage = (e) => {console.log('Msg received', e);};
ws.onclose = (e) => {console.log('Socket closed', e);};
ws.onerror = (e) => {console.log('Socket error', e);};
};
Regarding the immediate question
The message is referring to the second parameter of the Websocket constructor: protocols. (MDN reference here)
You need to specify the protocol (more precise subprotocol) like below:
ws = new WebSocket(url, ['graphql-ws']);
However
Be aware, that you're also missing header information, payload information and the whole handshake.
Only changing adding the protocol will present you with the next error: Both, the "header", and the "payload" query string parameters are missing.
Further Information
Read more about how to build up a connection on aws.amazon.com.
Open websocket with header and payload
const api_header = {
host: 'XXXX.appsync-api.YYYY.amazonaws.com',
'x-api-key': '<YOUR APPSYNC API KEY>',
};
// payload should be an empty JSON object
const payload = {};
const base64_api_header = btoa(JSON.stringify(api_header));
const base64_payload = btoa(JSON.stringify(payload));
const appsync_url = url + '?header=' + base64_api_header + '&payload=' + base64_payload;
ws = new WebSocket(appsync_url, ['graphql-ws']);
Note that the host is appsync-api and not appsync-realtime-api
With this, you should see a 'ka' message and can go forward with the handshake (see next section).
How to do the handshake
See this document on AWS
References
WebSocket API referene (mentioning of second parameter)
PHP implementation for WebSocket to AppSync
Amazon's doc on how to connect to AppSync
I need to set connection id before hubConnection.start();
I look here https://github.com/SignalR/SignalR/wiki/SignalR-JS-Client and what i found:
connection.id - Gets or sets the client id for the current connection.
Tried this. No working. Here is the client code:
let hubUrl = 'http://localhost:5000/chat';
let hubConnection = new signalR.HubConnectionBuilder()
.withUrl(hubUrl)
.configureLogging(signalR.LogLevel.Information)
.build();
hubConnection.id = "12345";
hubConnection.on("Send", function (data) {
let elem = document.createElement("p");
elem.appendChild(document.createTextNode(data));
let firstElem = document.getElementById("chatroom").firstChild;
document.getElementById("chatroom").insertBefore(elem, firstElem);
});
document.getElementById("sendBtn").addEventListener("click", function (e) {
let message = document.getElementById("message").value;
hubConnection.invoke("Send", message);
});
hubConnection.start();
hubConnection.id = "12345"; this do not change real connection id.
And maybe you are know some other way how to write client-side connection?
The connectionId can't be manually created or manipulated at all and it is created during negotiation request with the server. You can read here that you can implement your own IConnectionIdFactory but it is not recommended to do that. Maximum what you can do is get the connectionId and use it for mapping users and groups and other logic inside your hub.
I have been searching for two days now looking for a solution that might work for me. Sadly I have only seen examples and guides on how to setup a websocket server (that sends messages back and forth to clients) and a websocket client (that resides in browser). None of these really work for me, and I am not sure how to achieve what I want here.
Basically I have the following websocket:
require('dotenv').config()
const WebSocket = require('ws');
var connection = new WebSocket('ws://XXX');
connection.onopen = function () {
connection.send(JSON.stringify({"authenticate":process.env.API}));
connection.send(JSON.stringify({"XXX":"YYY"}));
connection.send(JSON.stringify({
"db" : "unique_id",
"query" : {
"table" : "users"
}
}));
};
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
var myResponse = JSON.parse(e.data);
var qList = myResponse.results;
};
What I want to do is have my nodeJS-script running, for example an express script with a html page, that also includes the response from onmessage. Why I am complicating this instead of just using the websocket client-side is that I cannot send my auth-code publicly.
Hope I have been clear enough, let me know if you are unsure of my question!
PS. If you think I would be better off using another websocket-script such as Socket.io - I have been looking at them and have not gotten much wiser sadly.
You have a lot of options. Probably the easiest is to export the connection. At the bottom of the file, e.g. module.exports = connection
Then in the express application, import the connection, e.g. const connection = require('./path/connection');
Then make a function that calls itself at a given interval and sends the appropriate message.
Then within the Express app you can use something like connection.on('message', (data, flags) => // do stuff);
Your other option is to create a store object. E.g.
// ./store.js
class store {
constructor() {
this.wsMaterial = {};
}
add(wsInfo) {
this.wsMaterial[key] = wsInfo
}
get store() {
return this.wsMaterial
}
}
module.exports = new store()
Then import the store and updated it, e.g.
// ./websocket file
const store = require('./store');
...
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
var myResponse = JSON.parse(e.data);
var qList = myResponse.results;
store.add(qList)
};
Then from Express...
// ./express.js
const store = require('./store');
store.get // all of your stuff;
I'm moving to upgrade the web sockets on my page to use a secure connection. However, when I call new WebSocket() with a wss:// instead of ws://, there are no headers being sent at all.
This is what I have so far:
php:
$lcNull = NULL; //null var
$lcHost = '0.0.0.0'; //host
$lnPort = '8080'; //port
$lcCertPath = '/path/to/cert';
//Create TCP/IP stream socket
$lhContext = stream_context_create();
stream_context_set_option($lhContext, 'ssl', 'local_cert', $lcCertPath);
stream_context_set_option($lhContext, 'ssl', 'passphrase', 'something');
stream_context_set_option($lhContext, 'ssl', 'allow_self_signed', true);
stream_context_set_option($lhContext, 'ssl', 'verify_peer', false);
$lsSocket = stream_socket_server(
"ssl://0.0.0.0:$lnPort",
$errno,
$errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
$lhContext
);
if ($errno > 0) { //An error has occured
echo("Main socket error ($errno): $errstr");
die();
} else {
echo("Main socket started, listening on $lcHost:$lnPort");
}
//create & add listening socket to the list
$laClients = array($lsSocket);
$laSocketUser = [];
//start endless loop, so that our script doesn't stop
while (true) {
//manage multiple connections
$laChanged = $laClients;
//returns the socket resources in $laChanged array
stream_select($laChanged, $lcNull, $lcNull, 0, 10);
# User Connected!
if (in_array($lsSocket, $laChanged)) {
$lsSocketNew = stream_socket_accept($lsSocket); //accept new socket
print "accepted " . stream_socket_get_name($lsSocketNew, true) . "\n";
$laClients[] = $lsSocketNew; //add socket to client array
echo "\nReading header:>";
stream_set_blocking($lsSocketNew, true);
$lcHeader = fread($lsSocketNew, 5000); //read data sent by the socket
echo $lcHeader . "<\n\n";
...
And the output depends how the web socket is created in the Javascript:
this sends headers:
var wsUri = "ws://"+window.location.host;
websocket = new WebSocket(wsUri);
this sends nothing :P (i.e. the php above echos out Reading header:><, meaning it did not receive any headers.)
var wsUri = "wss://"+window.location.host+":8080";
websocket = new WebSocket(wsUri);
So my question is: Why does the wss:// example not send any headers, and how can I get it to send the headers correctly?
I'm trying to manage a bunch of socket connections. My app is basically an http server that receives posts and passes these along to a socket. When clients open a socket connection, they send a connect message with an id:
{"m":"connect","id":"1"}
The app then saves this id and socket in the id2socket and socket2id maps. On disconnect, the socket/id pair is deleted from the maps.
A post will also contain an id, which indicates the post data should be sent to the socket with that id.
That's great, and this works fine for a single open socket. However, when I have more than one socket open, and then I close a socket, that disconnect wipes everything from the map. I think my understanding of sockets in node is incomplete- is there only a single socket object that is used in the callback? Is there a better way to manage my open socket connections and ids?
start server:
>>node server.js
TCP server listening on 127.0.0.1:5280
HTTP server listening on 127.0.0.1:9002
telnet in:
>>telnet localhost 5280
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
{"m":"connect","id":"123"}
{"m":"connect","id":"123","success":"true"}
server after connection:
>>Connection from 127.0.0.1:57572
received data: {"m":"connect","id":"123"}
id: 1
m: connect
associating uid 1 with socket [object Object]
do a post:
python post.py {"foo":"bar"}
So this works fine for several open sockets (as long as 1 device is id 123, server has this hardwired for now). However, as soon as you close one connection all the socket connections are removed from the map.
Here's my code:
python script to do post:
import sys
import json
import httplib, urllib, urllib2
values = json.loads('{"foo":"bar"}')
headers = {"Content-type": "application/json"}
conn = httplib.HTTPConnection('127.0.0.1', 9002)
headers = {"Content-type": "application/json"}
conn.request("POST", "", json.dumps(values), headers)
response = conn.getresponse()
print "response.status: "+response.status
print "response.reason: "+response.reason
print "response.read: "+response.read()
conn.close()
node server (http and tcp), hardwired to send data to device '123' on post:
var net = require('net'); // tcp-server
var http = require("http"); // http-server
var qs = require('querystring'); // http-post
// Map of sockets to devices
var id2socket = new Object;
var socket2id = new Object;
// Setup a tcp server
var server_plug = net.createServer(function(socket) {
// Event handlers
socket.addListener("connect", function(conn) {
console.log("Connection from " + socket.remoteAddress + ":" + socket.remotePort );
});
socket.addListener("data", function(data) {
console.log("received data: " + data);
try {
request = JSON.parse(data);
response = request;
if(request.m !== undefined && request['id'] !== undefined){ // hack on 'id', id is js obj property
console.log("id: "+request['id']);
console.log("m: "+request.m);
if(request.m == 'connect'){
console.log("associating uid " + request['id'] + " with socket " + socket);
id2socket[request['id']] = socket;
socket2id[socket] = request['id'];
response.success = 'true';
} else {
response.success = 'true';
}
}
socket.write(JSON.stringify(response));
} catch (SyntaxError) {
console.log('Invalid JSON:' + data);
socket.write('{"success":"false","response":"invalid JSON"}');
}
});
socket.on('end', function() {
id = socket2id[socket]
console.log("socket disconnect by id " + id);
// wipe out the stored info
console.log("removing from map socket:"+socket+" id:"+id);
delete id2socket[id];
delete socket2id[socket];
});
socket.on('timeout', function() {
console.log('socket timeout');
});
});
// Setup http server
var server_http = http.createServer(
// Function to handle http:post requests, need two parts to it
// http://jnjnjn.com/113/node-js-for-noobs-grabbing-post-content/
function onRequest(request, response) {
request.setEncoding("utf8");
request.addListener("data", function(chunk) {
request.content += chunk;
});
request.addListener("end", function() {
console.log("post received!");
//console.log("Request received: "+request.content);
if (request.method == 'POST') {
//var json = qs.parse(request.content);
//console.log("Post: "+json);
// HACK TO TEST STUFF:
// send a message to one of the open sockets
try {
var socket = id2socket['123']; //hardwired
socket.write('{"m":"post"}');
} catch (Error) {
console.log("Cannot find socket with id "+'123');
}
}
});
}
);
// Fire up the servers
var HOST = '127.0.0.1';
var PORT = 5280;
var PORT2 = 9002;
server_plug.listen(PORT, HOST);
console.log("TCP server listening on "+HOST+":"+PORT);
server_http.listen(PORT2);
console.log("HTTP server listening on "+HOST+":"+PORT2);
Objects only take strings as keys for their properties. As your log shows, a socket object is converted into the string "[object Object]". As a result, socket #2 overwrites the id from socket #1 in the object, because all sockets are converted into the same string key. So, there is only one property in the object at all times, because all sockets come down to the same key. When you try to remove the id for socket #2, the single property is deleted and the object is empty.
You seem to want a custom property for each separate socket when used as a key. You can use WeakMaps for this. WeakMaps do allow objects as keys (as opposed to string-only keys), but as they're relatively new they may contain bugs at the moment.
(Note that the id2socket map can just be a plain object, because numbers are converted into strings just fine, and each number has its own, distinct string representation*.)
Using WeakMaps is as follows:
var socket2id = new WeakMap; // as if you were doing: var socket2id = {};
socket2id.set(socket, id); // as if you were doing: socket2id[socket] = id;
socket2id.get(socket); // as if you were doing: socket2id[socket];
socket2id.delete(socket); // as if you were doing: delete socket2id[socket];
Make sure to run with node --harmony (>= 0.7) or node --harmony_weakmaps (<= 0.6).
* 0 and -0 are exceptions, but you shouldn't be using -0 anyway because 0 === -0, so it's difficult to differ between them.