forked from TrueCloudLab/frostfs-s3-gw
[#60] Implement periodic white space XML writer
Periodic white space XML writer sends XML header and white spaces to the io.Writer. Signed-off-by: Alex Vanin <a.vanin@yadro.com>
This commit is contained in:
parent
5c62010331
commit
cf18158da4
2 changed files with 99 additions and 0 deletions
|
@ -2,6 +2,7 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -681,3 +682,53 @@ func encodeListPartsToResponse(info *layer.ListPartsInfo, params *layer.ListPart
|
||||||
Parts: info.Parts,
|
Parts: info.Parts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// periodicXMLWriter creates go routine to write xml header and whitespaces
|
||||||
|
// over time to avoid connection drop from the client. To work properly,
|
||||||
|
// pass `http.ResponseWriter` with implemented `http.Flusher` interface.
|
||||||
|
// Returns stop function which returns boolean if writer has been used
|
||||||
|
// during goroutine execution. To disable writer, pass 0 duration value.
|
||||||
|
func periodicXMLWriter(w io.Writer, dur time.Duration) (stop func() bool) {
|
||||||
|
if dur == 0 { // 0 duration disables periodic writer
|
||||||
|
return func() bool { return false }
|
||||||
|
}
|
||||||
|
|
||||||
|
whitespaceChar := []byte(" ")
|
||||||
|
closer := make(chan struct{})
|
||||||
|
done := make(chan struct{})
|
||||||
|
headerWritten := false
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
tick := time.NewTicker(dur)
|
||||||
|
defer tick.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick.C:
|
||||||
|
if !headerWritten {
|
||||||
|
_, err := w.Write([]byte(xml.Header))
|
||||||
|
headerWritten = err == nil
|
||||||
|
}
|
||||||
|
_, err := w.Write(whitespaceChar)
|
||||||
|
if err != nil {
|
||||||
|
return // is there anything we can do better than ignore error?
|
||||||
|
}
|
||||||
|
if buffered, ok := w.(http.Flusher); ok {
|
||||||
|
buffered.Flush()
|
||||||
|
}
|
||||||
|
case <-closer:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
stop = func() bool {
|
||||||
|
close(closer)
|
||||||
|
<-done // wait for goroutine to stop
|
||||||
|
return headerWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
return stop
|
||||||
|
}
|
||||||
|
|
48
api/handler/multipart_upload_test.go
Normal file
48
api/handler/multipart_upload_test.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPeriodicWriter(t *testing.T) {
|
||||||
|
const dur = 100 * time.Millisecond
|
||||||
|
const whitespaces = 8
|
||||||
|
expected := []byte(xml.Header)
|
||||||
|
for i := 0; i < whitespaces; i++ {
|
||||||
|
expected = append(expected, []byte(" ")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("writes data", func(t *testing.T) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
stop := periodicXMLWriter(buf, dur)
|
||||||
|
|
||||||
|
// N number of whitespaces + half durations to guarantee at least N writes in buffer
|
||||||
|
time.Sleep(whitespaces*dur + dur/2)
|
||||||
|
require.True(t, stop())
|
||||||
|
require.Equal(t, expected, buf.Bytes())
|
||||||
|
|
||||||
|
t.Run("no additional data after stop", func(t *testing.T) {
|
||||||
|
time.Sleep(2 * dur)
|
||||||
|
require.Equal(t, expected, buf.Bytes())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("does not write data", func(t *testing.T) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
stop := periodicXMLWriter(buf, dur)
|
||||||
|
time.Sleep(dur / 2)
|
||||||
|
require.False(t, stop())
|
||||||
|
require.Empty(t, buf.Bytes())
|
||||||
|
|
||||||
|
t.Run("disabled", func(t *testing.T) {
|
||||||
|
stop = periodicXMLWriter(buf, 0)
|
||||||
|
require.False(t, stop())
|
||||||
|
require.Empty(t, buf.Bytes())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue