This is a quick Friday blog post to talk about a recent experience I had working on a piece Juju code that needed to capture the data being sent over a net.Conn
.
Most Gophers know that the net
package provides a net.Pipe
function which returns a pair of net.Conn
s representing an in memory network connection. net.Pipe
is ideal for testing components that expect to talk over the network without all the mucking around of actually using the network.
The Go standard library also contains the super useful io.MultiWriter
function which takes any number of io.Writer
s and returns another io.Writer
that will send a copy of any data written to it to each of its underlying io.Writer
s. Now I had all the pieces I needed to create a net.Conn
that could record the data written through it.
func main() { client, server := net.Pipe() var buf bytes.Buffer client = io.MultiWriter(client, &buf) // ... }
Except this code does not compile.
# command-line-arguments /tmp/sandbox866813815/main.go:13: cannot use io.MultiWriter(client, &buf) (type io.Writer) as type net.Conn in assignment: io.Writer does not implement net.Conn (missing Close method)
The value returned by io.MultiWriter
is an implementation of io.Writer
, it doesn’t have the rest of the methods necessary to fulfil the net.Conn
interface; what I really need is the ability to replace the Write
method of an existing net.Conn
value. We can do this with embedding by creating a structure that embeds both a net.Conn
and an independant io.Writer
as anonymous fields.
type recordingConn struct { net.Conn io.Writer } func main() { client, server := net.Pipe() var buf bytes.Buffer client = &recordingConn { Conn: client, Writer: io.MultiWriter(client, &buf), } // ... }
The recodingConn
embeds a net.Conn
ensuring that recordingConn
implements net.Conn.
It also gives us a place to hang the io.MultiWriter
so we can syphon off the data written by the client. There is only one small problem remaining.
# command-line-arguments /tmp/sandbox439875759/main.go:24: recordingConn.Write is ambiguous
Because both fields in the structure are types that have a Write
method, the compiler cannot decide which one should be the called. To resolve this ambiguity we can add a Write
method on the recordingConn
type itself:
func (c *recordingConn) Write(buf []byte) (int, error) { return c.Writer.Write(buf) }
With the ambiguity resolved, the recordingConn
is now usable as a net.Conn
implementation. You can see the full code here.
This is just a small example of the power of struct composition using Go. Can you think of other ways to do this ?