31

I have created a Node.JS application that provides a web socket server (using npm ws). This websocket waits until a connection is established. Once a connection hits the server, the websocket executes a job. When the job is done, a message is sent over the socket, then the socket is being closed. This socket works as expected; already tested it with another Node.JS script.

How can I connect to the web socket using only linux command line tools? I already tried curl as described here. However, I can not find out how to connect properly to my websocket which runs at localhost:8088/socket/

Edit: My question has been identified as a possible duplicate of another question. However, the linked question only asks if there is a way to do it with curl. I'd be glad to see any solution which works on bash. Also, the answer to the linked question is a javascript file using autobahn.ws

Community
  • 1
  • 1
Brian
  • 1,318
  • 1
  • 16
  • 33

5 Answers5

43

My tool websocat is specifically designed for this.

websocat ws://your_server/url

You can connect and exchange data with your server. By default each line becomes a WebSocket text message and vice versa.

On Linux it is more comfortable to play with it using readline:

rlwrap websocat ws://your_server/url.

It is not the only CLI websocket client. There are also "ws" and "wscat" projects.

Vi.
  • 37,014
  • 18
  • 93
  • 148
  • Hi! Thanks for the tool. Is it possible to install it permissionless ? At the office we do not have sudo access – George Pligoropoulos Nov 14 '19 at 08:27
  • 1
    @GeorgiosPligoropoulos Yes, just download appropriate pre-built executable from Github releases and run it. Multiple variants may be runnable. You can also download and extract deb package. – Vi. Nov 16 '19 at 22:36
  • 1
    When I try: `websocat -H "Authorization: Bearer ...." ws://server/url`, I get the help message. It seems that this tool doesn't handle listening on a web socket with options. – Nicolas Rouquette Jun 09 '20 at 16:32
  • @NicolasRouquette In this mode options should be specified after the URL to connect. Note that headers for listening and for connecting are separate options. – Vi. Jun 09 '20 at 17:02
22

Try this one from here: How to hit the WebSocket Endpoint?

$ curl -i -N  \
    -H "Connection: Upgrade"  \
    -H "Upgrade: websocket"  \
    -H "Host: echo.websocket.org"  \
    -H "Origin: http://www.websocket.org"  \
    http://echo.websocket.org

Which he has from here: http://www.thenerdary.net/post/24889968081/debugging-websockets-with-curl

To quote the content from this site for the future:

Those flags say:

  1. Return headers in the output
  2. Don’t buffer the response
  3. Set a header that this connection needs to upgrade from HTTP to something else
  4. Set a header that this connection needs to upgrade to a WebSocket connection
  5. Set a header to define the host (required by later WebSocket standards)
  6. Set a header to define the origin of the request (required by later WebSocket standards)

If the websocket is working it should return the following:

$ curl -i -N  \
    -H "Connection: Upgrade" \
    -H "Upgrade: websocket" \
    -H "Host: echo.websocket.org" \
    -H "Origin:http://www.websocket.org"  \
    http://echo.websocket.org

HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://www.websocket.org
WebSocket-Location: ws://echo.websocket.org/
Server: Kaazing Gateway
Date: Mon, 11 Jun 2012 16:34:46 GMT
Access-Control-Allow-Origin: http://www.websocket.org
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Headers: x-websocket-extensions
Access-Control-Allow-Headers: x-websocket-version
Access-Control-Allow-Headers: x-websocket-protocol
P i
  • 29,020
  • 36
  • 159
  • 267
