diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 00000000..4cbc4554
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,49 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ master ]
+ schedule:
+ - cron: '0 17 * * 5'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+
+ permissions:
+ # required for all workflows
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ # Override automatic language detection by changing the below list
+ # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
+ # TODO: Enable for javascript later
+ language: [ 'go']
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml
index c25a9091..1aecda58 100644
--- a/.github/workflows/gin.yml
+++ b/.github/workflows/gin.yml
@@ -17,11 +17,11 @@ jobs:
with:
go-version: '^1.16'
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Setup golangci-lint
- uses: golangci/golangci-lint-action@v2
+ uses: golangci/golangci-lint-action@v3.1.0
with:
- version: v1.43.0
+ version: v1.44.0
args: --verbose
test:
needs: lint
@@ -48,7 +48,7 @@ jobs:
go-version: ${{ matrix.go }}
- name: Checkout Code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
diff --git a/README.md b/README.md
index f78a64eb..9a9785e6 100644
--- a/README.md
+++ b/README.md
@@ -384,8 +384,8 @@ func main() {
// Set a lower memory limit for multipart forms (default is 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
- // single file
- file, _ := c.FormFile("Filename")
+ // Single file
+ file, _ := c.FormFile("file")
log.Println(file.Filename)
// Upload the file to specific dst.
@@ -513,6 +513,7 @@ func main() {
// nested group
testing := authorized.Group("testing")
+ // visit 0.0.0.0:8080/testing/analytics
testing.GET("/analytics", analyticsEndpoint)
}
@@ -906,7 +907,7 @@ func startPage(c *gin.Context) {
var person Person
// If `GET`, only `Form` binding engine (`query`) used.
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
- // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
+ // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88
if c.ShouldBind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
@@ -1243,7 +1244,8 @@ func main() {
router.Static("/assets", "./assets")
router.StaticFS("/more_static", http.Dir("my_file_system"))
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
-
+ router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
+
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
@@ -1409,7 +1411,7 @@ import (
func formatAsDate(t time.Time) string {
year, month, day := t.Date()
- return fmt.Sprintf("%d%02d/%02d", year, month, day)
+ return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}
func main() {
diff --git a/binding/binding.go b/binding/binding.go
index 4ab42fdf..703a1cf8 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -40,7 +40,7 @@ type BindingBody interface {
}
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
-// but it read the Params.
+// but it reads the Params.
type BindingUri interface {
Name() string
BindUri(map[string][]string, any) error
diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go
index 86a25a10..b3818549 100644
--- a/binding/binding_nomsgpack.go
+++ b/binding/binding_nomsgpack.go
@@ -38,7 +38,7 @@ type BindingBody interface {
}
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
-// but it read the Params.
+// but it reads the Params.
type BindingUri interface {
Name() string
BindUri(map[string][]string, any) error
diff --git a/context.go b/context.go
index 0f8f4137..faa48133 100644
--- a/context.go
+++ b/context.go
@@ -6,7 +6,6 @@ package gin
import (
"errors"
- "fmt"
"io"
"io/ioutil"
"log"
@@ -59,7 +58,7 @@ type Context struct {
params *Params
skippedNodes *[]skippedNode
- // This mutex protect Keys map
+ // This mutex protects Keys map.
mu sync.RWMutex
// Keys is a key/value pair exclusively for the context of each request.
@@ -71,10 +70,10 @@ type Context struct {
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
- // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
+ // queryCache caches the query result from c.Request.URL.Query().
queryCache url.Values
- // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
+ // formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
// or PUT body parameters.
formCache url.Values
@@ -601,11 +600,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
return err
}
-// Bind checks the Content-Type to select a binding engine automatically,
-// Depending on the "Content-Type" header different bindings are used:
+// Bind checks the Method and Content-Type to select a binding engine automatically,
+// Depending on the "Content-Type" header different bindings are used, for example:
// "application/json" --> JSON binding
// "application/xml" --> XML binding
-// otherwise --> returns an error.
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer.
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
@@ -660,14 +658,13 @@ func (c *Context) MustBindWith(obj any, b binding.Binding) error {
return nil
}
-// ShouldBind checks the Content-Type to select a binding engine automatically,
-// Depending on the "Content-Type" header different bindings are used:
+// ShouldBind checks the Method and Content-Type to select a binding engine automatically,
+// Depending on the "Content-Type" header different bindings are used, for example:
// "application/json" --> JSON binding
// "application/xml" --> XML binding
-// otherwise --> returns an error
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer.
-// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid.
+// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
func (c *Context) ShouldBind(obj any) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.ShouldBindWith(obj, b)
@@ -843,7 +840,7 @@ func (c *Context) GetHeader(key string) string {
return c.requestHeader(key)
}
-// GetRawData return stream data.
+// GetRawData returns stream data.
func (c *Context) GetRawData() ([]byte, error) {
return ioutil.ReadAll(c.Request.Body)
}
@@ -1020,7 +1017,11 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {
// FileAttachment writes the specified file into the body stream in an efficient way
// On the client side, the file will typically be downloaded with the given filename
func (c *Context) FileAttachment(filepath, filename string) {
- c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
+ if isASCII(filename) {
+ c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
+ } else {
+ c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename))
+ }
http.ServeFile(c.Writer, c.Request, filepath)
}
diff --git a/context_test.go b/context_test.go
index fc1c0847..fb46e679 100644
--- a/context_test.go
+++ b/context_test.go
@@ -15,6 +15,7 @@ import (
"net"
"net/http"
"net/http/httptest"
+ "net/url"
"os"
"reflect"
"strings"
@@ -1033,6 +1034,19 @@ func TestContextRenderAttachment(t *testing.T) {
assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition"))
}
+func TestContextRenderUTF8Attachment(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, _ := CreateTestContext(w)
+ newFilename := "newπ§‘_filename.go"
+
+ c.Request, _ = http.NewRequest("GET", "/", nil)
+ c.FileAttachment("./gin.go", newFilename)
+
+ assert.Equal(t, 200, w.Code)
+ assert.Contains(t, w.Body.String(), "func New() *Engine {")
+ assert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get("Content-Disposition"))
+}
+
// TestContextRenderYAML tests that the response is serialized as YAML
// and Content-Type is set to application/x-yaml
func TestContextRenderYAML(t *testing.T) {
@@ -2036,8 +2050,8 @@ func TestRaceParamsContextCopy(t *testing.T) {
}(c.Copy(), c.Param("name"))
})
}
- performRequest(router, "GET", "/name1/api")
- performRequest(router, "GET", "/name2/api")
+ PerformRequest(router, "GET", "/name1/api")
+ PerformRequest(router, "GET", "/name2/api")
wg.Wait()
}
diff --git a/gin.go b/gin.go
index d5e8eb98..b0e0154f 100644
--- a/gin.go
+++ b/gin.go
@@ -16,6 +16,8 @@ import (
"github.com/gin-gonic/gin/internal/bytesconv"
"github.com/gin-gonic/gin/render"
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/h2c"
)
const defaultMultipartMemory = 32 << 20 // 32 MB
@@ -141,6 +143,9 @@ type Engine struct {
// method call.
MaxMultipartMemory int64
+ // Enable h2c support.
+ UseH2C bool
+
delims render.Delims
secureJSONPrefix string
HTMLRender render.HTMLRender
@@ -207,6 +212,15 @@ func Default() *Engine {
return engine
}
+func (engine *Engine) Handler() http.Handler {
+ if !engine.UseH2C {
+ return engine
+ }
+
+ h2s := &http2.Server{}
+ return h2c.NewHandler(engine, h2s)
+}
+
func (engine *Engine) allocateContext() *Context {
v := make(Params, 0, engine.maxParams)
skippedNodes := make([]skippedNode, 0, engine.maxSections)
@@ -361,7 +375,7 @@ func (engine *Engine) Run(addr ...string) (err error) {
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
- err = http.ListenAndServe(address, engine)
+ err = http.ListenAndServe(address, engine.Handler())
return
}
@@ -480,7 +494,7 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
- err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
+ err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
return
}
@@ -503,7 +517,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
defer listener.Close()
defer os.Remove(file)
- err = http.Serve(listener, engine)
+ err = http.Serve(listener, engine.Handler())
return
}
@@ -540,7 +554,7 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
- err = http.Serve(listener, engine)
+ err = http.Serve(listener, engine.Handler())
return
}
diff --git a/ginS/gins.go b/ginS/gins.go
index ed054bfd..0802e085 100644
--- a/ginS/gins.go
+++ b/ginS/gins.go
@@ -118,7 +118,7 @@ func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
return engine().StaticFS(relativePath, fs)
}
-// Use attaches a global middleware to the router. i.e. the middlewares attached though Use() will be
+// Use attaches a global middleware to the router. i.e. the middlewares attached through Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
diff --git a/gin_test.go b/gin_test.go
index 32dbce2c..0c11134f 100644
--- a/gin_test.go
+++ b/gin_test.go
@@ -19,6 +19,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
+ "golang.org/x/net/http2"
)
func formatAsDate(t time.Time) string {
@@ -79,6 +80,44 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) {
assert.Equal(t, "
Hello world
", string(resp))
}
+func TestH2c(t *testing.T) {
+ ln, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ fmt.Println(err)
+ }
+ r := Default()
+ r.UseH2C = true
+ r.GET("/", func(c *Context) {
+ c.String(200, "Hello world
")
+ })
+ go func() {
+ err := http.Serve(ln, r.Handler())
+ if err != nil {
+ fmt.Println(err)
+ }
+ }()
+ defer ln.Close()
+
+ url := "http://" + ln.Addr().String() + "/"
+
+ http := http.Client{
+ Transport: &http2.Transport{
+ AllowHTTP: true,
+ DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
+ return net.Dial(netw, addr)
+ },
+ },
+ }
+
+ res, err := http.Get(url)
+ if err != nil {
+ fmt.Println(err)
+ }
+
+ resp, _ := ioutil.ReadAll(res.Body)
+ assert.Equal(t, "Hello world
", string(resp))
+}
+
func TestLoadHTMLGlobTestMode(t *testing.T) {
ts := setupHTMLFiles(
t,
@@ -395,7 +434,6 @@ func TestNoMethodWithoutGlobalHandlers(t *testing.T) {
}
func TestRebuild404Handlers(t *testing.T) {
-
}
func TestNoMethodWithGlobalHandlers(t *testing.T) {
@@ -491,7 +529,7 @@ func TestEngineHandleContext(t *testing.T) {
}
assert.NotPanics(t, func() {
- w := performRequest(r, "GET", "/")
+ w := PerformRequest(r, "GET", "/")
assert.Equal(t, 301, w.Code)
})
}
@@ -524,7 +562,7 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
})
assert.NotPanics(t, func() {
- w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
+ w := PerformRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
assert.Equal(t, 200, w.Code)
assert.Equal(t, expectValue, w.Body.Len())
})
@@ -636,7 +674,6 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
assert.Nil(t, r.trustedCIDRs)
assert.Nil(t, err)
}
-
}
func parseCIDR(cidr string) *net.IPNet {
diff --git a/githubapi_test.go b/githubapi_test.go
index 3f440bce..e74bddd5 100644
--- a/githubapi_test.go
+++ b/githubapi_test.go
@@ -302,7 +302,7 @@ func TestShouldBindUri(t *testing.T) {
})
path, _ := exampleFromPath("/rest/:name/:id")
- w := performRequest(router, http.MethodGet, path)
+ w := PerformRequest(router, http.MethodGet, path)
assert.Equal(t, "ShouldBindUri test OK", w.Body.String())
assert.Equal(t, http.StatusOK, w.Code)
}
@@ -324,7 +324,7 @@ func TestBindUri(t *testing.T) {
})
path, _ := exampleFromPath("/rest/:name/:id")
- w := performRequest(router, http.MethodGet, path)
+ w := PerformRequest(router, http.MethodGet, path)
assert.Equal(t, "BindUri test OK", w.Body.String())
assert.Equal(t, http.StatusOK, w.Code)
}
@@ -342,7 +342,7 @@ func TestBindUriError(t *testing.T) {
})
path1, _ := exampleFromPath("/new/rest/:num")
- w1 := performRequest(router, http.MethodGet, path1)
+ w1 := PerformRequest(router, http.MethodGet, path1)
assert.Equal(t, http.StatusBadRequest, w1.Code)
}
@@ -358,7 +358,7 @@ func TestRaceContextCopy(t *testing.T) {
go readWriteKeys(c.Copy())
c.String(http.StatusOK, "run OK, no panics")
})
- w := performRequest(router, http.MethodGet, "/test/copy/race")
+ w := PerformRequest(router, http.MethodGet, "/test/copy/race")
assert.Equal(t, "run OK, no panics", w.Body.String())
}
@@ -389,7 +389,7 @@ func TestGithubAPI(t *testing.T) {
for _, route := range githubAPI {
path, values := exampleFromPath(route.path)
- w := performRequest(router, route.method, path)
+ w := PerformRequest(router, route.method, path)
// TEST
assert.Contains(t, w.Body.String(), "\"status\":\"good\"")
diff --git a/go.mod b/go.mod
index 05b9760f..7a9249c6 100644
--- a/go.mod
+++ b/go.mod
@@ -4,14 +4,13 @@ go 1.13
require (
github.com/gin-contrib/sse v0.1.0
- github.com/go-playground/validator/v10 v10.9.0
- github.com/goccy/go-json v0.8.1
+ github.com/go-playground/validator/v10 v10.10.0
+ github.com/goccy/go-json v0.9.5
github.com/json-iterator/go v1.1.12
github.com/mattn/go-isatty v0.0.14
github.com/stretchr/testify v1.7.0
- github.com/ugorji/go/codec v1.2.6
+ github.com/ugorji/go/codec v1.2.7
+ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v2 v2.4.0
)
-
-retract v1.7.5
diff --git a/go.sum b/go.sum
index adacf5ee..6006ffb9 100644
--- a/go.sum
+++ b/go.sum
@@ -10,10 +10,10 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
-github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
-github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
-github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
-github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
+github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
+github.com/goccy/go-json v0.9.5 h1:ooSMW526ZjK+EaL5elrSyN2EzIfi/3V0m4+HJEDYLik=
+github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -47,12 +47,13 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
-github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
-github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
-github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
+github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
+github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
+github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
+github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/logger_test.go b/logger_test.go
index 13a03d10..da1b654e 100644
--- a/logger_test.go
+++ b/logger_test.go
@@ -31,7 +31,7 @@ func TestLogger(t *testing.T) {
router.HEAD("/example", func(c *Context) {})
router.OPTIONS("/example", func(c *Context) {})
- performRequest(router, "GET", "/example?a=100")
+ PerformRequest(router, "GET", "/example?a=100")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/example")
@@ -41,43 +41,43 @@ func TestLogger(t *testing.T) {
// like integration tests because they test the whole logging process rather
// than individual functions. Im not sure where these should go.
buffer.Reset()
- performRequest(router, "POST", "/example")
+ PerformRequest(router, "POST", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "POST")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "PUT", "/example")
+ PerformRequest(router, "PUT", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PUT")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "DELETE", "/example")
+ PerformRequest(router, "DELETE", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "DELETE")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "PATCH", "/example")
+ PerformRequest(router, "PATCH", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PATCH")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "HEAD", "/example")
+ PerformRequest(router, "HEAD", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "HEAD")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "OPTIONS", "/example")
+ PerformRequest(router, "OPTIONS", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "OPTIONS")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "GET", "/notfound")
+ PerformRequest(router, "GET", "/notfound")
assert.Contains(t, buffer.String(), "404")
assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/notfound")
@@ -95,7 +95,7 @@ func TestLoggerWithConfig(t *testing.T) {
router.HEAD("/example", func(c *Context) {})
router.OPTIONS("/example", func(c *Context) {})
- performRequest(router, "GET", "/example?a=100")
+ PerformRequest(router, "GET", "/example?a=100")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/example")
@@ -105,43 +105,43 @@ func TestLoggerWithConfig(t *testing.T) {
// like integration tests because they test the whole logging process rather
// than individual functions. Im not sure where these should go.
buffer.Reset()
- performRequest(router, "POST", "/example")
+ PerformRequest(router, "POST", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "POST")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "PUT", "/example")
+ PerformRequest(router, "PUT", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PUT")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "DELETE", "/example")
+ PerformRequest(router, "DELETE", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "DELETE")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "PATCH", "/example")
+ PerformRequest(router, "PATCH", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PATCH")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "HEAD", "/example")
+ PerformRequest(router, "HEAD", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "HEAD")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "OPTIONS", "/example")
+ PerformRequest(router, "OPTIONS", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "OPTIONS")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
- performRequest(router, "GET", "/notfound")
+ PerformRequest(router, "GET", "/notfound")
assert.Contains(t, buffer.String(), "404")
assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/notfound")
@@ -169,7 +169,7 @@ func TestLoggerWithFormatter(t *testing.T) {
)
}))
router.GET("/example", func(c *Context) {})
- performRequest(router, "GET", "/example?a=100")
+ PerformRequest(router, "GET", "/example?a=100")
// output test
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
@@ -209,7 +209,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
gotKeys = c.Keys
})
- performRequest(router, "GET", "/example?a=100")
+ PerformRequest(router, "GET", "/example?a=100")
// output test
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
@@ -228,7 +228,6 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
assert.Equal(t, "/example?a=100", gotParam.Path)
assert.Empty(t, gotParam.ErrorMessage)
assert.Equal(t, gotKeys, gotParam.Keys)
-
}
func TestDefaultLogFormatter(t *testing.T) {
@@ -282,7 +281,6 @@ func TestDefaultLogFormatter(t *testing.T) {
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
-
}
func TestColorForMethod(t *testing.T) {
@@ -369,15 +367,15 @@ func TestErrorLogger(t *testing.T) {
c.String(http.StatusInternalServerError, "hola!")
})
- w := performRequest(router, "GET", "/error")
+ w := PerformRequest(router, "GET", "/error")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
- w = performRequest(router, "GET", "/abort")
+ w = PerformRequest(router, "GET", "/abort")
assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
- w = performRequest(router, "GET", "/print")
+ w = PerformRequest(router, "GET", "/print")
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
}
@@ -389,11 +387,11 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) {
router.GET("/logged", func(c *Context) {})
router.GET("/skipped", func(c *Context) {})
- performRequest(router, "GET", "/logged")
+ PerformRequest(router, "GET", "/logged")
assert.Contains(t, buffer.String(), "200")
buffer.Reset()
- performRequest(router, "GET", "/skipped")
+ PerformRequest(router, "GET", "/skipped")
assert.Contains(t, buffer.String(), "")
}
@@ -407,11 +405,11 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) {
router.GET("/logged", func(c *Context) {})
router.GET("/skipped", func(c *Context) {})
- performRequest(router, "GET", "/logged")
+ PerformRequest(router, "GET", "/logged")
assert.Contains(t, buffer.String(), "200")
buffer.Reset()
- performRequest(router, "GET", "/skipped")
+ PerformRequest(router, "GET", "/skipped")
assert.Contains(t, buffer.String(), "")
}
diff --git a/middleware_test.go b/middleware_test.go
index 4b4afd4a..e0a756c3 100644
--- a/middleware_test.go
+++ b/middleware_test.go
@@ -35,7 +35,7 @@ func TestMiddlewareGeneralCase(t *testing.T) {
signature += " XX "
})
// RUN
- w := performRequest(router, "GET", "/")
+ w := PerformRequest(router, "GET", "/")
// TEST
assert.Equal(t, http.StatusOK, w.Code)
@@ -71,7 +71,7 @@ func TestMiddlewareNoRoute(t *testing.T) {
signature += " X "
})
// RUN
- w := performRequest(router, "GET", "/")
+ w := PerformRequest(router, "GET", "/")
// TEST
assert.Equal(t, http.StatusNotFound, w.Code)
@@ -108,7 +108,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
signature += " XX "
})
// RUN
- w := performRequest(router, "GET", "/")
+ w := PerformRequest(router, "GET", "/")
// TEST
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
@@ -149,7 +149,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
})
// RUN
- w := performRequest(router, "GET", "/")
+ w := PerformRequest(router, "GET", "/")
// TEST
assert.Equal(t, http.StatusNotFound, w.Code)
@@ -175,7 +175,7 @@ func TestMiddlewareAbort(t *testing.T) {
})
// RUN
- w := performRequest(router, "GET", "/")
+ w := PerformRequest(router, "GET", "/")
// TEST
assert.Equal(t, http.StatusUnauthorized, w.Code)
@@ -190,14 +190,13 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
c.Next()
c.AbortWithStatus(http.StatusGone)
signature += "B"
-
})
router.GET("/", func(c *Context) {
signature += "C"
c.Next()
})
// RUN
- w := performRequest(router, "GET", "/")
+ w := PerformRequest(router, "GET", "/")
// TEST
assert.Equal(t, http.StatusGone, w.Code)
@@ -220,7 +219,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
signature += "C"
})
// RUN
- w := performRequest(router, "GET", "/")
+ w := PerformRequest(router, "GET", "/")
// TEST
assert.Equal(t, http.StatusInternalServerError, w.Code)
@@ -247,7 +246,7 @@ func TestMiddlewareWrite(t *testing.T) {
})
})
- w := performRequest(router, "GET", "/")
+ w := PerformRequest(router, "GET", "/")
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, strings.Replace("hola\n{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
diff --git a/recovery_test.go b/recovery_test.go
index 24429d58..2c327a65 100644
--- a/recovery_test.go
+++ b/recovery_test.go
@@ -27,7 +27,7 @@ func TestPanicClean(t *testing.T) {
panic("Oupps, Houston, we have a problem")
})
// RUN
- w := performRequest(router, "GET", "/recovery",
+ w := PerformRequest(router, "GET", "/recovery",
header{
Key: "Host",
Value: "www.google.com",
@@ -57,7 +57,7 @@ func TestPanicInHandler(t *testing.T) {
panic("Oupps, Houston, we have a problem")
})
// RUN
- w := performRequest(router, "GET", "/recovery")
+ w := PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, buffer.String(), "panic recovered")
@@ -68,7 +68,7 @@ func TestPanicInHandler(t *testing.T) {
// Debug mode prints the request
SetMode(DebugMode)
// RUN
- w = performRequest(router, "GET", "/recovery")
+ w = PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery")
@@ -85,7 +85,7 @@ func TestPanicWithAbort(t *testing.T) {
panic("Oupps, Houston, we have a problem")
})
// RUN
- w := performRequest(router, "GET", "/recovery")
+ w := PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusBadRequest, w.Code)
}
@@ -122,7 +122,6 @@ func TestPanicWithBrokenPipe(t *testing.T) {
for errno, expectMsg := range expectMsgs {
t.Run(expectMsg, func(t *testing.T) {
-
var buf bytes.Buffer
router := New()
@@ -137,7 +136,7 @@ func TestPanicWithBrokenPipe(t *testing.T) {
panic(e)
})
// RUN
- w := performRequest(router, "GET", "/recovery")
+ w := PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, expectCode, w.Code)
assert.Contains(t, strings.ToLower(buf.String()), expectMsg)
@@ -158,7 +157,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
panic("Oupps, Houston, we have a problem")
})
// RUN
- w := performRequest(router, "GET", "/recovery")
+ w := PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "panic recovered")
@@ -169,7 +168,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
// Debug mode prints the request
SetMode(DebugMode)
// RUN
- w = performRequest(router, "GET", "/recovery")
+ w = PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery")
@@ -193,7 +192,7 @@ func TestCustomRecovery(t *testing.T) {
panic("Oupps, Houston, we have a problem")
})
// RUN
- w := performRequest(router, "GET", "/recovery")
+ w := PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "panic recovered")
@@ -204,7 +203,7 @@ func TestCustomRecovery(t *testing.T) {
// Debug mode prints the request
SetMode(DebugMode)
// RUN
- w = performRequest(router, "GET", "/recovery")
+ w = PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery")
@@ -228,7 +227,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
panic("Oupps, Houston, we have a problem")
})
// RUN
- w := performRequest(router, "GET", "/recovery")
+ w := PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "panic recovered")
@@ -239,7 +238,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
// Debug mode prints the request
SetMode(DebugMode)
// RUN
- w = performRequest(router, "GET", "/recovery")
+ w = PerformRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery")
diff --git a/routergroup.go b/routergroup.go
index 27d7aad6..3fba3a91 100644
--- a/routergroup.go
+++ b/routergroup.go
@@ -12,7 +12,7 @@ import (
)
var (
- // reg match english letters for http method name
+ // regEnLetter matches english letters for http method name
regEnLetter = regexp.MustCompile("^[A-Z]+$")
// anyMethods for RouterGroup Any method
@@ -44,6 +44,7 @@ type IRoutes interface {
HEAD(string, ...HandlerFunc) IRoutes
StaticFile(string, string) IRoutes
+ StaticFileFS(string, string, http.FileSystem) IRoutes
Static(string, string) IRoutes
StaticFS(string, http.FileSystem) IRoutes
}
@@ -153,12 +154,24 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou
// StaticFile registers a single route in order to serve a single file of the local filesystem.
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
+ return group.staticFileHandler(relativePath, func(c *Context) {
+ c.File(filepath)
+ })
+}
+
+// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead..
+// router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false})
+// Gin by default user: gin.Dir()
+func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes {
+ return group.staticFileHandler(relativePath, func(c *Context) {
+ c.FileFromFS(filepath, fs)
+ })
+}
+
+func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes {
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
panic("URL parameters can not be used when serving a static file")
}
- handler := func(c *Context) {
- c.File(filepath)
- }
group.GET(relativePath, handler)
group.HEAD(relativePath, handler)
return group.returnObj()
diff --git a/routergroup_test.go b/routergroup_test.go
index d6d8b452..c1fad3a9 100644
--- a/routergroup_test.go
+++ b/routergroup_test.go
@@ -80,11 +80,11 @@ func performRequestInGroup(t *testing.T, method string) {
panic("unknown method")
}
- w := performRequest(router, method, "/v1/login/test")
+ w := PerformRequest(router, method, "/v1/login/test")
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
- w = performRequest(router, method, "/v1/test")
+ w = PerformRequest(router, method, "/v1/test")
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
}
@@ -111,6 +111,17 @@ func TestRouterGroupInvalidStaticFile(t *testing.T) {
})
}
+func TestRouterGroupInvalidStaticFileFS(t *testing.T) {
+ router := New()
+ assert.Panics(t, func() {
+ router.StaticFileFS("/path/:param", "favicon.ico", Dir(".", false))
+ })
+
+ assert.Panics(t, func() {
+ router.StaticFileFS("/path/*param", "favicon.ico", Dir(".", false))
+ })
+}
+
func TestRouterGroupTooManyHandlers(t *testing.T) {
const (
panicValue = "too many handlers"
@@ -177,6 +188,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) {
assert.Equal(t, r, r.HEAD("/", handler))
assert.Equal(t, r, r.StaticFile("/file", "."))
+ assert.Equal(t, r, r.StaticFileFS("/static2", ".", Dir(".", false)))
assert.Equal(t, r, r.Static("/static", "."))
assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false)))
}
diff --git a/routes_test.go b/routes_test.go
index ffe3469e..56430970 100644
--- a/routes_test.go
+++ b/routes_test.go
@@ -21,7 +21,8 @@ type header struct {
Value string
}
-func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
+// PerformRequest for testing gin router.
+func PerformRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, nil)
for _, h := range headers {
req.Header.Add(h.Key, h.Value)
@@ -42,11 +43,11 @@ func testRouteOK(method string, t *testing.T) {
passed = true
})
- w := performRequest(r, method, "/test")
+ w := PerformRequest(r, method, "/test")
assert.True(t, passed)
assert.Equal(t, http.StatusOK, w.Code)
- performRequest(r, method, "/test2")
+ PerformRequest(r, method, "/test2")
assert.True(t, passedAny)
}
@@ -58,7 +59,7 @@ func testRouteNotOK(method string, t *testing.T) {
passed = true
})
- w := performRequest(router, method, "/test")
+ w := PerformRequest(router, method, "/test")
assert.False(t, passed)
assert.Equal(t, http.StatusNotFound, w.Code)
@@ -79,7 +80,7 @@ func testRouteNotOK2(method string, t *testing.T) {
passed = true
})
- w := performRequest(router, method, "/test")
+ w := PerformRequest(router, method, "/test")
assert.False(t, passed)
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
@@ -99,7 +100,7 @@ func TestRouterMethod(t *testing.T) {
c.String(http.StatusOK, "sup3")
})
- w := performRequest(router, http.MethodPut, "/hey")
+ w := PerformRequest(router, http.MethodPut, "/hey")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "called", w.Body.String())
@@ -150,50 +151,50 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
router.POST("/path3", func(c *Context) {})
router.PUT("/path4/", func(c *Context) {})
- w := performRequest(router, http.MethodGet, "/path/")
+ w := PerformRequest(router, http.MethodGet, "/path/")
assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
- w = performRequest(router, http.MethodGet, "/path2")
+ w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, "/path2/", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
- w = performRequest(router, http.MethodPost, "/path3/")
+ w = PerformRequest(router, http.MethodPost, "/path3/")
assert.Equal(t, "/path3", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
- w = performRequest(router, http.MethodPut, "/path4")
+ w = PerformRequest(router, http.MethodPut, "/path4")
assert.Equal(t, "/path4/", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
- w = performRequest(router, http.MethodGet, "/path")
+ w = PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, http.StatusOK, w.Code)
- w = performRequest(router, http.MethodGet, "/path2/")
+ w = PerformRequest(router, http.MethodGet, "/path2/")
assert.Equal(t, http.StatusOK, w.Code)
- w = performRequest(router, http.MethodPost, "/path3")
+ w = PerformRequest(router, http.MethodPost, "/path3")
assert.Equal(t, http.StatusOK, w.Code)
- w = performRequest(router, http.MethodPut, "/path4/")
+ w = PerformRequest(router, http.MethodPut, "/path4/")
assert.Equal(t, http.StatusOK, w.Code)
- w = performRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
+ w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"})
assert.Equal(t, "/api/path2/", w.Header().Get("Location"))
assert.Equal(t, 301, w.Code)
- w = performRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
+ w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"})
assert.Equal(t, 200, w.Code)
router.RedirectTrailingSlash = false
- w = performRequest(router, http.MethodGet, "/path/")
+ w = PerformRequest(router, http.MethodGet, "/path/")
assert.Equal(t, http.StatusNotFound, w.Code)
- w = performRequest(router, http.MethodGet, "/path2")
+ w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, http.StatusNotFound, w.Code)
- w = performRequest(router, http.MethodPost, "/path3/")
+ w = PerformRequest(router, http.MethodPost, "/path3/")
assert.Equal(t, http.StatusNotFound, w.Code)
- w = performRequest(router, http.MethodPut, "/path4")
+ w = PerformRequest(router, http.MethodPut, "/path4")
assert.Equal(t, http.StatusNotFound, w.Code)
}
@@ -207,19 +208,19 @@ func TestRouteRedirectFixedPath(t *testing.T) {
router.POST("/PATH3", func(c *Context) {})
router.POST("/Path4/", func(c *Context) {})
- w := performRequest(router, http.MethodGet, "/PATH")
+ w := PerformRequest(router, http.MethodGet, "/PATH")
assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
- w = performRequest(router, http.MethodGet, "/path2")
+ w = PerformRequest(router, http.MethodGet, "/path2")
assert.Equal(t, "/Path2", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
- w = performRequest(router, http.MethodPost, "/path3")
+ w = PerformRequest(router, http.MethodPost, "/path3")
assert.Equal(t, "/PATH3", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
- w = performRequest(router, http.MethodPost, "/path4")
+ w = PerformRequest(router, http.MethodPost, "/path4")
assert.Equal(t, "/Path4/", w.Header().Get("Location"))
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
}
@@ -248,7 +249,7 @@ func TestRouteParamsByName(t *testing.T) {
assert.False(t, ok)
})
- w := performRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
+ w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "john", name)
@@ -281,7 +282,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
assert.False(t, ok)
})
- w := performRequest(router, http.MethodGet, "//test//john//smith//is//super//great")
+ w := PerformRequest(router, http.MethodGet, "//test//john//smith//is//super//great")
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "john", name)
@@ -309,16 +310,50 @@ func TestRouteStaticFile(t *testing.T) {
router.Static("/using_static", dir)
router.StaticFile("/result", f.Name())
- w := performRequest(router, http.MethodGet, "/using_static/"+filename)
- w2 := performRequest(router, http.MethodGet, "/result")
+ w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
+ w2 := PerformRequest(router, http.MethodGet, "/result")
assert.Equal(t, w, w2)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "Gin Web Framework", w.Body.String())
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
- w3 := performRequest(router, http.MethodHead, "/using_static/"+filename)
- w4 := performRequest(router, http.MethodHead, "/result")
+ w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
+ w4 := PerformRequest(router, http.MethodHead, "/result")
+
+ assert.Equal(t, w3, w4)
+ assert.Equal(t, http.StatusOK, w3.Code)
+}
+
+// TestHandleStaticFile - ensure the static file handles properly
+func TestRouteStaticFileFS(t *testing.T) {
+ // SETUP file
+ testRoot, _ := os.Getwd()
+ f, err := ioutil.TempFile(testRoot, "")
+ if err != nil {
+ t.Error(err)
+ }
+ defer os.Remove(f.Name())
+ _, err = f.WriteString("Gin Web Framework")
+ assert.NoError(t, err)
+ f.Close()
+
+ dir, filename := filepath.Split(f.Name())
+ // SETUP gin
+ router := New()
+ router.Static("/using_static", dir)
+ router.StaticFileFS("/result_fs", filename, Dir(dir, false))
+
+ w := PerformRequest(router, http.MethodGet, "/using_static/"+filename)
+ w2 := PerformRequest(router, http.MethodGet, "/result_fs")
+
+ assert.Equal(t, w, w2)
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Equal(t, "Gin Web Framework", w.Body.String())
+ assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
+
+ w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename)
+ w4 := PerformRequest(router, http.MethodHead, "/result_fs")
assert.Equal(t, w3, w4)
assert.Equal(t, http.StatusOK, w3.Code)
@@ -329,7 +364,7 @@ func TestRouteStaticListingDir(t *testing.T) {
router := New()
router.StaticFS("/", Dir("./", true))
- w := performRequest(router, http.MethodGet, "/")
+ w := PerformRequest(router, http.MethodGet, "/")
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "gin.go")
@@ -341,7 +376,7 @@ func TestRouteStaticNoListing(t *testing.T) {
router := New()
router.Static("/", "./")
- w := performRequest(router, http.MethodGet, "/")
+ w := PerformRequest(router, http.MethodGet, "/")
assert.Equal(t, http.StatusNotFound, w.Code)
assert.NotContains(t, w.Body.String(), "gin.go")
@@ -356,7 +391,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
})
static.Static("/", "./")
- w := performRequest(router, http.MethodGet, "/gin.go")
+ w := PerformRequest(router, http.MethodGet, "/gin.go")
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "package gin")
@@ -372,13 +407,13 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
router := New()
router.HandleMethodNotAllowed = true
router.POST("/path", func(c *Context) {})
- w := performRequest(router, http.MethodGet, "/path")
+ w := PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText")
})
- w = performRequest(router, http.MethodGet, "/path")
+ w = PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, "responseText", w.Body.String())
assert.Equal(t, http.StatusTeapot, w.Code)
}
@@ -389,7 +424,7 @@ func TestRouteNotAllowedEnabled2(t *testing.T) {
// add one methodTree to trees
router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}})
router.GET("/path2", func(c *Context) {})
- w := performRequest(router, http.MethodPost, "/path2")
+ w := PerformRequest(router, http.MethodPost, "/path2")
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
}
@@ -397,13 +432,13 @@ func TestRouteNotAllowedDisabled(t *testing.T) {
router := New()
router.HandleMethodNotAllowed = false
router.POST("/path", func(c *Context) {})
- w := performRequest(router, http.MethodGet, "/path")
+ w := PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, http.StatusNotFound, w.Code)
router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText")
})
- w = performRequest(router, http.MethodGet, "/path")
+ w = PerformRequest(router, http.MethodGet, "/path")
assert.Equal(t, "404 page not found", w.Body.String())
assert.Equal(t, http.StatusNotFound, w.Code)
}
@@ -423,7 +458,7 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {
{"/nope", http.StatusNotFound, ""}, // NotFound
}
for _, tr := range testRoutes {
- w := performRequest(router, "GET", tr.route)
+ w := PerformRequest(router, "GET", tr.route)
assert.Equal(t, tr.code, w.Code)
if w.Code != http.StatusNotFound {
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
@@ -453,7 +488,7 @@ func TestRouterNotFound(t *testing.T) {
{"/nope", http.StatusNotFound, ""}, // NotFound
}
for _, tr := range testRoutes {
- w := performRequest(router, http.MethodGet, tr.route)
+ w := PerformRequest(router, http.MethodGet, tr.route)
assert.Equal(t, tr.code, w.Code)
if w.Code != http.StatusNotFound {
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
@@ -466,20 +501,20 @@ func TestRouterNotFound(t *testing.T) {
c.AbortWithStatus(http.StatusNotFound)
notFound = true
})
- w := performRequest(router, http.MethodGet, "/nope")
+ w := PerformRequest(router, http.MethodGet, "/nope")
assert.Equal(t, http.StatusNotFound, w.Code)
assert.True(t, notFound)
// Test other method than GET (want 307 instead of 301)
router.PATCH("/path", func(c *Context) {})
- w = performRequest(router, http.MethodPatch, "/path/")
+ w = PerformRequest(router, http.MethodPatch, "/path/")
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
// Test special case where no node for the prefix "/" exists
router = New()
router.GET("/a", func(c *Context) {})
- w = performRequest(router, http.MethodGet, "/")
+ w = PerformRequest(router, http.MethodGet, "/")
assert.Equal(t, http.StatusNotFound, w.Code)
// Reproduction test for the bug of issue #2843
@@ -492,9 +527,9 @@ func TestRouterNotFound(t *testing.T) {
router.GET("/logout", func(c *Context) {
c.String(200, "logout")
})
- w = performRequest(router, http.MethodGet, "/login")
+ w = PerformRequest(router, http.MethodGet, "/login")
assert.Equal(t, "login", w.Body.String())
- w = performRequest(router, http.MethodGet, "/logout")
+ w = PerformRequest(router, http.MethodGet, "/logout")
assert.Equal(t, "logout", w.Body.String())
}
@@ -505,10 +540,10 @@ func TestRouterStaticFSNotFound(t *testing.T) {
c.String(404, "non existent")
})
- w := performRequest(router, http.MethodGet, "/nonexistent")
+ w := PerformRequest(router, http.MethodGet, "/nonexistent")
assert.Equal(t, "non existent", w.Body.String())
- w = performRequest(router, http.MethodHead, "/nonexistent")
+ w = PerformRequest(router, http.MethodHead, "/nonexistent")
assert.Equal(t, "non existent", w.Body.String())
}
@@ -518,7 +553,7 @@ func TestRouterStaticFSFileNotFound(t *testing.T) {
router.StaticFS("/", http.FileSystem(http.Dir(".")))
assert.NotPanics(t, func() {
- performRequest(router, http.MethodGet, "/nonexistent")
+ PerformRequest(router, http.MethodGet, "/nonexistent")
})
}
@@ -535,11 +570,11 @@ func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
// First access
- performRequest(router, http.MethodGet, "/nonexistent")
+ PerformRequest(router, http.MethodGet, "/nonexistent")
assert.Equal(t, 1, middlewareCalledNum)
// Second access
- performRequest(router, http.MethodHead, "/nonexistent")
+ PerformRequest(router, http.MethodHead, "/nonexistent")
assert.Equal(t, 2, middlewareCalledNum)
}
@@ -558,7 +593,7 @@ func TestRouteRawPath(t *testing.T) {
assert.Equal(t, "222", num)
})
- w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222")
+ w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222")
assert.Equal(t, http.StatusOK, w.Code)
}
@@ -578,7 +613,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
assert.Equal(t, "333", num)
})
- w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333")
+ w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333")
assert.Equal(t, http.StatusOK, w.Code)
}
@@ -589,7 +624,7 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) {
c.Next()
})
- w := performRequest(route, http.MethodGet, "/NotFound")
+ w := PerformRequest(route, http.MethodGet, "/NotFound")
assert.Equal(t, 421, w.Code)
assert.Equal(t, 0, w.Body.Len())
}
@@ -623,7 +658,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
}
for _, route := range routes {
- w := performRequest(router, http.MethodGet, route)
+ w := PerformRequest(router, http.MethodGet, route)
assert.Equal(t, http.StatusOK, w.Code)
}
@@ -633,6 +668,6 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
assert.Equal(t, "", c.FullPath())
})
- w := performRequest(router, http.MethodGet, "/not-found")
+ w := PerformRequest(router, http.MethodGet, "/not-found")
assert.Equal(t, http.StatusNotFound, w.Code)
}
diff --git a/tree.go b/tree.go
index a30d3496..88100eec 100644
--- a/tree.go
+++ b/tree.go
@@ -81,7 +81,7 @@ func longestCommonPrefix(a, b string) int {
return i
}
-// addChild will add a child node, keeping wildcards at the end
+// addChild will add a child node, keeping wildcardChild at the end
func (n *node) addChild(child *node) {
if n.wildChild && len(n.children) > 0 {
wildcardChild := n.children[len(n.children)-1]
@@ -296,7 +296,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
break
}
- // The wildcard name must not contain ':' and '*'
+ // The wildcard name must only contain one ':' or '*' character
if !valid {
panic("only one wildcard per path segment is allowed, has: '" +
wildcard + "' in path '" + fullPath + "'")
@@ -325,7 +325,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
n.priority++
// if the path doesn't end with the wildcard, then there
- // will be another non-wildcard subpath starting with '/'
+ // will be another subpath starting with '/'
if len(wildcard) < len(path) {
path = path[len(wildcard):]
diff --git a/utils.go b/utils.go
index 6c9a2cfa..b41a3b0f 100644
--- a/utils.go
+++ b/utils.go
@@ -12,6 +12,7 @@ import (
"reflect"
"runtime"
"strings"
+ "unicode"
)
// BindKey indicates a default bind key.
@@ -151,3 +152,13 @@ func resolveAddress(addr []string) string {
panic("too many parameters")
}
}
+
+// https://stackoverflow.com/questions/53069040/checking-a-string-contains-only-ascii-characters
+func isASCII(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if s[i] > unicode.MaxASCII {
+ return false
+ }
+ }
+ return true
+}
diff --git a/utils_test.go b/utils_test.go
index cc486c35..d2a740bf 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -45,11 +45,11 @@ func TestWrap(t *testing.T) {
fmt.Fprint(w, "hola!")
}))
- w := performRequest(router, "POST", "/path")
+ w := PerformRequest(router, "POST", "/path")
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, "hello", w.Body.String())
- w = performRequest(router, "GET", "/path2")
+ w = PerformRequest(router, "GET", "/path2")
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "hola!", w.Body.String())
}
@@ -119,13 +119,13 @@ func TestBindMiddleware(t *testing.T) {
called = true
value = c.MustGet(BindKey).(*bindTestStruct)
})
- performRequest(router, "GET", "/?foo=hola&bar=10")
+ PerformRequest(router, "GET", "/?foo=hola&bar=10")
assert.True(t, called)
assert.Equal(t, "hola", value.Foo)
assert.Equal(t, 10, value.Bar)
called = false
- performRequest(router, "GET", "/?foo=hola&bar=1")
+ PerformRequest(router, "GET", "/?foo=hola&bar=1")
assert.False(t, called)
assert.Panics(t, func() {
@@ -143,3 +143,8 @@ func TestMarshalXMLforH(t *testing.T) {
e := h.MarshalXML(enc, x)
assert.Error(t, e)
}
+
+func TestIsASCII(t *testing.T) {
+ assert.Equal(t, isASCII("test"), true)
+ assert.Equal(t, isASCII("π§‘ππππ"), false)
+}