Browse Source

even more cleanup

master
AJ ONeal 4 years ago
parent
commit
44d605d38a
  1. 56
      README.md
  2. 15
      chatserver-http.go
  3. 7
      chatserver-telnet.go
  4. 49
      chatserver.go
  5. 1
      config.sample.yml
  6. 12
      public/index.html

56
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 <http://localhost:4080>
You can also use HTTP.
```
curl http://localhost:4080
```
# API Docs
The API docs and examples can be seen at <http://localhost:4080>
# 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

15
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"`

7
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, "> ")
}
}

49
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

1
config.sample.yml

@ -1,3 +1,4 @@
addr: 127.0.0.1
port: 4080
root_path: ./public
mailer:

12
public/index.html

@ -5,22 +5,22 @@
<pre><code># 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":"<strong><em>jon@example.com</em></strong>"}'
# 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/<strong><em>xyz</em></strong> \
-H 'Content-Type: application/json; charset=utf-8' \
-d '{"otp":"secret123"}'
-d '{"otp":"<strong><em>secret123</em></strong>"}'
# Post a message (swap api-token)
curl -X POST http://localhost:4080/api/rooms/general \
-H 'Authorization: Bearer api-token' \
-H 'Authorization: Bearer <strong><em>api-token</em></strong>' \
-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 <strong><em>api-token</em></strong>'
</code></pre>
</body>
</html>

Loading…
Cancel
Save