I'm currently dabbling in Go and I'm having trouble figuring out the following:
I am trying to proxy a MySQL connection through my application intending to do some introspection on both the requests coming from the client and responses coming from the server. I did the exact same thing for a Redis connection, which works well.
Now the issue is, if I simply do
go io.Copy(serverConnection, clientConnection)
go io.Copy(clientConnection, serverConnection)
This works fine. I can use a mysql client to connect to my proxy and issue queries to and receive responses from the MySQL server.
io.MultiWriter
The implementation (simplified) looks like this (and works this way):
package main
import (
"fmt"
"io"
"log"
"net"
)
func main () {
port := 21001
listenAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
log.Fatalln(err)
}
targetAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:11001")
if err != nil {
log.Fatalln(err)
}
listener, err := net.ListenTCP("tcp", listenAddr)
if err != nil {
log.Fatalln(err)
}
for {
clientConnection, err := listener.AcceptTCP()
if err != nil {
log.Fatalln(err)
}
go func () {
serverConnection, err := net.DialTCP("tcp", nil, targetAddr)
if err != nil {
return
}
connectionDone := make(chan bool)
go monitorClientConnection(clientConnection, serverConnection, connectionDone)
go monitorServerConnection(serverConnection, clientConnection, connectionDone)
_ = <-connectionDone
_ = <-connectionDone
fmt.Println("MySQL connection closed")
}()
}
}
func monitorClientConnection(clientConnection *net.TCPConn, serverConnection *net.TCPConn, connectionDone chan bool) {
go func() {
_, err := io.Copy(serverConnection, clientConnection)
fmt.Println(err)
_ = serverConnection.Close()
connectionDone <- true
}()
}
func monitorServerConnection(serverConnection *net.TCPConn, clientConnection *net.TCPConn, connectionDone chan bool) {
go func() {
_, err := io.Copy(clientConnection, serverConnection)
fmt.Println(err)
_ = clientConnection.Close()
connectionDone <- true
}()
}
Where it starts to fail
If I change the implementation of the monitoring functions from (client and server are identical, just reversed):
func monitorServerConnection(serverConnection *net.TCPConn, clientConnection *net.TCPConn, connectionDone chan bool) {
go func() {
_, err := io.Copy(clientConnection, serverConnection)
fmt.Println(err)
_ = clientConnection.Close()
connectionDone <- true
}()
}
to
func monitorServerConnection(serverConnection *net.TCPConn, clientConnection *net.TCPConn, connectionDone chan bool) {
_, w := io.Pipe() // The reader here would be used to consume the stream, left out for simplicity
go func() {
mw := io.MultiWriter(clientConnection, w)
_, err := io.Copy(mw, serverConnection)
fmt.Println(err)
_ = clientConnection.Close()
connectionDone <- true
}()
}
It just hangs. This is the exact same approach that works fine for the Redis proxy (the Redis Serialization Protocol is plaintext, newline delimited, could be a factor maybe?).
What I'd like to knowio.MultiWriter