fix func by passing conn, go fmt (oops), start http detection
This commit is contained in:
		
							parent
							
								
									64425b41d2
								
							
						
					
					
						commit
						9c507ae68e
					
				
							
								
								
									
										126
									
								
								chatserver.go
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								chatserver.go
									
									
									
									
									
								
							| @ -5,6 +5,7 @@ package main | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bufio" | 	"bufio" | ||||||
|  | 	"bytes" | ||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"flag" | 	"flag" | ||||||
| @ -35,7 +36,6 @@ type ConfMailer struct { | |||||||
| 	From   string `yaml:"from,omitempty"` | 	From   string `yaml:"from,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| type tcpUser struct { | type tcpUser struct { | ||||||
| 	bufConn   bufferedConn | 	bufConn   bufferedConn | ||||||
| 	userCount chan int | 	userCount chan int | ||||||
| @ -58,7 +58,7 @@ func (b bufferedConn) Peek(n int) ([]byte, error) { | |||||||
| 	return b.r.Peek(n) | 	return b.r.Peek(n) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (b bufferedConn) Buffered() (int) { | func (b bufferedConn) Buffered() int { | ||||||
| 	return b.r.Buffered() | 	return b.r.Buffered() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -80,19 +80,22 @@ type myMsg struct { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var firstMsgs chan myMsg | var firstMsgs chan myMsg | ||||||
|  | 
 | ||||||
| //var myRooms map[string](chan myMsg) | //var myRooms map[string](chan myMsg) | ||||||
| var myMsgs chan myMsg | var myMsgs chan myMsg | ||||||
|  | 
 | ||||||
| //var myUnsortedConns map[net.Conn]bool | //var myUnsortedConns map[net.Conn]bool | ||||||
| var newConns chan net.Conn | var newConns chan net.Conn | ||||||
| var newTcpChat chan bufferedConn | var newTcpChat chan bufferedConn | ||||||
| var authTcpChat chan tcpUser | var authTcpChat chan tcpUser | ||||||
| var delTcpChat chan bufferedConn | var delTcpChat chan bufferedConn | ||||||
| var newHttpChat chan bufferedConn | var newHttpChat chan bufferedConn | ||||||
|  | var newHttpClient chan bufferedConn | ||||||
| var delHttpChat chan bufferedConn | var delHttpChat chan bufferedConn | ||||||
| 
 | 
 | ||||||
| func usage() { | func usage() { | ||||||
| 	fmt.Fprintf(os.Stderr, "\nusage: go run chatserver.go\n") | 	fmt.Fprintf(os.Stderr, "\nusage: go run chatserver.go\n") | ||||||
|   flag.PrintDefaults(); | 	flag.PrintDefaults() | ||||||
| 	fmt.Println() | 	fmt.Println() | ||||||
| 
 | 
 | ||||||
| 	os.Exit(1) | 	os.Exit(1) | ||||||
| @ -124,7 +127,7 @@ func handleRaw(bufConn bufferedConn) { | |||||||
| 	// Handle all subsequent packets | 	// Handle all subsequent packets | ||||||
| 	buffer := make([]byte, 1024) | 	buffer := make([]byte, 1024) | ||||||
| 	for { | 	for { | ||||||
|     //fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n"); | 		//fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n") | ||||||
| 		count, err := bufConn.Read(buffer) | 		count, err := bufConn.Read(buffer) | ||||||
| 		if nil != err { | 		if nil != err { | ||||||
| 			if io.EOF != err { | 			if io.EOF != err { | ||||||
| @ -172,13 +175,13 @@ func handleRaw(bufConn bufferedConn) { | |||||||
| 				var wg sync.WaitGroup | 				var wg sync.WaitGroup | ||||||
| 				wg.Add(1) | 				wg.Add(1) | ||||||
| 				go func() { | 				go func() { | ||||||
|           time.Sleep(50 * 1000000) | 					time.Sleep(50 * time.Millisecond) | ||||||
| 					const msg = "Mailing auth code..." | 					const msg = "Mailing auth code..." | ||||||
| 					for _, r := range msg { | 					for _, r := range msg { | ||||||
|             time.Sleep(20 * 1000000) | 						time.Sleep(20 * time.Millisecond) | ||||||
| 						fmt.Fprintf(bufConn, string(r)) | 						fmt.Fprintf(bufConn, string(r)) | ||||||
| 					} | 					} | ||||||
|           time.Sleep(50 * 1000000) | 					time.Sleep(50 * time.Millisecond) | ||||||
| 					wg.Done() | 					wg.Done() | ||||||
| 				}() | 				}() | ||||||
| 				if "" != config.Mailer.ApiKey { | 				if "" != config.Mailer.ApiKey { | ||||||
| @ -200,9 +203,9 @@ func handleRaw(bufConn bufferedConn) { | |||||||
| 				} | 				} | ||||||
| 				// so I don't have to actually go check my email | 				// 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== AUTHORIZATION ==\n[cheat code for %s]: %s\n", email, code) | ||||||
|         time.Sleep(150 * 1000000) | 				time.Sleep(150 * time.Millisecond) | ||||||
| 				fmt.Fprintf(bufConn, " done\n") | 				fmt.Fprintf(bufConn, " done\n") | ||||||
|         time.Sleep(150 * 1000000) | 				time.Sleep(150 * time.Millisecond) | ||||||
| 				fmt.Fprintf(bufConn, "Auth Code: ") | 				fmt.Fprintf(bufConn, "Auth Code: ") | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| @ -211,7 +214,7 @@ func handleRaw(bufConn bufferedConn) { | |||||||
| 				fmt.Fprintf(bufConn, "Incorrect Code\nAuth Code: ") | 				fmt.Fprintf(bufConn, "Incorrect Code\nAuth Code: ") | ||||||
| 			} else { | 			} else { | ||||||
| 				authn = true | 				authn = true | ||||||
|         time.Sleep(150 * 1000000) | 				time.Sleep(150 * time.Millisecond) | ||||||
| 				fmt.Fprintf(bufConn, "\n") | 				fmt.Fprintf(bufConn, "\n") | ||||||
| 				u := tcpUser{ | 				u := tcpUser{ | ||||||
| 					bufConn:   bufConn, | 					bufConn:   bufConn, | ||||||
| @ -221,18 +224,18 @@ func handleRaw(bufConn bufferedConn) { | |||||||
| 				authTcpChat <- u | 				authTcpChat <- u | ||||||
| 				// prevent data race on len(myRawConns) | 				// prevent data race on len(myRawConns) | ||||||
| 				// XXX (there can't be a race between these two lines, right?) | 				// XXX (there can't be a race between these two lines, right?) | ||||||
|         count := <- u.userCount | 				count := <-u.userCount | ||||||
| 				u.userCount = nil | 				u.userCount = nil | ||||||
|         time.Sleep(50 * 1000000) | 				time.Sleep(50 * time.Millisecond) | ||||||
| 				fmt.Fprintf(bufConn, "\n") | 				fmt.Fprintf(bufConn, "\n") | ||||||
|         time.Sleep(50 * 1000000) | 				time.Sleep(50 * time.Millisecond) | ||||||
| 				fmt.Fprintf(bufConn, "Welcome to #general (%d users)!", count) | 				fmt.Fprintf(bufConn, "Welcome to #general (%d users)!", count) | ||||||
|         time.Sleep(50 * 1000000) | 				time.Sleep(50 * time.Millisecond) | ||||||
| 				fmt.Fprintf(bufConn, "\n") | 				fmt.Fprintf(bufConn, "\n") | ||||||
|         time.Sleep(50 * 1000000) | 				time.Sleep(50 * time.Millisecond) | ||||||
| 				// TODO /help /join <room> /users /channels /block <user> /upgrade <http/ws> | 				// TODO /help /join <room> /users /channels /block <user> /upgrade <http/ws> | ||||||
| 				//fmt.Fprintf(bufConn, "(TODO `/help' for list of commands)") | 				//fmt.Fprintf(bufConn, "(TODO `/help' for list of commands)") | ||||||
|         time.Sleep(100 * 1000000) | 				time.Sleep(100 * time.Millisecond) | ||||||
| 				fmt.Fprintf(bufConn, "\n") | 				fmt.Fprintf(bufConn, "\n") | ||||||
| 
 | 
 | ||||||
