Add support for Protobuf format response and unit test (#1479)
`Gin` now have the `protobufBinding` function to check the request format, but didn't have a protobuf response function like `c.YAML()`. In our company [ByteDance](http://bytedance.com/), the largest internet company using golang in China, we use `gin` to transfer __Protobuf__ instead of __Json__, we have to write some internal library to make some wrappers to achieve that, and the code is not elegant. So we really want such a feature.
This commit is contained in:
		
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								README.md
									
									
									
									
									
								
							@ -40,7 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
 | 
				
			|||||||
    - [Bind Query String or Post Data](#bind-query-string-or-post-data)
 | 
					    - [Bind Query String or Post Data](#bind-query-string-or-post-data)
 | 
				
			||||||
    - [Bind HTML checkboxes](#bind-html-checkboxes)
 | 
					    - [Bind HTML checkboxes](#bind-html-checkboxes)
 | 
				
			||||||
    - [Multipart/Urlencoded binding](#multiparturlencoded-binding)
 | 
					    - [Multipart/Urlencoded binding](#multiparturlencoded-binding)
 | 
				
			||||||
    - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering)
 | 
					    - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-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)
 | 
					    - [Serving data from reader](#serving-data-from-reader)
 | 
				
			||||||
@ -871,7 +871,7 @@ Test it with:
 | 
				
			|||||||
$ curl -v --form user=user --form password=password http://localhost:8080/login
 | 
					$ curl -v --form user=user --form password=password http://localhost:8080/login
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### XML, JSON and YAML rendering
 | 
					### XML, JSON, YAML and ProtoBuf rendering
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```go
 | 
					```go
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
@ -905,6 +905,19 @@ func main() {
 | 
				
			|||||||
		c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
 | 
							c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.GET("/someProtoBuf", func(c *gin.Context) {
 | 
				
			||||||
 | 
							reps := []int64{int64(1), int64(2)}
 | 
				
			||||||
 | 
							label := "test"
 | 
				
			||||||
 | 
							// The specific definition of protobuf is written in the testdata/protoexample file.
 | 
				
			||||||
 | 
							data := &protoexample.Test{
 | 
				
			||||||
 | 
								Label: &label,
 | 
				
			||||||
 | 
								Reps:  reps,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Note that data becomes binary data in the response
 | 
				
			||||||
 | 
							// Will output protoexample.Test protobuf serialized data
 | 
				
			||||||
 | 
							c.ProtoBuf(http.StatusOK, data)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Listen and serve on 0.0.0.0:8080
 | 
						// Listen and serve on 0.0.0.0:8080
 | 
				
			||||||
	r.Run(":8080")
 | 
						r.Run(":8080")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
	"github.com/gin-contrib/sse"
 | 
						"github.com/gin-contrib/sse"
 | 
				
			||||||
	"github.com/gin-gonic/gin/binding"
 | 
						"github.com/gin-gonic/gin/binding"
 | 
				
			||||||
	"github.com/gin-gonic/gin/render"
 | 
						"github.com/gin-gonic/gin/render"
 | 
				
			||||||
 | 
						"github.com/golang/protobuf/proto"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Content-Type MIME of the most common data formats.
 | 
					// Content-Type MIME of the most common data formats.
 | 
				
			||||||
@ -845,6 +846,11 @@ func (c *Context) Stream(step func(w io.Writer) bool) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ProtoBuf serializes the given struct as ProtoBuf into the response body.
 | 
				
			||||||
 | 
					func (c *Context) ProtoBuf(code int, obj proto.Message) {
 | 
				
			||||||
 | 
						c.Render(code, render.ProtoBuf{Data: obj})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/************************************/
 | 
					/************************************/
 | 
				
			||||||
/******** CONTENT NEGOTIATION *******/
 | 
					/******** CONTENT NEGOTIATION *******/
 | 
				
			||||||
/************************************/
 | 
					/************************************/
 | 
				
			||||||
 | 
				
			|||||||
@ -20,8 +20,11 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/gin-contrib/sse"
 | 
						"github.com/gin-contrib/sse"
 | 
				
			||||||
	"github.com/gin-gonic/gin/binding"
 | 
						"github.com/gin-gonic/gin/binding"
 | 
				
			||||||
 | 
						"github.com/golang/protobuf/proto"
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
	"golang.org/x/net/context"
 | 
						"golang.org/x/net/context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testdata "github.com/gin-gonic/gin/testdata/protoexample"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ context.Context = &Context{}
 | 
					var _ context.Context = &Context{}
 | 
				
			||||||
@ -954,6 +957,30 @@ func TestContextRenderYAML(t *testing.T) {
 | 
				
			|||||||
	assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
 | 
						assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf
 | 
				
			||||||
 | 
					// and Content-Type is set to application/x-protobuf
 | 
				
			||||||
 | 
					// and we just use the example protobuf to check if the response is correct
 | 
				
			||||||
 | 
					func TestContextRenderProtoBuf(t *testing.T) {
 | 
				
			||||||
 | 
						w := httptest.NewRecorder()
 | 
				
			||||||
 | 
						c, _ := CreateTestContext(w)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						reps := []int64{int64(1), int64(2)}
 | 
				
			||||||
 | 
						label := "test"
 | 
				
			||||||
 | 
						data := &testdata.Test{
 | 
				
			||||||
 | 
							Label: &label,
 | 
				
			||||||
 | 
							Reps:  reps,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.ProtoBuf(http.StatusCreated, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protoData, err := proto.Marshal(data)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, http.StatusCreated, w.Code)
 | 
				
			||||||
 | 
						assert.Equal(t, string(protoData[:]), w.Body.String())
 | 
				
			||||||
 | 
						assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestContextHeaders(t *testing.T) {
 | 
					func TestContextHeaders(t *testing.T) {
 | 
				
			||||||
	c, _ := CreateTestContext(httptest.NewRecorder())
 | 
						c, _ := CreateTestContext(httptest.NewRecorder())
 | 
				
			||||||
	c.Header("Content-Type", "text/plain")
 | 
						c.Header("Content-Type", "text/plain")
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										33
									
								
								render/protobuf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								render/protobuf.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					// 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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/golang/protobuf/proto"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ProtoBuf struct {
 | 
				
			||||||
 | 
						Data proto.Message
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var protobufContentType = []string{"application/x-protobuf"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r ProtoBuf) Render(w http.ResponseWriter) error {
 | 
				
			||||||
 | 
						r.WriteContentType(w)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bytes, err := proto.Marshal(r.Data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.Write(bytes)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r ProtoBuf) WriteContentType(w http.ResponseWriter) {
 | 
				
			||||||
 | 
						writeContentType(w, protobufContentType)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -27,6 +27,7 @@ var (
 | 
				
			|||||||
	_ Render     = MsgPack{}
 | 
						_ Render     = MsgPack{}
 | 
				
			||||||
	_ Render     = Reader{}
 | 
						_ Render     = Reader{}
 | 
				
			||||||
	_ Render     = AsciiJSON{}
 | 
						_ Render     = AsciiJSON{}
 | 
				
			||||||
 | 
						_ Render     = ProtoBuf{}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func writeContentType(w http.ResponseWriter, value []string) {
 | 
					func writeContentType(w http.ResponseWriter, value []string) {
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,8 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testdata "github.com/gin-gonic/gin/testdata/protoexample"
 | 
				
			||||||
 | 
						"github.com/golang/protobuf/proto"
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
	"github.com/ugorji/go/codec"
 | 
						"github.com/ugorji/go/codec"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -265,6 +267,35 @@ func TestRenderYAMLFail(t *testing.T) {
 | 
				
			|||||||
	assert.Error(t, err)
 | 
						assert.Error(t, err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// test Protobuf rendering
 | 
				
			||||||
 | 
					func TestRenderProtoBuf(t *testing.T) {
 | 
				
			||||||
 | 
						w := httptest.NewRecorder()
 | 
				
			||||||
 | 
						reps := []int64{int64(1), int64(2)}
 | 
				
			||||||
 | 
						label := "test"
 | 
				
			||||||
 | 
						data := &testdata.Test{
 | 
				
			||||||
 | 
							Label: &label,
 | 
				
			||||||
 | 
							Reps:  reps,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						(ProtoBuf{data}).WriteContentType(w)
 | 
				
			||||||
 | 
						protoData, err := proto.Marshal(data)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = (ProtoBuf{data}).Render(w)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Equal(t, string(protoData[:]), w.Body.String())
 | 
				
			||||||
 | 
						assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRenderProtoBufFail(t *testing.T) {
 | 
				
			||||||
 | 
						w := httptest.NewRecorder()
 | 
				
			||||||
 | 
						data := &testdata.Test{}
 | 
				
			||||||
 | 
						err := (ProtoBuf{data}).Render(w)
 | 
				
			||||||
 | 
						assert.Error(t, err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestRenderXML(t *testing.T) {
 | 
					func TestRenderXML(t *testing.T) {
 | 
				
			||||||
	w := httptest.NewRecorder()
 | 
						w := httptest.NewRecorder()
 | 
				
			||||||
	data := xmlmap{
 | 
						data := xmlmap{
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user