mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-23 10:02:10 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
427ca1c5cd
32
README.md
32
README.md
@ -7,6 +7,7 @@
|
||||
[](https://goreportcard.com/report/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://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
||||
[](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.
|
||||
@ -27,6 +28,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
- [Querystring parameters](#querystring-parameters)
|
||||
- [Multipart/Urlencoded Form](#multiparturlencoded-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)
|
||||
- [Grouping routes](#grouping-routes)
|
||||
- [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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
#### Single file
|
||||
@ -1782,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.
|
||||
|
||||
|
57
context.go
57
context.go
@ -162,16 +162,15 @@ func (c *Context) Error(err error) *Error {
|
||||
if err == nil {
|
||||
panic("err is nil")
|
||||
}
|
||||
var parsedError *Error
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
parsedError = err.(*Error)
|
||||
default:
|
||||
|
||||
parsedError, ok := err.(*Error)
|
||||
if !ok {
|
||||
parsedError = &Error{
|
||||
Err: err,
|
||||
Type: ErrorTypePrivate,
|
||||
}
|
||||
}
|
||||
|
||||
c.Errors = append(c.Errors, parsedError)
|
||||
return parsedError
|
||||
}
|
||||
@ -364,6 +363,18 @@ func (c *Context) GetQueryArray(key string) ([]string, bool) {
|
||||
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
|
||||
// when it exists, otherwise it returns an empty string `("")`.
|
||||
func (c *Context) PostForm(key string) string {
|
||||
@ -419,6 +430,42 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||
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.
|
||||
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
|
||||
_, fh, err := c.Request.FormFile(name)
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
"io"
|
||||
)
|
||||
|
||||
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_utc", "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)
|
||||
must(err)
|
||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||
@ -371,7 +374,8 @@ func TestContextQuery(t *testing.T) {
|
||||
func TestContextQueryAndPostForm(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
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)
|
||||
|
||||
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
|
||||
@ -439,6 +443,30 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
||||
values = c.QueryArray("both")
|
||||
assert.Equal(t, 1, len(values))
|
||||
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) {
|
||||
@ -515,6 +543,22 @@ func TestContextPostFormMultipart(t *testing.T) {
|
||||
values = c.PostFormArray("foo")
|
||||
assert.Equal(t, 1, len(values))
|
||||
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) {
|
||||
@ -1515,3 +1559,38 @@ func TestContextRenderDataFromReader(t *testing.T) {
|
||||
assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length"))
|
||||
assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition"))
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
@ -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.
|
||||
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ var (
|
||||
_ Render = YAML{}
|
||||
_ Render = MsgPack{}
|
||||
_ Render = Reader{}
|
||||
_ Render = AsciiJSON{}
|
||||
)
|
||||
|
||||
func writeContentType(w http.ResponseWriter, value []string) {
|
||||
|
@ -110,5 +110,6 @@ func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
|
||||
// Flush implements the http.Flush interface.
|
||||
func (w *responseWriter) Flush() {
|
||||
w.WriteHeaderNow()
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
@ -113,3 +113,19 @@ func TestResponseWriterHijack(t *testing.T) {
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package gin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
)
|
||||
|
||||
// CreateTestContext returns a fresh engine and context for testing purposes
|
||||
@ -16,3 +17,23 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
|
||||
c.writermem.reset(w)
|
||||
return
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user