| 				// this would be cool, but won't work since other messages will come | 				// this would be cool, but won't work since other messages will come | ||||||
| @ -242,7 +245,7 @@ func handleRaw(bufConn bufferedConn) { | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|     //fmt.Fprintf(os.Stdout, "Queing message...\n"); | 		//fmt.Fprintf(os.Stdout, "Queing message...\n") | ||||||
| 		//myRooms["general"] <- myMsg{ | 		//myRooms["general"] <- myMsg{ | ||||||
| 		myMsgs <- myMsg{ | 		myMsgs <- myMsg{ | ||||||
| 			receivedAt: time.Now(), | 			receivedAt: time.Now(), | ||||||
| @ -258,6 +261,7 @@ func handleRaw(bufConn bufferedConn) { | |||||||
| func handleSorted(conn bufferedConn) { | func handleSorted(conn bufferedConn) { | ||||||
| 	// Wish List for protocol detection | 	// Wish List for protocol detection | ||||||
| 	// * PROXY protocol (and loop) | 	// * PROXY protocol (and loop) | ||||||
|  | 	// * HTTP CONNECT (proxy) (and loop) | ||||||
| 	// * tls (and loop) https://github.com/polvi/sni | 	// * tls (and loop) https://github.com/polvi/sni | ||||||
| 	// * http/ws | 	// * http/ws | ||||||
| 	// * irc | 	// * irc | ||||||
| @ -269,11 +273,50 @@ func handleSorted(conn bufferedConn) { | |||||||
| 	// Note: Realistically no tls/http/irc client is going to send so few bytes | 	// Note: Realistically no tls/http/irc client is going to send so few bytes | ||||||
| 	//       (and no router is going to chunk so small) | 	//       (and no router is going to chunk so small) | ||||||
| 	//       that it cannot reasonably detect the protocol in the first packet | 	//       that it cannot reasonably detect the protocol in the first packet | ||||||
|  | 	//       However, typical MTU is 1,500 and HTTP can have a 2k URL | ||||||
|  | 	//       so expecting to find the "HTTP/1.1" in the Peek is not always reasonable | ||||||
| 	n := conn.Buffered() | 	n := conn.Buffered() | ||||||
| 	firstMsg, err := conn.Peek(n) | 	firstMsg, err := conn.Peek(n) | ||||||
| 	if nil != err { | 	if nil != err { | ||||||
|     panic(err) | 		conn.Close() | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
|  | 	var protocol string | ||||||
|  | 	// between A and z | ||||||
|  | 	if firstMsg[0] >= 65 && firstMsg[0] <= 122 { | ||||||
|  | 		i := bytes.Index(firstMsg, []byte(" /")) | ||||||
|  | 		if -1 != i { | ||||||
|  | 			protocol = "HTTP" | ||||||
|  | 			// very likely HTTP | ||||||
|  | 			j := bytes.IndexAny(firstMsg, "\r\n") | ||||||
|  | 			if -1 != j { | ||||||
|  | 				k := bytes.Index(bytes.ToLower(firstMsg[:j]), []byte("HTTP/1")) | ||||||
|  | 				if -1 != k { | ||||||
|  | 					// positively HTTP | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else if 0x16 /*22*/ == firstMsg[0] { | ||||||
|  | 		// Because I don't always remember off the top of my head what the first byte is | ||||||
|  | 		// http://blog.fourthbit.com/2014/12/23/traffic-analysis-of-an-ssl-slash-tls-session | ||||||
|  | 		// https://tlseminar.github.io/first-few-milliseconds/ | ||||||
|  | 		// TODO I want to learn about ALPN | ||||||
|  | 		protocol = "TLS" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if "" == protocol { | ||||||
|  | 		fmt.Fprintf(conn, "\n\nWelcome to Sample Chat! You're not an HTTP client, assuming Telnet.\nYou must authenticate via email to participate\n\nEmail: ") | ||||||
|  | 		newTcpChat <- conn | ||||||
|  | 		return | ||||||
|  | 	} else if "HTTP" != protocol { | ||||||
|  | 		defer conn.Close() | ||||||
|  | 		fmt.Fprintf(conn, "\n\nNot yet supported. Try HTTP or Telnet\n\n") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	newHttpClient <- conn | ||||||
|  | 
 | ||||||
|  | 	/* | ||||||
| 	   firstMsgs <- myMsg{ | 	   firstMsgs <- myMsg{ | ||||||
| 	     receivedAt: time.Now(), | 	     receivedAt: time.Now(), | ||||||
| 	     sender: conn, | 	     sender: conn, | ||||||
| @ -289,7 +332,7 @@ func handleSorted(conn bufferedConn) { | |||||||
| 	   // Handle all subsequent packets | 	   // Handle all subsequent packets | ||||||
| 	   buf := make([]byte, 1024) | 	   buf := make([]byte, 1024) | ||||||
| 	   for { | 	   for { | ||||||
|     fmt.Fprintf(os.Stdout, "[sortable] Waiting for message...\n"); | 	     fmt.Fprintf(os.Stdout, "[sortable] Waiting for message...\n") | ||||||
| 	     count, err := conn.Read(buf) | 	     count, err := conn.Read(buf) | ||||||
| 	     if nil != err { | 	     if nil != err { | ||||||
| 	       if io.EOF != err { | 	       if io.EOF != err { | ||||||
| @ -312,6 +355,7 @@ func handleSorted(conn bufferedConn) { | |||||||
| 	       channel: "general", | 	       channel: "general", | ||||||
| 	     } | 	     } | ||||||
| 	   } | 	   } | ||||||
|  | 	*/ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func handleConnection(netConn net.Conn) { | func handleConnection(netConn net.Conn) { | ||||||
| @ -337,15 +381,14 @@ func handleConnection(netConn net.Conn) { | |||||||
| 		} | 		} | ||||||
| 		//fmt.Fprintf(os.Stdout, "[First Byte] %s\n", fmsg) | 		//fmt.Fprintf(os.Stdout, "[First Byte] %s\n", fmsg) | ||||||
| 
 | 
 | ||||||
|     m.Lock(); | 		m.Lock() | ||||||
| 		if virgin { | 		if virgin { | ||||||
| 			virgin = false | 			virgin = false | ||||||
| 			newHttpChat <- bufConn | 			newHttpChat <- bufConn | ||||||
| 		} else { | 		} else { | ||||||
|       // TODO probably needs to go into a channel |  | ||||||
| 			newTcpChat <- bufConn | 			newTcpChat <- bufConn | ||||||
| 		} | 		} | ||||||
|     m.Unlock(); | 		m.Unlock() | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	time.Sleep(250 * 1000000) | 	time.Sleep(250 * 1000000) | ||||||
| @ -378,7 +421,7 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) { | |||||||
| 	form := url.Values{} | 	form := url.Values{} | ||||||
| 	form.Add("from", cnf.From) | 	form.Add("from", cnf.From) | ||||||
| 	form.Add("to", to) | 	form.Add("to", to) | ||||||
| 	form.Add("subject", "Sample Chat Auth Code: " + code) | 	form.Add("subject", "Sample Chat Auth Code: "+code) | ||||||
| 	form.Add("text", text) | 	form.Add("text", text) | ||||||
| 	form.Add("html", html) | 	form.Add("html", html) | ||||||
| 
 | 
 | ||||||
| @ -414,6 +457,7 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var config Conf | var config Conf | ||||||
|  | 
 | ||||||
| func main() { | func main() { | ||||||
| 	flag.Usage = usage | 	flag.Usage = usage | ||||||
| 	port := flag.Uint("telnet-port", 0, "tcp telnet chat port") | 	port := flag.Uint("telnet-port", 0, "tcp telnet chat port") | ||||||
| @ -438,6 +482,7 @@ func main() { | |||||||
| 	authTcpChat = make(chan tcpUser, 128) | 	authTcpChat = make(chan tcpUser, 128) | ||||||
| 	newTcpChat = make(chan bufferedConn, 128) | 	newTcpChat = make(chan bufferedConn, 128) | ||||||
| 	newHttpChat = make(chan bufferedConn, 128) | 	newHttpChat = make(chan bufferedConn, 128) | ||||||
|  | 	newHttpClient = make(chan bufferedConn, 128) | ||||||
| 	//myUnsortedConns = make(map[net.Conn]bool) | 	//myUnsortedConns = make(map[net.Conn]bool) | ||||||
| 
 | 
 | ||||||
| 	// TODO dynamically select on channels? | 	// TODO dynamically select on channels? | ||||||
| @ -458,7 +503,7 @@ func main() { | |||||||
| 		fmt.Fprintf(os.Stderr, "Couldn't bind to TCP socket %q: %s\n", addr, err) | 		fmt.Fprintf(os.Stderr, "Couldn't bind to TCP socket %q: %s\n", addr, err) | ||||||
| 		os.Exit(2) | 		os.Exit(2) | ||||||
| 	} | 	} | ||||||
|   fmt.Println("Listening on", addr); | 	fmt.Println("Listening on", addr) | ||||||
| 
 | 
 | ||||||
| 	go func() { | 	go func() { | ||||||
| 		for { | 		for { | ||||||
| @ -471,14 +516,20 @@ func main() { | |||||||
| 			newConns <- conn | 			newConns <- conn | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
|  | 	/* | ||||||
|  | 	   server := &http.Server{ | ||||||
|  | 	     Addr:    addr, | ||||||
|  | 	     Handler: &myHandler{}, | ||||||
|  | 	   } | ||||||
|  | 	*/ | ||||||
| 
 | 
 | ||||||
| 	// Main event loop handling most access to shared data | 	// Main event loop handling most access to shared data | ||||||
| 	for { | 	for { | ||||||
| 		select { | 		select { | ||||||
|     case conn := <- newConns: | 		case conn := <-newConns: | ||||||
| 			// This is short lived | 			// This is short lived | ||||||
| 			go handleConnection(conn) | 			go handleConnection(conn) | ||||||
|     case u := <- authTcpChat: | 		case u := <-authTcpChat: | ||||||
| 			// allow to receive messages | 			// allow to receive messages | ||||||
| 			// (and be counted among the users) | 			// (and be counted among the users) | ||||||
| 			myRawConns[u.bufConn] = true | 			myRawConns[u.bufConn] = true | ||||||
| @ -492,17 +543,19 @@ func main() { | |||||||
| 				channel:    "general", | 				channel:    "general", | ||||||
| 				email:      "system", | 				email:      "system", | ||||||
| 			} | 			} | ||||||
|     case bufConn := <- newTcpChat: | 		case bufConn := <-newTcpChat: | ||||||
| 			go handleRaw(bufConn) | 			go handleRaw(bufConn) | ||||||
|     case bufConn := <- delTcpChat: | 		case bufConn := <-delTcpChat: | ||||||
| 			// we can safely ignore this error | 			// we can safely ignore this error | ||||||
| 			bufConn.Close() | 			bufConn.Close() | ||||||
| 			delete(myRawConns, bufConn) | 			delete(myRawConns, bufConn) | ||||||
|     case bufConn := <- newHttpChat: | 		case bufConn := <-newHttpChat: | ||||||
| 			go handleSorted(bufConn) | 			go handleSorted(bufConn) | ||||||
| 		//case msg := <- myRooms["general"]: | 		//case msg := <- myRooms["general"]: | ||||||
| 		//delete(myRooms["general"], bufConn) | 		//delete(myRooms["general"], bufConn) | ||||||
|     case msg := <- myMsgs: | 		//case bufConn := <-newHttpClient: | ||||||
|  | 		//server.Serve(bufConn) | ||||||
|  | 		case msg := <-myMsgs: | ||||||
| 			t := msg.receivedAt | 			t := msg.receivedAt | ||||||
| 			tf := "%d-%02d-%02d %02d:%02d:%02d (%s)" | 			tf := "%d-%02d-%02d %02d:%02d:%02d (%s)" | ||||||
| 			var sender string | 			var sender string | ||||||
| @ -516,7 +569,7 @@ func main() { | |||||||
| 			zone, _ := msg.receivedAt.Zone() | 			zone, _ := msg.receivedAt.Zone() | ||||||
| 
 | 
 | ||||||
| 			//ts, err := msg.receivedAt.MarshalJSON() | 			//ts, err := msg.receivedAt.MarshalJSON() | ||||||
|       fmt.Fprintf(os.Stdout, tf + " [%s] (%s):\n\t%s", | 			fmt.Fprintf(os.Stdout, tf+" [%s] (%s):\n\t%s", | ||||||
| 				t.Year(), t.Month(), t.Day(), | 				t.Year(), t.Month(), t.Day(), | ||||||
| 				t.Hour(), t.Minute(), t.Second(), zone, | 				t.Hour(), t.Minute(), t.Second(), zone, | ||||||
| 				sender, | 				sender, | ||||||
| @ -528,23 +581,24 @@ func main() { | |||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  | 				// 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 | 				// Don't block the rest of the loop | ||||||
| 				// TODO maybe use a chan to send to the socket's event loop | 				// TODO maybe use a chan to send to the socket's event loop | ||||||
|         go func() { | 				go func(conn bufferedConn) { | ||||||
| 					// Protect against malicious clients to prevent DoS | 					// Protect against malicious clients to prevent DoS | ||||||
| 					// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/ | 					// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/ | ||||||
| 					timeoutDuration := 5 * time.Second | 					timeoutDuration := 5 * time.Second | ||||||
| 					conn.SetWriteDeadline(time.Now().Add(timeoutDuration)) | 					conn.SetWriteDeadline(time.Now().Add(timeoutDuration)) | ||||||
|           _, err := fmt.Fprintf(conn, tf + " [%s]: %s", | 					_, err := fmt.Fprintf(conn, tf+" [%s]: %s", | ||||||
| 						t.Year(), t.Month(), t.Day(), | 						t.Year(), t.Month(), t.Day(), | ||||||
| 						t.Hour(), t.Minute(), t.Second(), zone, | 						t.Hour(), t.Minute(), t.Second(), zone, | ||||||
| 						msg.email, msg.bytes) | 						msg.email, msg.bytes) | ||||||
| 					if nil != err { | 					if nil != err { | ||||||
| 						delTcpChat <- conn | 						delTcpChat <- conn | ||||||
| 					} | 					} | ||||||
|         }() | 				}(conn) | ||||||
| 			} | 			} | ||||||
|     case msg := <- firstMsgs: | 		case msg := <-firstMsgs: | ||||||
| 			fmt.Fprintf(os.Stdout, "f [First Message]\n") | 			fmt.Fprintf(os.Stdout, "f [First Message]\n") | ||||||
| 			ts, err := msg.receivedAt.MarshalJSON() | 			ts, err := msg.receivedAt.MarshalJSON() | ||||||
| 			if nil != err { | 			if nil != err { | ||||||
| @ -552,7 +606,7 @@ func main() { | |||||||
| 			} | 			} | ||||||
| 			fmt.Fprintf(os.Stdout, "f [Timestamp] %s\n", ts) | 			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 [Remote] %s\n", msg.sender.RemoteAddr().String()) | ||||||
|       fmt.Fprintf(os.Stdout, "f [Message] %s\n", msg.bytes); | 			fmt.Fprintf(os.Stdout, "f [Message] %s\n", msg.bytes) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user