mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-23 10:02:10 +08:00
Merge branch 'master' into bv
This commit is contained in:
commit
40ba7d57c3
63
README.md
63
README.md
@ -3,10 +3,11 @@
|
|||||||
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
|
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
|
||||||
|
|
||||||
[](https://travis-ci.org/gin-gonic/gin)
|
[](https://travis-ci.org/gin-gonic/gin)
|
||||||
[](https://codecov.io/gh/gin-gonic/gin)
|
[](https://codecov.io/gh/gin-gonic/gin)
|
||||||
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||||
[](https://godoc.org/github.com/gin-gonic/gin)
|
[](https://godoc.org/github.com/gin-gonic/gin)
|
||||||
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
||||||
[](https://www.codetriage.com/gin-gonic/gin)
|
[](https://www.codetriage.com/gin-gonic/gin)
|
||||||
|
|
||||||
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
||||||
@ -27,6 +28,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
- [Querystring parameters](#querystring-parameters)
|
- [Querystring parameters](#querystring-parameters)
|
||||||
- [Multipart/Urlencoded Form](#multiparturlencoded-form)
|
- [Multipart/Urlencoded Form](#multiparturlencoded-form)
|
||||||
- [Another example: query + post form](#another-example-query--post-form)
|
- [Another example: query + post form](#another-example-query--post-form)
|
||||||
|
- [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters)
|
||||||
- [Upload files](#upload-files)
|
- [Upload files](#upload-files)
|
||||||
- [Grouping routes](#grouping-routes)
|
- [Grouping routes](#grouping-routes)
|
||||||
- [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
|
- [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
|
||||||
@ -323,6 +325,34 @@ func main() {
|
|||||||
id: 1234; page: 1; name: manu; message: this_is_great
|
id: 1234; page: 1; name: manu; message: this_is_great
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Map as querystring or postform parameters
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
|
||||||
|
names[first]=thinkerou&names[second]=tianou
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
router.POST("/post", func(c *gin.Context) {
|
||||||
|
|
||||||
|
ids := c.QueryMap("ids")
|
||||||
|
names := c.PostFormMap("names")
|
||||||
|
|
||||||
|
fmt.Printf("ids: %v; names: %v", ids, names)
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]
|
||||||
|
```
|
||||||
|
|
||||||
### Upload files
|
### Upload files
|
||||||
|
|
||||||
#### Single file
|
#### Single file
|
||||||
@ -900,6 +930,29 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### AsciiJSON
|
||||||
|
|
||||||
|
Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
r.GET("/someJSON", func(c *gin.Context) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"lang": "GO语言",
|
||||||
|
"tag": "<br>",
|
||||||
|
}
|
||||||
|
|
||||||
|
// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
|
||||||
|
c.AsciiJSON(http.StatusOK, data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Serving static files
|
### Serving static files
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -1759,7 +1812,7 @@ func TestPingRoute(t *testing.T) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Users [](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
## Users
|
||||||
|
|
||||||
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
|
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
|
||||||
|
|
||||||
|
@ -4,10 +4,9 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
// Content-Type MIME of the most common data formats.
|
||||||
const (
|
const (
|
||||||
MIMEJSON = "application/json"
|
MIMEJSON = "application/json"
|
||||||
MIMEHTML = "text/html"
|
MIMEHTML = "text/html"
|
||||||
|
@ -18,11 +18,17 @@ type defaultValidator struct {
|
|||||||
|
|
||||||
var _ StructValidator = &defaultValidator{}
|
var _ StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
|
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
|
||||||
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||||
if kindOfData(obj) == reflect.Struct {
|
value := reflect.ValueOf(obj)
|
||||||
|
valueType := value.Kind()
|
||||||
|
if valueType == reflect.Ptr {
|
||||||
|
valueType = value.Elem().Kind()
|
||||||
|
}
|
||||||
|
if valueType == reflect.Struct {
|
||||||
v.lazyinit()
|
v.lazyinit()
|
||||||
if err := v.validate.Struct(obj); err != nil {
|
if err := v.validate.Struct(obj); err != nil {
|
||||||
return error(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -43,12 +49,3 @@ func (v *defaultValidator) lazyinit() {
|
|||||||
v.validate = validator.New(config)
|
v.validate = validator.New(config)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func kindOfData(data interface{}) reflect.Kind {
|
|
||||||
value := reflect.ValueOf(data)
|
|
||||||
valueType := value.Kind()
|
|
||||||
if valueType == reflect.Ptr {
|
|
||||||
valueType = value.Elem().Kind()
|
|
||||||
}
|
|
||||||
return valueType
|
|
||||||
}
|
|
||||||
|
70
context.go
70
context.go
@ -159,16 +159,15 @@ func (c *Context) Error(err error) *Error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
panic("err is nil")
|
panic("err is nil")
|
||||||
}
|
}
|
||||||
var parsedError *Error
|
|
||||||
switch err.(type) {
|
parsedError, ok := err.(*Error)
|
||||||
case *Error:
|
if !ok {
|
||||||
parsedError = err.(*Error)
|
|
||||||
default:
|
|
||||||
parsedError = &Error{
|
parsedError = &Error{
|
||||||
Err: err,
|
Err: err,
|
||||||
Type: ErrorTypePrivate,
|
Type: ErrorTypePrivate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Errors = append(c.Errors, parsedError)
|
c.Errors = append(c.Errors, parsedError)
|
||||||
return parsedError
|
return parsedError
|
||||||
}
|
}
|
||||||
@ -361,6 +360,18 @@ func (c *Context) GetQueryArray(key string) ([]string, bool) {
|
|||||||
return []string{}, false
|
return []string{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryMap returns a map for a given query key.
|
||||||
|
func (c *Context) QueryMap(key string) map[string]string {
|
||||||
|
dicts, _ := c.GetQueryMap(key)
|
||||||
|
return dicts
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryMap returns a map for a given query key, plus a boolean value
|
||||||
|
// whether at least one value exists for the given key.
|
||||||
|
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
|
||||||
|
return c.get(c.Request.URL.Query(), key)
|
||||||
|
}
|
||||||
|
|
||||||
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
||||||
// when it exists, otherwise it returns an empty string `("")`.
|
// when it exists, otherwise it returns an empty string `("")`.
|
||||||
func (c *Context) PostForm(key string) string {
|
func (c *Context) PostForm(key string) string {
|
||||||
@ -416,6 +427,42 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
|||||||
return []string{}, false
|
return []string{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostFormMap returns a map for a given form key.
|
||||||
|
func (c *Context) PostFormMap(key string) map[string]string {
|
||||||
|
dicts, _ := c.GetPostFormMap(key)
|
||||||
|
return dicts
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPostFormMap returns a map for a given form key, plus a boolean value
|
||||||
|
// whether at least one value exists for the given key.
|
||||||
|
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
|
||||||
|
req := c.Request
|
||||||
|
req.ParseForm()
|
||||||
|
req.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
||||||
|
dicts, exist := c.get(req.PostForm, key)
|
||||||
|
|
||||||
|
if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil {
|
||||||
|
dicts, exist = c.get(req.MultipartForm.Value, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dicts, exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// get is an internal method and returns a map which satisfy conditions.
|
||||||
|
func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
|
||||||
|
dicts := make(map[string]string)
|
||||||
|
exist := false
|
||||||
|
for k, v := range m {
|
||||||
|
if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
|
||||||
|
if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
|
||||||
|
exist = true
|
||||||
|
dicts[k[i+1:][:j]] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dicts, exist
|
||||||
|
}
|
||||||
|
|
||||||
// FormFile returns the first file for the provided form key.
|
// FormFile returns the first file for the provided form key.
|
||||||
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
|
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
|
||||||
_, fh, err := c.Request.FormFile(name)
|
_, fh, err := c.Request.FormFile(name)
|
||||||
@ -695,7 +742,12 @@ func (c *Context) SecureJSON(code int, obj interface{}) {
|
|||||||
// It add padding to response body to request data from a server residing in a different domain than the client.
|
// It add padding to response body to request data from a server residing in a different domain than the client.
|
||||||
// It also sets the Content-Type as "application/javascript".
|
// It also sets the Content-Type as "application/javascript".
|
||||||
func (c *Context) JSONP(code int, obj interface{}) {
|
func (c *Context) JSONP(code int, obj interface{}) {
|
||||||
c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj})
|
callback := c.DefaultQuery("callback", "")
|
||||||
|
if callback == "" {
|
||||||
|
c.Render(code, render.JSON{Data: obj})
|
||||||
|
} else {
|
||||||
|
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON serializes the given struct as JSON into the response body.
|
// JSON serializes the given struct as JSON into the response body.
|
||||||
@ -704,6 +756,12 @@ func (c *Context) JSON(code int, obj interface{}) {
|
|||||||
c.Render(code, render.JSON{Data: obj})
|
c.Render(code, render.JSON{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
|
||||||
|
// It also sets the Content-Type as "application/json".
|
||||||
|
func (c *Context) AsciiJSON(code int, obj interface{}) {
|
||||||
|
c.Render(code, render.AsciiJSON{Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
// XML serializes the given struct as XML into the response body.
|
// XML serializes the given struct as XML into the response body.
|
||||||
// It also sets the Content-Type as "application/xml".
|
// It also sets the Content-Type as "application/xml".
|
||||||
func (c *Context) XML(code int, obj interface{}) {
|
func (c *Context) XML(code int, obj interface{}) {
|
||||||
|
126
context_test.go
126
context_test.go
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ context.Context = &Context{}
|
var _ context.Context = &Context{}
|
||||||
@ -47,6 +48,8 @@ func createMultipartRequest() *http.Request {
|
|||||||
must(mw.WriteField("time_local", "31/12/2016 14:55"))
|
must(mw.WriteField("time_local", "31/12/2016 14:55"))
|
||||||
must(mw.WriteField("time_utc", "31/12/2016 14:55"))
|
must(mw.WriteField("time_utc", "31/12/2016 14:55"))
|
||||||
must(mw.WriteField("time_location", "31/12/2016 14:55"))
|
must(mw.WriteField("time_location", "31/12/2016 14:55"))
|
||||||
|
must(mw.WriteField("names[a]", "thinkerou"))
|
||||||
|
must(mw.WriteField("names[b]", "tianou"))
|
||||||
req, err := http.NewRequest("POST", "/", body)
|
req, err := http.NewRequest("POST", "/", body)
|
||||||
must(err)
|
must(err)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
@ -371,7 +374,8 @@ func TestContextQuery(t *testing.T) {
|
|||||||
func TestContextQueryAndPostForm(t *testing.T) {
|
func TestContextQueryAndPostForm(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second")
|
body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second")
|
||||||
c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second", body)
|
c.Request, _ = http.NewRequest("POST",
|
||||||
|
"/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body)
|
||||||
c.Request.Header.Add("Content-Type", MIMEPOSTForm)
|
c.Request.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
|
|
||||||
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
|
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
|
||||||
@ -439,6 +443,30 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
|||||||
values = c.QueryArray("both")
|
values = c.QueryArray("both")
|
||||||
assert.Equal(t, 1, len(values))
|
assert.Equal(t, 1, len(values))
|
||||||
assert.Equal(t, "GET", values[0])
|
assert.Equal(t, "GET", values[0])
|
||||||
|
|
||||||
|
dicts, ok := c.GetQueryMap("ids")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "hi", dicts["a"])
|
||||||
|
assert.Equal(t, "3.14", dicts["b"])
|
||||||
|
|
||||||
|
dicts, ok = c.GetQueryMap("nokey")
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.Equal(t, 0, len(dicts))
|
||||||
|
|
||||||
|
dicts, ok = c.GetQueryMap("both")
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.Equal(t, 0, len(dicts))
|
||||||
|
|
||||||
|
dicts, ok = c.GetQueryMap("array")
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.Equal(t, 0, len(dicts))
|
||||||
|
|
||||||
|
dicts = c.QueryMap("ids")
|
||||||
|
assert.Equal(t, "hi", dicts["a"])
|
||||||
|
assert.Equal(t, "3.14", dicts["b"])
|
||||||
|
|
||||||
|
dicts = c.QueryMap("nokey")
|
||||||
|
assert.Equal(t, 0, len(dicts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextPostFormMultipart(t *testing.T) {
|
func TestContextPostFormMultipart(t *testing.T) {
|
||||||
@ -515,6 +543,22 @@ func TestContextPostFormMultipart(t *testing.T) {
|
|||||||
values = c.PostFormArray("foo")
|
values = c.PostFormArray("foo")
|
||||||
assert.Equal(t, 1, len(values))
|
assert.Equal(t, 1, len(values))
|
||||||
assert.Equal(t, "bar", values[0])
|
assert.Equal(t, "bar", values[0])
|
||||||
|
|
||||||
|
dicts, ok := c.GetPostFormMap("names")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "thinkerou", dicts["a"])
|
||||||
|
assert.Equal(t, "tianou", dicts["b"])
|
||||||
|
|
||||||
|
dicts, ok = c.GetPostFormMap("nokey")
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.Equal(t, 0, len(dicts))
|
||||||
|
|
||||||
|
dicts = c.PostFormMap("names")
|
||||||
|
assert.Equal(t, "thinkerou", dicts["a"])
|
||||||
|
assert.Equal(t, "tianou", dicts["b"])
|
||||||
|
|
||||||
|
dicts = c.PostFormMap("nokey")
|
||||||
|
assert.Equal(t, 0, len(dicts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextSetCookie(t *testing.T) {
|
func TestContextSetCookie(t *testing.T) {
|
||||||
@ -596,6 +640,20 @@ func TestContextRenderJSONP(t *testing.T) {
|
|||||||
assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that the response is serialized as JSONP
|
||||||
|
// and Content-Type is set to application/json
|
||||||
|
func TestContextRenderJSONPWithoutCallback(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.Request, _ = http.NewRequest("GET", "http://example.com", nil)
|
||||||
|
|
||||||
|
c.JSONP(201, H{"foo": "bar"})
|
||||||
|
|
||||||
|
assert.Equal(t, 201, w.Code)
|
||||||
|
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that no JSON is rendered if code is 204
|
// Tests that no JSON is rendered if code is 204
|
||||||
func TestContextRenderNoContentJSON(t *testing.T) {
|
func TestContextRenderNoContentJSON(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@ -686,6 +744,17 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextRenderNoContentAsciiJSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"})
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||||
|
assert.Empty(t, w.Body.String())
|
||||||
|
assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that the response executes the templates
|
// Tests that the response executes the templates
|
||||||
// and responds with Content-Type set to text/html
|
// and responds with Content-Type set to text/html
|
||||||
func TestContextRenderHTML(t *testing.T) {
|
func TestContextRenderHTML(t *testing.T) {
|
||||||
@ -1490,3 +1559,58 @@ func TestContextRenderDataFromReader(t *testing.T) {
|
|||||||
assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length"))
|
assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length"))
|
||||||
assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition"))
|
assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TestResponseRecorder struct {
|
||||||
|
*httptest.ResponseRecorder
|
||||||
|
closeChannel chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TestResponseRecorder) CloseNotify() <-chan bool {
|
||||||
|
return r.closeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TestResponseRecorder) closeClient() {
|
||||||
|
r.closeChannel <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTestResponseRecorder() *TestResponseRecorder {
|
||||||
|
return &TestResponseRecorder{
|
||||||
|
httptest.NewRecorder(),
|
||||||
|
make(chan bool, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextStream(t *testing.T) {
|
||||||
|
w := CreateTestResponseRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
stopStream := true
|
||||||
|
c.Stream(func(w io.Writer) bool {
|
||||||
|
defer func() {
|
||||||
|
stopStream = false
|
||||||
|
}()
|
||||||
|
|
||||||
|
w.Write([]byte("test"))
|
||||||
|
|
||||||
|
return stopStream
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, "testtest", w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextStreamWithClientGone(t *testing.T) {
|
||||||
|
w := CreateTestResponseRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Stream(func(writer io.Writer) bool {
|
||||||
|
defer func() {
|
||||||
|
w.closeClient()
|
||||||
|
}()
|
||||||
|
|
||||||
|
writer.Write([]byte("test"))
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, "test", w.Body.String())
|
||||||
|
}
|
||||||
|
@ -19,17 +19,17 @@ func TestError(t *testing.T) {
|
|||||||
Type: ErrorTypePrivate,
|
Type: ErrorTypePrivate,
|
||||||
}
|
}
|
||||||
assert.Equal(t, err.Error(), baseError.Error())
|
assert.Equal(t, err.Error(), baseError.Error())
|
||||||
assert.Equal(t, err.JSON(), H{"error": baseError.Error()})
|
assert.Equal(t, H{"error": baseError.Error()}, err.JSON())
|
||||||
|
|
||||||
assert.Equal(t, err.SetType(ErrorTypePublic), err)
|
assert.Equal(t, err.SetType(ErrorTypePublic), err)
|
||||||
assert.Equal(t, err.Type, ErrorTypePublic)
|
assert.Equal(t, ErrorTypePublic, err.Type)
|
||||||
|
|
||||||
assert.Equal(t, err.SetMeta("some data"), err)
|
assert.Equal(t, err.SetMeta("some data"), err)
|
||||||
assert.Equal(t, err.Meta, "some data")
|
assert.Equal(t, "some data", err.Meta)
|
||||||
assert.Equal(t, err.JSON(), H{
|
assert.Equal(t, H{
|
||||||
"error": baseError.Error(),
|
"error": baseError.Error(),
|
||||||
"meta": "some data",
|
"meta": "some data",
|
||||||
})
|
}, err.JSON())
|
||||||
|
|
||||||
jsonBytes, _ := json.Marshal(err)
|
jsonBytes, _ := json.Marshal(err)
|
||||||
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
|
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
|
||||||
@ -38,22 +38,22 @@ func TestError(t *testing.T) {
|
|||||||
"status": "200",
|
"status": "200",
|
||||||
"data": "some data",
|
"data": "some data",
|
||||||
})
|
})
|
||||||
assert.Equal(t, err.JSON(), H{
|
assert.Equal(t, H{
|
||||||
"error": baseError.Error(),
|
"error": baseError.Error(),
|
||||||
"status": "200",
|
"status": "200",
|
||||||
"data": "some data",
|
"data": "some data",
|
||||||
})
|
}, err.JSON())
|
||||||
|
|
||||||
err.SetMeta(H{
|
err.SetMeta(H{
|
||||||
"error": "custom error",
|
"error": "custom error",
|
||||||
"status": "200",
|
"status": "200",
|
||||||
"data": "some data",
|
"data": "some data",
|
||||||
})
|
})
|
||||||
assert.Equal(t, err.JSON(), H{
|
assert.Equal(t, H{
|
||||||
"error": "custom error",
|
"error": "custom error",
|
||||||
"status": "200",
|
"status": "200",
|
||||||
"data": "some data",
|
"data": "some data",
|
||||||
})
|
}, err.JSON())
|
||||||
|
|
||||||
type customError struct {
|
type customError struct {
|
||||||
status string
|
status string
|
||||||
|
79
gin.go
79
gin.go
@ -171,14 +171,14 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
|||||||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||||
left := engine.delims.Left
|
left := engine.delims.Left
|
||||||
right := engine.delims.Right
|
right := engine.delims.Right
|
||||||
|
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
||||||
|
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
debugPrintLoadTemplate(template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)))
|
debugPrintLoadTemplate(templ)
|
||||||
engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
|
engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
|
||||||
engine.SetHTMLTemplate(templ)
|
engine.SetHTMLTemplate(templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,38 +349,40 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
// Find root of the tree for the given HTTP method
|
// Find root of the tree for the given HTTP method
|
||||||
t := engine.trees
|
t := engine.trees
|
||||||
for i, tl := 0, len(t); i < tl; i++ {
|
for i, tl := 0, len(t); i < tl; i++ {
|
||||||
if t[i].method == httpMethod {
|
if t[i].method != httpMethod {
|
||||||
root := t[i].root
|
continue
|
||||||
// Find route in tree
|
}
|
||||||
handlers, params, tsr := root.getValue(path, c.Params, unescape)
|
root := t[i].root
|
||||||
if handlers != nil {
|
// Find route in tree
|
||||||
c.handlers = handlers
|
handlers, params, tsr := root.getValue(path, c.Params, unescape)
|
||||||
c.Params = params
|
if handlers != nil {
|
||||||
c.Next()
|
c.handlers = handlers
|
||||||
c.writermem.WriteHeaderNow()
|
c.Params = params
|
||||||
|
c.Next()
|
||||||
|
c.writermem.WriteHeaderNow()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if httpMethod != "CONNECT" && path != "/" {
|
||||||
|
if tsr && engine.RedirectTrailingSlash {
|
||||||
|
redirectTrailingSlash(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if httpMethod != "CONNECT" && path != "/" {
|
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
|
||||||
if tsr && engine.RedirectTrailingSlash {
|
return
|
||||||
redirectTrailingSlash(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if engine.HandleMethodNotAllowed {
|
if engine.HandleMethodNotAllowed {
|
||||||
for _, tree := range engine.trees {
|
for _, tree := range engine.trees {
|
||||||
if tree.method != httpMethod {
|
if tree.method == httpMethod {
|
||||||
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
continue
|
||||||
c.handlers = engine.allNoMethod
|
}
|
||||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
||||||
return
|
c.handlers = engine.allNoMethod
|
||||||
}
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -393,14 +395,16 @@ var mimePlain = []string{MIMEPlain}
|
|||||||
func serveError(c *Context, code int, defaultMessage []byte) {
|
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||||
c.writermem.status = code
|
c.writermem.status = code
|
||||||
c.Next()
|
c.Next()
|
||||||
if !c.writermem.Written() {
|
if c.writermem.Written() {
|
||||||
if c.writermem.Status() == code {
|
return
|
||||||
c.writermem.Header()["Content-Type"] = mimePlain
|
|
||||||
c.Writer.Write(defaultMessage)
|
|
||||||
} else {
|
|
||||||
c.writermem.WriteHeaderNow()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if c.writermem.Status() == code {
|
||||||
|
c.writermem.Header()["Content-Type"] = mimePlain
|
||||||
|
c.Writer.Write(defaultMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.writermem.WriteHeaderNow()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirectTrailingSlash(c *Context) {
|
func redirectTrailingSlash(c *Context) {
|
||||||
@ -411,10 +415,9 @@ func redirectTrailingSlash(c *Context) {
|
|||||||
code = http.StatusTemporaryRedirect
|
code = http.StatusTemporaryRedirect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.URL.Path = path + "/"
|
||||||
if length := len(path); length > 1 && path[length-1] == '/' {
|
if length := len(path); length > 1 && path[length-1] == '/' {
|
||||||
req.URL.Path = path[:length-1]
|
req.URL.Path = path[:length-1]
|
||||||
} else {
|
|
||||||
req.URL.Path = path + "/"
|
|
||||||
}
|
}
|
||||||
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
||||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||||
@ -425,11 +428,7 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
|||||||
req := c.Request
|
req := c.Request
|
||||||
path := req.URL.Path
|
path := req.URL.Path
|
||||||
|
|
||||||
fixedPath, found := root.findCaseInsensitivePath(
|
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok {
|
||||||
cleanPath(path),
|
|
||||||
trailingSlash,
|
|
||||||
)
|
|
||||||
if found {
|
|
||||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||||
if req.Method != "GET" {
|
if req.Method != "GET" {
|
||||||
code = http.StatusTemporaryRedirect
|
code = http.StatusTemporaryRedirect
|
||||||
|
@ -128,7 +128,7 @@ func Run(addr ...string) (err error) {
|
|||||||
// RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests.
|
// RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests.
|
||||||
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||||
// Note: this method will block the calling goroutine undefinitelly unless an error happens.
|
// Note: this method will block the calling goroutine undefinitelly unless an error happens.
|
||||||
func RunTLS(addr string, certFile string, keyFile string) (err error) {
|
func RunTLS(addr, certFile, keyFile string) (err error) {
|
||||||
return engine().RunTLS(addr, certFile, keyFile)
|
return engine().RunTLS(addr, certFile, keyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
path.go
4
path.go
@ -59,11 +59,11 @@ func cleanPath(p string) string {
|
|||||||
|
|
||||||
case p[r] == '.' && p[r+1] == '/':
|
case p[r] == '.' && p[r+1] == '/':
|
||||||
// . element
|
// . element
|
||||||
r++
|
r += 2
|
||||||
|
|
||||||
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
||||||
// .. element: remove to last /
|
// .. element: remove to last /
|
||||||
r += 2
|
r += 3
|
||||||
|
|
||||||
if w > 1 {
|
if w > 1 {
|
||||||
// can backtrack
|
// can backtrack
|
||||||
|
@ -41,3 +41,23 @@ func TestPanicWithAbort(t *testing.T) {
|
|||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, 400, w.Code)
|
assert.Equal(t, 400, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSource(t *testing.T) {
|
||||||
|
bs := source(nil, 0)
|
||||||
|
assert.Equal(t, []byte("???"), bs)
|
||||||
|
|
||||||
|
in := [][]byte{
|
||||||
|
[]byte("Hello world."),
|
||||||
|
[]byte("Hi, gin.."),
|
||||||
|
}
|
||||||
|
bs = source(in, 10)
|
||||||
|
assert.Equal(t, []byte("???"), bs)
|
||||||
|
|
||||||
|
bs = source(in, 1)
|
||||||
|
assert.Equal(t, []byte("Hello world."), bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunction(t *testing.T) {
|
||||||
|
bs := function(1)
|
||||||
|
assert.Equal(t, []byte("???"), bs)
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ package render
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -30,10 +31,15 @@ type JsonpJSON struct {
|
|||||||
Data interface{}
|
Data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AsciiJSON struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
type SecureJSONPrefix string
|
type SecureJSONPrefix string
|
||||||
|
|
||||||
var jsonContentType = []string{"application/json; charset=utf-8"}
|
var jsonContentType = []string{"application/json; charset=utf-8"}
|
||||||
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
||||||
|
var jsonAsciiContentType = []string{"application/json"}
|
||||||
|
|
||||||
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
||||||
if err = WriteJSON(w, r.Data); err != nil {
|
if err = WriteJSON(w, r.Data); err != nil {
|
||||||
@ -112,3 +118,29 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
|||||||
func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
|
func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, jsonpContentType)
|
writeContentType(w, jsonpContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
ret, err := json.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
for _, r := range string(ret) {
|
||||||
|
cvt := ""
|
||||||
|
if r < 128 {
|
||||||
|
cvt = string(r)
|
||||||
|
} else {
|
||||||
|
cvt = fmt.Sprintf("\\u%04x", int64(r))
|
||||||
|
}
|
||||||
|
buffer.WriteString(cvt)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(buffer.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonAsciiContentType)
|
||||||
|
}
|
||||||
|
@ -26,6 +26,7 @@ var (
|
|||||||
_ Render = YAML{}
|
_ Render = YAML{}
|
||||||
_ Render = MsgPack{}
|
_ Render = MsgPack{}
|
||||||
_ Render = Reader{}
|
_ Render = Reader{}
|
||||||
|
_ Render = AsciiJSON{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeContentType(w http.ResponseWriter, value []string) {
|
func writeContentType(w http.ResponseWriter, value []string) {
|
||||||
|
@ -158,6 +158,21 @@ func TestRenderJsonpJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type"))
|
assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderJsonpJSONError2(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
(JsonpJSON{"", data}).WriteContentType(w)
|
||||||
|
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
e := (JsonpJSON{"", data}).Render(w)
|
||||||
|
assert.NoError(t, e)
|
||||||
|
|
||||||
|
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
||||||
|
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderJsonpJSONFail(t *testing.T) {
|
func TestRenderJsonpJSONFail(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := make(chan int)
|
data := make(chan int)
|
||||||
@ -167,6 +182,35 @@ func TestRenderJsonpJSONFail(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderAsciiJSON(t *testing.T) {
|
||||||
|
w1 := httptest.NewRecorder()
|
||||||
|
data1 := map[string]interface{}{
|
||||||
|
"lang": "GO语言",
|
||||||
|
"tag": "<br>",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := (AsciiJSON{data1}).Render(w1)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String())
|
||||||
|
assert.Equal(t, "application/json", w1.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
w2 := httptest.NewRecorder()
|
||||||
|
data2 := float64(3.1415926)
|
||||||
|
|
||||||
|
err = (AsciiJSON{data2}).Render(w2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "3.1415926", w2.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderAsciiJSONFail(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := make(chan int)
|
||||||
|
|
||||||
|
// json: unsupported type: chan int
|
||||||
|
assert.Error(t, (AsciiJSON{data}).Render(w))
|
||||||
|
}
|
||||||
|
|
||||||
type xmlmap map[string]interface{}
|
type xmlmap map[string]interface{}
|
||||||
|
|
||||||
// Allows type H to be used with xml.Marshal
|
// Allows type H to be used with xml.Marshal
|
||||||
|
@ -110,5 +110,6 @@ func (w *responseWriter) CloseNotify() <-chan bool {
|
|||||||
|
|
||||||
// Flush implements the http.Flush interface.
|
// Flush implements the http.Flush interface.
|
||||||
func (w *responseWriter) Flush() {
|
func (w *responseWriter) Flush() {
|
||||||
|
w.WriteHeaderNow()
|
||||||
w.ResponseWriter.(http.Flusher).Flush()
|
w.ResponseWriter.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func TestResponseWriterWriteHeader(t *testing.T) {
|
|||||||
w.WriteHeader(300)
|
w.WriteHeader(300)
|
||||||
assert.False(t, w.Written())
|
assert.False(t, w.Written())
|
||||||
assert.Equal(t, 300, w.Status())
|
assert.Equal(t, 300, w.Status())
|
||||||
assert.NotEqual(t, testWritter.Code, 300)
|
assert.NotEqual(t, 300, testWritter.Code)
|
||||||
|
|
||||||
w.WriteHeader(-1)
|
w.WriteHeader(-1)
|
||||||
assert.Equal(t, 300, w.Status())
|
assert.Equal(t, 300, w.Status())
|
||||||
@ -113,3 +113,19 @@ func TestResponseWriterHijack(t *testing.T) {
|
|||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResponseWriterFlush(t *testing.T) {
|
||||||
|
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
writer := &responseWriter{}
|
||||||
|
writer.reset(w)
|
||||||
|
|
||||||
|
writer.WriteHeader(http.StatusInternalServerError)
|
||||||
|
writer.Flush()
|
||||||
|
}))
|
||||||
|
defer testServer.Close()
|
||||||
|
|
||||||
|
// should return 500
|
||||||
|
resp, err := http.Get(testServer.URL)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
|
||||||
|
}
|
||||||
|
@ -333,6 +333,16 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusTeapot, w.Code)
|
assert.Equal(t, http.StatusTeapot, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteNotAllowedEnabled2(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
router.HandleMethodNotAllowed = true
|
||||||
|
// add one methodTree to trees
|
||||||
|
router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}})
|
||||||
|
router.GET("/path2", func(c *Context) {})
|
||||||
|
w := performRequest(router, "POST", "/path2")
|
||||||
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRouteNotAllowedDisabled(t *testing.T) {
|
func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
router.HandleMethodNotAllowed = false
|
router.HandleMethodNotAllowed = false
|
||||||
|
@ -4,9 +4,7 @@
|
|||||||
|
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateTestContext returns a fresh engine and context for testing purposes
|
// CreateTestContext returns a fresh engine and context for testing purposes
|
||||||
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
|
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
@ -124,3 +126,14 @@ func TestBindMiddleware(t *testing.T) {
|
|||||||
Bind(&bindTestStruct{})
|
Bind(&bindTestStruct{})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalXMLforH(t *testing.T) {
|
||||||
|
h := H{
|
||||||
|
"": "test",
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
enc := xml.NewEncoder(&b)
|
||||||
|
var x xml.StartElement
|
||||||
|
e := h.MarshalXML(enc, x)
|
||||||
|
assert.Error(t, e)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user