Stefan Rein
  • 8,084
  • 3
  • 37
  • 37
  • ... which I have already mentioned in my question :P No seriousely: I can't get it to work with localhost. I get a HTTP/1.1 400 Bad Request Content-type: text/html on my express.js webserver – Brian Dec 01 '16 at 10:34
  • Oh I'm sorry! Maybe you need to handle the update request yourself then? if the server only serves the ws protocol? Because if I understand right, the curl request tries to do a http and update it to a ws protocol - but if the webserver doesn't accept the http incoming calls, nothing can happen http://stackoverflow.com/questions/18045352/node-js-how-to-respond-to-an-upgrade-request – Stefan Rein Dec 01 '16 at 10:41
  • Ok so I thought about what you said. I have'nt heard of this upgrade mechanism before. However, this could of course be the reason why it doesn't work ;) So I have uploaded my node.js app to a online cloud provider now. Therefore I have a http url now. I guess that my express.js framework could internally use the upgrade mechansim, too. How can I add my path and port to the curl command? – Brian Dec 02 '16 at 15:25
  • Okay. I was curious what will happen if the connection establishes after the upgrade. Because it is a bidirectional stream, it must be open in the terminal somehow. But how? What will happen? Then I tried this and got this: `curl: (1) Protocol "ws" not supported or disabled in libcurl` From this one: `curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Host: echo.websocket.org" -H "Origin:http://www.websocket.org" ws://echo.websocket.org/` – Stefan Rein Dec 02 '16 at 15:55
  • Okay, so to answer your question: I think you can not go that far and do the communication after the upgrade with the linux curl tool. Since you asked about `linux tools`, maybe it could be possible to install something like this https://github.com/progrium/wssh and run then from command line (which can handle the protocol) – Stefan Rein Dec 02 '16 at 15:59
  • @StefanRein take out the `ws://` from the end of your command – Janac Meena Apr 27 '18 at 19:28
  • If you use `curl` you need to use `https://` URL to get it to initiate the TLS connection and then use custom headers to upgrade to websocket connection. – Mikko Rantalainen Jan 28 '22 at 09:46
  • Note that `socket.io` is not a WebSocket implementation and upgrading connection using standard methods may fail: https://socket.io/docs/v4/#what-socketio-is-not – I think you should use dedicated port if you want to use `socket.io` library and have `socket.io` server listening that port. – Mikko Rantalainen Jan 31 '22 at 12:28
  • echo.websocket.org service no longer available. – vitro Jul 13 '22 at 14:26
15

The vi answer provides a very useful tool. websocat is so easy to use.

I want to do it only with bash builtins. I think it can be useful to others to understand better the protocol.

The websocket protocol handshake is easy.

To open a tcp channel in bash we can use:

wshost=echo.websocket.org
wsport=80
exec 3<>/dev/tcp/${wshost}/${wsport}

Then we can read and write data to that tcp connection using file descriptor &3.

First let's open a read channel:

CR=$(echo -en "\r")
while read <&3; do echo "WS MSG:[${REPLY//$CR/}]"; done &

We don't really need replace the carriage return bytes \r, but the terminal output is weird if we don't.

Each websocket server implementation can require specific details. websocket.org requires Origin header and \r byte before end line.

Then we can start the connection upgrade:

echo -e "GET / HTTP/1.1\r
Host: ${wshost}\r
Connection: Upgrade\r
Upgrade: websocket\r
Sec-WebSocket-Accept: $(echo -n "somekey"|base64)\r
Origin: http://www.websocket.org\r
\r" >&3

Imediatelly we see the ws upgrade output:

