home » torr/wsserv.git
Author C. Torres <torr.c@mailgw.com> 2024-12-05 15:34:04
Committer C. Torres <torr.c@mailgw.com> 2024-12-05 15:34:04
Commit 8129c96 (patch)
Tree df21245
Parent(s)

Implement first prototype Implement the 1st working prototype. At this stage the program is able to listen and accept TCP connections, and parse incoming HTTP requests for websocket upgrades. Deviant requests are treated as errors and reported back to the caller as such. Signed-off-by: C. Torres <torr.c@mailgw.com>


commits diff: 5924d0a..8129c96
3 files changed, 201 insertions, 0 deletionsdownload


Diffstat
-rw-r--r-- log.go 19
-rw-r--r-- main.go 79
-rw-r--r-- wshttp/wshttp.go 103

Diff options
View
Side
Whitespace
Context lines
Inter-hunk lines
+19/-0 A   log.go
index 0000000..41642b7
old size: 0B - new size: 356B
new file mode: -rw-r--r--
@@ -0,0 +1,19 @@
1 + package main
2 +
3 + import (
4 + "fmt"
5 + "log"
6 + "os"
7 + )
8 +
9 + const (
10 + // sep holds white space used as log type separator.
11 + sep = " "
12 + )
13 +
14 + var (
15 + // logInfo is used to log stable runtime information.
16 + logInfo = log.New(os.Stdout, fmt.Sprintf("%s%s", "info", sep), 0)
17 + // logErr is used to log errors.
18 + logErr = log.New(os.Stdout, fmt.Sprintf("%s%s", "error", sep), 0)
19 + )

+79/-0 A   main.go
index 0000000..5f90dc5
old size: 0B - new size: 1K
new file mode: -rw-r--r--
@@ -0,0 +1,79 @@
1 + package main
2 +
3 + import (
4 + "bufio"
5 + "fmt"
6 + "net"
7 + "net/http"
8 + wshttp "wsserv/wshttp"
9 +
10 + "github.com/gorilla/websocket"
11 + )
12 +
13 + const (
14 + flagHost = "host"
15 + flagPort = "port"
16 +
17 + defHost = "localhost"
18 + defPort = 8070
19 + defBufSiz = 256
20 + )
21 +
22 + var (
23 + port int = 8070
24 + stream bool
25 + )
26 +
27 + func acceptTcpCon(addr string) (net.Conn, error) {
28 + var (
29 + err error
30 + l net.Listener
31 + )
32 +
33 + if l, err = net.Listen("tcp", addr); err != nil {
34 + return nil, err
35 + }
36 + return l.Accept()
37 + }
38 +
39 + func acceptWsCon(tCon net.Conn) *websocket.Conn {
40 + var (
41 + err error
42 + req *http.Request
43 + con *websocket.Conn
44 +
45 + r = bufio.NewReader(tCon)
46 + rw = wshttp.NewResponse(tCon)
47 + )
48 +
49 + for con == nil {
50 + if req, err = http.ReadRequest(r); err != nil {
51 + http.Error(rw, fmt.Sprintf("error: unable to read request: %v", err), http.StatusBadRequest)
52 + continue
53 + }
54 +
55 + if con, err = websocket.Upgrade(rw, req, nil, defBufSiz, defBufSiz); err != nil {
56 + http.Error(rw, fmt.Sprintf("error: unable to create websocket connection: %v", err), http.StatusBadRequest)
57 + }
58 + }
59 +
60 + return con
61 + }
62 +
63 + func main() {
64 + var (
65 + err error
66 + tCon net.Conn
67 + con *websocket.Conn
68 +
69 + listenAddr = fmt.Sprintf("%s:%d", defHost, port)
70 + )
71 +
72 + logInfo.Printf("listening on '%s'", listenAddr)
73 + if tCon, err = acceptTcpCon(listenAddr); err != nil {
74 + panic(err)
75 + }
76 + con = acceptWsCon(tCon)
77 +
78 + fmt.Println(con.WriteMessage(websocket.TextMessage, []byte("hellooooooo\n")))
79 + }

+103/-0 A   wshttp/wshttp.go
index 0000000..397de0f
old size: 0B - new size: 2K
new file mode: -rw-r--r--
@@ -0,0 +1,103 @@
1 + // Package wshttp contains a basic implementation of the interfaces
2 + // 'http.ResponseWriter' and 'http.Hijacker' centered on websocket usage,
3 + // specially the handshake response.
4 + package wshttp
5 +
6 + import (
7 + "bufio"
8 + "bytes"
9 + "fmt"
10 + "io"
11 + "net"
12 + "net/http"
13 + "net/http/httputil"
14 + )
15 +
16 + // Response implements 'http.ResponseWriter' and 'http.Hijacker'.
17 + type Response struct {
18 + Res http.Response
19 + Con net.Conn
20 + hasStatus bool
21 + hijacked bool
22 + }
23 +
24 + // Header implements 'http.ResponseWriter' strictly for websocket connections.
25 + func (r *Response) Header() http.Header {
26 + if r == nil {
27 + return nil
28 + }
29 + if r.Res.Header == nil {
30 + r.Res.Header = make(map[string][]string)
31 + }
32 + return r.Res.Header
33 + }
34 +
35 + // WriteHeader implements 'http.ResponseWriter' strictly for websocket
36 + // connections.
37 + func (r *Response) WriteHeader(code int) {
38 + if r == nil {
39 + return
40 + }
41 +
42 + if r.hasStatus {
43 + return
44 + }
45 + r.hasStatus = true
46 +
47 + r.Res.StatusCode = code
48 + r.Res.Status = http.StatusText(code)
49 + }
50 +
51 + // Write implements 'http.ResponseWriter' strictly for websocket connections.
52 + func (r *Response) Write(data []byte) (int, error) {
53 + if r == nil {
54 + return 0, fmt.Errorf("nil response")
55 + }
56 + if r.hijacked {
57 + return 0, http.ErrHijacked
58 + }
59 +
60 + if !r.hasStatus {
61 + r.Res.StatusCode = http.StatusOK
62 + r.Res.Status = http.StatusText(http.StatusOK)
63 + }
64 +
65 + if r.Res.Body == nil {
66 + r.Res.Body = io.NopCloser(bytes.NewBuffer(data))
67 + }
68 + var dump, err = httputil.DumpResponse(&r.Res, true)
69 + if err != nil {
70 + return 0, err
71 + }
72 +
73 + return r.Con.Write(dump)
74 + }
75 +
76 + // Hijack implements 'http.Hijacker'. The response body is drained, as it's not
77 + // used in websocket handshakes.
78 + func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
79 + if r == nil {
80 + return nil, nil, fmt.Errorf("nil response")
81 + }
82 + if r.hijacked {
83 + return nil, nil, http.ErrHijacked
84 + }
85 + var err error
86 +
87 + r.hijacked = true
88 + if r.Res.Body != nil {
89 + if _, err = io.Copy(io.Discard, r.Res.Body); err != nil {
90 + return nil, nil, fmt.Errorf("unable to drain response body: %v", err)
91 + }
92 + }
93 +
94 + return r.Con, bufio.NewReadWriter(bufio.NewReader(r.Con), bufio.NewWriter(r.Con)), nil
95 + }
96 +
97 + // NewResponse instantiates an HTTP response for websocket handshake requests.
98 + func NewResponse(con net.Conn) *Response {
99 + return &Response{
100 + Con: con,
101 + Res: http.Response{Header: make(map[string][]string)},
102 + }
103 + }