GEFN (good enough for now)
This commit is contained in:
		
							förälder
							
								
									2b88da7a71
								
							
						
					
					
						incheckning
						3943c14845
					
				| @ -13,6 +13,10 @@ import ( | ||||
| 	restful "github.com/emicklei/go-restful" | ||||
| ) | ||||
| 
 | ||||
| type JsonMsg struct { | ||||
| 	Messages []*chatMsg `json:"messages"` | ||||
| } | ||||
| 
 | ||||
| type myHttpServer struct { | ||||
| 	chans chan bufferedConn | ||||
| 	net.Listener | ||||
| @ -43,10 +47,7 @@ type authReq struct { | ||||
| func serveStatic(req *restful.Request, resp *restful.Response) { | ||||
| 	actual := path.Join(config.RootPath, req.PathParameter("subpath")) | ||||
| 	fmt.Printf("serving %s ... (from %s)\n", actual, req.PathParameter("subpath")) | ||||
| 	http.ServeFile( | ||||
| 		resp.ResponseWriter, | ||||
| 		req.Request, | ||||
| 		actual) | ||||
| 	http.ServeFile(resp.ResponseWriter, req.Request, actual) | ||||
| } | ||||
| 
 | ||||
| func serveHello(req *restful.Request, resp *restful.Response) { | ||||
| @ -93,7 +94,7 @@ func requestAuth(req *restful.Request, resp *restful.Response) { | ||||
| 	} | ||||
| 	ar.Otp = otp | ||||
| 
 | ||||
| 	// Cheat code in case you didn't set up mailgun keys | ||||
| 	// Cheat code in case you didn't set up mailgun keys in the config file | ||||
| 	fmt.Fprintf(os.Stdout, "\n== HTTP AUTHORIZATION ==\n[cheat code for %s]: %s\n", ar.Subject, ar.Otp) | ||||
| 
 | ||||
| 	cid, _ := genAuthCode() | ||||
| @ -102,10 +103,8 @@ func requestAuth(req *restful.Request, resp *restful.Response) { | ||||
| 	} | ||||
| 	ar.Cid = cid | ||||
| 
 | ||||
| 	newAuthReqs <- ar | ||||
| 	authReqs <- 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+"\" }") | ||||
| } | ||||
| 
 | ||||
| @ -118,7 +117,6 @@ func issueToken(req *restful.Request, resp *restful.Response) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	//err := json.NewDecoder(r.Body).Decode(&ar) | ||||
| 	err := req.ReadEntity(&ar) | ||||
| 	if nil != err { | ||||
| 		fmt.Fprintf(resp, "{ \"error\": { \"message\": \"bad json in request body\"} }") | ||||
| @ -152,12 +150,12 @@ func issueToken(req *restful.Request, resp *restful.Response) { | ||||
| 		// to overwrite the original with the updated | ||||
| 		// (these are copies, not pointers, IIRC) | ||||
| 		// and it seems like this is how I might write to a DB anyway | ||||
| 		newAuthReqs <- av | ||||
| 		authReqs <- av | ||||
| 		return | ||||
| 	} | ||||
| 	av.DidAuth = true | ||||
| 	ar.VerifiedAt = time.Now() | ||||
| 	newAuthReqs <- av | ||||
| 	authReqs <- av | ||||
| 
 | ||||
| 	// TODO I would use a JWT, but I need to wrap up this project | ||||
| 	fmt.Fprintf(resp, "{ \"success\": true, \"token\": \""+ar.Cid+"\" }") | ||||
| @ -201,9 +199,8 @@ func requireToken(req *restful.Request, resp *restful.Response, chain *restful.F | ||||
| } | ||||
| 
 | ||||
| func listMsgs(req *restful.Request, resp *restful.Response) { | ||||
| 	// 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 | ||||
| 	// TODO support ?since=<ISO_TS>, but for now we'll just let the client sort the list | ||||
| 	// TODO Could this have a data race if the list were added to while this is iterating? | ||||
| 	resp.WriteEntity(&JsonMsg{ | ||||
| 		Messages: myChatHist.msgs[:myChatHist.c], | ||||
| 	}) | ||||
|  | ||||
| @ -9,6 +9,13 @@ import ( | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type telnetUser struct { | ||||
| 	bufConn   bufferedConn | ||||
| 	userCount chan int | ||||
| 	email     string | ||||
| 	newMsg    chan string | ||||
| } | ||||
| 
 | ||||
| // Trying to keep it slim with just one goroutine per client for each reads and writes. | ||||
| // Initially I was spawning a goroutine per write in the main select, but my guess is that | ||||
| // constantly allocating and cleaning up 4k of memory (or perhaps less these days | ||||
| @ -26,7 +33,7 @@ func handleTelnetConn(bufConn bufferedConn) { | ||||
| 
 | ||||
| 	// Handle all subsequent packets | ||||
| 	buffer := make([]byte, 1024) | ||||
| 	var u *tcpUser | ||||
| 	var u *telnetUser | ||||
| 	for { | ||||
| 		//fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n") | ||||
| 		count, err := bufConn.Read(buffer) | ||||
| @ -136,7 +143,7 @@ func handleTelnetConn(bufConn bufferedConn) { | ||||
| 				authn = true | ||||
| 				time.Sleep(150 * time.Millisecond) | ||||
| 				fmt.Fprintf(bufConn, "\n") | ||||
| 				u = &tcpUser{ | ||||
| 				u = &telnetUser{ | ||||
| 					bufConn:   bufConn, | ||||
| 					email:     email, | ||||
| 					userCount: make(chan int, 1), | ||||
| @ -156,6 +163,7 @@ func handleTelnetConn(bufConn bufferedConn) { | ||||
| 				time.Sleep(50 * time.Millisecond) | ||||
| 				fmt.Fprintf(bufConn, "\n") | ||||
| 				time.Sleep(50 * time.Millisecond) | ||||
| 				// It turns out that ANSI characters work in Telnet just fine | ||||
| 				fmt.Fprintf(bufConn, "\033[1;32m"+"Welcome to #general (%d users)!"+"\033[22;39m", count) | ||||
| 				time.Sleep(50 * time.Millisecond) | ||||
| 				fmt.Fprintf(bufConn, "\n") | ||||
| @ -166,7 +174,8 @@ func handleTelnetConn(bufConn bufferedConn) { | ||||
| 				fmt.Fprintf(bufConn, "\n") | ||||
| 
 | ||||
| 				// Would be cool to write a prompt... | ||||
| 				// I wonder if I could send the correct ANSI codes for that... | ||||
| 				// I wonder if I could send fudge some ANSI codes to keep the prompt | ||||
| 				// even when new messages come in, but not overwrite what he user typed... | ||||
| 				//fmt.Fprintf(bufConn, "\n%s> ", email) | ||||
| 
 | ||||
| 				go handleTelnetBroadcast(u) | ||||
| @ -185,7 +194,7 @@ func handleTelnetConn(bufConn bufferedConn) { | ||||
| } | ||||
| 
 | ||||
| // Writes (post Auth) | ||||
| func handleTelnetBroadcast(u *tcpUser) { | ||||
| func handleTelnetBroadcast(u *telnetUser) { | ||||
| 	for { | ||||
| 		msg, more := <-u.newMsg | ||||
| 		if !more { | ||||
|  | ||||
| @ -39,13 +39,6 @@ type ConfMailer struct { | ||||
| 	From   string `yaml:"from,omitempty"` | ||||
| } | ||||
| 
 | ||||
| type tcpUser struct { | ||||
| 	bufConn   bufferedConn | ||||
| 	userCount chan int | ||||
| 	email     string | ||||
| 	newMsg    chan string | ||||
| } | ||||
| 
 | ||||
| // So we can peek at net.Conn, which we can't do natively | ||||
| // https://stackoverflow.com/questions/51472020/how-to-get-the-size-of-available-tcp-data | ||||
| type bufferedConn struct { | ||||
| @ -80,28 +73,29 @@ type chatMsg struct { | ||||
| 	Channel    string    `json:"channel"` | ||||
| 	User       string    `json:"user"` | ||||
| } | ||||
| type JsonMsg struct { | ||||
| 	Messages []*chatMsg `json:"messages"` | ||||
| } | ||||
| 
 | ||||
| // Poor-Man's container/ring (circular buffer) | ||||
| type chatHist struct { | ||||
| 	msgs []*chatMsg | ||||
| 	i    int | ||||
| 	c    int | ||||
| 	i    int // current index | ||||
| 	c    int // current count (number of elements) | ||||
| } | ||||
| 
 | ||||
| // Multi-use | ||||
| var config Conf | ||||
| var virginConns chan net.Conn | ||||
| var gotClientHello chan bufferedConn | ||||
| var myChatHist chatHist | ||||
| var broadcastMsg chan chatMsg | ||||
| 
 | ||||
| var virginConns chan net.Conn | ||||
| // Telnet | ||||
| var wantsServerHello chan bufferedConn | ||||
| var authTelnet chan tcpUser | ||||
| var cleanTelnet chan tcpUser | ||||
| var gotClientHello chan bufferedConn | ||||
| var authTelnet chan telnetUser | ||||
| var cleanTelnet chan telnetUser | ||||
| 
 | ||||
| // HTTP | ||||
| var demuxHttpClient chan bufferedConn | ||||
| var newAuthReqs chan authReq | ||||
| var authChallenge chan authReq | ||||
| var valAuthReqs chan authReq | ||||
| var delAuthReqs chan authReq | ||||
| 
 | ||||
| @ -184,7 +178,7 @@ func muxTcp(conn bufferedConn) { | ||||
| 	demuxHttpClient <- conn | ||||
| } | ||||
| 
 | ||||
| func handleConnection(netConn net.Conn) { | ||||
| func testForHello(netConn net.Conn) { | ||||
| 	ts := time.Now() | ||||
| 	fmt.Fprintf(os.Stdout, "[New Connection] (%s) welcome %s\n", ts, netConn.RemoteAddr().String()) | ||||
| 
 | ||||
| @ -193,13 +187,11 @@ func handleConnection(netConn net.Conn) { | ||||
| 
 | ||||
| 	bufConn := newBufferedConn(netConn) | ||||
| 	go func() { | ||||
| 		// Handle First Packet | ||||
| 		// Cause first packet to be loaded into buffer | ||||
| 		_, err := bufConn.Peek(1) | ||||
| 		//fmsg, err := bufConn.Peek(1) | ||||
| 		if nil != err { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		//fmt.Fprintf(os.Stdout, "[First Byte] %s\n", fmsg) | ||||
| 
 | ||||
| 		m.Lock() | ||||
| 		if virgin { | ||||
| @ -211,14 +203,17 @@ func handleConnection(netConn net.Conn) { | ||||
| 		m.Unlock() | ||||
| 	}() | ||||
| 
 | ||||
| 	// Wait for a hello packet of some sort from the client | ||||
| 	// (obviously this wouldn't work in extremely high latency situations) | ||||
| 	time.Sleep(250 * 1000000) | ||||
| 
 | ||||
| 	// If we still haven't received data from the client | ||||
| 	// assume that the client must be expecting a welcome from us | ||||
| 	m.Lock() | ||||
| 	if virgin { | ||||
| 		virgin = false | ||||
| 		// don't block for this | ||||
| 		// let it be handled after the unlock | ||||
| 		// Defer as to not block and prolonging the mutex | ||||
| 		// (not that those few cycles much matter...) | ||||
| 		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: ") | ||||
| @ -251,7 +246,7 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) { | ||||
| 	if nil != err { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	//req.PostForm = form | ||||
| 	//req.PostForm = form ?? | ||||
| 	req.Header.Add("User-Agent", "golang http.Client - Sample Chat App Authenticator") | ||||
| 	req.Header.Add("Content-Type", "application/x-www-form-urlencoded") | ||||
| 	req.SetBasicAuth("api", cnf.ApiKey) | ||||
| @ -276,8 +271,6 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) { | ||||
| 	return code, nil | ||||
| } | ||||
| 
 | ||||
| var config Conf | ||||
| 
 | ||||
| func main() { | ||||
| 	flag.Usage = usage | ||||
| 	port := flag.Uint("port", 0, "tcp telnet chat port") | ||||
| @ -295,7 +288,8 @@ func main() { | ||||
| 		config = Conf{} | ||||
| 	} | ||||
| 	if "" == config.RootPath { | ||||
| 		// TODO Embed the public dir at the default | ||||
| 		// TODO Maybe embed the public dir into the binary | ||||
| 		// (and provide a flag with path for override - like gitea) | ||||
| 		config.RootPath = "./public" | ||||
| 	} | ||||
| 
 | ||||
| @ -303,28 +297,24 @@ func main() { | ||||
| 	virginConns = make(chan net.Conn, 128) | ||||
| 
 | ||||
| 	// TCP & Authentication | ||||
| 	telnetConns := make(map[bufferedConn]tcpUser) | ||||
| 	telnetConns := make(map[bufferedConn]telnetUser) | ||||
| 	wantsServerHello = make(chan bufferedConn, 128) | ||||
| 	authTelnet = make(chan tcpUser, 128) | ||||
| 	authTelnet = make(chan telnetUser, 128) | ||||
| 
 | ||||
| 	// HTTP & Authentication | ||||
| 	myAuthReqs := make(map[string]authReq) | ||||
| 	newAuthReqs = make(chan authReq, 128) | ||||
| 	authReqs = make(chan authReq, 128) | ||||
| 	valAuthReqs = make(chan authReq, 128) | ||||
| 	delAuthReqs = make(chan authReq, 128) | ||||
| 	gotClientHello = make(chan bufferedConn, 128) | ||||
| 	demuxHttpClient = make(chan bufferedConn, 128) | ||||
| 
 | ||||
| 	// cruft to delete | ||||
| 	//myRooms = make(map[string](chan chatMsg)) | ||||
| 
 | ||||
| 	//myRooms["general"] = make(chan chatMsg, 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 chatMsg, 128) | ||||
| 	// Poor-Man's container/ring (circular buffer) | ||||
| 	myChatHist.msgs = make([]*chatMsg, 128) | ||||
| 
 | ||||
| 	var addr string | ||||
| @ -391,7 +381,7 @@ func main() { | ||||
| 		select { | ||||
| 		case conn := <-virginConns: | ||||
| 			// This is short lived | ||||
| 			go handleConnection(conn) | ||||
| 			go testForHello(conn) | ||||
| 		case u := <-authTelnet: | ||||
| 			// allow to receive messages | ||||
| 			// (and be counted among the users) | ||||
| @ -405,7 +395,7 @@ func main() { | ||||
| 				Channel:    "general", | ||||
| 				User:       "system", | ||||
| 			} | ||||
| 		case ar := <-newAuthReqs: | ||||
| 		case ar := <-authReqs: | ||||
| 			myAuthReqs[ar.Cid] = ar | ||||
| 		case ar := <-valAuthReqs: | ||||
| 			// TODO In this case it's probably more conventional (and efficient) to | ||||
| @ -455,7 +445,7 @@ func main() { | ||||
| 				sender = "system" | ||||
| 			} | ||||
| 			// Tangential thought: | ||||
| 			// I wonder if we could use IP detection to get the client's tz | ||||
| 			// I wonder if we could use IP detection to get a Telnet client's tz | ||||
| 			// ... could probably make time for this in the authentication loop | ||||
| 			zone, _ := msg.ReceivedAt.Zone() | ||||
| 			fmt.Fprintf(os.Stdout, tf+" [%s] (%s): %s\r\n", | ||||
| @ -484,23 +474,6 @@ func main() { | ||||
| 					// It can reconnect. | ||||
| 					cleanTelnet <- u | ||||
| 				} | ||||
| 
 | ||||
| 				/* | ||||
| 					// To ask: Why do I have to pass in conn to prevent a data race? Is it garbage collection? | ||||
| 					// Don't block the rest of the loop | ||||
| 					// TODONE maybe use a chan to send to the socket's event loop | ||||
| 					// (left this in to remind myself to ask questions) | ||||
| 					go func(conn bufferedConn) { | ||||
| 						// Protect against malicious clients to prevent DoS | ||||
| 						// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/ | ||||
| 						timeoutDuration := 2 * time.Second | ||||
| 						conn.SetWriteDeadline(time.Now().Add(timeoutDuration)) | ||||
| 						_, err := fmt.Fprintf(conn, msg) | ||||
| 						if nil != err { | ||||
| 							cleanTelnet <- u | ||||
| 						} | ||||
| 					}(conn) | ||||
| 				*/ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
		Laddar…
	
	
			
			x
			
			
		
	
		Referens i nytt ärende
	
	Block a user