$ WS MSG:[HTTP/1.1 101 Web Socket Protocol Handshake]
WS MSG:[Access-Control-Allow-Credentials: true]
WS MSG:[Access-Control-Allow-Headers: content-type]
WS MSG:[Access-Control-Allow-Headers: authorization]
WS MSG:[Access-Control-Allow-Headers: x-websocket-extensions]
WS MSG:[Access-Control-Allow-Headers: x-websocket-version]
WS MSG:[Access-Control-Allow-Headers: x-websocket-protocol]
WS MSG:[Access-Control-Allow-Origin: http://www.websocket.org]
WS MSG:[Connection: Upgrade]
WS MSG:[Date: Thu, 29 Oct 2020 15:08:01 GMT]
WS MSG:[Sec-WebSocket-Accept: eXT5yQBZ/TOhFBUi6nLY8cfzs1s=]
WS MSG:[Server: Kaazing Gateway]
WS MSG:[Upgrade: websocket]
WS MSG:[]

You see the Sec-WebSocket-Accept Header ? The server resolve it as described in RFC 6455. We can calculate it with:

$ echo -n "$(echo -n "somekey" | base64)258EAFA5-E914-47DA-95CA-C5AB0DC85B11" | 
   sha1sum | cut -d " " -f1 | xxd --ps -r  | base64
eXT5yQBZ/TOhFBUi6nLY8cfzs1s=

I know... cut, xxd, base64 and sha1sum are not builtin, but this validation step is just for clarify.

The handshake are done and our tcp connection are now upgraded to a websocket connection.

Now the hard part.

We need to send data. We can learn how to do it in section 5 of the RFC6455.

a client MUST mask all frames that it sends to the server (see Section 5.3 for further details)

and

The server MUST close the connection upon receiving a frame that is not masked

Read the RFC Section 5.2 to know how to mask data.

The RFC even provides a ascii-art for data masking:

  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+

I want to write a bash function to mask ws data, but I can not do it now. I'll update this post later.

Community
  • 1
  • 1
ton
  • 3,827
  • 1
  • 42
  • 40
10

I've been using wscat, an npm package. https://github.com/websockets/wscat.

Instructions from the package readme at the time of posting:

Installation

This module needs to be installed globally so use the -g flag when installing:

npm install -g wscat

Example

$ wscat -c ws://echo.websocket.org
Connected (press CTRL+C to quit)
> hi there
< hi there
> are you a happy parrot?
< are you a happy parrot?
Atul Payapilly
  • 101
  • 1
  • 2
0

tl;dr curl -H 'Upgrade: websocket' -H "Sec-WebSocket-Key: `openssl rand -base64 16`" -H 'Sec-WebSocket-Version: 13' --http1.1 -sSv https://ws.ifelse.io (depending on the server you might need to provide Origin and/or Connection: Upgrade)

First, how does the websocket protocol work in a few words? A client connects to a server, sends a handshake request, receives "101 Switching Protocols" (a handshake response) after which they send frames back and forth.

The handshake looks along the following lines:

GET / HTTP/1.1
Host: ws.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Origin: http://example.com

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=

Upgrade makes it switch from HTTP(s) to the websocket protocol.

Connection specifies that Upgrade is a hop-by-hop header (headers that intermediaries should consume, not forward). But my experiments show that it works w/o this header. Or to be more precise, it might be optional if there's a reverse proxy in front of the server (e.g. nginx).

Sec-WebSocket-Key/Sec-WebSocket-Accept is a security measure described here, here and here.

Sec-WebSocket-Version specifies the websocket protocol version. According to RFC 6455 it should be equal 13.

Origin is needed when the client is a browser and the origin of the requesting page doesn't match the origin of the websocket server URL.

A detailed description of what should constitute a websocket handshake request can be found here.

That's how it goes with HTTP/1.1. HTTP/2 is a different story.

Knowing this to establish a websocket connection with curl:

$ curl -H 'Upgrade: websocket' \
       -H "Sec-WebSocket-Key: `openssl rand -base64 16`" \
       -H 'Sec-WebSocket-Version: 13' \
       --http1.1 \
       -sSv \
       https://ws.ifelse.io
...
> GET / HTTP/1.1
> Host: ws.ifelse.io
> Upgrade: websocket
> Sec-WebSocket-Key: e2dujvcbYbN747lapeH+WA==
> Sec-WebSocket-Version: 13
...
< HTTP/1.1 101 Switching Protocols
< Connection: upgrade
< upgrade: websocket
< sec-websocket-accept: 6wmMGMtN00aWw3loYd6P36EHKMI=

The other options are wscat, websocat.

x-yuri
  • 16,722
  • 15
  • 114
  • 161