From 44d605d38a54751405db26daaf6eb9477f2a86df Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 2 Aug 2018 02:13:56 -0600 Subject: [PATCH] even more cleanup --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++---- chatserver-http.go | 15 ++++++++++++ chatserver-telnet.go | 7 +----- chatserver.go | 49 ++++++++++++++++++-------------------- config.sample.yml | 1 + public/index.html | 12 +++++----- 6 files changed, 98 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 363188e..5982e69 100644 --- a/README.md +++ b/README.md @@ -32,23 +32,71 @@ You can connect multiple clients. telnet localhost 4080 ``` -You can also use HTTP. The API docs and examples can be seen at +You can also use HTTP. ``` curl http://localhost:4080 ``` +# API Docs + +The API docs and examples can be seen at + +# Project Approach + +I've understood theoretical principles of Go for a long time and I've always loved it. +However, most of that came from watching Go Tech Talks and going to meetups. + +This is my first Go project and my primary goal was to learn how to use Go +for the kinds of things that I'm personally interested in while also satisfying +a mix of the requirements and optional add-ons, and show you that I know how +to write code and learn. + +Criteria Met +----- + +* [x] Works +* [x] Attention to Detail + * [x] Security + * [x] UX + * [x] Performance +* [x] Commenting +* [x] Creativity + +Limitations +---------- + +* [ ] Good coding style + +This is my first Go project so I was learning as I went and trying different approaches. +As a result my code style is inconsistent and probably does some things the wrong way +(especially confusing since I probably did it the right way in other places). Implemented ----- -* [x] Awesome telnet server (would -* [x] HTTP API (no UI for the sake of time) +* [x] Awesome telnet server + * [x] Multiple clients can connect + * [x] Messages are relayed to all clients + * [x] Includes timestamp, name of client + * [x] Config file for port +* [x] HTTP API + * [x] Post message + * [x] List messages + * [x] Serve Docs + * [x] Working curl examples * [x] Multiplex the same port (because I wanted to learn) * [x] E-mail "magic link" authentication (minus the link since it's localhost) Not Implemented ---- -* [ ] Write to log file (just `go run ./chatserver.go > /path/to/log` +I don't think these things would be difficult to add, +but I was having fun learning lots of other things +and I figured Some of these things I didn't implement + +* [ ] local log file +* [ ] Listening IP config * [ ] Rooms +* [ ] Blocking +* [ ] UI for HTTP API diff --git a/chatserver-http.go b/chatserver-http.go index cee83b3..d87579d 100644 --- a/chatserver-http.go +++ b/chatserver-http.go @@ -3,6 +3,7 @@ package main import ( "crypto/subtle" "fmt" + "net" "net/http" "os" "path" @@ -12,6 +13,20 @@ import ( restful "github.com/emicklei/go-restful" ) +type myHttpServer struct { + chans chan bufferedConn + net.Listener +} + +func (m *myHttpServer) Accept() (net.Conn, error) { + bufConn := <-m.chans + return bufConn, nil +} + +func newHttpServer(l net.Listener) *myHttpServer { + return &myHttpServer{make(chan bufferedConn), l} +} + // TODO I probably should just make the non-exportable properties private/lowercase type authReq struct { Cid string `json:"cid"` diff --git a/chatserver-telnet.go b/chatserver-telnet.go index f0393b6..5ede008 100644 --- a/chatserver-telnet.go +++ b/chatserver-telnet.go @@ -34,7 +34,6 @@ func handleTelnetConn(bufConn bufferedConn) { if io.EOF != err { fmt.Fprintf(os.Stderr, "Non-EOF socket error: %s\n", err) } - fmt.Fprintf(os.Stdout, "Ending socket\n") if nil != u { cleanTelnet <- *u @@ -136,8 +135,7 @@ func handleTelnetConn(bufConn bufferedConn) { newMsg: make(chan string, 10), // reasonably sized } authTelnet <- *u - // prevent data race on len(myRawConns) - // XXX (there can't be a race between these two lines, right?) + // prevent data race on len(telnetConns) count := <-u.userCount close(u.userCount) u.userCount = nil @@ -168,8 +166,6 @@ func handleTelnetConn(bufConn bufferedConn) { continue } - //fmt.Fprintf(os.Stdout, "Queing message...\n") - //myRooms["general"] <- myMsg{ broadcastMsg <- myMsg{ ReceivedAt: time.Now(), sender: bufConn, @@ -177,7 +173,6 @@ func handleTelnetConn(bufConn bufferedConn) { Channel: "general", User: email, } - //fmt.Fprintf(bufConn, "> ") } } diff --git a/chatserver.go b/chatserver.go index d01e786..8178b40 100644 --- a/chatserver.go +++ b/chatserver.go @@ -28,7 +28,8 @@ import ( // I'm not sure how to pass nested structs, so I de-nested this. // TODO Learn if passing nested structs is desirable? type Conf struct { - Port uint `yaml:"port,omitempty"` + Addr string `yaml:"addr,omitempty"` + Port uint `yaml:"port,omitempty"` Mailer ConfMailer RootPath string `yaml:"root_path,omitempty"` } @@ -100,7 +101,7 @@ var authTelnet chan tcpUser var cleanTelnet chan tcpUser var gotClientHello chan bufferedConn -// Http +// HTTP var demuxHttpClient chan bufferedConn var newAuthReqs chan authReq var valAuthReqs chan authReq @@ -277,25 +278,11 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) { return code, nil } -type myHttpServer struct { - chans chan bufferedConn - net.Listener -} - -func (m *myHttpServer) Accept() (net.Conn, error) { - bufConn := <-m.chans - return bufConn, nil -} - -func newHttpServer(l net.Listener) *myHttpServer { - return &myHttpServer{make(chan bufferedConn), l} -} - var config Conf func main() { flag.Usage = usage - port := flag.Uint("telnet-port", 0, "tcp telnet chat port") + port := flag.Uint("port", 0, "tcp telnet chat port") confname := flag.String("conf", "./config.yml", "yaml config file") flag.Parse() @@ -318,7 +305,7 @@ func main() { virginConns = make(chan net.Conn, 128) // TCP & Authentication - myRawConns := make(map[bufferedConn]tcpUser) + telnetConns := make(map[bufferedConn]tcpUser) wantsServerHello = make(chan bufferedConn, 128) authTelnet = make(chan tcpUser, 128) @@ -344,9 +331,9 @@ func main() { var addr string if 0 != int(*port) { - addr = ":" + strconv.Itoa(int(*port)) + addr = config.Addr + ":" + strconv.Itoa(int(*port)) } else { - addr = ":" + strconv.Itoa(int(config.Port)) + addr = config.Addr + ":" + strconv.Itoa(int(config.Port)) } // https://golang.org/pkg/net/#Conn @@ -388,8 +375,8 @@ func main() { wsApi.Route(wsApi.GET("/hello").To(serveHello)) wsApi.Route(wsApi.POST("/sessions").To(requestAuth)) wsApi.Route(wsApi.POST("/sessions/{cid}").To(issueToken)) - wsApi.Route(wsApi.GET("/rooms/general").Filter(requireToken).To(listMsgs)) - wsApi.Route(wsApi.POST("/rooms/general").Filter(requireToken).To(postMsg)) + wsApi.Route(wsApi.GET("/rooms/{room}").Filter(requireToken).To(listMsgs)) + wsApi.Route(wsApi.POST("/rooms/{room}").Filter(requireToken).To(postMsg)) container.Add(wsApi) server := &http.Server{ @@ -410,9 +397,9 @@ func main() { case u := <-authTelnet: // allow to receive messages // (and be counted among the users) - myRawConns[u.bufConn] = u + telnetConns[u.bufConn] = u // is chan chan the right way to handle this? - u.userCount <- len(myRawConns) + u.userCount <- len(telnetConns) broadcastMsg <- myMsg{ sender: nil, // TODO fmt.Fprintf()? template? @@ -442,9 +429,19 @@ func main() { go handleTelnetConn(bufConn) case u := <-cleanTelnet: // we can safely ignore this error, if any + if "" != u.email { + broadcastMsg <- myMsg{ + sender: nil, + // TODO fmt.Fprintf()? template? + Message: "<" + u.email + "> left #general\n", + ReceivedAt: time.Now(), + Channel: "general", + User: "system", + } + } close(u.newMsg) u.bufConn.Close() - delete(myRawConns, u.bufConn) + delete(telnetConns, u.bufConn) case bufConn := <-gotClientHello: go muxTcp(bufConn) case bufConn := <-demuxHttpClient: @@ -480,7 +477,7 @@ func main() { sender, msg.User, msg.Message) - for _, u := range myRawConns { + for _, u := range telnetConns { // Don't echo back to the original client if msg.sender == u.bufConn { continue diff --git a/config.sample.yml b/config.sample.yml index 78dcfda..8ae9d29 100644 --- a/config.sample.yml +++ b/config.sample.yml @@ -1,3 +1,4 @@ +addr: 127.0.0.1 port: 4080 root_path: ./public mailer: diff --git a/public/index.html b/public/index.html index f44ba22..1bd6026 100644 --- a/public/index.html +++ b/public/index.html @@ -5,22 +5,22 @@
# Ask for an auth code (swap sub)
 curl -X POST http://localhost:4080/api/sessions \
   -H 'Content-Type: application/json; charset=utf-8' \
-  -d '{"sub":"jon@example.com"}'
+  -d '{"sub":"jon@example.com"}'
 
 # Validate auth code (swap session id, sub, and otp)
-curl -X POST http://localhost:4080/api/sessions/xyz \
+curl -X POST http://localhost:4080/api/sessions/xyz \
   -H 'Content-Type: application/json; charset=utf-8' \
-  -d '{"otp":"secret123"}'
+  -d '{"otp":"secret123"}'
 
 # Post a message (swap api-token)
 curl -X POST http://localhost:4080/api/rooms/general \
-  -H 'Authorization: Bearer api-token' \
+  -H 'Authorization: Bearer api-token' \
   -H 'Content-Type: application/json; charset=utf-8' \
-  -d '{"message":"hello"}'
+  -d '{"message":"Hello, World!"}'
 
 # Get a room's messages (swap api-token, since unix-epoch)
 curl http://localhost:4080/api/rooms/general?since=0 \
-  -H 'Authorization: Bearer api-token'
+  -H 'Authorization: Bearer api-token'