even more cleanup
This commit is contained in:
parent
92340069b1
commit
44d605d38a
56
README.md
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
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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, "> ")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ 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 {
|
||||
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,3 +1,4 @@
|
|||
addr: 127.0.0.1
|
||||
port: 4080
|
||||
root_path: ./public
|
||||
mailer:
|
||||
|
|
|
@ -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…
Reference in New Issue