vi / websocat

Command-line client for WebSockets, like netcat (or curl) for ws:// with advanced socat-like functions
MIT License
6.73k stars 259 forks source link

Make connection "read only" for use in daemon #134

Closed mh166 closed 4 months ago

mh166 commented 2 years ago

Hi,

before anything, I want to say thanks for this amazing tool! It really helped me accomplish so much with it! šŸ˜Š

TL;DR: How can I use websockat as a "read only" service for use with systemd, that does not need an active STDIN?


At the moment I'm having a slight problem, but maybe I'm just overlooking something: I have a working script, that connects to a wss:// endpoint and reads log messages from there. I don't need or want to send anything towards that direction, it should only listen!

For now, I'm using a shellscript to work with the messages and let it sit in a screen instance, so I can safely detach from the terminal and have it running in the background. websocat's command line in this script is nothing but standard:

/usr/local/bin/websocat wss://example.net/websocket -H 'Cookie: X-Authorization=MyVeryLongAndSecureToken'

Ultimately, I would like to have it running as a daemon. Therefore I created the following little systemd unit:

cortex:~# systemctl cat jwc-monitor.service 
# /etc/systemd/system/jwc-monitor.service
[Unit]
Description=JWC Monitor

[Service]
ExecStart=/usr/local/bin/jwc-monitor
Restart=on-failure

[Install]
WantedBy=multi-user.target 
cortex:~#

But instead of enjoying a nice little service running in the background, it always fails on me and results in the following state:

cortex:~# systemctl status jwc-monitor.service ā— jwc-monitor.service - JWC Monitor
   Loaded: loaded (/etc/systemd/system/jwc-monitor.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Mon 2021-08-23 22:35:50 CEST; 45min ago
  Process: 13753 ExecStart=/usr/local/bin/jwc-monitor (code=exited, status=0/SUCCESS)
 Main PID: 13753 (code=exited, status=0/SUCCESS)
cortex:~#

It soon occured to me, that the problem is probably with websocat not having a valid STDIN available. After a bit of debugging, I was able to reproduce the same error also in a normal shell by closing fd 0 like so (note the 0>&- at the end of the command line):

cortex:~# /usr/local/bin/websocat wss://example.net/websocket -H 'Cookie: X-Authorization=MyVeryLongAndSecureToken' -vv 0>&-
[INFO  websocat::lints] Auto-inserting the line mode
[DEBUG websocat] Done third phase of interpreting options.
[DEBUG websocat] Done fourth phase of interpreting options.
[DEBUG websocat] Preparation done. Now actually starting.
[DEBUG websocat::sessionserve] Serving Line2Message(ThreadedStdio) to Message2Line(WsClient("wss://example.net/websocket")) with Options { websocket_text_mode: true, websocket_protocol: None, websocket_reply_protocol: None, udp_oneshot_mode: false, udp_broadcast: false, udp_multicast_loop: false, udp_ttl: None, udp_join_multicast_addr: [], udp_join_multicast_iface_v4: [], udp_join_multicast_iface_v6: [], udp_reuseaddr: false, unidirectional: false, unidirectional_reverse: false, max_messages: None, max_messages_rev: None, exit_on_eof: false, oneshot: false, unlink_unix_socket: false, unix_socket_accept_from_fd: false, exec_args: [], ws_c_uri: "ws://0.0.0.0/", linemode_strip_newlines: false, linemode_strict: false, origin: None, custom_headers: [("Cookie", [88, 45, 65, 117, 116, 104, 111, 114, 105, 122, 97, 116, 105, 111, 110, 61, 77, 121, 86, 101, 114, 121, 76, 111, 110, 103, 65, 110, 100, 83, 101, 99, 117, 114, 101, 84, 111, 107, 101, 110])], custom_reply_headers: [], websocket_version: None, websocket_dont_close: false, websocket_ignore_zeromsg: false, one_message: false, no_auto_linemode: false, buffer_size: 65536, broadcast_queue_len: 16, read_debt_handling: Warn, linemode_zero_terminated: false, restrict_uri: None, serve_static_files: [], exec_set_env: false, no_exit_on_zeromsg: false, reuser_send_zero_msg_on_disconnect: false, process_zero_sighup: false, process_exit_sighup: false, socks_destination: None, auto_socks5: None, socks5_bind_script: None, tls_domain: None, tls_insecure: false, headers_to_env: [], max_parallel_conns: None, ws_ping_interval: None, ws_ping_timeout: None, request_uri: None, request_method: None, request_headers: [], autoreconnect_delay_millis: 20, ws_text_prefix: None, ws_binary_prefix: None, ws_binary_base64: false, ws_text_base64: false, asyncstdio: false, foreachmsg_wait_reads: false }[INFO  websocat::stdio_threaded_peer] get_stdio_peer (threaded)
[DEBUG websocat::sessionserve] Underlying connection established
[INFO  websocat::ws_client_peer] get_ws_client_peer
[INFO  websocat::ws_client_peer] Connected to ws
[DEBUG websocat::my_copy] BrokenPipe: read_done
[DEBUG websocat::my_copy] done
[INFO  websocat::sessionserve] Forward finished
[DEBUG websocat::sessionserve] Forward shutdown finished
[DEBUG websocat::ws_peer] drop WsWriteWrapper
[INFO  websocat::ws_peer] Received WebSocket close message
[DEBUG websocat::ws_peer] The close message is None
[DEBUG websocat::my_copy] BrokenPipe: read_done
[DEBUG websocat::my_copy] done
[INFO  websocat::sessionserve] Reverse finished
[DEBUG websocat::sessionserve] Reverse shutdown finished
[INFO  websocat::sessionserve] Both directions finished
cortex:~#

As you can see, the "BrokenPipe" leads to the termination of the session. If I run it like that without closing STDIN, everything works just fine (in the shell).

How could I make this work as a read only service?

mh166 commented 2 years ago

Apparently, I just found a workaround. Kind of...

When running websocat with the following command line options, it even works as a systemd service:

/usr/local/bin/websocat -t - autoreconnect:wss://example.net/websocket -H 'Cookie: X-Authorization=MyVeryLongAndSecureToken'

Notice that the important part is -t - autoreconnect: as a whole. When using it like that, the service keeps running in the background as it should. As soon as I leave out the autoreconnect: part and just use -t - wss://..., it fails just as before.

While this seems like a happy accident (and indeed, it kind of is), I would prefer to handle reconnects on my own, as I might have to renew the auth token.

Therefore, I'll leave this issue open, as any input or suggestions are still very much welcome to get this sorted out properly.

vi commented 2 years ago

Sorry, I have missed the issue when if was originally posted.

I don't know why usage or non-usage of stdin is mapped to read-write or read-only modes, but all mentioned command lines (including the adjusted one) do attempt to read from stdin.

If shell closes stdin, I except Rust standard library to re-open it as /dev/null, so reading it would immediately produce EOF, which sends typically WebSocket close message (maybe autoreconnect: blocks it), which causes server to also close the connection.

Maybe you want -u/-U (unidirectional, in one direction or the other) mode and/or -n (do not send WebSocket close message) options. autoreconnect: may or may not be appropriate. I'd left it out, making Websocat exit in case of broken connections, but also making Systemd restart it more controllably.

mh166 commented 4 months ago

Sorry, completely missed to respond: using the -u/-U worked perfectly. Thanks so much for the help! :)