diff --git a/README.md b/README.md index 2477d0b0..1c2ec169 100644 --- a/README.md +++ b/README.md @@ -1308,6 +1308,37 @@ func main() { } ``` +### Streaming data + +```go +func main() { + router := gin.Default() + router.GET("/someDataFromStream", func(c *gin.Context) { + pr, pw := io.Pipe() + defer pr.Close() + + go func() { + defer pw.Close() + var counter int + for { + select { + case <-c.Done(): + return + case <-time.Tick(500 * time.Millisecond): + if _, err := fmt.Fprintf(pw, "message: %d\n", counter); err != nil { + return + } + } + } + }() + + contentType := "text/plain" + c.DataFromStream(http.StatusOK, contentType, pr, nil) + }) + router.Run(":8080") +} +``` + ### HTML rendering Using LoadHTMLGlob() or LoadHTMLFiles() diff --git a/context.go b/context.go index 46bf1133..f5e2ee04 100644 --- a/context.go +++ b/context.go @@ -1018,6 +1018,19 @@ func (c *Context) DataFromReader(code int, contentLength int64, contentType stri }) } +// DataFromStream streams the specified reader into the body stream without delay and updates the HTTP code. +func (c *Context) DataFromStream(code int, contentType string, reader io.Reader, extraHeaders map[string]string) { + sr := render.StreamReader{ + Reader: render.Reader{ + Headers: extraHeaders, + ContentType: contentType, + ContentLength: -1, + Reader: reader, + }, + } + c.Render(code, sr) +} + // File writes the specified file into the body stream in an efficient way. func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) diff --git a/render/render.go b/render/render.go index 7955000c..29f8d1ff 100644 --- a/render/render.go +++ b/render/render.go @@ -26,6 +26,7 @@ var ( _ Render = HTML{} _ HTMLRender = HTMLDebug{} _ HTMLRender = HTMLProduction{} + _ Render = StreamReader{} _ Render = YAML{} _ Render = Reader{} _ Render = AsciiJSON{} diff --git a/render/stream_reader.go b/render/stream_reader.go new file mode 100644 index 00000000..0dbb6653 --- /dev/null +++ b/render/stream_reader.go @@ -0,0 +1,31 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package render + +import ( + "net/http" +) + +// StreamReader contains the IO reader and its length, and custom ContentType and other headers. +type StreamReader struct { + Reader +} + +type writerFlusher struct { + http.ResponseWriter +} + +func (w *writerFlusher) Write(buf []byte) (n int, err error) { + n, err = w.ResponseWriter.Write(buf) + if err == nil { + w.ResponseWriter.(http.Flusher).Flush() + } + return +} + +// Render (StreamReader) writes data with custom ContentType and headers. +func (r StreamReader) Render(w http.ResponseWriter) (err error) { + return r.Reader.Render(&writerFlusher{w}) +}