Linux Bash: How to open a websocket connection as client
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.
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:
- Return headers in the output
- Don’t buffer the response
- Set a header that this connection needs to upgrade from HTTP to something else
- Set a header that this connection needs to upgrade to a WebSocket connection
- Set a header to define the host (required by later WebSocket standards)
- 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.orgHTTP/1.1 101 Web Socket Protocol HandshakeUpgrade: WebSocketConnection: UpgradeWebSocket-Origin: http://www.websocket.orgWebSocket-Location: ws://echo.websocket.org/Server: Kaazing GatewayDate: Mon, 11 Jun 2012 16:34:46 GMTAccess-Control-Allow-Origin: http://www.websocket.orgAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Headers: content-typeAccess-Control-Allow-Headers: authorizationAccess-Control-Allow-Headers: x-websocket-extensionsAccess-Control-Allow-Headers: x-websocket-versionAccess-Control-Allow-Headers: x-websocket-protocol
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.orgwsport=80exec 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\rHost: ${wshost}\rConnection: Upgrade\rUpgrade: websocket\rSec-WebSocket-Accept: $(echo -n "somekey"|base64)\rOrigin: 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 | base64eXT5yQBZ/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.