I'm working on a streaming XML encoder that will simultaneously write the XML to a local file, and an S3 bucket. However, just by testing it writing to two local files, I can see that one of the files is missing the closing tags every time.
Here is roughly how I do it (omitting error handling):
func copyToFile (fileName string) {
f, _ := os.Create(fileName)
defer f.Close()
io.Copy(f, pr)
}
func main () {
pr, pw := io.Pipe()
defer pw.Close()
encoder := xml.NewEncoder(pw)
go copyToFile("file1.xml")
go copyToFile("file2.xml")
encoder.EncodeToken(xml.StartElement{...})
encoder.Encode(SomeStruct{})
encoder.EncodeToken(xml.EndElement{...})
encoder.Flush()
}
The result in file1.xml
is as expected, with all tags properly closed, but in file2.xml
the closing tag (the call of encoder.EncodeToken(xml.EndElement{...})
) is missing.
What am I doing wrong? Can I expect the same result when I copy the reader to S3?
You cannot have multiple readers on the returned io.PipeReader
, data will not be duplicated for all readers. The io.PipeReader
can only "serve" a single reader, and you launch 2 goroutines to read from it.
To achieve what you want, use io.MultiWriter()
. It returns you a single io.Writer
to where you can write, and it will replicate the writes to all the writers you pass to it.
For example:
f1 := &bytes.Buffer{}
f2 := &bytes.Buffer{}
w := io.MultiWriter(f1, f2)
encoder := xml.NewEncoder(w)
encoder.EncodeToken(xml.StartElement{Name: xml.Name{Local: "test"}})
encoder.Encode(image.Point{1, 2})
encoder.EncodeToken(xml.EndElement{Name: xml.Name{Local: "test"}})
encoder.Flush()
fmt.Println(f1)
fmt.Println(f2)
This will output (try it on the Go Playground):
<test><Point><X>1</X><Y>2</Y></Point></test>
<test><Point><X>1</X><Y>2</Y></Point></test>
The above example writes to 2 in-memory buffers. To write to 2 files, you may pass 2 os.File
s to io.MultiWriter()
(or anything else that implements io.Writer
):
f1, err := os.Create("file1.xml")
if err != nil {
panic(err)
}
defer f1.Close()
f2, err := os.Create("file2.xml")
if err != nil {
panic(err)
}
defer f2.Close()
w := io.MultiWriter(f1, f2)
// ...