mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-19 15:57:48 +08:00
Merge branch 'master' into render-tc-2
This commit is contained in:
commit
08e7b49be4
46
.github/ISSUE_TEMPLATE.md
vendored
46
.github/ISSUE_TEMPLATE.md
vendored
@ -3,11 +3,47 @@
|
||||
- Please provide source code and commit sha if you found a bug.
|
||||
- Review existing issues and provide feedback or react to them.
|
||||
|
||||
## Description
|
||||
|
||||
<!-- Description of a problem -->
|
||||
|
||||
## How to reproduce
|
||||
|
||||
<!-- The smallest possible code example to show the problem that can be compiled, like -->
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g := gin.Default()
|
||||
g.GET("/hello/:name", func(c *gin.Context) {
|
||||
c.String(200, "Hello %s", c.Param("name"))
|
||||
})
|
||||
g.Run(":9000")
|
||||
}
|
||||
```
|
||||
|
||||
## Expectations
|
||||
|
||||
<!-- Your expectation result of 'curl' command, like -->
|
||||
```
|
||||
$ curl http://localhost:8201/hello/world
|
||||
Hello world
|
||||
```
|
||||
|
||||
## Actual result
|
||||
|
||||
<!-- Actual result showing the problem -->
|
||||
```
|
||||
$ curl -i http://localhost:8201/hello/world
|
||||
<YOUR RESULT>
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
- go version:
|
||||
- gin version (or commit ref):
|
||||
- operating system:
|
||||
|
||||
## Description
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
- [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749)
|
||||
- [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252)
|
||||
- [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775)
|
||||
- [NEW] Extend context.File to allow for the content-dispositon attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260)
|
||||
- [NEW] Extend context.File to allow for the content-disposition attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260)
|
||||
- [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112)
|
||||
- [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238)
|
||||
- [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020)
|
||||
@ -231,7 +231,7 @@
|
||||
- [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults.
|
||||
- [NEW] Flexible rendering API
|
||||
- [NEW] Add Context.File()
|
||||
- [NEW] Add shorcut RunTLS() for http.ListenAndServeTLS
|
||||
- [NEW] Add shortcut RunTLS() for http.ListenAndServeTLS
|
||||
- [FIX] Rename NotFound404() to NoRoute()
|
||||
- [FIX] Errors in context are purged
|
||||
- [FIX] Adds HEAD method in Static file serving
|
||||
@ -254,7 +254,7 @@
|
||||
- [NEW] New Bind() and BindWith() methods for parsing request body.
|
||||
- [NEW] Add Content.Copy()
|
||||
- [NEW] Add context.LastError()
|
||||
- [NEW] Add shorcut for OPTIONS HTTP method
|
||||
- [NEW] Add shortcut for OPTIONS HTTP method
|
||||
- [FIX] Tons of README fixes
|
||||
- [FIX] Header is written before body
|
||||
- [FIX] BasicAuth() and changes API a little bit
|
||||
|
@ -1149,7 +1149,7 @@ func main() {
|
||||
|
||||
#### AsciiJSON
|
||||
|
||||
Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters.
|
||||
Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
|
@ -1,72 +0,0 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin/testdata/protoexample"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
func TestBindingBody(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
binding BindingBody
|
||||
body string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "JSON binding",
|
||||
binding: JSON,
|
||||
body: `{"foo":"FOO"}`,
|
||||
},
|
||||
{
|
||||
name: "XML binding",
|
||||
binding: XML,
|
||||
body: `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<foo>FOO</foo>
|
||||
</root>`,
|
||||
},
|
||||
{
|
||||
name: "MsgPack binding",
|
||||
binding: MsgPack,
|
||||
body: msgPackBody(t),
|
||||
},
|
||||
{
|
||||
name: "YAML binding",
|
||||
binding: YAML,
|
||||
body: `foo: FOO`,
|
||||
},
|
||||
} {
|
||||
t.Logf("testing: %s", tt.name)
|
||||
req := requestWithBody("POST", "/", tt.body)
|
||||
form := FooStruct{}
|
||||
body, _ := ioutil.ReadAll(req.Body)
|
||||
assert.NoError(t, tt.binding.BindBody(body, &form))
|
||||
assert.Equal(t, FooStruct{"FOO"}, form)
|
||||
}
|
||||
}
|
||||
|
||||
func msgPackBody(t *testing.T) string {
|
||||
test := FooStruct{"FOO"}
|
||||
h := new(codec.MsgpackHandle)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
assert.NoError(t, codec.NewEncoder(buf, h).Encode(test))
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func TestBindingBodyProto(t *testing.T) {
|
||||
test := protoexample.Test{
|
||||
Label: proto.String("FOO"),
|
||||
}
|
||||
data, _ := proto.Marshal(&test)
|
||||
req := requestWithBody("POST", "/", string(data))
|
||||
form := protoexample.Test{}
|
||||
body, _ := ioutil.ReadAll(req.Body)
|
||||
assert.NoError(t, ProtoBuf.BindBody(body, &form))
|
||||
assert.Equal(t, test, form)
|
||||
}
|
21
binding/json_test.go
Normal file
21
binding/json_test.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2019 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 binding
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestJSONBindingBindBody(t *testing.T) {
|
||||
var s struct {
|
||||
Foo string `json:"foo"`
|
||||
}
|
||||
err := jsonBinding{}.BindBody([]byte(`{"foo": "FOO"}`), &s)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "FOO", s.Foo)
|
||||
}
|
32
binding/msgpack_test.go
Normal file
32
binding/msgpack_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2019 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 binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
func TestMsgpackBindingBindBody(t *testing.T) {
|
||||
type teststruct struct {
|
||||
Foo string `msgpack:"foo"`
|
||||
}
|
||||
var s teststruct
|
||||
err := msgpackBinding{}.BindBody(msgpackBody(t, teststruct{"FOO"}), &s)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "FOO", s.Foo)
|
||||
}
|
||||
|
||||
func msgpackBody(t *testing.T, obj interface{}) []byte {
|
||||
var bs bytes.Buffer
|
||||
h := &codec.MsgpackHandle{}
|
||||
err := codec.NewEncoder(&bs, h).Encode(obj)
|
||||
require.NoError(t, err)
|
||||
return bs.Bytes()
|
||||
}
|
25
binding/xml_test.go
Normal file
25
binding/xml_test.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2019 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 binding
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestXMLBindingBindBody(t *testing.T) {
|
||||
var s struct {
|
||||
Foo string `xml:"foo"`
|
||||
}
|
||||
xmlBody := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<foo>FOO</foo>
|
||||
</root>`
|
||||
err := xmlBinding{}.BindBody([]byte(xmlBody), &s)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "FOO", s.Foo)
|
||||
}
|
21
binding/yaml_test.go
Normal file
21
binding/yaml_test.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2019 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 binding
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestYAMLBindingBindBody(t *testing.T) {
|
||||
var s struct {
|
||||
Foo string `yaml:"foo"`
|
||||
}
|
||||
err := yamlBinding{}.BindBody([]byte("foo: FOO"), &s)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "FOO", s.Foo)
|
||||
}
|
@ -744,7 +744,7 @@ func bodyAllowedForStatus(status int) bool {
|
||||
|
||||
// Status sets the HTTP response code.
|
||||
func (c *Context) Status(code int) {
|
||||
c.writermem.WriteHeader(code)
|
||||
c.Writer.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value).
|
||||
|
9
gin.go
9
gin.go
@ -338,6 +338,15 @@ func (engine *Engine) RunFd(fd int) (err error) {
|
||||
return
|
||||
}
|
||||
defer listener.Close()
|
||||
err = engine.RunListener(listener)
|
||||
return
|
||||
}
|
||||
|
||||
// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||
// through the specified net.Listener
|
||||
func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
||||
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
|
||||
defer func() { debugPrintError(err) }()
|
||||
err = http.Serve(listener, engine)
|
||||
return
|
||||
}
|
||||
|
@ -207,6 +207,42 @@ func TestBadFileDescriptor(t *testing.T) {
|
||||
assert.Error(t, router.RunFd(0))
|
||||
}
|
||||
|
||||
func TestListener(t *testing.T) {
|
||||
router := New()
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
assert.NoError(t, err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
assert.NoError(t, err)
|
||||
go func() {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
assert.NoError(t, router.RunListener(listener))
|
||||
}()
|
||||
// have to wait for the goroutine to start and run the server
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
c, err := net.Dial("tcp", listener.Addr().String())
|
||||
assert.NoError(t, err)
|
||||
|
||||
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
scanner := bufio.NewScanner(c)
|
||||
var response string
|
||||
for scanner.Scan() {
|
||||
response += scanner.Text()
|
||||
}
|
||||
assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
|
||||
assert.Contains(t, response, "it worked", "resp body should match")
|
||||
}
|
||||
|
||||
func TestBadListener(t *testing.T) {
|
||||
router := New()
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:10086")
|
||||
assert.NoError(t, err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
listener.Close()
|
||||
assert.Error(t, router.RunListener(listener))
|
||||
}
|
||||
|
||||
func TestWithHttptestWithAutoSelectedPort(t *testing.T) {
|
||||
router := New()
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
|
1
go.sum
1
go.sum
@ -1,4 +1,5 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
|
@ -29,38 +29,38 @@ func init() {
|
||||
}
|
||||
|
||||
func TestResponseWriterReset(t *testing.T) {
|
||||
testWritter := httptest.NewRecorder()
|
||||
testWriter := httptest.NewRecorder()
|
||||
writer := &responseWriter{}
|
||||
var w ResponseWriter = writer
|
||||
|
||||
writer.reset(testWritter)
|
||||
writer.reset(testWriter)
|
||||
assert.Equal(t, -1, writer.size)
|
||||
assert.Equal(t, http.StatusOK, writer.status)
|
||||
assert.Equal(t, testWritter, writer.ResponseWriter)
|
||||
assert.Equal(t, testWriter, writer.ResponseWriter)
|
||||
assert.Equal(t, -1, w.Size())
|
||||
assert.Equal(t, http.StatusOK, w.Status())
|
||||
assert.False(t, w.Written())
|
||||
}
|
||||
|
||||
func TestResponseWriterWriteHeader(t *testing.T) {
|
||||
testWritter := httptest.NewRecorder()
|
||||
testWriter := httptest.NewRecorder()
|
||||
writer := &responseWriter{}
|
||||
writer.reset(testWritter)
|
||||
writer.reset(testWriter)
|
||||
w := ResponseWriter(writer)
|
||||
|
||||
w.WriteHeader(http.StatusMultipleChoices)
|
||||
assert.False(t, w.Written())
|
||||
assert.Equal(t, http.StatusMultipleChoices, w.Status())
|
||||
assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code)
|
||||
assert.NotEqual(t, http.StatusMultipleChoices, testWriter.Code)
|
||||
|
||||
w.WriteHeader(-1)
|
||||
assert.Equal(t, http.StatusMultipleChoices, w.Status())
|
||||
}
|
||||
|
||||
func TestResponseWriterWriteHeadersNow(t *testing.T) {
|
||||
testWritter := httptest.NewRecorder()
|
||||
testWriter := httptest.NewRecorder()
|
||||
writer := &responseWriter{}
|
||||
writer.reset(testWritter)
|
||||
writer.reset(testWriter)
|
||||
w := ResponseWriter(writer)
|
||||
|
||||
w.WriteHeader(http.StatusMultipleChoices)
|
||||
@ -68,7 +68,7 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) {
|
||||
|
||||
assert.True(t, w.Written())
|
||||
assert.Equal(t, 0, w.Size())
|
||||
assert.Equal(t, http.StatusMultipleChoices, testWritter.Code)
|
||||
assert.Equal(t, http.StatusMultipleChoices, testWriter.Code)
|
||||
|
||||
writer.size = 10
|
||||
w.WriteHeaderNow()
|
||||
@ -76,30 +76,30 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResponseWriterWrite(t *testing.T) {
|
||||
testWritter := httptest.NewRecorder()
|
||||
testWriter := httptest.NewRecorder()
|
||||
writer := &responseWriter{}
|
||||
writer.reset(testWritter)
|
||||
writer.reset(testWriter)
|
||||
w := ResponseWriter(writer)
|
||||
|
||||
n, err := w.Write([]byte("hola"))
|
||||
assert.Equal(t, 4, n)
|
||||
assert.Equal(t, 4, w.Size())
|
||||
assert.Equal(t, http.StatusOK, w.Status())
|
||||
assert.Equal(t, http.StatusOK, testWritter.Code)
|
||||
assert.Equal(t, "hola", testWritter.Body.String())
|
||||
assert.Equal(t, http.StatusOK, testWriter.Code)
|
||||
assert.Equal(t, "hola", testWriter.Body.String())
|
||||
assert.NoError(t, err)
|
||||
|
||||
n, err = w.Write([]byte(" adios"))
|
||||
assert.Equal(t, 6, n)
|
||||
assert.Equal(t, 10, w.Size())
|
||||
assert.Equal(t, "hola adios", testWritter.Body.String())
|
||||
assert.Equal(t, "hola adios", testWriter.Body.String())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestResponseWriterHijack(t *testing.T) {
|
||||
testWritter := httptest.NewRecorder()
|
||||
testWriter := httptest.NewRecorder()
|
||||
writer := &responseWriter{}
|
||||
writer.reset(testWritter)
|
||||
writer.reset(testWriter)
|
||||
w := ResponseWriter(writer)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user