I want to provide a meaningful error to the client when too many users are connected or when they're connecting from an unsupported domain, so...
I wrote some WebSocket server code:
var http = require('http');
var httpServer = http.createServer(function (request, response)
{
// i see this if i hit http://localhost:8001/
response.end('go away');
});
httpServer.listen(8001);
// https://github.com/Worlize/WebSocket-Node/wiki/Documentation
var webSocket = require('websocket');
var webSocketServer = new webSocket.server({ 'httpServer': httpServer });
webSocketServer.on('request', function (request)
{
var connection = request.reject(102, 'gtfo');
});
And some WebSocket client code:
var connection = new WebSocket('ws://127.0.0.1:8001');
connection.onopen = function (openEvent)
{
alert('onopen');
console.log(openEvent);
};
connection.onclose = function (closeEvent)
{
alert('onclose');
console.log(closeEvent);
}
connection.onerror = function (errorEvent)
{
alert('onerror');
console.log(errorEvent);
};
connection.onmessage = function (messageEvent)
{
alert('onmessage');
console.log(messageEvent);
};
All I get is alert('onclose'); with a CloseEvent object logged to the console without any status code or message that I can find. When I connect via ws://localhost:8001 the httpServer callback doesn't come into play, so I can't catch it there. The RFC suggests I should be able to send any status code other than 101 when there's a problem, but Chrome throws an error in the console Unexpected response code: 102. If I call request.reject(101, 'gtfo'), implying it was successful I get a handshake error, as I'd expect.
Not really sure what else I can do. Is it just not possible right now to get the server response in Chrome's WebSocket implementation?
ETA: Here's a really nasty hack in the mean time, I hope that's not what I have to end up doing.
var connection = request.accept(null, request.origin);
connection.sendUTF('gtfo');
connection.close();
I'm the author of WebSocket-Node and I've also posted this response to the corresponding issue on GitHub: https://github.com/Worlize/WebSocket-Node/issues/46
Unfortunately, the WebSocket protocol does not provide any specific mechanism for providing a close code or reason at this stage when rejecting a client connection. The rejection is in the form of an HTTP response with an HTTP status of something like 40x or 50x. The spec allows for this but does not define a specific way that the client should attempt to divine any specific error messaging from such a response.
In reality, connections should be rejected at this stage only when you are rejecting a user from a disallowed origin (i.e. someone from another website is trying to connect users to your websocket server without permission) or when a user otherwise does not have permission to connect (i.e. they are not logged in). The latter case should be handled by other code on your site: a user should not be able to attempt to connect the websocket connection if they are not logged in.
The code and reason that WebSocket-Node allow you to specify here are an HTTP Status code (e.g. 404, 500, etc.) and a reason to include as a non-standard "X-WebSocket-Reject-Reason" HTTP header in the response. It is mostly useful when analyzing the connection with a packet sniffer, such as WireShark. No browser has any facility for providing rejection codes or reasons to the client-side JavaScript code when a connection is rejected in this way, because it's not provided for in the WebSocket specification.
Related
Sometimes I want to refuse a http client's request to upgrade connection to websocket.
Code
(using go's Gin and gorilla/websocket framework:)
To allow upgrade:
c, err := ctl.upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
err = c.WriteJSON(resp)
To refuse upgrade (due to invalid request params):
if contentId == "" || !exists {
// FIXME: provide response in a way that ws client can recognize & show tip?
ctx.String(http.StatusBadRequest, "invalid or non-existing contentId")
return
}
Explaination: Here to refuse the upgrade I just return a http 400 code, then terminate the connection, and didn't do the upgrade at all.
The issue
The problem to refuse websocket upgrade request with about code is that, the websocket client (e.g js), can't read data (text or json) in my response.
Code - client-side (js):
ws.onerror = function (evt) {
// TOOD: handle error, (e.g print error msg?),
print("ERROR");
}
It does print the "ERROR" on refuse, but after checking chrome developer tool about the evt object, can't find a way to get server response data, so I can't show tip to frontend UI with reason of refuse.
Questions
How to refuse websocket upgrade request properly, and let client be able to receive the returned reason/data ? (e.g client is js, server is go / gin / gorilla/websocket).
Is there a better way to refuse websocket upgrade request, other than return http code like 400?
To reject a websocket connection, do not upgrade the connection as described in the question.
The browser API does not provide information about why the connection was rejected because the information can violate the same-origin policy.
Do the following to send an error reason back to the client application or user:
Upgrade the connection.
Send a close message with the error reason.
Close the connection.
Here's an example:
c, err := ctl.upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
// TODO: handle error
}
if contentId == "" || !exists {
c.WriteMessage(websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.ClosePolicyViolation,
"bad content id or not exist"))
c.Close()
return
}
// Continue with non-error case here.
Access the reason from the close handler in JS:
ws.onclose = function (evt) {
if (evt.code == 1008) { // 1008 is policy violation
console.log(evt.reason)
}
}
If a browser opens a connection to a remote server, is it possible to access that same connection via Javascript?
I have a small Ethernet module on my network that I program sort of like this (pseudocode):
private var socket
while(true) {
if(socket is disconnected) {
open socket
listen on socket (port 80)
}
if(connection interrupt) {
connect socket
}
if(data receive interrupt) {
serve
}
if(disconnection interrupt) {
disconnect socket
}
}
The point is that it listens on one socket for HTTP requests and serves them.
In my web browser, I can connect to the device, making an HTTP GET request for some HTML/JS that I've written, and it works. A connection is opened on the socket and the files come back as HTTP responses.
Now I want to click a button on the webpage and have the browser send an HTTP POST request over that same connection. In my Javascript, I have (edited and formatted for clarity):
// This function sends an HTTP request
function http(type, url, data, callbacks) {
// make a new HTTP request
var request = new XMLHttpRequest();
// open a connection to the URL
request.open(type, url + (data ? "?" + data : ""));
// add headers
if(type == "POST")
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// register callbacks for the request
Object.keys(callbacks).forEach(function(callback) {
request[callback] = function() {
callbacks[callback](request.responseText);
};
});
// send and return the request
request.send();
return request;
}
// Here is where I call the function
http("POST", // use POST method
"http://192.168.1.99", // IP address of the network device
dataToSend, // the data that needs to be sent
{ // callbacks
onloadend: function(data) {
console.log("success. got: " + data); // print 'success' when the request is done
},
onerror: function(data) {
console.log("There was an error."); // print 'error' when it fails
console.log(data);
}
}
);
The issue here is that this opens a new connection to the device, but I want to use the same socket that the browser is already connected to. Is this possible, and if so, how?
There is no application control inside the browser to decide if a new connection is used for the next request or if an existing connection is used. In fact, it is perfectly normal that the browser will use multiple connections in parallel to the same server and your server has to be able to deal with this.
Since your server architecture seems to be only able to deal with one connection at a time you either would need to change the architecture to handle multiple parallel connections or to make sure that you only need to handle a single connection at a time. The latter could be achieved by not supporting HTTP keep-alive, i.e. by closing the connection immediately after each response. This way a new request will result in a new connection (which is not what you wanted according to your question) but your server will also be able to handle this new connection (which is what you likely ultimately need) since the previous one was closed.
I wrote a program in c++ to upgrade an HTTP session to a WebSocket. The server correctly receives the upgrade request, and responds with 101 switching protocols. Firefox receives the 101 response, but in the console claims it couldn't connect. Here's the full output in Firefox.
The interesting thing is, in the network tab you can see the 101 response come in so it did establish an HTTP connection, the server got it, accepted it, tried to upgrade, and sent an upgrade response, but it's at that point that firefox doesn't upgrade to a WebSocket and prints the error.
I tried in Google Chrome as well. Similar error, but a bit more detail. WebSocket connection to 'ws://localhost:3000/' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
My code c++ hangs on the while loop, no errors thrown, which I think means it thinks the connection is still working?
I did notice it's sending Sec-Websocket-Protocol, even though I only asked for ws, not wss. Do chrome and firefox disallow non secure websockets? Or am I missing something in my code?
C++:
#define PORT (unsigned short) 3000
#define MAX_BUF 2048
int main()
{
Poco::Net::ServerSocket x(PORT);
Poco::Timespan timeout(25000000);
if (x.poll(timeout, 1)) {
Poco::Net::StreamSocket ss = x.acceptConnection();
Poco::AutoPtr<Poco::Net::HTTPServerParams> params = new Poco::Net::HTTPServerParams();
Poco::Net::HTTPServerSession sess(ss,params);
Poco::Net::HTTPServerResponseImpl res(sess);
Poco::Net::HTTPServerRequestImpl req(res, sess, params);
if (req.getMethod()=="GET" && req.get("Upgrade")=="websocket") {
Poco::Net::WebSocket webSock(req,res);
while (!webSock.available());
char buf[MAX_BUF];
memset(buf,0,MAX_BUF);
int flags = 0;
webSock.sendFrame("Hello, World!\n",15);
webSock.receiveFrame(buf,MAX_BUF,flags);
printf("received %s\n",buf);
}
}
else {
printf("Timeout!\n");
}
return 0;
}
Javascript:
$(document).ready(function() {
webSoc = new WebSocket("ws://localhost:3000");
webSoc.onopen = function (event) {
$("#c").click(function () {
console.log("Sending 'Hello, World!'");
webSoc.send("Hello, World!\n");
});
}
});
I've tried new WebSocket("ws://localhost:3000") with and without the echo-protocol parameter, because I read it in an answer to a question I didn't understand, which I can't find now.
Is it possible to programmatically check if a WebSocket connection failed with a 403 response? For instance, with the following server code:
package main
import (
"errors"
"io"
"log"
"net/http"
"golang.org/x/net/websocket"
)
func main() {
handler := websocket.Handler(func(ws *websocket.Conn) {
io.Copy(ws, ws)
})
handshake := func(conf *websocket.Config, req *http.Request) error {
if req.URL.Path == "/sekret" {
return nil
}
return errors.New("Oops!")
}
server := &websocket.Server{
Handshake: handshake,
Handler: handler,
}
log.Fatal(http.ListenAndServe(":8080", server))
}
And the following sample JS connection that triggers a 403 response:
var ws = new WebSocket("ws://localhost:8080/something");
ws.onerror = console.log;
The error response is an Event with type "error". On the other hand, the following JS code triggers a net::ERR_CONNECTION_REFUSED (at least on Chrome):
var ws = new WebSocket("ws://localhost:8081/something");
ws.onerror = console.log;
The error event object looks almost exactly the same. Is there some way to distinguish error types in WebSocket connections from the client side? (I'm specifically looking for 403 responses, so I can alert the user to take special action.)
Apparently it's deliberate:
My understanding is that for security reasons, WebSocket error statuses are fairly restricted to limit the ability of malicious JS to probe an internal network.
I've written a web application that uses web-sockets. The idea is that my app tries to auto-connect to recently connected to hosts when it starts up. If it can't establish a connection to any of them, then it directs the user to the connection part and asks them to establish a connection manually.
All of this works. In summary, I try each known host in order, and if 200ms later it hasn't connected (`readyState != 1), it tries the next one. All these hosts should be on the LAN so 200ms works pretty reliably. If the last one on the list fails too, then the web opens up a modal directing the user to manually type in a host.
The problem is, by trying to auto-connect, I have to create websockets to my attempted hosts, which outputs error messages like the following to the console:
WebSocket connection to 'ws://lightmate:8080/' failed: Error in
connection establishment: net::ERR_NAME_NOT_RESOLVED
WebSocket connection to 'ws://localhost:8080/' failed: Error in
connection establishment: net::ERR_CONNECTION_REFUSED
While not a fatal flaw by any means, it's unsightly and gets in the way of my debugging.
I've tried to remove it by surrounding the calls to new WebSocket(address) with a try/catch block, and the errors still get through, and I've also tried to set an onerror handler, hoping that would suppress the error messages. Nothing's worked.
connect: function(){
var fulladdr = completeServerAddress(address);
try {
connection = new WebSocket(fulladdr);
connection.suppressErrorsBecauseOfAutoConnection = suppressErrorsBecauseOfAutoConnection; //Store this module-scoped variable in connection, so if the module changes suppression state, this connection won't.
} catch (e){
//Make sure we don't try to send anything down this dead websocket
connection = false;
return false;
}
connection.binaryType = "arraybuffer";
connection.onerror = function(){
if (connection !== false && !connection.suppressErrorsBecauseOfAutoConnection){
Announce.announceMessage("Connection failed with server");
}
connection = false;
};
connection.onmessage = function(m){
rxMessage(ConnectionProtocol.BaseMessage.parseChunk(m.data));
};
connection.onclose = function(){
hooks.swing("disconnected", "", 0);
if (connection !== false && !connection.suppressErrorsBecauseOfAutoConnection){
Announce.announceMessage("Connection lost with server");
}
};
connection.onopen = function(){
sendMessages(ConnectionProtocol.HandshakeMessage.create(name, sources, sinks));
while (idlingmessages.length){
websocketConnection.send(idlingmessages.splice(0,1)[0]);
}
hooks.swing("connected", "", 0);
};
},
Dupl Disclaimer:
This question is similar to this StackOverflow question, but that question is out of date by a year, and the consensus there was "you can't". I'm hoping things have changed since then.
There is no way to trap that error message, which occurs asynchronously to the code where the WebSocket object is created.
More details here: Javascript doesn't catch error in WebSocket instantiation