home » torr/wssrv.git
Author C. Torres <torr.c@mailgw.com> 2025-01-11 01:09:57
Committer C. Torres <torr.c@mailgw.com> 2025-01-11 01:23:11
Commit 8aaa565 (patch)
Tree 86daf6d
Parent(s)

Implement raw Stdin serving mode Implement serving mode whereby any byte received through the program's Stdin is sent in the websocket. This mode sets the terminal in pseudo raw mode, in the sense that the 'OPOST' Termios attribute is not set. Signed-off-by: C. Torres <torr.c@mailgw.com>


commits diff: 80ab375..8aaa565
4 files changed, 128 insertions, 4 deletionsdownload


Diffstat
-rw-r--r-- go.mod 5
-rw-r--r-- go.sum 2
-rw-r--r-- ws/common.go 45
-rw-r--r-- ws/raw_stdin.go 80

Diff options
View
Side
Whitespace
Context lines
Inter-hunk lines
+4/-1 M   go.mod
index cd905c1..9686469
old size: 69B - new size: 100B
@@ -2,4 +2,7 @@ module wssrv
2 2
3 3 go 1.22.6
4 4
5 - require github.com/gorilla/websocket v1.5.3
5 + require (
6 + github.com/gorilla/websocket v1.5.3
7 + golang.org/x/sys v0.29.0
8 + )

+2/-0 M   go.sum
index 25a9fc4..478d5e3
old size: 175B - new size: 328B
@@ -1,2 +1,4 @@
1 1 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
2 2 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
3 + golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
4 + golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

+42/-3 M   ws/common.go
index fe8a586..57770d9
old size: 422B - new size: 1K
@@ -3,16 +3,55 @@ package ws
3 3 import (
4 4 "fmt"
5 5 "os"
6 +
7 + "golang.org/x/sys/unix"
6 8 )
7 9
8 - func checkFile(fileNa string) error {
10 + func restore(fd int, t *unix.Termios) error {
11 + return unix.IoctlSetTermios(fd, unix.TCSETS, t)
12 + }
13 +
14 + // setupTerm sets the terminal with raw mode attributes for input, whereby
15 + // 'OPOST' is not set.
16 + func setupTerm() (int, *unix.Termios, error) {
17 + var (
18 + err error
19 + fd int
20 + term *unix.Termios
21 +
22 + oldTerm = &unix.Termios{}
23 + )
24 +
25 + if fd, err = unix.Open("/dev/tty", unix.O_RDONLY, 0); err != nil {
26 + return 0, nil, err
27 + }
28 + if term, err = unix.IoctlGetTermios(fd, unix.TCGETS); err != nil {
29 + return 0, nil, err
30 + }
31 + *oldTerm = *term
32 +
33 + term.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP |
34 + unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
35 + term.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
36 + term.Cflag &^= unix.CSIZE | unix.PARENB
37 + term.Cflag |= unix.CS8
38 + term.Cc[unix.VMIN] = 1
39 + term.Cc[unix.VTIME] = 0
40 +
41 + if err = unix.IoctlSetTermios(fd, unix.TCSETS, term); err != nil {
42 + return 0, nil, err
43 + }
44 + return fd, oldTerm, nil
45 + }
46 +
47 + func checkFile(name string) error {
9 48 var (
10 49 err error
11 50 stat os.FileInfo
12 51 )
13 52
14 - if stat, err = os.Stat(fileNa); err != nil {
15 - return fmt.Errorf("unable to check metadata for file '%s': %v", fileNa, err)
53 + if stat, err = os.Stat(name); err != nil {
54 + return fmt.Errorf("unable to check metadata for file '%s': %v", name, err)
16 55 }
17 56 if !stat.Mode().IsRegular() {
18 57 return fmt.Errorf("file '%s' is not regular")

+80/-0 A   ws/raw_stdin.go
index 0000000..430fa1e
old size: 0B - new size: 2K
new file mode: -rw-r--r--
@@ -0,0 +1,80 @@
1 + package ws
2 +
3 + import (
4 + "context"
5 + "fmt"
6 + "wssrv/log"
7 +
8 + "github.com/gorilla/websocket"
9 + "golang.org/x/sys/unix"
10 + )
11 +
12 + const (
13 + bufSize = 8
14 + quitKey = 17 // Key '<c-q>'.
15 + )
16 +
17 + func sendRawStdin(ctx context.Context, cancel context.CancelCauseFunc, con *websocket.Conn, fd int) {
18 + var (
19 + err error
20 + n int
21 +
22 + buf, clean = make([]byte, bufSize), make([]byte, bufSize)
23 + )
24 +
25 + for {
26 + select {
27 + case <-ctx.Done():
28 + return
29 + default:
30 + buf = clean
31 + if n, err = unix.Read(fd, buf); err != nil {
32 + cancel(fmt.Errorf("unable to read from descriptor '%d': %v", fd, err))
33 + return
34 + }
35 + if n == 1 && buf[0] == quitKey {
36 + cancel(nil)
37 + return
38 + }
39 + fmt.Printf("> byte '%d'\n", buf[:n])
40 + if err = con.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil {
41 + cancel(fmt.Errorf("unable write on websocket on '%s': %v", con.RemoteAddr(), err))
42 + return
43 + }
44 + }
45 + }
46 + }
47 +
48 + func sendRawStdinStream(con *websocket.Conn) error {
49 + var (
50 + err error
51 + fd int
52 + term *unix.Termios
53 +
54 + ctx, cancel = context.WithCancelCause(context.Background())
55 + )
56 +
57 + if fd, term, err = setupTerm(); err != nil {
58 + return fmt.Errorf("unable to setup terminal for raw input: %v", err)
59 + }
60 + defer func(fd int, term *unix.Termios) {
61 + var err error
62 + if err = unix.IoctlSetTermios(fd, unix.TCSETS, term); err != nil {
63 + log.Err.Printf("unable to restore terminal with descriptor '%d': %v", fd, err)
64 + }
65 + }(fd, term)
66 +
67 + go readMessage(ctx, cancel, con)
68 + go sendRawStdin(ctx, cancel, con, fd)
69 +
70 + <-ctx.Done()
71 + return context.Cause(ctx)
72 + }
73 +
74 + func SendRawStdinStream(con *websocket.Conn) error {
75 + switch {
76 + case con == nil:
77 + return fmt.Errorf("nil connection parameter")
78 + }
79 + return sendRawStdinStream(con)
80 + }