Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serve easily dynamic files with DataFromReader context method #1304

Merged
merged 4 commits into from
May 12, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- [JSONP rendering](#jsonp)
- [Serving static files](#serving-static-files)
- [Serving data from reader](#serving-data-from-reader)
- [HTML rendering](#html-rendering)
- [Multitemplate](#multitemplate)
- [Redirects](#redirects)
Expand Down Expand Up @@ -900,6 +901,32 @@ func main() {
}
```

### Serving data from reader
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add content link in README, thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @thinkerou I'm not sure to understand the target of the asked link. This page ?

Copy link
Member

@thinkerou thinkerou May 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add [Serving data from reader](#serving-data-from-reader) after the line:
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks


```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

Using LoadHTMLGlob() or LoadHTMLFiles()
Expand Down
10 changes: 10 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,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.
func (c *Context) File(filepath string) {
http.ServeFile(c.Writer, c.Request, filepath)
Expand Down
19 changes: 19 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1391,3 +1391,22 @@ func TestContextGetRawData(t *testing.T) {
assert.Nil(t, err)
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 changes: 36 additions & 0 deletions render/reader.go
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I prefer to using val == "" for judging empty string.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jclebreton Please update and I will merge this.

header[k] = []string{v}
}
}
}
1 change: 1 addition & 0 deletions render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var (
_ HTMLRender = HTMLProduction{}
_ Render = YAML{}
_ Render = MsgPack{}
_ Render = Reader{}
)

func writeContentType(w http.ResponseWriter, value []string) {
Expand Down
23 changes: 23 additions & 0 deletions render/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"html/template"
"net/http"
"net/http/httptest"
"strconv"
"strings"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the extra empty line between internal package.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done ;-)

"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -384,3 +386,24 @@ func TestRenderHTMLDebugPanics(t *testing.T) {
}
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"))
}