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:
		
				
					committed by
					
						
						Bo-Yi Wu
					
				
			
			
				
	
			
			
			
						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"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user