|
|
@ -1,6 +1,6 @@ |
|
|
|
package main |
|
|
|
|
|
|
|
// TODO learn about chan chan's
|
|
|
|
// TODO learn more about chan chan's
|
|
|
|
// http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/
|
|
|
|
|
|
|
|
import ( |
|
|
@ -28,7 +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?
|
|
|
|
// TODO Learn if passing nested structs is desirable?
|
|
|
|
type Conf struct { |
|
|
|
Port uint `yaml:"port,omitempty"` |
|
|
|
Mailer ConfMailer |
|
|
@ -50,7 +50,7 @@ type tcpUser struct { |
|
|
|
// https://stackoverflow.com/questions/51472020/how-to-get-the-size-of-available-tcp-data
|
|
|
|
type bufferedConn struct { |
|
|
|
r *bufio.Reader |
|
|
|
rout io.Reader |
|
|
|
rout io.Reader // See https://github.com/polvi/sni/blob/master/sni.go#L135
|
|
|
|
net.Conn |
|
|
|
} |
|
|
|
|
|
|
@ -77,18 +77,20 @@ func (b bufferedConn) Read(p []byte) (int, error) { |
|
|
|
// because... I can clean it up later
|
|
|
|
type myMsg struct { |
|
|
|
sender net.Conn |
|
|
|
bytes []byte |
|
|
|
receivedAt time.Time |
|
|
|
channel string |
|
|
|
email string |
|
|
|
Message string `json:"message"` |
|
|
|
ReceivedAt time.Time `json:"received_at"` |
|
|
|
Channel string `json:"channel"` |
|
|
|
User string `json:"user"` |
|
|
|
} |
|
|
|
type JsonMsg struct { |
|
|
|
Messages []myMsg `json:"messages"` |
|
|
|
} |
|
|
|
|
|
|
|
var firstMsgs chan myMsg |
|
|
|
|
|
|
|
//var firstMsgs chan myMsg
|
|
|
|
//var myRooms map[string](chan myMsg)
|
|
|
|
var myMsgs chan myMsg |
|
|
|
var msgHistory []myMsg |
|
|
|
var broadcastMsg chan myMsg |
|
|
|
|
|
|
|
//var myUnsortedConns map[net.Conn]bool
|
|
|
|
var newConns chan net.Conn |
|
|
|
var newTcpChat chan bufferedConn |
|
|
|
var authTcpChat chan tcpUser |
|
|
@ -209,7 +211,7 @@ func handleRaw(bufConn bufferedConn) { |
|
|
|
panic(err) |
|
|
|
} |
|
|
|
// so I don't have to actually go check my email
|
|
|
|
fmt.Fprintf(os.Stdout, "\n== AUTHORIZATION ==\n[cheat code for %s]: %s\n", email, code) |
|
|
|
fmt.Fprintf(os.Stdout, "\n== TELNET AUTHORIZATION ==\n[cheat code for %s]: %s\n", email, code) |
|
|
|
time.Sleep(150 * time.Millisecond) |
|
|
|
fmt.Fprintf(bufConn, " done\n") |
|
|
|
time.Sleep(150 * time.Millisecond) |
|
|
@ -255,12 +257,12 @@ func handleRaw(bufConn bufferedConn) { |
|
|
|
|
|
|
|
//fmt.Fprintf(os.Stdout, "Queing message...\n")
|
|
|
|
//myRooms["general"] <- myMsg{
|
|
|
|
myMsgs <- myMsg{ |
|
|
|
receivedAt: time.Now(), |
|
|
|
broadcastMsg <- myMsg{ |
|
|
|
ReceivedAt: time.Now(), |
|
|
|
sender: bufConn, |
|
|
|
bytes: buf[0:count], |
|
|
|
channel: "general", |
|
|
|
email: email, |
|
|
|
Message: string(buf[0:count]), |
|
|
|
Channel: "general", |
|
|
|
User: email, |
|
|
|
} |
|
|
|
//fmt.Fprintf(bufConn, "> ")
|
|
|
|
} |
|
|
@ -326,10 +328,10 @@ func handleSorted(conn bufferedConn) { |
|
|
|
|
|
|
|
/* |
|
|
|
firstMsgs <- myMsg{ |
|
|
|
receivedAt: time.Now(), |
|
|
|
ReceivedAt: time.Now(), |
|
|
|
sender: conn, |
|
|
|
bytes: firstMsg, |
|
|
|
channel: "general", |
|
|
|
Message: firstMsg, |
|
|
|
Channel: "general", |
|
|
|
} |
|
|
|
|
|
|
|
// TODO
|
|
|
@ -356,11 +358,11 @@ func handleSorted(conn bufferedConn) { |
|
|
|
continue |
|
|
|
} |
|
|
|
//myRooms["general"] <- myMsg{
|
|
|
|
myMsgs <- myMsg{ |
|
|
|
receivedAt: time.Now(), |
|
|
|
broadcastMsg <- myMsg{ |
|
|
|
ReceivedAt: time.Now(), |
|
|
|
sender: conn, |
|
|
|
bytes: buf[0:count], |
|
|
|
channel: "general", |
|
|
|
Message: string(buf[0:count]), |
|
|
|
Channel: "general", |
|
|
|
} |
|
|
|
} |
|
|
|
*/ |
|
|
@ -373,13 +375,7 @@ func handleConnection(netConn net.Conn) { |
|
|
|
m := sync.Mutex{} |
|
|
|
virgin := true |
|
|
|
|
|
|
|
// Why don't these work?
|
|
|
|
//buf := make([]byte, 0, 1024)
|
|
|
|
//buf := []byte{}
|
|
|
|
// But this does
|
|
|
|
|
|
|
|
bufConn := newBufferedConn(netConn) |
|
|
|
//myUnsortedConns[bufConn] = true
|
|
|
|
go func() { |
|
|
|
// Handle First Packet
|
|
|
|
_, err := bufConn.Peek(1) |
|
|
@ -407,7 +403,9 @@ func handleConnection(netConn net.Conn) { |
|
|
|
virgin = false |
|
|
|
// don't block for this
|
|
|
|
// let it be handled after the unlock
|
|
|
|
defer fmt.Fprintf(netConn, "\n\nWelcome to Sample Chat! You appear to be using Telnet.\nYou must authenticate via email to participate\n\nEmail: ") |
|
|
|
defer fmt.Fprintf(netConn, |
|
|
|
"\n\nWelcome to Sample Chat! You appear to be using Telnet (http is also available on this port)."+ |
|
|
|
"\nYou must authenticate via email to participate\n\nEmail: ") |
|
|
|
} |
|
|
|
m.Unlock() |
|
|
|
} |
|
|
@ -493,14 +491,15 @@ func serveHello(req *restful.Request, resp *restful.Response) { |
|
|
|
fmt.Fprintf(resp, "{\"msg\":\"hello\"}") |
|
|
|
} |
|
|
|
|
|
|
|
// TODO I probably should just make the non-exportable properties private/lowercase
|
|
|
|
type authReq struct { |
|
|
|
Cid string `json:"cid"` |
|
|
|
ChallengedAt time.Time `json:"-"` |
|
|
|
Chan chan authReq `json:"-"` |
|
|
|
Code string `json:"code"` |
|
|
|
Otp string `json:"otp"` |
|
|
|
CreatedAt time.Time `json:"-"` |
|
|
|
DidAuth bool `json:"-"` |
|
|
|
Email string `json:"email"` |
|
|
|
Subject string `json:"sub"` // Subject as in 'sub' as per OIDC
|
|
|
|
VerifiedAt time.Time `json:"-"` |
|
|
|
Tries int `json:"-"` |
|
|
|
} |
|
|
@ -508,54 +507,56 @@ type authReq struct { |
|
|
|
func requestAuth(req *restful.Request, resp *restful.Response) { |
|
|
|
ar := authReq{ |
|
|
|
CreatedAt: time.Now(), |
|
|
|
DidAuth: false, |
|
|
|
Tries: 0, |
|
|
|
} |
|
|
|
|
|
|
|
// Not sure why go restful finds it easier to do ReadEntity() than the "normal" way...
|
|
|
|
// err := json.NewDecoder(req.Body).Decode(&ar)
|
|
|
|
err := req.ReadEntity(&ar) |
|
|
|
// Looks like restful handles JSON automatically?
|
|
|
|
/* |
|
|
|
err := json.NewDecoder(req.Body).Decode(&ar) |
|
|
|
*/ |
|
|
|
if nil != err { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"bad json in request body\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
email := strings.TrimSpace(ar.Email) |
|
|
|
email := strings.TrimSpace(ar.Subject) |
|
|
|
emailParts := strings.Split(email, "@") |
|
|
|
// TODO better pre-mailer validation (whitelist characters or use lib)
|
|
|
|
if 2 != len(emailParts) { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"bad email address '"+email+"'\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
ar.Subject = email |
|
|
|
|
|
|
|
var code string |
|
|
|
var otp string |
|
|
|
if "" != config.Mailer.ApiKey { |
|
|
|
code, err = sendAuthCode(config.Mailer, email) |
|
|
|
otp, err = sendAuthCode(config.Mailer, ar.Subject) |
|
|
|
if nil != err { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"error sending auth code via mailgun\" } }") |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
if "" == code { |
|
|
|
code, err = genAuthCode() |
|
|
|
if "" == otp { |
|
|
|
otp, err = genAuthCode() |
|
|
|
if nil != err { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"error generating random number (code)\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
ar.Otp = otp |
|
|
|
|
|
|
|
fmt.Fprintf(os.Stdout, "\n== AUTHORIZATION ==\n[cheat code for %s]: %s\n", ar.Email, ar.Code) |
|
|
|
// Cheat code in case you didn't set up mailgun keys
|
|
|
|
fmt.Fprintf(os.Stdout, "\n== HTTP AUTHORIZATION ==\n[cheat code for %s]: %s\n", ar.Subject, ar.Otp) |
|
|
|
|
|
|
|
cid, _ := genAuthCode() |
|
|
|
if "" == cid { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"error generating random number (cid)\"} }") |
|
|
|
} |
|
|
|
ar.Cid = cid |
|
|
|
ar.DidAuth = false |
|
|
|
ar.Email = email |
|
|
|
ar.Code = code |
|
|
|
|
|
|
|
newAuthReqs <- ar |
|
|
|
|
|
|
|
// Not sure why this works... technically there needs to be some sort of "end"
|
|
|
|
// maybe it just figures that if I've returned
|
|
|
|
fmt.Fprintf(resp, "{ \"success\": true, \"cid\": \""+ar.Cid+"\" }") |
|
|
|
} |
|
|
|
|
|
|
@ -581,6 +582,11 @@ func issueToken(req *restful.Request, resp *restful.Response) { |
|
|
|
av := <-ar.Chan |
|
|
|
close(ar.Chan) |
|
|
|
ar.Chan = nil |
|
|
|
// TODO use a pointer instead?
|
|
|
|
if "" == av.Otp { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"invalid request: empty authorization challenge\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
av.Tries += 1 |
|
|
|
av.ChallengedAt = time.Now() |
|
|
|
|
|
|
@ -590,13 +596,13 @@ func issueToken(req *restful.Request, resp *restful.Response) { |
|
|
|
// * CreatedAt is not more than 15 minutes old
|
|
|
|
// Probably also need to make sure than not more than n emails are sent per y minutes
|
|
|
|
|
|
|
|
// Not that this would even matter with the above, just a habit
|
|
|
|
if 1 != subtle.ConstantTimeCompare([]byte(av.Code), []byte(ar.Code)) { |
|
|
|
// Not that this would even matter if the above were implemented, just a habit
|
|
|
|
if 1 != subtle.ConstantTimeCompare([]byte(av.Otp), []byte(ar.Otp)) { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"invalid authorization code\"} }") |
|
|
|
// I'm not sure if this is necessary, but I think it is
|
|
|
|
// to overwrite the original with the updated
|
|
|
|
// (these are copies, not pointers, IIRC)
|
|
|
|
// of course, should use a DB anyway...
|
|
|
|
// and it seems like this is how I might write to a DB anyway
|
|
|
|
newAuthReqs <- av |
|
|
|
return |
|
|
|
} |
|
|
@ -604,19 +610,85 @@ func issueToken(req *restful.Request, resp *restful.Response) { |
|
|
|
ar.VerifiedAt = time.Now() |
|
|
|
newAuthReqs <- av |
|
|
|
|
|
|
|
// TODO I would use a JWT, but I need to wrap up this project
|
|
|
|
fmt.Fprintf(resp, "{ \"success\": true, \"token\": \""+ar.Cid+"\" }") |
|
|
|
} |
|
|
|
|
|
|
|
func requireToken(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { |
|
|
|
//req.SetAttribute("go", "there")
|
|
|
|
//req.Attribute("go") // "there"
|
|
|
|
ar := authReq{} |
|
|
|
|
|
|
|
auth := req.HeaderParameter("Authorization") |
|
|
|
if "" == auth { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"missing Authorization header\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
authParts := strings.Split(auth, " ") |
|
|
|
if "bearer" != strings.ToLower(authParts[0]) || "" == authParts[1] { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"expected 'Authorization: Bearer <Token>'\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
ar.Cid = authParts[1] |
|
|
|
ar.Chan = make(chan authReq) |
|
|
|
valAuthReqs <- ar |
|
|
|
av := <-ar.Chan |
|
|
|
close(ar.Chan) |
|
|
|
ar.Chan = nil |
|
|
|
// TODO use a pointer instead?
|
|
|
|
if "" == av.Cid { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"invalid token: no session found\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// I prefer testing for "if not good" to "if bad"
|
|
|
|
// (much safer in the dynamic world I come from)
|
|
|
|
if true != av.DidAuth { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"message\": \"bad session'\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
req.SetAttribute("user", av.Subject) |
|
|
|
chain.ProcessFilter(req, resp) |
|
|
|
} |
|
|
|
func listMsgs(req *restful.Request, resp *restful.Response) { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"code\": \"E_NO_IMPL\", \"message\": \"invalid authorization code\"} }") |
|
|
|
// TODO support ?since=<ISO_TS>
|
|
|
|
// Also, data race? the list could be added to while this is iterating?
|
|
|
|
// For now we'll just let the client sort the list
|
|
|
|
resp.WriteEntity(&JsonMsg{ |
|
|
|
Messages: msgHistory, |
|
|
|
}) |
|
|
|
} |
|
|
|
func postMsg(req *restful.Request, resp *restful.Response) { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"code\": \"E_NO_IMPL\", \"message\": \"invalid authorization code\"} }") |
|
|
|
user, ok := req.Attribute("user").(string) |
|
|
|
if !ok { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"code\": \"E_SANITY\", \"message\": \"SANITY FAIL user was not set, nor session error sent\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
if "" == user { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"code\": \"E_SESSION\", \"message\": \"invalid session\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
msg := myMsg{} |
|
|
|
err := req.ReadEntity(&msg) |
|
|
|
if nil != err { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"code\": \"E_FORMAT\", \"message\": \"invalid json POST\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
msg.sender = nil |
|
|
|
msg.ReceivedAt = time.Now() |
|
|
|
msg.User = user |
|
|
|
if "" == msg.Channel { |
|
|
|
msg.Channel = "general" |
|
|
|
} |
|
|
|
if "" == msg.Message { |
|
|
|
fmt.Fprintf(resp, "{ \"error\": { \"code\": \"E_FORMAT\", \"message\": \"please specify a 'message'\"} }") |
|
|
|
return |
|
|
|
} |
|
|
|
broadcastMsg <- msg |
|
|
|
|
|
|
|
fmt.Fprintf(resp, "{ \"success\": true }") |
|
|
|
} |
|
|
|
|
|
|
|
func main() { |
|
|
@ -640,24 +712,35 @@ func main() { |
|
|
|
config.RootPath = "./public" |
|
|
|
} |
|
|
|
|
|
|
|
myRawConns := make(map[bufferedConn]bool) |
|
|
|
myAuthReqs := make(map[string]authReq) |
|
|
|
firstMsgs = make(chan myMsg, 128) |
|
|
|
//myRooms = make(map[string](chan myMsg))
|
|
|
|
// The magical sorting hat
|
|
|
|
newConns = make(chan net.Conn, 128) |
|
|
|
|
|
|
|
// TCP & Authentication
|
|
|
|
myRawConns := make(map[bufferedConn]bool) |
|
|
|
newTcpChat = make(chan bufferedConn, 128) |
|
|
|
authTcpChat = make(chan tcpUser, 128) |
|
|
|
|
|
|
|
// HTTP & Authentication
|
|
|
|
myAuthReqs := make(map[string]authReq) |
|
|
|
newAuthReqs = make(chan authReq, 128) |
|
|
|
valAuthReqs = make(chan authReq, 128) |
|
|
|
delAuthReqs = make(chan authReq, 128) |
|
|
|
newTcpChat = make(chan bufferedConn, 128) |
|
|
|
newHttpChat = make(chan bufferedConn, 128) |
|
|
|
newHttpClient = make(chan bufferedConn, 128) |
|
|
|
//myUnsortedConns = make(map[net.Conn]bool)
|
|
|
|
|
|
|
|
// TODO dynamically select on channels?
|
|
|
|
// https://stackoverflow.com/questions/19992334/how-to-listen-to-n-channels-dynamic-select-statement
|
|
|
|
// cruft to delete
|
|
|
|
//myRooms = make(map[string](chan myMsg))
|
|
|
|
//firstMsgs = make(chan myMsg, 128)
|
|
|
|
|
|
|
|
//myRooms["general"] = make(chan myMsg, 128)
|
|
|
|
myMsgs = make(chan myMsg, 128) |
|
|
|
// Note: I had considered dynamically select on channels for rooms.
|
|
|
|
// https://stackoverflow.com/questions/19992334/how-to-listen-to-n-channels-dynamic-select-statement
|
|
|
|
// I don't think that's actually the best approach, but I just wanted to save the link
|
|
|
|
|
|
|
|
broadcastMsg = make(chan myMsg, 128) |
|
|
|
// Poor-Man's container/ring (circular buffer)
|
|
|
|
msgHistory = make([]myMsg, 128) |
|
|
|
msgIndex := 0 |
|
|
|
|
|
|
|
var addr string |
|
|
|
if 0 != int(*port) { |
|
|
@ -730,20 +813,26 @@ func main() { |
|
|
|
myRawConns[u.bufConn] = true |
|
|
|
// is chan chan the right way to handle this?
|
|
|
|
u.userCount <- len(myRawConns) |
|
|
|
myMsgs <- myMsg{ |
|
|
|
broadcastMsg <- myMsg{ |
|
|
|
sender: nil, |
|
|
|
// TODO fmt.Fprintf()? template?
|
|
|
|
bytes: []byte("<" + u.email + "> joined #general\n"), |
|
|
|
receivedAt: time.Now(), |
|
|
|
channel: "general", |
|
|
|
email: "system", |
|
|
|
Message: "<" + u.email + "> joined #general\n", |
|
|
|
ReceivedAt: time.Now(), |
|
|
|
Channel: "general", |
|
|
|
User: "system", |
|
|
|
} |
|
|
|
case ar := <-newAuthReqs: |
|
|
|
myAuthReqs[ar.Cid] = ar |
|
|
|
case av := <-valAuthReqs: |
|
|
|
case ar := <-valAuthReqs: |
|
|
|
// TODO In this case it's probably more conventional (and efficient) to
|
|
|
|
// use struct with a mutex and the authReqs map than a chan chan
|
|
|
|
av.Chan <- myAuthReqs[av.Cid] |
|
|
|
av, ok := myAuthReqs[ar.Cid] |
|
|
|
//ar.Chan <- nil // TODO
|
|
|
|
if ok { |
|
|
|
ar.Chan <- av |
|
|
|
} else { |
|
|
|
ar.Chan <- authReq{} |
|
|
|
} |
|
|
|
case ar := <-delAuthReqs: |
|
|
|
delete(myAuthReqs, ar.Cid) |
|
|
|
case bufConn := <-newTcpChat: |
|
|
@ -758,9 +847,15 @@ func main() { |
|
|
|
//delete(myRooms["general"], bufConn)
|
|
|
|
case bufConn := <-newHttpClient: |
|
|
|
// this will be Accept()ed immediately by restful
|
|
|
|
// NOTE: we don't store these HTTP connections for broadcast
|
|
|
|
// as we manage the session by HTTP Auth Bearer rather than TCP
|
|
|
|
myHttpServer.chans <- bufConn |
|
|
|
case msg := <-myMsgs: |
|
|
|
t := msg.receivedAt |
|
|
|
case msg := <-broadcastMsg: |
|
|
|
msgHistory[msgIndex] = msg |
|
|
|
msgIndex += 1 |
|
|
|
msgIndex %= len(msgHistory) |
|
|
|
|
|
|
|
t := msg.ReceivedAt |
|
|
|
tf := "%d-%02d-%02d %02d:%02d:%02d (%s)" |
|
|
|
var sender string |
|
|
|
if nil != msg.sender { |
|
|
@ -768,16 +863,18 @@ func main() { |
|
|
|
} else { |
|
|
|
sender = "system" |
|
|
|
} |
|
|
|
// Tangential thought:
|
|
|
|
// I wonder if we could use IP detection to get the client's tz
|
|
|
|
// ... could probably make time for this in the authentication loop
|
|
|
|
zone, _ := msg.receivedAt.Zone() |
|
|
|
zone, _ := msg.ReceivedAt.Zone() |
|
|
|
|
|
|
|
//ts, err := msg.receivedAt.MarshalJSON()
|
|
|
|
// TODO put logging here
|
|
|
|
//ts, err := msg.ReceivedAt.MarshalJSON()
|
|
|
|
fmt.Fprintf(os.Stdout, tf+" [%s] (%s):\n\t%s", |
|
|
|
t.Year(), t.Month(), t.Day(), |
|
|
|
t.Hour(), t.Minute(), t.Second(), zone, |
|
|
|
sender, |
|
|
|
msg.email, msg.bytes) |
|
|
|
msg.User, msg.Message) |
|
|
|
|
|
|
|
for conn, _ := range myRawConns { |
|
|
|
// Don't echo back to the original client
|
|
|
@ -796,21 +893,23 @@ func main() { |
|
|
|
_, err := fmt.Fprintf(conn, tf+" [%s]: %s", |
|
|
|
t.Year(), t.Month(), t.Day(), |
|
|
|
t.Hour(), t.Minute(), t.Second(), zone, |
|
|
|
msg.email, msg.bytes) |
|
|
|
msg.User, msg.Message) |
|
|
|
if nil != err { |
|
|
|
delTcpChat <- conn |
|
|
|
} |
|
|
|
}(conn) |
|
|
|
} |
|
|
|
case msg := <-firstMsgs: |
|
|
|
fmt.Fprintf(os.Stdout, "f [First Message]\n") |
|
|
|
ts, err := msg.receivedAt.MarshalJSON() |
|
|
|
if nil != err { |
|
|
|
fmt.Fprintf(os.Stderr, "f [Error] %s\n", err) |
|
|
|
} |
|
|
|
fmt.Fprintf(os.Stdout, "f [Timestamp] %s\n", ts) |
|
|
|
fmt.Fprintf(os.Stdout, "f [Remote] %s\n", msg.sender.RemoteAddr().String()) |
|
|
|
fmt.Fprintf(os.Stdout, "f [Message] %s\n", msg.bytes) |
|
|
|
/* |
|
|
|
case msg := <-firstMsgs: |
|
|
|
fmt.Fprintf(os.Stdout, "f [First Message]\n") |
|
|
|
ts, err := msg.ReceivedAt.MarshalJSON() |
|
|
|
if nil != err { |
|
|
|
fmt.Fprintf(os.Stderr, "f [Error] %s\n", err) |
|
|
|
} |
|
|
|
fmt.Fprintf(os.Stdout, "f [Timestamp] %s\n", ts) |
|
|
|
fmt.Fprintf(os.Stdout, "f [Remote] %s\n", msg.sender.RemoteAddr().String()) |
|
|
|
fmt.Fprintf(os.Stdout, "f [Message] %s\n", msg.Message) |
|
|
|
*/ |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|