Serve easily dynamic files with DataFromReader
context method (#1304)
* Add DataFromReader context method * Replace fmt by strconv.FormatInt * Add pull request link to README
This commit is contained in:
parent
5636afe02d
commit
bf7803815b
27
README.md
27
README.md
@ -40,6 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
- [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering)
|
- [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering)
|
||||||
- [JSONP rendering](#jsonp)
|
- [JSONP rendering](#jsonp)
|
||||||
- [Serving static files](#serving-static-files)
|
- [Serving static files](#serving-static-files)
|
||||||
|
- [Serving data from reader](#serving-data-from-reader)
|
||||||
- [HTML rendering](#html-rendering)
|
- [HTML rendering](#html-rendering)
|
||||||
- [Multitemplate](#multitemplate)
|
- [Multitemplate](#multitemplate)
|
||||||
- [Redirects](#redirects)
|
- [Redirects](#redirects)
|
||||||
@ -901,6 +902,32 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Serving data from reader
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
router.GET("/someDataFromReader", func(c *gin.Context) {
|
||||||
|
response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
|
||||||
|
if err != nil || response.StatusCode != http.StatusOK {
|
||||||
|
c.Status(http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := response.Body
|
||||||
|
contentLength := response.ContentLength
|
||||||
|
contentType := response.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
extraHeaders := map[string]string{
|
||||||
|
"Content-Disposition": `attachment; filename="gopher.png"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### HTML rendering
|
### HTML rendering
|
||||||
|
|
||||||
Using LoadHTMLGlob() or LoadHTMLFiles()
|
Using LoadHTMLGlob() or LoadHTMLFiles()
|
||||||
|
10
context.go
10
context.go
@ -741,6 +741,16 @@ func (c *Context) Data(code int, contentType string, data []byte) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DataFromReader writes the specified reader into the body stream and updates the HTTP code.
|
||||||
|
func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) {
|
||||||
|
c.Render(code, render.Reader{
|
||||||
|
Headers: extraHeaders,
|
||||||
|
ContentType: contentType,
|
||||||
|
ContentLength: contentLength,
|
||||||
|
Reader: reader,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// File writes the specified file into the body stream in a efficient way.
|
// File writes the specified file into the body stream in a efficient way.
|
||||||
func (c *Context) File(filepath string) {
|
func (c *Context) File(filepath string) {
|
||||||
http.ServeFile(c.Writer, c.Request, filepath)
|
http.ServeFile(c.Writer, c.Request, filepath)
|
||||||
|
@ -1471,3 +1471,22 @@ func TestContextGetRawData(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "Fetch binary post data", string(data))
|
assert.Equal(t, "Fetch binary post data", string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextRenderDataFromReader(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
body := "#!PNG some raw data"
|
||||||
|
reader := strings.NewReader(body)
|
||||||
|
contentLength := int64(len(body))
|
||||||
|
contentType := "image/png"
|
||||||
|
extraHeaders := map[string]string{"Content-Disposition": `attachment; filename="gopher.png"`}
|
||||||
|
|
||||||
|
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, body, w.Body.String())
|
||||||
|
assert.Equal(t, contentType, w.HeaderMap.Get("Content-Type"))
|
||||||
|
assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length"))
|
||||||
|
assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition"))
|
||||||
|
}
|
||||||
|
36
render/reader.go
Normal file
36
render/reader.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
ContentType string
|
||||||
|
ContentLength int64
|
||||||
|
Reader io.Reader
|
||||||
|
Headers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render (Reader) writes data with custom ContentType and headers.
|
||||||
|
func (r Reader) Render(w http.ResponseWriter) (err error) {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
|
||||||
|
r.writeHeaders(w, r.Headers)
|
||||||
|
_, err = io.Copy(w, r.Reader)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reader) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, []string{r.ContentType})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) {
|
||||||
|
header := w.Header()
|
||||||
|
for k, v := range headers {
|
||||||
|
if val := header[k]; len(val) == 0 {
|
||||||
|
header[k] = []string{v}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ var (
|
|||||||
_ HTMLRender = HTMLProduction{}
|
_ HTMLRender = HTMLProduction{}
|
||||||
_ Render = YAML{}
|
_ Render = YAML{}
|
||||||
_ Render = MsgPack{}
|
_ Render = MsgPack{}
|
||||||
|
_ Render = Reader{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeContentType(w http.ResponseWriter, value []string) {
|
func writeContentType(w http.ResponseWriter, value []string) {
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -384,3 +386,24 @@ func TestRenderHTMLDebugPanics(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Panics(t, func() { htmlRender.Instance("", nil) })
|
assert.Panics(t, func() { htmlRender.Instance("", nil) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderReader(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
body := "#!PNG some raw data"
|
||||||
|
headers := make(map[string]string)
|
||||||
|
headers["Content-Disposition"] = `attachment; filename="filename.png"`
|
||||||
|
|
||||||
|
err := (Reader{
|
||||||
|
ContentLength: int64(len(body)),
|
||||||
|
ContentType: "image/png",
|
||||||
|
Reader: strings.NewReader(body),
|
||||||
|
Headers: headers,
|
||||||
|
}).Render(w)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, body, w.Body.String())
|
||||||
|
assert.Equal(t, "image/png", w.HeaderMap.Get("Content-Type"))
|
||||||
|
assert.Equal(t, strconv.Itoa(len(body)), w.HeaderMap.Get("Content-Length"))
|
||||||
|
assert.Equal(t, headers["Content-Disposition"], w.HeaderMap.Get("Content-Disposition"))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user