mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-18 23:12:17 +08:00
commit
3603be9c57
@ -3,7 +3,6 @@ language: go
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- go: 1.10.x
|
||||
- go: 1.11.x
|
||||
env: GO111MODULE=on
|
||||
- go: 1.12.x
|
||||
@ -18,7 +17,7 @@ before_install:
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi
|
||||
|
||||
install:
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then go mod download; else make install; fi
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi
|
||||
|
||||
|
1135
BENCHMARKS.md
1135
BENCHMARKS.md
File diff suppressed because it is too large
Load Diff
38
CHANGELOG.md
38
CHANGELOG.md
@ -1,5 +1,39 @@
|
||||
### Gin v1.5.0
|
||||
|
||||
### Gin 1.4.0
|
||||
- [FIX] Use DefaultWriter and DefaultErrorWriter for debug messages [#1891](https://github.com/gin-gonic/gin/pull/1891)
|
||||
- [NEW] Now you can parse the inline lowercase start structure [#1893](https://github.com/gin-gonic/gin/pull/1893)
|
||||
- [FIX] Some code improvements [#1909](https://github.com/gin-gonic/gin/pull/1909)
|
||||
- [FIX] Use encode replace json marshal increase json encoder speed [#1546](https://github.com/gin-gonic/gin/pull/1546)
|
||||
- [NEW] Hold matched route full path in the Context [#1826](https://github.com/gin-gonic/gin/pull/1826)
|
||||
- [FIX] Fix context.Params race condition on Copy() [#1841](https://github.com/gin-gonic/gin/pull/1841)
|
||||
- [NEW] Add context param query cache [#1450](https://github.com/gin-gonic/gin/pull/1450)
|
||||
- [FIX] Improve GetQueryMap performance [#1918](https://github.com/gin-gonic/gin/pull/1918)
|
||||
- [FIX] Improve get post data [#1920](https://github.com/gin-gonic/gin/pull/1920)
|
||||
- [FIX] Use context instead of x/net/context [#1922](https://github.com/gin-gonic/gin/pull/1922)
|
||||
- [FIX] Attempt to fix PostForm cache bug [#1931](https://github.com/gin-gonic/gin/pull/1931)
|
||||
- [NEW] Add support of multipart multi files [#1949](https://github.com/gin-gonic/gin/pull/1949)
|
||||
- [NEW] Support bind http header param [#1957](https://github.com/gin-gonic/gin/pull/1957)
|
||||
- [FIX] Drop support for go1.8 and go1.9 [#1933](https://github.com/gin-gonic/gin/pull/1933)
|
||||
- [FIX] Bugfix for the FullPath feature [#1919](https://github.com/gin-gonic/gin/pull/1919)
|
||||
- [FIX] Gin1.5 bytes.Buffer to strings.Builder [#1939](https://github.com/gin-gonic/gin/pull/1939)
|
||||
- [FIX] Upgrade github.com/ugorji/go/codec [#1969](https://github.com/gin-gonic/gin/pull/1969)
|
||||
- [NEW] Support bind unix time [#1980](https://github.com/gin-gonic/gin/pull/1980)
|
||||
- [FIX] Simplify code [#2004](https://github.com/gin-gonic/gin/pull/2004)
|
||||
- [NEW] Support negative Content-Length in DataFromReader [#1981](https://github.com/gin-gonic/gin/pull/1981)
|
||||
- [FIX] Identify terminal on a RISC-V architecture for auto-colored logs [#2019](https://github.com/gin-gonic/gin/pull/2019)
|
||||
- [BREAKING] `Context.JSONP()` now expects a semicolon (`;`) at the end [#2007](https://github.com/gin-gonic/gin/pull/2007)
|
||||
- [BREAKING] Upgrade default `binding.Validator` to v9 (see [its changelog](https://github.com/go-playground/validator/releases/tag/v9.0.0)) [#1015](https://github.com/gin-gonic/gin/pull/1015)
|
||||
- [NEW] Add `DisallowUnknownFields()` in `Context.BindJSON()` [#2028](https://github.com/gin-gonic/gin/pull/2028)
|
||||
- [NEW] Use specific `net.Listener` with `Engine.RunListener()` [#2023](https://github.com/gin-gonic/gin/pull/2023)
|
||||
- [FIX] Fix some typo [#2079](https://github.com/gin-gonic/gin/pull/2079) [#2080](https://github.com/gin-gonic/gin/pull/2080)
|
||||
- [FIX] Relocate binding body tests [#2086](https://github.com/gin-gonic/gin/pull/2086)
|
||||
- [FIX] Use Writer in Context.Status [#1606](https://github.com/gin-gonic/gin/pull/1606)
|
||||
- [FIX] `Engine.RunUnix()` now returns the error if it can't change the file mode [#2093](https://github.com/gin-gonic/gin/pull/2093)
|
||||
- [FIX] `RouterGroup.StaticFS()` leaked files. Now it closes them. [#2118](https://github.com/gin-gonic/gin/pull/2118)
|
||||
- [FIX] `Context.Request.FormFile` leaked file. Now it closes it. [#2114](https://github.com/gin-gonic/gin/pull/2114)
|
||||
- [FIX] Ignore walking on `form:"-"` mapping [#1943](https://github.com/gin-gonic/gin/pull/1943)
|
||||
|
||||
### Gin v1.4.0
|
||||
|
||||
- [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569)
|
||||
- [NEW] Refactor of form mapping multipart request [#1829](https://github.com/gin-gonic/gin/pull/1829)
|
||||
@ -56,7 +90,7 @@
|
||||
- [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491)
|
||||
|
||||
|
||||
### Gin 1.3.0
|
||||
### Gin v1.3.0
|
||||
|
||||
- [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383)
|
||||
- [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358)
|
||||
|
16
Makefile
16
Makefile
@ -1,15 +1,10 @@
|
||||
GO ?= go
|
||||
GOFMT ?= gofmt "-s"
|
||||
PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/)
|
||||
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/ | grep -v /examples/)
|
||||
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
|
||||
PACKAGES ?= $(shell $(GO) list ./...)
|
||||
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
|
||||
GOFILES := $(shell find . -name "*.go")
|
||||
TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples)
|
||||
|
||||
all: install
|
||||
|
||||
install: deps
|
||||
govendor sync
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
echo "mode: count" > coverage.out
|
||||
@ -48,11 +43,6 @@ fmt-check:
|
||||
vet:
|
||||
$(GO) vet $(VETPACKAGES)
|
||||
|
||||
deps:
|
||||
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/kardianos/govendor; \
|
||||
fi
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
|
43
README.md
43
README.md
@ -11,7 +11,7 @@
|
||||
[](https://www.codetriage.com/gin-gonic/gin)
|
||||
[](https://github.com/gin-gonic/gin/releases)
|
||||
|
||||
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 performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
||||
|
||||
|
||||
## Contents
|
||||
@ -70,7 +70,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
|
||||
To install Gin package, you need to install Go and set your Go workspace first.
|
||||
|
||||
1. The first need [Go](https://golang.org/) installed (**version 1.10+ is required**), then you can use the below Go command to install Gin.
|
||||
1. The first need [Go](https://golang.org/) installed (**version 1.11+ is required**), then you can use the below Go command to install Gin.
|
||||
|
||||
```sh
|
||||
$ go get -u github.com/gin-gonic/gin
|
||||
@ -88,44 +88,6 @@ import "github.com/gin-gonic/gin"
|
||||
import "net/http"
|
||||
```
|
||||
|
||||
### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor)
|
||||
|
||||
1. `go get` govendor
|
||||
|
||||
```sh
|
||||
$ go get github.com/kardianos/govendor
|
||||
```
|
||||
2. Create your project folder and `cd` inside
|
||||
|
||||
```sh
|
||||
$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
|
||||
```
|
||||
|
||||
If you are on a Mac and you're installing Go 1.8 (released: Feb 2017) or later, GOPATH is automatically determined by the Go toolchain for you. It defaults to $HOME/go on macOS so you can create your project like this
|
||||
|
||||
```sh
|
||||
$ mkdir -p $HOME/go/src/github.com/myusername/project && cd "$_"
|
||||
```
|
||||
|
||||
3. Vendor init your project and add gin
|
||||
|
||||
```sh
|
||||
$ govendor init
|
||||
$ govendor fetch github.com/gin-gonic/gin@v1.3
|
||||
```
|
||||
|
||||
4. Copy a starting template inside your project
|
||||
|
||||
```sh
|
||||
$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go
|
||||
```
|
||||
|
||||
5. Run your project
|
||||
|
||||
```sh
|
||||
$ go run main.go
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
@ -2134,3 +2096,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
|
||||
* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares.
|
||||
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
||||
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
||||
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
||||
|
@ -84,7 +84,7 @@ var (
|
||||
// Default returns the appropriate Binding instance based on the HTTP method
|
||||
// and the content type.
|
||||
func Default(method, contentType string) Binding {
|
||||
if method == "GET" {
|
||||
if method == http.MethodGet {
|
||||
return Form
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type defaultValidator struct {
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const defaultMemory = 32 * 1024 * 1024
|
||||
const defaultMemory = 32 << 20
|
||||
|
||||
type formBinding struct{}
|
||||
type formPostBinding struct{}
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
type testInterface interface {
|
||||
|
@ -1799,6 +1799,23 @@ func TestContextRenderDataFromReader(t *testing.T) {
|
||||
assert.Equal(t, extraHeaders["Content-Disposition"], w.Header().Get("Content-Disposition"))
|
||||
}
|
||||
|
||||
func TestContextRenderDataFromReaderNoHeaders(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
body := "#!PNG some raw data"
|
||||
reader := strings.NewReader(body)
|
||||
contentLength := int64(len(body))
|
||||
contentType := "image/png"
|
||||
|
||||
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, nil)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, body, w.Body.String())
|
||||
assert.Equal(t, contentType, w.Header().Get("Content-Type"))
|
||||
assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length"))
|
||||
}
|
||||
|
||||
type TestResponseRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
closeChannel chan bool
|
||||
|
2
debug.go
2
debug.go
@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) {
|
||||
|
||||
func debugPrintWARNINGDefault() {
|
||||
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
||||
debugPrint(`[WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon.
|
||||
debugPrint(`[WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.
|
||||
|
||||
`)
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
||||
})
|
||||
m, e := getMinVer(runtime.Version())
|
||||
if e == nil && m <= ginSupportMinGoVer {
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||
} else {
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||
}
|
||||
|
41
gin.go
41
gin.go
@ -97,6 +97,10 @@ type Engine struct {
|
||||
// method call.
|
||||
MaxMultipartMemory int64
|
||||
|
||||
// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
|
||||
// See the PR #1817 and issue #1644
|
||||
RemoveExtraSlash bool
|
||||
|
||||
delims render.Delims
|
||||
secureJsonPrefix string
|
||||
HTMLRender render.HTMLRender
|
||||
@ -134,6 +138,7 @@ func New() *Engine {
|
||||
ForwardedByClientIP: true,
|
||||
AppEngine: defaultAppEngine,
|
||||
UseRawPath: false,
|
||||
RemoveExtraSlash: false,
|
||||
UnescapePathValues: true,
|
||||
MaxMultipartMemory: defaultMultipartMemory,
|
||||
trees: make(methodTrees, 0, 9),
|
||||
@ -385,7 +390,10 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
rPath = c.Request.URL.RawPath
|
||||
unescape = engine.UnescapePathValues
|
||||
}
|
||||
rPath = cleanPath(rPath)
|
||||
|
||||
if engine.RemoveExtraSlash {
|
||||
rPath = cleanPath(rPath)
|
||||
}
|
||||
|
||||
// Find root of the tree for the given HTTP method
|
||||
t := engine.trees
|
||||
@ -457,18 +465,11 @@ func redirectTrailingSlash(c *Context) {
|
||||
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
|
||||
p = prefix + "/" + req.URL.Path
|
||||
}
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
if req.Method != "GET" {
|
||||
code = http.StatusTemporaryRedirect
|
||||
}
|
||||
|
||||
req.URL.Path = p + "/"
|
||||
if length := len(p); length > 1 && p[length-1] == '/' {
|
||||
req.URL.Path = p[:length-1]
|
||||
}
|
||||
debugPrint("redirecting request %d: %s --> %s", code, p, req.URL.String())
|
||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||
c.writermem.WriteHeaderNow()
|
||||
redirectRequest(c)
|
||||
}
|
||||
|
||||
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||
@ -476,15 +477,23 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||
rPath := req.URL.Path
|
||||
|
||||
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
if req.Method != "GET" {
|
||||
code = http.StatusTemporaryRedirect
|
||||
}
|
||||
req.URL.Path = string(fixedPath)
|
||||
debugPrint("redirecting request %d: %s --> %s", code, rPath, req.URL.String())
|
||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||
c.writermem.WriteHeaderNow()
|
||||
redirectRequest(c)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func redirectRequest(c *Context) {
|
||||
req := c.Request
|
||||
rPath := req.URL.Path
|
||||
rURL := req.URL.String()
|
||||
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
if req.Method != http.MethodGet {
|
||||
code = http.StatusTemporaryRedirect
|
||||
}
|
||||
debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
|
||||
http.Redirect(c.Writer, req, rURL, code)
|
||||
c.writermem.WriteHeaderNow()
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ func TestConcurrentHandleContext(t *testing.T) {
|
||||
// }
|
||||
|
||||
func testGetRequestHandler(t *testing.T, h http.Handler, url string) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
@ -24,265 +24,265 @@ type route struct {
|
||||
// http://developer.github.com/v3/
|
||||
var githubAPI = []route{
|
||||
// OAuth Authorizations
|
||||
{"GET", "/authorizations"},
|
||||
{"GET", "/authorizations/:id"},
|
||||
{"POST", "/authorizations"},
|
||||
//{"PUT", "/authorizations/clients/:client_id"},
|
||||
//{"PATCH", "/authorizations/:id"},
|
||||
{"DELETE", "/authorizations/:id"},
|
||||
{"GET", "/applications/:client_id/tokens/:access_token"},
|
||||
{"DELETE", "/applications/:client_id/tokens"},
|
||||
{"DELETE", "/applications/:client_id/tokens/:access_token"},
|
||||
{http.MethodGet, "/authorizations"},
|
||||
{http.MethodGet, "/authorizations/:id"},
|
||||
{http.MethodPost, "/authorizations"},
|
||||
//{http.MethodPut, "/authorizations/clients/:client_id"},
|
||||
//{http.MethodPatch, "/authorizations/:id"},
|
||||
{http.MethodDelete, "/authorizations/:id"},
|
||||
{http.MethodGet, "/applications/:client_id/tokens/:access_token"},
|
||||
{http.MethodDelete, "/applications/:client_id/tokens"},
|
||||
{http.MethodDelete, "/applications/:client_id/tokens/:access_token"},
|
||||
|
||||
// Activity
|
||||
{"GET", "/events"},
|
||||
{"GET", "/repos/:owner/:repo/events"},
|
||||
{"GET", "/networks/:owner/:repo/events"},
|
||||
{"GET", "/orgs/:org/events"},
|
||||
{"GET", "/users/:user/received_events"},
|
||||
{"GET", "/users/:user/received_events/public"},
|
||||
{"GET", "/users/:user/events"},
|
||||
{"GET", "/users/:user/events/public"},
|
||||
{"GET", "/users/:user/events/orgs/:org"},
|
||||
{"GET", "/feeds"},
|
||||
{"GET", "/notifications"},
|
||||
{"GET", "/repos/:owner/:repo/notifications"},
|
||||
{"PUT", "/notifications"},
|
||||
{"PUT", "/repos/:owner/:repo/notifications"},
|
||||
{"GET", "/notifications/threads/:id"},
|
||||
//{"PATCH", "/notifications/threads/:id"},
|
||||
{"GET", "/notifications/threads/:id/subscription"},
|
||||
{"PUT", "/notifications/threads/:id/subscription"},
|
||||
{"DELETE", "/notifications/threads/:id/subscription"},
|
||||
{"GET", "/repos/:owner/:repo/stargazers"},
|
||||
{"GET", "/users/:user/starred"},
|
||||
{"GET", "/user/starred"},
|
||||
{"GET", "/user/starred/:owner/:repo"},
|
||||
{"PUT", "/user/starred/:owner/:repo"},
|
||||
{"DELETE", "/user/starred/:owner/:repo"},
|
||||
{"GET", "/repos/:owner/:repo/subscribers"},
|
||||
{"GET", "/users/:user/subscriptions"},
|
||||
{"GET", "/user/subscriptions"},
|
||||
{"GET", "/repos/:owner/:repo/subscription"},
|
||||
{"PUT", "/repos/:owner/:repo/subscription"},
|
||||
{"DELETE", "/repos/:owner/:repo/subscription"},
|
||||
{"GET", "/user/subscriptions/:owner/:repo"},
|
||||
{"PUT", "/user/subscriptions/:owner/:repo"},
|
||||
{"DELETE", "/user/subscriptions/:owner/:repo"},
|
||||
{http.MethodGet, "/events"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/events"},
|
||||
{http.MethodGet, "/networks/:owner/:repo/events"},
|
||||
{http.MethodGet, "/orgs/:org/events"},
|
||||
{http.MethodGet, "/users/:user/received_events"},
|
||||
{http.MethodGet, "/users/:user/received_events/public"},
|
||||
{http.MethodGet, "/users/:user/events"},
|
||||
{http.MethodGet, "/users/:user/events/public"},
|
||||
{http.MethodGet, "/users/:user/events/orgs/:org"},
|
||||
{http.MethodGet, "/feeds"},
|
||||
{http.MethodGet, "/notifications"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/notifications"},
|
||||
{http.MethodPut, "/notifications"},
|
||||
{http.MethodPut, "/repos/:owner/:repo/notifications"},
|
||||
{http.MethodGet, "/notifications/threads/:id"},
|
||||
//{http.MethodPatch, "/notifications/threads/:id"},
|
||||
{http.MethodGet, "/notifications/threads/:id/subscription"},
|
||||
{http.MethodPut, "/notifications/threads/:id/subscription"},
|
||||
{http.MethodDelete, "/notifications/threads/:id/subscription"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/stargazers"},
|
||||
{http.MethodGet, "/users/:user/starred"},
|
||||
{http.MethodGet, "/user/starred"},
|
||||
{http.MethodGet, "/user/starred/:owner/:repo"},
|
||||
{http.MethodPut, "/user/starred/:owner/:repo"},
|
||||
{http.MethodDelete, "/user/starred/:owner/:repo"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/subscribers"},
|
||||
{http.MethodGet, "/users/:user/subscriptions"},
|
||||
{http.MethodGet, "/user/subscriptions"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/subscription"},
|
||||
{http.MethodPut, "/repos/:owner/:repo/subscription"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/subscription"},
|
||||
{http.MethodGet, "/user/subscriptions/:owner/:repo"},
|
||||
{http.MethodPut, "/user/subscriptions/:owner/:repo"},
|
||||
{http.MethodDelete, "/user/subscriptions/:owner/:repo"},
|
||||
|
||||
// Gists
|
||||
{"GET", "/users/:user/gists"},
|
||||
{"GET", "/gists"},
|
||||
//{"GET", "/gists/public"},
|
||||
//{"GET", "/gists/starred"},
|
||||
{"GET", "/gists/:id"},
|
||||
{"POST", "/gists"},
|
||||
//{"PATCH", "/gists/:id"},
|
||||
{"PUT", "/gists/:id/star"},
|
||||
{"DELETE", "/gists/:id/star"},
|
||||
{"GET", "/gists/:id/star"},
|
||||
{"POST", "/gists/:id/forks"},
|
||||
{"DELETE", "/gists/:id"},
|
||||
{http.MethodGet, "/users/:user/gists"},
|
||||
{http.MethodGet, "/gists"},
|
||||
//{http.MethodGet, "/gists/public"},
|
||||
//{http.MethodGet, "/gists/starred"},
|
||||
{http.MethodGet, "/gists/:id"},
|
||||
{http.MethodPost, "/gists"},
|
||||
//{http.MethodPatch, "/gists/:id"},
|
||||
{http.MethodPut, "/gists/:id/star"},
|
||||
{http.MethodDelete, "/gists/:id/star"},
|
||||
{http.MethodGet, "/gists/:id/star"},
|
||||
{http.MethodPost, "/gists/:id/forks"},
|
||||
{http.MethodDelete, "/gists/:id"},
|
||||
|
||||
// Git Data
|
||||
{"GET", "/repos/:owner/:repo/git/blobs/:sha"},
|
||||
{"POST", "/repos/:owner/:repo/git/blobs"},
|
||||
{"GET", "/repos/:owner/:repo/git/commits/:sha"},
|
||||
{"POST", "/repos/:owner/:repo/git/commits"},
|
||||
//{"GET", "/repos/:owner/:repo/git/refs/*ref"},
|
||||
{"GET", "/repos/:owner/:repo/git/refs"},
|
||||
{"POST", "/repos/:owner/:repo/git/refs"},
|
||||
//{"PATCH", "/repos/:owner/:repo/git/refs/*ref"},
|
||||
//{"DELETE", "/repos/:owner/:repo/git/refs/*ref"},
|
||||
{"GET", "/repos/:owner/:repo/git/tags/:sha"},
|
||||
{"POST", "/repos/:owner/:repo/git/tags"},
|
||||
{"GET", "/repos/:owner/:repo/git/trees/:sha"},
|
||||
{"POST", "/repos/:owner/:repo/git/trees"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/git/blobs/:sha"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/git/blobs"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/git/commits/:sha"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/git/commits"},
|
||||
//{http.MethodGet, "/repos/:owner/:repo/git/refs/*ref"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/git/refs"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/git/refs"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/git/refs/*ref"},
|
||||
//{http.MethodDelete, "/repos/:owner/:repo/git/refs/*ref"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/git/tags/:sha"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/git/tags"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/git/trees/:sha"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/git/trees"},
|
||||
|
||||
// Issues
|
||||
{"GET", "/issues"},
|
||||
{"GET", "/user/issues"},
|
||||
{"GET", "/orgs/:org/issues"},
|
||||
{"GET", "/repos/:owner/:repo/issues"},
|
||||
{"GET", "/repos/:owner/:repo/issues/:number"},
|
||||
{"POST", "/repos/:owner/:repo/issues"},
|
||||
//{"PATCH", "/repos/:owner/:repo/issues/:number"},
|
||||
{"GET", "/repos/:owner/:repo/assignees"},
|
||||
{"GET", "/repos/:owner/:repo/assignees/:assignee"},
|
||||
{"GET", "/repos/:owner/:repo/issues/:number/comments"},
|
||||
//{"GET", "/repos/:owner/:repo/issues/comments"},
|
||||
//{"GET", "/repos/:owner/:repo/issues/comments/:id"},
|
||||
{"POST", "/repos/:owner/:repo/issues/:number/comments"},
|
||||
//{"PATCH", "/repos/:owner/:repo/issues/comments/:id"},
|
||||
//{"DELETE", "/repos/:owner/:repo/issues/comments/:id"},
|
||||
{"GET", "/repos/:owner/:repo/issues/:number/events"},
|
||||
//{"GET", "/repos/:owner/:repo/issues/events"},
|
||||
//{"GET", "/repos/:owner/:repo/issues/events/:id"},
|
||||
{"GET", "/repos/:owner/:repo/labels"},
|
||||
{"GET", "/repos/:owner/:repo/labels/:name"},
|
||||
{"POST", "/repos/:owner/:repo/labels"},
|
||||
//{"PATCH", "/repos/:owner/:repo/labels/:name"},
|
||||
{"DELETE", "/repos/:owner/:repo/labels/:name"},
|
||||
{"GET", "/repos/:owner/:repo/issues/:number/labels"},
|
||||
{"POST", "/repos/:owner/:repo/issues/:number/labels"},
|
||||
{"DELETE", "/repos/:owner/:repo/issues/:number/labels/:name"},
|
||||
{"PUT", "/repos/:owner/:repo/issues/:number/labels"},
|
||||
{"DELETE", "/repos/:owner/:repo/issues/:number/labels"},
|
||||
{"GET", "/repos/:owner/:repo/milestones/:number/labels"},
|
||||
{"GET", "/repos/:owner/:repo/milestones"},
|
||||
{"GET", "/repos/:owner/:repo/milestones/:number"},
|
||||
{"POST", "/repos/:owner/:repo/milestones"},
|
||||
//{"PATCH", "/repos/:owner/:repo/milestones/:number"},
|
||||
{"DELETE", "/repos/:owner/:repo/milestones/:number"},
|
||||
{http.MethodGet, "/issues"},
|
||||
{http.MethodGet, "/user/issues"},
|
||||
{http.MethodGet, "/orgs/:org/issues"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/issues"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/issues/:number"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/issues"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/issues/:number"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/assignees"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/assignees/:assignee"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/issues/:number/comments"},
|
||||
//{http.MethodGet, "/repos/:owner/:repo/issues/comments"},
|
||||
//{http.MethodGet, "/repos/:owner/:repo/issues/comments/:id"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/issues/:number/comments"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/issues/comments/:id"},
|
||||
//{http.MethodDelete, "/repos/:owner/:repo/issues/comments/:id"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/issues/:number/events"},
|
||||
//{http.MethodGet, "/repos/:owner/:repo/issues/events"},
|
||||
//{http.MethodGet, "/repos/:owner/:repo/issues/events/:id"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/labels"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/labels/:name"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/labels"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/labels/:name"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/labels/:name"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/issues/:number/labels"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/issues/:number/labels"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/issues/:number/labels/:name"},
|
||||
{http.MethodPut, "/repos/:owner/:repo/issues/:number/labels"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/issues/:number/labels"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/milestones/:number/labels"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/milestones"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/milestones/:number"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/milestones"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/milestones/:number"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/milestones/:number"},
|
||||
|
||||
// Miscellaneous
|
||||
{"GET", "/emojis"},
|
||||
{"GET", "/gitignore/templates"},
|
||||
{"GET", "/gitignore/templates/:name"},
|
||||
{"POST", "/markdown"},
|
||||
{"POST", "/markdown/raw"},
|
||||
{"GET", "/meta"},
|
||||
{"GET", "/rate_limit"},
|
||||
{http.MethodGet, "/emojis"},
|
||||
{http.MethodGet, "/gitignore/templates"},
|
||||
{http.MethodGet, "/gitignore/templates/:name"},
|
||||
{http.MethodPost, "/markdown"},
|
||||
{http.MethodPost, "/markdown/raw"},
|
||||
{http.MethodGet, "/meta"},
|
||||
{http.MethodGet, "/rate_limit"},
|
||||
|
||||
// Organizations
|
||||
{"GET", "/users/:user/orgs"},
|
||||
{"GET", "/user/orgs"},
|
||||
{"GET", "/orgs/:org"},
|
||||
//{"PATCH", "/orgs/:org"},
|
||||
{"GET", "/orgs/:org/members"},
|
||||
{"GET", "/orgs/:org/members/:user"},
|
||||
{"DELETE", "/orgs/:org/members/:user"},
|
||||
{"GET", "/orgs/:org/public_members"},
|
||||
{"GET", "/orgs/:org/public_members/:user"},
|
||||
{"PUT", "/orgs/:org/public_members/:user"},
|
||||
{"DELETE", "/orgs/:org/public_members/:user"},
|
||||
{"GET", "/orgs/:org/teams"},
|
||||
{"GET", "/teams/:id"},
|
||||
{"POST", "/orgs/:org/teams"},
|
||||
//{"PATCH", "/teams/:id"},
|
||||
{"DELETE", "/teams/:id"},
|
||||
{"GET", "/teams/:id/members"},
|
||||
{"GET", "/teams/:id/members/:user"},
|
||||
{"PUT", "/teams/:id/members/:user"},
|
||||
{"DELETE", "/teams/:id/members/:user"},
|
||||
{"GET", "/teams/:id/repos"},
|
||||
{"GET", "/teams/:id/repos/:owner/:repo"},
|
||||
{"PUT", "/teams/:id/repos/:owner/:repo"},
|
||||
{"DELETE", "/teams/:id/repos/:owner/:repo"},
|
||||
{"GET", "/user/teams"},
|
||||
{http.MethodGet, "/users/:user/orgs"},
|
||||
{http.MethodGet, "/user/orgs"},
|
||||
{http.MethodGet, "/orgs/:org"},
|
||||
//{http.MethodPatch, "/orgs/:org"},
|
||||
{http.MethodGet, "/orgs/:org/members"},
|
||||
{http.MethodGet, "/orgs/:org/members/:user"},
|
||||
{http.MethodDelete, "/orgs/:org/members/:user"},
|
||||
{http.MethodGet, "/orgs/:org/public_members"},
|
||||
{http.MethodGet, "/orgs/:org/public_members/:user"},
|
||||
{http.MethodPut, "/orgs/:org/public_members/:user"},
|
||||
{http.MethodDelete, "/orgs/:org/public_members/:user"},
|
||||
{http.MethodGet, "/orgs/:org/teams"},
|
||||
{http.MethodGet, "/teams/:id"},
|
||||
{http.MethodPost, "/orgs/:org/teams"},
|
||||
//{http.MethodPatch, "/teams/:id"},
|
||||
{http.MethodDelete, "/teams/:id"},
|
||||
{http.MethodGet, "/teams/:id/members"},
|
||||
{http.MethodGet, "/teams/:id/members/:user"},
|
||||
{http.MethodPut, "/teams/:id/members/:user"},
|
||||
{http.MethodDelete, "/teams/:id/members/:user"},
|
||||
{http.MethodGet, "/teams/:id/repos"},
|
||||
{http.MethodGet, "/teams/:id/repos/:owner/:repo"},
|
||||
{http.MethodPut, "/teams/:id/repos/:owner/:repo"},
|
||||
{http.MethodDelete, "/teams/:id/repos/:owner/:repo"},
|
||||
{http.MethodGet, "/user/teams"},
|
||||
|
||||
// Pull Requests
|
||||
{"GET", "/repos/:owner/:repo/pulls"},
|
||||
{"GET", "/repos/:owner/:repo/pulls/:number"},
|
||||
{"POST", "/repos/:owner/:repo/pulls"},
|
||||
//{"PATCH", "/repos/:owner/:repo/pulls/:number"},
|
||||
{"GET", "/repos/:owner/:repo/pulls/:number/commits"},
|
||||
{"GET", "/repos/:owner/:repo/pulls/:number/files"},
|
||||
{"GET", "/repos/:owner/:repo/pulls/:number/merge"},
|
||||
{"PUT", "/repos/:owner/:repo/pulls/:number/merge"},
|
||||
{"GET", "/repos/:owner/:repo/pulls/:number/comments"},
|
||||
//{"GET", "/repos/:owner/:repo/pulls/comments"},
|
||||
//{"GET", "/repos/:owner/:repo/pulls/comments/:number"},
|
||||
{"PUT", "/repos/:owner/:repo/pulls/:number/comments"},
|
||||
//{"PATCH", "/repos/:owner/:repo/pulls/comments/:number"},
|
||||
//{"DELETE", "/repos/:owner/:repo/pulls/comments/:number"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/pulls"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/pulls/:number"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/pulls"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/pulls/:number"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/pulls/:number/commits"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/pulls/:number/files"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/pulls/:number/merge"},
|
||||
{http.MethodPut, "/repos/:owner/:repo/pulls/:number/merge"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/pulls/:number/comments"},
|
||||
//{http.MethodGet, "/repos/:owner/:repo/pulls/comments"},
|
||||
//{http.MethodGet, "/repos/:owner/:repo/pulls/comments/:number"},
|
||||
{http.MethodPut, "/repos/:owner/:repo/pulls/:number/comments"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/pulls/comments/:number"},
|
||||
//{http.MethodDelete, "/repos/:owner/:repo/pulls/comments/:number"},
|
||||
|
||||
// Repositories
|
||||
{"GET", "/user/repos"},
|
||||
{"GET", "/users/:user/repos"},
|
||||
{"GET", "/orgs/:org/repos"},
|
||||
{"GET", "/repositories"},
|
||||
{"POST", "/user/repos"},
|
||||
{"POST", "/orgs/:org/repos"},
|
||||
{"GET", "/repos/:owner/:repo"},
|
||||
//{"PATCH", "/repos/:owner/:repo"},
|
||||
{"GET", "/repos/:owner/:repo/contributors"},
|
||||
{"GET", "/repos/:owner/:repo/languages"},
|
||||
{"GET", "/repos/:owner/:repo/teams"},
|
||||
{"GET", "/repos/:owner/:repo/tags"},
|
||||
{"GET", "/repos/:owner/:repo/branches"},
|
||||
{"GET", "/repos/:owner/:repo/branches/:branch"},
|
||||
{"DELETE", "/repos/:owner/:repo"},
|
||||
{"GET", "/repos/:owner/:repo/collaborators"},
|
||||
{"GET", "/repos/:owner/:repo/collaborators/:user"},
|
||||
{"PUT", "/repos/:owner/:repo/collaborators/:user"},
|
||||
{"DELETE", "/repos/:owner/:repo/collaborators/:user"},
|
||||
{"GET", "/repos/:owner/:repo/comments"},
|
||||
{"GET", "/repos/:owner/:repo/commits/:sha/comments"},
|
||||
{"POST", "/repos/:owner/:repo/commits/:sha/comments"},
|
||||
{"GET", "/repos/:owner/:repo/comments/:id"},
|
||||
//{"PATCH", "/repos/:owner/:repo/comments/:id"},
|
||||
{"DELETE", "/repos/:owner/:repo/comments/:id"},
|
||||
{"GET", "/repos/:owner/:repo/commits"},
|
||||
{"GET", "/repos/:owner/:repo/commits/:sha"},
|
||||
{"GET", "/repos/:owner/:repo/readme"},
|
||||
//{"GET", "/repos/:owner/:repo/contents/*path"},
|
||||
//{"PUT", "/repos/:owner/:repo/contents/*path"},
|
||||
//{"DELETE", "/repos/:owner/:repo/contents/*path"},
|
||||
//{"GET", "/repos/:owner/:repo/:archive_format/:ref"},
|
||||
{"GET", "/repos/:owner/:repo/keys"},
|
||||
{"GET", "/repos/:owner/:repo/keys/:id"},
|
||||
{"POST", "/repos/:owner/:repo/keys"},
|
||||
//{"PATCH", "/repos/:owner/:repo/keys/:id"},
|
||||
{"DELETE", "/repos/:owner/:repo/keys/:id"},
|
||||
{"GET", "/repos/:owner/:repo/downloads"},
|
||||
{"GET", "/repos/:owner/:repo/downloads/:id"},
|
||||
{"DELETE", "/repos/:owner/:repo/downloads/:id"},
|
||||
{"GET", "/repos/:owner/:repo/forks"},
|
||||
{"POST", "/repos/:owner/:repo/forks"},
|
||||
{"GET", "/repos/:owner/:repo/hooks"},
|
||||
{"GET", "/repos/:owner/:repo/hooks/:id"},
|
||||
{"POST", "/repos/:owner/:repo/hooks"},
|
||||
//{"PATCH", "/repos/:owner/:repo/hooks/:id"},
|
||||
{"POST", "/repos/:owner/:repo/hooks/:id/tests"},
|
||||
{"DELETE", "/repos/:owner/:repo/hooks/:id"},
|
||||
{"POST", "/repos/:owner/:repo/merges"},
|
||||
{"GET", "/repos/:owner/:repo/releases"},
|
||||
{"GET", "/repos/:owner/:repo/releases/:id"},
|
||||
{"POST", "/repos/:owner/:repo/releases"},
|
||||
//{"PATCH", "/repos/:owner/:repo/releases/:id"},
|
||||
{"DELETE", "/repos/:owner/:repo/releases/:id"},
|
||||
{"GET", "/repos/:owner/:repo/releases/:id/assets"},
|
||||
{"GET", "/repos/:owner/:repo/stats/contributors"},
|
||||
{"GET", "/repos/:owner/:repo/stats/commit_activity"},
|
||||
{"GET", "/repos/:owner/:repo/stats/code_frequency"},
|
||||
{"GET", "/repos/:owner/:repo/stats/participation"},
|
||||
{"GET", "/repos/:owner/:repo/stats/punch_card"},
|
||||
{"GET", "/repos/:owner/:repo/statuses/:ref"},
|
||||
{"POST", "/repos/:owner/:repo/statuses/:ref"},
|
||||
{http.MethodGet, "/user/repos"},
|
||||
{http.MethodGet, "/users/:user/repos"},
|
||||
{http.MethodGet, "/orgs/:org/repos"},
|
||||
{http.MethodGet, "/repositories"},
|
||||
{http.MethodPost, "/user/repos"},
|
||||
{http.MethodPost, "/orgs/:org/repos"},
|
||||
{http.MethodGet, "/repos/:owner/:repo"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/contributors"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/languages"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/teams"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/tags"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/branches"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/branches/:branch"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/collaborators"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/collaborators/:user"},
|
||||
{http.MethodPut, "/repos/:owner/:repo/collaborators/:user"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/collaborators/:user"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/comments"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/commits/:sha/comments"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/commits/:sha/comments"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/comments/:id"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/comments/:id"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/comments/:id"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/commits"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/commits/:sha"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/readme"},
|
||||
//{http.MethodGet, "/repos/:owner/:repo/contents/*path"},
|
||||
//{http.MethodPut, "/repos/:owner/:repo/contents/*path"},
|
||||
//{http.MethodDelete, "/repos/:owner/:repo/contents/*path"},
|
||||
//{http.MethodGet, "/repos/:owner/:repo/:archive_format/:ref"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/keys"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/keys/:id"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/keys"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/keys/:id"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/keys/:id"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/downloads"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/downloads/:id"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/downloads/:id"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/forks"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/forks"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/hooks"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/hooks/:id"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/hooks"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/hooks/:id"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/hooks/:id/tests"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/hooks/:id"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/merges"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/releases"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/releases/:id"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/releases"},
|
||||
//{http.MethodPatch, "/repos/:owner/:repo/releases/:id"},
|
||||
{http.MethodDelete, "/repos/:owner/:repo/releases/:id"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/releases/:id/assets"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/stats/contributors"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/stats/commit_activity"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/stats/code_frequency"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/stats/participation"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/stats/punch_card"},
|
||||
{http.MethodGet, "/repos/:owner/:repo/statuses/:ref"},
|
||||
{http.MethodPost, "/repos/:owner/:repo/statuses/:ref"},
|
||||
|
||||
// Search
|
||||
{"GET", "/search/repositories"},
|
||||
{"GET", "/search/code"},
|
||||
{"GET", "/search/issues"},
|
||||
{"GET", "/search/users"},
|
||||
{"GET", "/legacy/issues/search/:owner/:repository/:state/:keyword"},
|
||||
{"GET", "/legacy/repos/search/:keyword"},
|
||||
{"GET", "/legacy/user/search/:keyword"},
|
||||
{"GET", "/legacy/user/email/:email"},
|
||||
{http.MethodGet, "/search/repositories"},
|
||||
{http.MethodGet, "/search/code"},
|
||||
{http.MethodGet, "/search/issues"},
|
||||
{http.MethodGet, "/search/users"},
|
||||
{http.MethodGet, "/legacy/issues/search/:owner/:repository/:state/:keyword"},
|
||||
{http.MethodGet, "/legacy/repos/search/:keyword"},
|
||||
{http.MethodGet, "/legacy/user/search/:keyword"},
|
||||
{http.MethodGet, "/legacy/user/email/:email"},
|
||||
|
||||
// Users
|
||||
{"GET", "/users/:user"},
|
||||
{"GET", "/user"},
|
||||
//{"PATCH", "/user"},
|
||||
{"GET", "/users"},
|
||||
{"GET", "/user/emails"},
|
||||
{"POST", "/user/emails"},
|
||||
{"DELETE", "/user/emails"},
|
||||
{"GET", "/users/:user/followers"},
|
||||
{"GET", "/user/followers"},
|
||||
{"GET", "/users/:user/following"},
|
||||
{"GET", "/user/following"},
|
||||
{"GET", "/user/following/:user"},
|
||||
{"GET", "/users/:user/following/:target_user"},
|
||||
{"PUT", "/user/following/:user"},
|
||||
{"DELETE", "/user/following/:user"},
|
||||
{"GET", "/users/:user/keys"},
|
||||
{"GET", "/user/keys"},
|
||||
{"GET", "/user/keys/:id"},
|
||||
{"POST", "/user/keys"},
|
||||
//{"PATCH", "/user/keys/:id"},
|
||||
{"DELETE", "/user/keys/:id"},
|
||||
{http.MethodGet, "/users/:user"},
|
||||
{http.MethodGet, "/user"},
|
||||
//{http.MethodPatch, "/user"},
|
||||
{http.MethodGet, "/users"},
|
||||
{http.MethodGet, "/user/emails"},
|
||||
{http.MethodPost, "/user/emails"},
|
||||
{http.MethodDelete, "/user/emails"},
|
||||
{http.MethodGet, "/users/:user/followers"},
|
||||
{http.MethodGet, "/user/followers"},
|
||||
{http.MethodGet, "/users/:user/following"},
|
||||
{http.MethodGet, "/user/following"},
|
||||
{http.MethodGet, "/user/following/:user"},
|
||||
{http.MethodGet, "/users/:user/following/:target_user"},
|
||||
{http.MethodPut, "/user/following/:user"},
|
||||
{http.MethodDelete, "/user/following/:user"},
|
||||
{http.MethodGet, "/users/:user/keys"},
|
||||
{http.MethodGet, "/user/keys"},
|
||||
{http.MethodGet, "/user/keys/:id"},
|
||||
{http.MethodPost, "/user/keys"},
|
||||
//{http.MethodPatch, "/user/keys/:id"},
|
||||
{http.MethodDelete, "/user/keys/:id"},
|
||||
}
|
||||
|
||||
func TestShouldBindUri(t *testing.T) {
|
||||
@ -293,7 +293,7 @@ func TestShouldBindUri(t *testing.T) {
|
||||
Name string `uri:"name" binding:"required"`
|
||||
Id string `uri:"id" binding:"required"`
|
||||
}
|
||||
router.Handle("GET", "/rest/:name/:id", func(c *Context) {
|
||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||
var person Person
|
||||
assert.NoError(t, c.ShouldBindUri(&person))
|
||||
assert.True(t, "" != person.Name)
|
||||
@ -302,7 +302,7 @@ func TestShouldBindUri(t *testing.T) {
|
||||
})
|
||||
|
||||
path, _ := exampleFromPath("/rest/:name/:id")
|
||||
w := performRequest(router, "GET", path)
|
||||
w := performRequest(router, http.MethodGet, path)
|
||||
assert.Equal(t, "ShouldBindUri test OK", w.Body.String())
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
@ -315,7 +315,7 @@ func TestBindUri(t *testing.T) {
|
||||
Name string `uri:"name" binding:"required"`
|
||||
Id string `uri:"id" binding:"required"`
|
||||
}
|
||||
router.Handle("GET", "/rest/:name/:id", func(c *Context) {
|
||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||
var person Person
|
||||
assert.NoError(t, c.BindUri(&person))
|
||||
assert.True(t, "" != person.Name)
|
||||
@ -324,7 +324,7 @@ func TestBindUri(t *testing.T) {
|
||||
})
|
||||
|
||||
path, _ := exampleFromPath("/rest/:name/:id")
|
||||
w := performRequest(router, "GET", path)
|
||||
w := performRequest(router, http.MethodGet, path)
|
||||
assert.Equal(t, "BindUri test OK", w.Body.String())
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
@ -336,13 +336,13 @@ func TestBindUriError(t *testing.T) {
|
||||
type Member struct {
|
||||
Number string `uri:"num" binding:"required,uuid"`
|
||||
}
|
||||
router.Handle("GET", "/new/rest/:num", func(c *Context) {
|
||||
router.Handle(http.MethodGet, "/new/rest/:num", func(c *Context) {
|
||||
var m Member
|
||||
assert.Error(t, c.BindUri(&m))
|
||||
})
|
||||
|
||||
path1, _ := exampleFromPath("/new/rest/:num")
|
||||
w1 := performRequest(router, "GET", 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, "GET", "/test/copy/race")
|
||||
w := performRequest(router, http.MethodGet, "/test/copy/race")
|
||||
assert.Equal(t, "run OK, no panics", w.Body.String())
|
||||
}
|
||||
|
||||
@ -438,7 +438,7 @@ func exampleFromPath(path string) (string, Params) {
|
||||
func BenchmarkGithub(b *testing.B) {
|
||||
router := New()
|
||||
githubConfigRouter(router)
|
||||
runRequest(b, router, "GET", "/legacy/issues/search/:owner/:repository/:state/:keyword")
|
||||
runRequest(b, router, http.MethodGet, "/legacy/issues/search/:owner/:repository/:state/:keyword")
|
||||
}
|
||||
|
||||
func BenchmarkParallelGithub(b *testing.B) {
|
||||
@ -446,7 +446,7 @@ func BenchmarkParallelGithub(b *testing.B) {
|
||||
router := New()
|
||||
githubConfigRouter(router)
|
||||
|
||||
req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil)
|
||||
req, _ := http.NewRequest(http.MethodPost, "/repos/manucorporat/sse/git/blobs", nil)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
// Each goroutine has its own bytes.Buffer.
|
||||
@ -462,7 +462,7 @@ func BenchmarkParallelGithubDefault(b *testing.B) {
|
||||
router := New()
|
||||
githubConfigRouter(router)
|
||||
|
||||
req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil)
|
||||
req, _ := http.NewRequest(http.MethodPost, "/repos/manucorporat/sse/git/blobs", nil)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
// Each goroutine has its own bytes.Buffer.
|
||||
|
6
go.mod
6
go.mod
@ -4,15 +4,11 @@ go 1.12
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/sse v0.1.0
|
||||
github.com/go-playground/locales v0.12.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.16.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.0.1
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/json-iterator/go v1.1.7
|
||||
github.com/leodido/go-urn v1.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.9
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/ugorji/go/codec v1.1.7
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
|
22
go.sum
22
go.sum
@ -3,17 +3,21 @@ 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=
|
||||
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.0.1 h1:QgDDZpXlR/L3atIL2PbFt0TpazbtN7N6PxTGcgcyEUg=
|
||||
github.com/go-playground/validator/v10 v10.0.1/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
@ -32,11 +36,9 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
14
logger.go
14
logger.go
@ -99,19 +99,19 @@ func (p *LogFormatterParams) MethodColor() string {
|
||||
method := p.Method
|
||||
|
||||
switch method {
|
||||
case "GET":
|
||||
case http.MethodGet:
|
||||
return blue
|
||||
case "POST":
|
||||
case http.MethodPost:
|
||||
return cyan
|
||||
case "PUT":
|
||||
case http.MethodPut:
|
||||
return yellow
|
||||
case "DELETE":
|
||||
case http.MethodDelete:
|
||||
return red
|
||||
case "PATCH":
|
||||
case http.MethodPatch:
|
||||
return green
|
||||
case "HEAD":
|
||||
case http.MethodHead:
|
||||
return magenta
|
||||
case "OPTIONS":
|
||||
case http.MethodOptions:
|
||||
return white
|
||||
default:
|
||||
return reset
|
||||
|
2
mode.go
2
mode.go
@ -77,7 +77,7 @@ func EnableJsonDecoderUseNumber() {
|
||||
binding.EnableDecoderUseNumber = true
|
||||
}
|
||||
|
||||
// EnableJsonDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to
|
||||
// EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to
|
||||
// call the DisallowUnknownFields method on the JSON Decoder instance.
|
||||
func EnableJsonDecoderDisallowUnknownFields() {
|
||||
binding.EnableDecoderDisallowUnknownFields = true
|
||||
|
@ -22,6 +22,9 @@ type Reader struct {
|
||||
func (r Reader) Render(w http.ResponseWriter) (err error) {
|
||||
r.WriteContentType(w)
|
||||
if r.ContentLength >= 0 {
|
||||
if r.Headers == nil {
|
||||
r.Headers = map[string]string{}
|
||||
}
|
||||
r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
|
||||
}
|
||||
r.writeHeaders(w, r.Headers)
|
||||
|
23
render/reader_test.go
Normal file
23
render/reader_test.go
Normal file
@ -0,0 +1,23 @@
|
||||
// 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 render
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReaderRenderNoHeaders(t *testing.T) {
|
||||
content := "test"
|
||||
r := Reader{
|
||||
ContentLength: int64(len(content)),
|
||||
Reader: strings.NewReader(content),
|
||||
}
|
||||
err := r.Render(httptest.NewRecorder())
|
||||
require.NoError(t, err)
|
||||
}
|
@ -95,51 +95,51 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha
|
||||
|
||||
// POST is a shortcut for router.Handle("POST", path, handle).
|
||||
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("POST", relativePath, handlers)
|
||||
return group.handle(http.MethodPost, relativePath, handlers)
|
||||
}
|
||||
|
||||
// GET is a shortcut for router.Handle("GET", path, handle).
|
||||
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("GET", relativePath, handlers)
|
||||
return group.handle(http.MethodGet, relativePath, handlers)
|
||||
}
|
||||
|
||||
// DELETE is a shortcut for router.Handle("DELETE", path, handle).
|
||||
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("DELETE", relativePath, handlers)
|
||||
return group.handle(http.MethodDelete, relativePath, handlers)
|
||||
}
|
||||
|
||||
// PATCH is a shortcut for router.Handle("PATCH", path, handle).
|
||||
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("PATCH", relativePath, handlers)
|
||||
return group.handle(http.MethodPatch, relativePath, handlers)
|
||||
}
|
||||
|
||||
// PUT is a shortcut for router.Handle("PUT", path, handle).
|
||||
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("PUT", relativePath, handlers)
|
||||
return group.handle(http.MethodPut, relativePath, handlers)
|
||||
}
|
||||
|
||||
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
|
||||
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("OPTIONS", relativePath, handlers)
|
||||
return group.handle(http.MethodOptions, relativePath, handlers)
|
||||
}
|
||||
|
||||
// HEAD is a shortcut for router.Handle("HEAD", path, handle).
|
||||
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("HEAD", relativePath, handlers)
|
||||
return group.handle(http.MethodHead, relativePath, handlers)
|
||||
}
|
||||
|
||||
// Any registers a route that matches all the HTTP methods.
|
||||
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
|
||||
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
group.handle("GET", relativePath, handlers)
|
||||
group.handle("POST", relativePath, handlers)
|
||||
group.handle("PUT", relativePath, handlers)
|
||||
group.handle("PATCH", relativePath, handlers)
|
||||
group.handle("HEAD", relativePath, handlers)
|
||||
group.handle("OPTIONS", relativePath, handlers)
|
||||
group.handle("DELETE", relativePath, handlers)
|
||||
group.handle("CONNECT", relativePath, handlers)
|
||||
group.handle("TRACE", relativePath, handlers)
|
||||
group.handle(http.MethodGet, relativePath, handlers)
|
||||
group.handle(http.MethodPost, relativePath, handlers)
|
||||
group.handle(http.MethodPut, relativePath, handlers)
|
||||
group.handle(http.MethodPatch, relativePath, handlers)
|
||||
group.handle(http.MethodHead, relativePath, handlers)
|
||||
group.handle(http.MethodOptions, relativePath, handlers)
|
||||
group.handle(http.MethodDelete, relativePath, handlers)
|
||||
group.handle(http.MethodConnect, relativePath, handlers)
|
||||
group.handle(http.MethodTrace, relativePath, handlers)
|
||||
return group.returnObj()
|
||||
}
|
||||
|
||||
|
@ -33,13 +33,13 @@ func TestRouterGroupBasic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRouterGroupBasicHandle(t *testing.T) {
|
||||
performRequestInGroup(t, "GET")
|
||||
performRequestInGroup(t, "POST")
|
||||
performRequestInGroup(t, "PUT")
|
||||
performRequestInGroup(t, "PATCH")
|
||||
performRequestInGroup(t, "DELETE")
|
||||
performRequestInGroup(t, "HEAD")
|
||||
performRequestInGroup(t, "OPTIONS")
|
||||
performRequestInGroup(t, http.MethodGet)
|
||||
performRequestInGroup(t, http.MethodPost)
|
||||
performRequestInGroup(t, http.MethodPut)
|
||||
performRequestInGroup(t, http.MethodPatch)
|
||||
performRequestInGroup(t, http.MethodDelete)
|
||||
performRequestInGroup(t, http.MethodHead)
|
||||
performRequestInGroup(t, http.MethodOptions)
|
||||
}
|
||||
|
||||
func performRequestInGroup(t *testing.T, method string) {
|
||||
@ -55,25 +55,25 @@ func performRequestInGroup(t *testing.T, method string) {
|
||||
}
|
||||
|
||||
switch method {
|
||||
case "GET":
|
||||
case http.MethodGet:
|
||||
v1.GET("/test", handler)
|
||||
login.GET("/test", handler)
|
||||
case "POST":
|
||||
case http.MethodPost:
|
||||
v1.POST("/test", handler)
|
||||
login.POST("/test", handler)
|
||||
case "PUT":
|
||||
case http.MethodPut:
|
||||
v1.PUT("/test", handler)
|
||||
login.PUT("/test", handler)
|
||||
case "PATCH":
|
||||
case http.MethodPatch:
|
||||
v1.PATCH("/test", handler)
|
||||
login.PATCH("/test", handler)
|
||||
case "DELETE":
|
||||
case http.MethodDelete:
|
||||
v1.DELETE("/test", handler)
|
||||
login.DELETE("/test", handler)
|
||||
case "HEAD":
|
||||
case http.MethodHead:
|
||||
v1.HEAD("/test", handler)
|
||||
login.HEAD("/test", handler)
|
||||
case "OPTIONS":
|
||||
case http.MethodOptions:
|
||||
v1.OPTIONS("/test", handler)
|
||||
login.OPTIONS("/test", handler)
|
||||
default:
|
||||
@ -128,7 +128,7 @@ func TestRouterGroupTooManyHandlers(t *testing.T) {
|
||||
func TestRouterGroupBadMethod(t *testing.T) {
|
||||
router := New()
|
||||
assert.Panics(t, func() {
|
||||
router.Handle("get", "/")
|
||||
router.Handle(http.MethodGet, "/")
|
||||
})
|
||||
assert.Panics(t, func() {
|
||||
router.Handle(" GET", "/")
|
||||
@ -162,7 +162,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) {
|
||||
handler := func(c *Context) {}
|
||||
assert.Equal(t, r, r.Use(handler))
|
||||
|
||||
assert.Equal(t, r, r.Handle("GET", "/handler", handler))
|
||||
assert.Equal(t, r, r.Handle(http.MethodGet, "/handler", handler))
|
||||
assert.Equal(t, r, r.Any("/any", handler))
|
||||
assert.Equal(t, r, r.GET("/", handler))
|
||||
assert.Equal(t, r, r.POST("/", handler))
|
||||
|
196
routes_test.go
196
routes_test.go
@ -70,10 +70,10 @@ func testRouteNotOK2(method string, t *testing.T) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = true
|
||||
var methodRoute string
|
||||
if method == "POST" {
|
||||
methodRoute = "GET"
|
||||
if method == http.MethodPost {
|
||||
methodRoute = http.MethodGet
|
||||
} else {
|
||||
methodRoute = "POST"
|
||||
methodRoute = http.MethodPost
|
||||
}
|
||||
router.Handle(methodRoute, "/test", func(c *Context) {
|
||||
passed = true
|
||||
@ -99,46 +99,46 @@ func TestRouterMethod(t *testing.T) {
|
||||
c.String(http.StatusOK, "sup3")
|
||||
})
|
||||
|
||||
w := performRequest(router, "PUT", "/hey")
|
||||
w := performRequest(router, http.MethodPut, "/hey")
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "called", w.Body.String())
|
||||
}
|
||||
|
||||
func TestRouterGroupRouteOK(t *testing.T) {
|
||||
testRouteOK("GET", t)
|
||||
testRouteOK("POST", t)
|
||||
testRouteOK("PUT", t)
|
||||
testRouteOK("PATCH", t)
|
||||
testRouteOK("HEAD", t)
|
||||
testRouteOK("OPTIONS", t)
|
||||
testRouteOK("DELETE", t)
|
||||
testRouteOK("CONNECT", t)
|
||||
testRouteOK("TRACE", t)
|
||||
testRouteOK(http.MethodGet, t)
|
||||
testRouteOK(http.MethodPost, t)
|
||||
testRouteOK(http.MethodPut, t)
|
||||
testRouteOK(http.MethodPatch, t)
|
||||
testRouteOK(http.MethodHead, t)
|
||||
testRouteOK(http.MethodOptions, t)
|
||||
testRouteOK(http.MethodDelete, t)
|
||||
testRouteOK(http.MethodConnect, t)
|
||||
testRouteOK(http.MethodTrace, t)
|
||||
}
|
||||
|
||||
func TestRouteNotOK(t *testing.T) {
|
||||
testRouteNotOK("GET", t)
|
||||
testRouteNotOK("POST", t)
|
||||
testRouteNotOK("PUT", t)
|
||||
testRouteNotOK("PATCH", t)
|
||||
testRouteNotOK("HEAD", t)
|
||||
testRouteNotOK("OPTIONS", t)
|
||||
testRouteNotOK("DELETE", t)
|
||||
testRouteNotOK("CONNECT", t)
|
||||
testRouteNotOK("TRACE", t)
|
||||
testRouteNotOK(http.MethodGet, t)
|
||||
testRouteNotOK(http.MethodPost, t)
|
||||
testRouteNotOK(http.MethodPut, t)
|
||||
testRouteNotOK(http.MethodPatch, t)
|
||||
testRouteNotOK(http.MethodHead, t)
|
||||
testRouteNotOK(http.MethodOptions, t)
|
||||
testRouteNotOK(http.MethodDelete, t)
|
||||
testRouteNotOK(http.MethodConnect, t)
|
||||
testRouteNotOK(http.MethodTrace, t)
|
||||
}
|
||||
|
||||
func TestRouteNotOK2(t *testing.T) {
|
||||
testRouteNotOK2("GET", t)
|
||||
testRouteNotOK2("POST", t)
|
||||
testRouteNotOK2("PUT", t)
|
||||
testRouteNotOK2("PATCH", t)
|
||||
testRouteNotOK2("HEAD", t)
|
||||
testRouteNotOK2("OPTIONS", t)
|
||||
testRouteNotOK2("DELETE", t)
|
||||
testRouteNotOK2("CONNECT", t)
|
||||
testRouteNotOK2("TRACE", t)
|
||||
testRouteNotOK2(http.MethodGet, t)
|
||||
testRouteNotOK2(http.MethodPost, t)
|
||||
testRouteNotOK2(http.MethodPut, t)
|
||||
testRouteNotOK2(http.MethodPatch, t)
|
||||
testRouteNotOK2(http.MethodHead, t)
|
||||
testRouteNotOK2(http.MethodOptions, t)
|
||||
testRouteNotOK2(http.MethodDelete, t)
|
||||
testRouteNotOK2(http.MethodConnect, t)
|
||||
testRouteNotOK2(http.MethodTrace, t)
|
||||
}
|
||||
|
||||
func TestRouteRedirectTrailingSlash(t *testing.T) {
|
||||
@ -150,50 +150,50 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
|
||||
router.POST("/path3", func(c *Context) {})
|
||||
router.PUT("/path4/", func(c *Context) {})
|
||||
|
||||
w := performRequest(router, "GET", "/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, "GET", "/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, "POST", "/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, "PUT", "/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, "GET", "/path")
|
||||
w = performRequest(router, http.MethodGet, "/path")
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/path2/")
|
||||
w = performRequest(router, http.MethodGet, "/path2/")
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
w = performRequest(router, "POST", "/path3")
|
||||
w = performRequest(router, http.MethodPost, "/path3")
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
w = performRequest(router, "PUT", "/path4/")
|
||||
w = performRequest(router, http.MethodPut, "/path4/")
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/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, "GET", "/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, "GET", "/path/")
|
||||
w = performRequest(router, http.MethodGet, "/path/")
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
w = performRequest(router, "GET", "/path2")
|
||||
w = performRequest(router, http.MethodGet, "/path2")
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
w = performRequest(router, "POST", "/path3/")
|
||||
w = performRequest(router, http.MethodPost, "/path3/")
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
w = performRequest(router, "PUT", "/path4")
|
||||
w = performRequest(router, http.MethodPut, "/path4")
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
@ -207,19 +207,19 @@ func TestRouteRedirectFixedPath(t *testing.T) {
|
||||
router.POST("/PATH3", func(c *Context) {})
|
||||
router.POST("/Path4/", func(c *Context) {})
|
||||
|
||||
w := performRequest(router, "GET", "/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, "GET", "/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, "POST", "/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, "POST", "/path4")
|
||||
w = performRequest(router, http.MethodPost, "/path4")
|
||||
assert.Equal(t, "/Path4/", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||
}
|
||||
@ -249,7 +249,7 @@ func TestRouteParamsByName(t *testing.T) {
|
||||
assert.False(t, ok)
|
||||
})
|
||||
|
||||
w := performRequest(router, "GET", "/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)
|
||||
@ -263,6 +263,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
||||
lastName := ""
|
||||
wild := ""
|
||||
router := New()
|
||||
router.RemoveExtraSlash = true
|
||||
router.GET("/test/:name/:last_name/*wild", func(c *Context) {
|
||||
name = c.Params.ByName("name")
|
||||
lastName = c.Params.ByName("last_name")
|
||||
@ -282,7 +283,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
||||
assert.False(t, ok)
|
||||
})
|
||||
|
||||
w := performRequest(router, "GET", "//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)
|
||||
@ -310,16 +311,16 @@ func TestRouteStaticFile(t *testing.T) {
|
||||
router.Static("/using_static", dir)
|
||||
router.StaticFile("/result", f.Name())
|
||||
|
||||
w := performRequest(router, "GET", "/using_static/"+filename)
|
||||
w2 := performRequest(router, "GET", "/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, "HEAD", "/using_static/"+filename)
|
||||
w4 := performRequest(router, "HEAD", "/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)
|
||||
@ -330,7 +331,7 @@ func TestRouteStaticListingDir(t *testing.T) {
|
||||
router := New()
|
||||
router.StaticFS("/", Dir("./", true))
|
||||
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := performRequest(router, http.MethodGet, "/")
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "gin.go")
|
||||
@ -342,7 +343,7 @@ func TestRouteStaticNoListing(t *testing.T) {
|
||||
router := New()
|
||||
router.Static("/", "./")
|
||||
|
||||
w := performRequest(router, "GET", "/")
|
||||
w := performRequest(router, http.MethodGet, "/")
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.NotContains(t, w.Body.String(), "gin.go")
|
||||
@ -357,7 +358,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
||||
})
|
||||
static.Static("/", "./")
|
||||
|
||||
w := performRequest(router, "GET", "/gin.go")
|
||||
w := performRequest(router, http.MethodGet, "/gin.go")
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "package gin")
|
||||
@ -371,13 +372,13 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = true
|
||||
router.POST("/path", func(c *Context) {})
|
||||
w := performRequest(router, "GET", "/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, "GET", "/path")
|
||||
w = performRequest(router, http.MethodGet, "/path")
|
||||
assert.Equal(t, "responseText", w.Body.String())
|
||||
assert.Equal(t, http.StatusTeapot, w.Code)
|
||||
}
|
||||
@ -386,9 +387,9 @@ func TestRouteNotAllowedEnabled2(t *testing.T) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = true
|
||||
// add one methodTree to trees
|
||||
router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}})
|
||||
router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}})
|
||||
router.GET("/path2", func(c *Context) {})
|
||||
w := performRequest(router, "POST", "/path2")
|
||||
w := performRequest(router, http.MethodPost, "/path2")
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||
}
|
||||
|
||||
@ -396,17 +397,40 @@ func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = false
|
||||
router.POST("/path", func(c *Context) {})
|
||||
w := performRequest(router, "GET", "/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, "GET", "/path")
|
||||
w = performRequest(router, http.MethodGet, "/path")
|
||||
assert.Equal(t, "404 page not found", w.Body.String())
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {
|
||||
router := New()
|
||||
router.RemoveExtraSlash = true
|
||||
router.GET("/path", func(c *Context) {})
|
||||
router.GET("/", func(c *Context) {})
|
||||
|
||||
testRoutes := []struct {
|
||||
route string
|
||||
code int
|
||||
location string
|
||||
}{
|
||||
{"/../path", http.StatusOK, ""}, // CleanPath
|
||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||
}
|
||||
for _, tr := range testRoutes {
|
||||
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")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterNotFound(t *testing.T) {
|
||||
router := New()
|
||||
router.RedirectFixedPath = true
|
||||
@ -419,17 +443,17 @@ func TestRouterNotFound(t *testing.T) {
|
||||
code int
|
||||
location string
|
||||
}{
|
||||
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
|
||||
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
|
||||
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
|
||||
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
|
||||
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
|
||||
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
|
||||
{"/../path", http.StatusOK, ""}, // CleanPath
|
||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
|
||||
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
|
||||
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
|
||||
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
|
||||
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
|
||||
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
|
||||
{"/../path", http.StatusMovedPermanently, "/path"}, // Without CleanPath
|
||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||
}
|
||||
for _, tr := range testRoutes {
|
||||
w := performRequest(router, "GET", 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")))
|
||||
@ -442,20 +466,20 @@ func TestRouterNotFound(t *testing.T) {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
notFound = true
|
||||
})
|
||||
w := performRequest(router, "GET", "/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, "PATCH", "/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, "GET", "/")
|
||||
w = performRequest(router, http.MethodGet, "/")
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
@ -466,10 +490,10 @@ func TestRouterStaticFSNotFound(t *testing.T) {
|
||||
c.String(404, "non existent")
|
||||
})
|
||||
|
||||
w := performRequest(router, "GET", "/nonexistent")
|
||||
w := performRequest(router, http.MethodGet, "/nonexistent")
|
||||
assert.Equal(t, "non existent", w.Body.String())
|
||||
|
||||
w = performRequest(router, "HEAD", "/nonexistent")
|
||||
w = performRequest(router, http.MethodHead, "/nonexistent")
|
||||
assert.Equal(t, "non existent", w.Body.String())
|
||||
}
|
||||
|
||||
@ -479,7 +503,7 @@ func TestRouterStaticFSFileNotFound(t *testing.T) {
|
||||
router.StaticFS("/", http.FileSystem(http.Dir(".")))
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
performRequest(router, "GET", "/nonexistent")
|
||||
performRequest(router, http.MethodGet, "/nonexistent")
|
||||
})
|
||||
}
|
||||
|
||||
@ -496,11 +520,11 @@ func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {
|
||||
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
||||
|
||||
// First access
|
||||
performRequest(router, "GET", "/nonexistent")
|
||||
performRequest(router, http.MethodGet, "/nonexistent")
|
||||
assert.Equal(t, 1, middlewareCalledNum)
|
||||
|
||||
// Second access
|
||||
performRequest(router, "HEAD", "/nonexistent")
|
||||
performRequest(router, http.MethodHead, "/nonexistent")
|
||||
assert.Equal(t, 2, middlewareCalledNum)
|
||||
}
|
||||
|
||||
@ -519,7 +543,7 @@ func TestRouteRawPath(t *testing.T) {
|
||||
assert.Equal(t, "222", num)
|
||||
})
|
||||
|
||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
|
||||
w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222")
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
@ -539,7 +563,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
|
||||
assert.Equal(t, "333", num)
|
||||
})
|
||||
|
||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
|
||||
w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333")
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
@ -550,7 +574,7 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
||||
c.Next()
|
||||
})
|
||||
|
||||
w := performRequest(route, "GET", "/NotFound")
|
||||
w := performRequest(route, http.MethodGet, "/NotFound")
|
||||
assert.Equal(t, 421, w.Code)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
@ -581,7 +605,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
w := performRequest(router, "GET", route)
|
||||
w := performRequest(router, http.MethodGet, route)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
@ -591,6 +615,6 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
|
||||
assert.Equal(t, "", c.FullPath())
|
||||
})
|
||||
|
||||
w := performRequest(router, "GET", "/not-found")
|
||||
w := performRequest(router, http.MethodGet, "/not-found")
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
578
tree.go
578
tree.go
@ -62,6 +62,15 @@ func min(a, b int) int {
|
||||
return b
|
||||
}
|
||||
|
||||
func longestCommonPrefix(a, b string) int {
|
||||
i := 0
|
||||
max := min(len(a), len(b))
|
||||
for i < max && a[i] == b[i] {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func countParams(path string) uint8 {
|
||||
var n uint
|
||||
for i := 0; i < len(path); i++ {
|
||||
@ -98,16 +107,15 @@ type node struct {
|
||||
|
||||
// increments priority of the given child and reorders if necessary.
|
||||
func (n *node) incrementChildPrio(pos int) int {
|
||||
n.children[pos].priority++
|
||||
prio := n.children[pos].priority
|
||||
cs := n.children
|
||||
cs[pos].priority++
|
||||
prio := cs[pos].priority
|
||||
|
||||
// adjust position (move to front)
|
||||
// Adjust position (move to front)
|
||||
newPos := pos
|
||||
for newPos > 0 && n.children[newPos-1].priority < prio {
|
||||
// swap node positions
|
||||
n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
|
||||
|
||||
newPos--
|
||||
for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
|
||||
// Swap node positions
|
||||
cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
|
||||
}
|
||||
|
||||
// build new index char string
|
||||
@ -127,196 +135,209 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
||||
n.priority++
|
||||
numParams := countParams(path)
|
||||
|
||||
// Empty tree
|
||||
if len(n.path) == 0 && len(n.children) == 0 {
|
||||
n.insertChild(numParams, path, fullPath, handlers)
|
||||
n.nType = root
|
||||
return
|
||||
}
|
||||
|
||||
parentFullPathIndex := 0
|
||||
|
||||
// non-empty tree
|
||||
if len(n.path) > 0 || len(n.children) > 0 {
|
||||
walk:
|
||||
for {
|
||||
// Update maxParams of the current node
|
||||
if numParams > n.maxParams {
|
||||
n.maxParams = numParams
|
||||
walk:
|
||||
for {
|
||||
// Update maxParams of the current node
|
||||
if numParams > n.maxParams {
|
||||
n.maxParams = numParams
|
||||
}
|
||||
|
||||
// Find the longest common prefix.
|
||||
// This also implies that the common prefix contains no ':' or '*'
|
||||
// since the existing key can't contain those chars.
|
||||
i := longestCommonPrefix(path, n.path)
|
||||
|
||||
// Split edge
|
||||
if i < len(n.path) {
|
||||
child := node{
|
||||
path: n.path[i:],
|
||||
wildChild: n.wildChild,
|
||||
indices: n.indices,
|
||||
children: n.children,
|
||||
handlers: n.handlers,
|
||||
priority: n.priority - 1,
|
||||
fullPath: n.fullPath,
|
||||
}
|
||||
|
||||
// Find the longest common prefix.
|
||||
// This also implies that the common prefix contains no ':' or '*'
|
||||
// since the existing key can't contain those chars.
|
||||
i := 0
|
||||
max := min(len(path), len(n.path))
|
||||
for i < max && path[i] == n.path[i] {
|
||||
i++
|
||||
// Update maxParams (max of all children)
|
||||
for i := range child.children {
|
||||
if child.children[i].maxParams > child.maxParams {
|
||||
child.maxParams = child.children[i].maxParams
|
||||
}
|
||||
}
|
||||
|
||||
// Split edge
|
||||
if i < len(n.path) {
|
||||
child := node{
|
||||
path: n.path[i:],
|
||||
wildChild: n.wildChild,
|
||||
indices: n.indices,
|
||||
children: n.children,
|
||||
handlers: n.handlers,
|
||||
priority: n.priority - 1,
|
||||
fullPath: n.fullPath,
|
||||
n.children = []*node{&child}
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices = string([]byte{n.path[i]})
|
||||
n.path = path[:i]
|
||||
n.handlers = nil
|
||||
n.wildChild = false
|
||||
n.fullPath = fullPath[:parentFullPathIndex+i]
|
||||
}
|
||||
|
||||
// Make new node a child of this node
|
||||
if i < len(path) {
|
||||
path = path[i:]
|
||||
|
||||
if n.wildChild {
|
||||
parentFullPathIndex += len(n.path)
|
||||
n = n.children[0]
|
||||
n.priority++
|
||||
|
||||
// Update maxParams of the child node
|
||||
if numParams > n.maxParams {
|
||||
n.maxParams = numParams
|
||||
}
|
||||
numParams--
|
||||
|
||||
// Update maxParams (max of all children)
|
||||
for i := range child.children {
|
||||
if child.children[i].maxParams > child.maxParams {
|
||||
child.maxParams = child.children[i].maxParams
|
||||
}
|
||||
}
|
||||
|
||||
n.children = []*node{&child}
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices = string([]byte{n.path[i]})
|
||||
n.path = path[:i]
|
||||
n.handlers = nil
|
||||
n.wildChild = false
|
||||
n.fullPath = fullPath[:parentFullPathIndex+i]
|
||||
}
|
||||
|
||||
// Make new node a child of this node
|
||||
if i < len(path) {
|
||||
path = path[i:]
|
||||
|
||||
if n.wildChild {
|
||||
parentFullPathIndex += len(n.path)
|
||||
n = n.children[0]
|
||||
n.priority++
|
||||
|
||||
// Update maxParams of the child node
|
||||
if numParams > n.maxParams {
|
||||
n.maxParams = numParams
|
||||
}
|
||||
numParams--
|
||||
|
||||
// Check if the wildcard matches
|
||||
if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
|
||||
// check for longer wildcard, e.g. :name and :names
|
||||
if len(n.path) >= len(path) || path[len(n.path)] == '/' {
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
pathSeg := path
|
||||
if n.nType != catchAll {
|
||||
pathSeg = strings.SplitN(path, "/", 2)[0]
|
||||
}
|
||||
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||
panic("'" + pathSeg +
|
||||
"' in new path '" + fullPath +
|
||||
"' conflicts with existing wildcard '" + n.path +
|
||||
"' in existing prefix '" + prefix +
|
||||
"'")
|
||||
}
|
||||
|
||||
c := path[0]
|
||||
|
||||
// slash after param
|
||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||
parentFullPathIndex += len(n.path)
|
||||
n = n.children[0]
|
||||
n.priority++
|
||||
continue walk
|
||||
}
|
||||
|
||||
// Check if a child with the next path byte exists
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if c == n.indices[i] {
|
||||
parentFullPathIndex += len(n.path)
|
||||
i = n.incrementChildPrio(i)
|
||||
n = n.children[i]
|
||||
// Check if the wildcard matches
|
||||
if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
|
||||
// check for longer wildcard, e.g. :name and :names
|
||||
if len(n.path) >= len(path) || path[len(n.path)] == '/' {
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise insert it
|
||||
if c != ':' && c != '*' {
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices += string([]byte{c})
|
||||
child := &node{
|
||||
maxParams: numParams,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = append(n.children, child)
|
||||
n.incrementChildPrio(len(n.indices) - 1)
|
||||
n = child
|
||||
pathSeg := path
|
||||
if n.nType != catchAll {
|
||||
pathSeg = strings.SplitN(path, "/", 2)[0]
|
||||
}
|
||||
n.insertChild(numParams, path, fullPath, handlers)
|
||||
return
|
||||
|
||||
} else if i == len(path) { // Make node a (in-path) leaf
|
||||
if n.handlers != nil {
|
||||
panic("handlers are already registered for path '" + fullPath + "'")
|
||||
}
|
||||
n.handlers = handlers
|
||||
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||
panic("'" + pathSeg +
|
||||
"' in new path '" + fullPath +
|
||||
"' conflicts with existing wildcard '" + n.path +
|
||||
"' in existing prefix '" + prefix +
|
||||
"'")
|
||||
}
|
||||
|
||||
c := path[0]
|
||||
|
||||
// slash after param
|
||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||
parentFullPathIndex += len(n.path)
|
||||
n = n.children[0]
|
||||
n.priority++
|
||||
continue walk
|
||||
}
|
||||
|
||||
// Check if a child with the next path byte exists
|
||||
for i, max := 0, len(n.indices); i < max; i++ {
|
||||
if c == n.indices[i] {
|
||||
parentFullPathIndex += len(n.path)
|
||||
i = n.incrementChildPrio(i)
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise insert it
|
||||
if c != ':' && c != '*' {
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices += string([]byte{c})
|
||||
child := &node{
|
||||
maxParams: numParams,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = append(n.children, child)
|
||||
n.incrementChildPrio(len(n.indices) - 1)
|
||||
n = child
|
||||
}
|
||||
n.insertChild(numParams, path, fullPath, handlers)
|
||||
return
|
||||
}
|
||||
} else { // Empty tree
|
||||
n.insertChild(numParams, path, fullPath, handlers)
|
||||
n.nType = root
|
||||
|
||||
// Otherwise and handle to current node
|
||||
if n.handlers != nil {
|
||||
panic("handlers are already registered for path '" + fullPath + "'")
|
||||
}
|
||||
n.handlers = handlers
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) {
|
||||
var offset int // already handled bytes of the path
|
||||
|
||||
// find prefix until first wildcard (beginning with ':' or '*')
|
||||
for i, max := 0, len(path); numParams > 0; i++ {
|
||||
c := path[i]
|
||||
// Search for a wildcard segment and check the name for invalid characters.
|
||||
// Returns -1 as index, if no wildcard war found.
|
||||
func findWildcard(path string) (wildcard string, i int, valid bool) {
|
||||
// Find start
|
||||
for start, c := range []byte(path) {
|
||||
// A wildcard starts with ':' (param) or '*' (catch-all)
|
||||
if c != ':' && c != '*' {
|
||||
continue
|
||||
}
|
||||
|
||||
// find wildcard end (either '/' or path end)
|
||||
end := i + 1
|
||||
for end < max && path[end] != '/' {
|
||||
switch path[end] {
|
||||
// the wildcard name must not contain ':' and '*'
|
||||
// Find end and check for invalid characters
|
||||
valid = true
|
||||
for end, c := range []byte(path[start+1:]) {
|
||||
switch c {
|
||||
case '/':
|
||||
return path[start : start+1+end], start, valid
|
||||
case ':', '*':
|
||||
panic("only one wildcard per path segment is allowed, has: '" +
|
||||
path[i:] + "' in path '" + fullPath + "'")
|
||||
default:
|
||||
end++
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
return path[start:], start, valid
|
||||
}
|
||||
return "", -1, false
|
||||
}
|
||||
|
||||
// check if this Node existing children which would be
|
||||
// unreachable if we insert the wildcard here
|
||||
if len(n.children) > 0 {
|
||||
panic("wildcard route '" + path[i:end] +
|
||||
"' conflicts with existing children in path '" + fullPath + "'")
|
||||
func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) {
|
||||
for numParams > 0 {
|
||||
// Find prefix until first wildcard
|
||||
wildcard, i, valid := findWildcard(path)
|
||||
if i < 0 { // No wildcard found
|
||||
break
|
||||
}
|
||||
|
||||
// The wildcard name must not contain ':' and '*'
|
||||
if !valid {
|
||||
panic("only one wildcard per path segment is allowed, has: '" +
|
||||
wildcard + "' in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// check if the wildcard has a name
|
||||
if end-i < 2 {
|
||||
if len(wildcard) < 2 {
|
||||
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if c == ':' { // param
|
||||
// split path at the beginning of the wildcard
|
||||
// Check if this node has existing children which would be
|
||||
// unreachable if we insert the wildcard here
|
||||
if len(n.children) > 0 {
|
||||
panic("wildcard segment '" + wildcard +
|
||||
"' conflicts with existing children in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if wildcard[0] == ':' { // param
|
||||
if i > 0 {
|
||||
n.path = path[offset:i]
|
||||
offset = i
|
||||
// Insert prefix before the current wildcard
|
||||
n.path = path[:i]
|
||||
path = path[i:]
|
||||
}
|
||||
|
||||
n.wildChild = true
|
||||
child := &node{
|
||||
nType: param,
|
||||
path: wildcard,
|
||||
maxParams: numParams,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n.wildChild = true
|
||||
n = child
|
||||
n.priority++
|
||||
numParams--
|
||||
|
||||
// if the path doesn't end with the wildcard, then there
|
||||
// will be another non-wildcard subpath starting with '/'
|
||||
if end < max {
|
||||
n.path = path[offset:end]
|
||||
offset = end
|
||||
if len(wildcard) < len(path) {
|
||||
path = path[len(wildcard):]
|
||||
|
||||
child := &node{
|
||||
maxParams: numParams,
|
||||
@ -325,54 +346,63 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n = child
|
||||
continue
|
||||
}
|
||||
|
||||
} else { // catchAll
|
||||
if end != max || numParams > 1 {
|
||||
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// currently fixed width 1 for '/'
|
||||
i--
|
||||
if path[i] != '/' {
|
||||
panic("no / before catch-all in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
n.path = path[offset:i]
|
||||
|
||||
// first node: catchAll node with empty path
|
||||
child := &node{
|
||||
wildChild: true,
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n.indices = string(path[i])
|
||||
n = child
|
||||
n.priority++
|
||||
|
||||
// second node: node holding the variable
|
||||
child = &node{
|
||||
path: path[i:],
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
handlers: handlers,
|
||||
priority: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
|
||||
// Otherwise we're done. Insert the handle in the new leaf
|
||||
n.handlers = handlers
|
||||
return
|
||||
}
|
||||
|
||||
// catchAll
|
||||
if i+len(wildcard) != len(path) || numParams > 1 {
|
||||
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// currently fixed width 1 for '/'
|
||||
i--
|
||||
if path[i] != '/' {
|
||||
panic("no / before catch-all in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
n.path = path[:i]
|
||||
|
||||
// First node: catchAll node with empty path
|
||||
child := &node{
|
||||
wildChild: true,
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
// update maxParams of the parent node
|
||||
if n.maxParams < 1 {
|
||||
n.maxParams = 1
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n.indices = string('/')
|
||||
n = child
|
||||
n.priority++
|
||||
|
||||
// second node: node holding the variable
|
||||
child = &node{
|
||||
path: path[i:],
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
handlers: handlers,
|
||||
priority: 1,
|
||||
fullPath: fullPath,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// insert remaining path part and handle to the leaf
|
||||
n.path = path[offset:]
|
||||
// If no wildcard was found, simple insert the path and handle
|
||||
n.path = path
|
||||
n.handlers = handlers
|
||||
n.fullPath = fullPath
|
||||
}
|
||||
@ -394,17 +424,20 @@ func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue)
|
||||
value.params = po
|
||||
walk: // Outer loop for walking the tree
|
||||
for {
|
||||
if len(path) > len(n.path) {
|
||||
if path[:len(n.path)] == n.path {
|
||||
path = path[len(n.path):]
|
||||
prefix := n.path
|
||||
if len(path) > len(prefix) {
|
||||
if path[:len(prefix)] == prefix {
|
||||
path = path[len(prefix):]
|
||||
// If this node does not have a wildcard (param or catchAll)
|
||||
// child, we can just look up the next child node and continue
|
||||
// to walk down the tree
|
||||
if !n.wildChild {
|
||||
c := path[0]
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if c == n.indices[i] {
|
||||
indices := n.indices
|
||||
for i, max := 0, len(indices); i < max; i++ {
|
||||
if c == indices[i] {
|
||||
n = n.children[i]
|
||||
prefix = n.path
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
@ -448,6 +481,7 @@ walk: // Outer loop for walking the tree
|
||||
if len(n.children) > 0 {
|
||||
path = path[end:]
|
||||
n = n.children[0]
|
||||
prefix = n.path
|
||||
continue walk
|
||||
}
|
||||
|
||||
@ -494,7 +528,7 @@ walk: // Outer loop for walking the tree
|
||||
panic("invalid node type")
|
||||
}
|
||||
}
|
||||
} else if path == n.path {
|
||||
} else if path == prefix {
|
||||
// We should have reached the node containing the handle.
|
||||
// Check if this node has a handle registered.
|
||||
if value.handlers = n.handlers; value.handlers != nil {
|
||||
@ -509,8 +543,9 @@ walk: // Outer loop for walking the tree
|
||||
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists for trailing slash recommendation
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if n.indices[i] == '/' {
|
||||
indices := n.indices
|
||||
for i, max := 0, len(indices); i < max; i++ {
|
||||
if indices[i] == '/' {
|
||||
n = n.children[i]
|
||||
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
|
||||
(n.nType == catchAll && n.children[0].handlers != nil)
|
||||
@ -524,8 +559,8 @@ walk: // Outer loop for walking the tree
|
||||
// Nothing found. We can recommend to redirect to the same URL with an
|
||||
// extra trailing slash if a leaf exists for that path
|
||||
value.tsr = (path == "/") ||
|
||||
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
||||
path == n.path[:len(n.path)-1] && n.handlers != nil)
|
||||
(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
|
||||
path == prefix[:len(prefix)-1] && n.handlers != nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -542,75 +577,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||
path = path[len(n.path):]
|
||||
ciPath = append(ciPath, n.path...)
|
||||
|
||||
if len(path) > 0 {
|
||||
// If this node does not have a wildcard (param or catchAll) child,
|
||||
// we can just look up the next child node and continue to walk down
|
||||
// the tree
|
||||
if !n.wildChild {
|
||||
r := unicode.ToLower(rune(path[0]))
|
||||
for i, index := range n.indices {
|
||||
// must use recursive approach since both index and
|
||||
// ToLower(index) could exist. We must check both.
|
||||
if r == unicode.ToLower(index) {
|
||||
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
|
||||
if found {
|
||||
return append(ciPath, out...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found. We can recommend to redirect to the same URL
|
||||
// without a trailing slash if a leaf exists for that path
|
||||
found = fixTrailingSlash && path == "/" && n.handlers != nil
|
||||
return
|
||||
}
|
||||
|
||||
n = n.children[0]
|
||||
switch n.nType {
|
||||
case param:
|
||||
// find param end (either '/' or path end)
|
||||
k := 0
|
||||
for k < len(path) && path[k] != '/' {
|
||||
k++
|
||||
}
|
||||
|
||||
// add param value to case insensitive path
|
||||
ciPath = append(ciPath, path[:k]...)
|
||||
|
||||
// we need to go deeper!
|
||||
if k < len(path) {
|
||||
if len(n.children) > 0 {
|
||||
path = path[k:]
|
||||
n = n.children[0]
|
||||
continue
|
||||
}
|
||||
|
||||
// ... but we can't
|
||||
if fixTrailingSlash && len(path) == k+1 {
|
||||
return ciPath, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if n.handlers != nil {
|
||||
return ciPath, true
|
||||
} else if fixTrailingSlash && len(n.children) == 1 {
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists
|
||||
n = n.children[0]
|
||||
if n.path == "/" && n.handlers != nil {
|
||||
return append(ciPath, '/'), true
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
case catchAll:
|
||||
return append(ciPath, path...), true
|
||||
|
||||
default:
|
||||
panic("invalid node type")
|
||||
}
|
||||
} else {
|
||||
if len(path) == 0 {
|
||||
// We should have reached the node containing the handle.
|
||||
// Check if this node has a handle registered.
|
||||
if n.handlers != nil {
|
||||
@ -633,6 +600,75 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If this node does not have a wildcard (param or catchAll) child,
|
||||
// we can just look up the next child node and continue to walk down
|
||||
// the tree
|
||||
if !n.wildChild {
|
||||
r := unicode.ToLower(rune(path[0]))
|
||||
for i, index := range n.indices {
|
||||
// must use recursive approach since both index and
|
||||
// ToLower(index) could exist. We must check both.
|
||||
if r == unicode.ToLower(index) {
|
||||
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
|
||||
if found {
|
||||
return append(ciPath, out...), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found. We can recommend to redirect to the same URL
|
||||
// without a trailing slash if a leaf exists for that path
|
||||
found = fixTrailingSlash && path == "/" && n.handlers != nil
|
||||
return
|
||||
}
|
||||
|
||||
n = n.children[0]
|
||||
switch n.nType {
|
||||
case param:
|
||||
// Find param end (either '/' or path end)
|
||||
end := 0
|
||||
for end < len(path) && path[end] != '/' {
|
||||
end++
|
||||
}
|
||||
|
||||
// add param value to case insensitive path
|
||||
ciPath = append(ciPath, path[:end]...)
|
||||
|
||||
// we need to go deeper!
|
||||
if end < len(path) {
|
||||
if len(n.children) > 0 {
|
||||
path = path[end:]
|
||||
n = n.children[0]
|
||||
continue
|
||||
}
|
||||
|
||||
// ... but we can't
|
||||
if fixTrailingSlash && len(path) == end+1 {
|
||||
return ciPath, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if n.handlers != nil {
|
||||
return ciPath, true
|
||||
}
|
||||
if fixTrailingSlash && len(n.children) == 1 {
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists
|
||||
n = n.children[0]
|
||||
if n.path == "/" && n.handlers != nil {
|
||||
return append(ciPath, '/'), true
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
case catchAll:
|
||||
return append(ciPath, path...), true
|
||||
|
||||
default:
|
||||
panic("invalid node type")
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found.
|
||||
|
@ -368,6 +368,13 @@ func TestTreeCatchAllConflictRoot(t *testing.T) {
|
||||
testRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestTreeCatchMaxParams(t *testing.T) {
|
||||
tree := &node{}
|
||||
var route = "/cmd/*filepath"
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
checkMaxParams(t, tree)
|
||||
}
|
||||
|
||||
func TestTreeDoubleWildcard(t *testing.T) {
|
||||
const panicMsg = "only one wildcard per path segment is allowed"
|
||||
|
||||
|
153
vendor/vendor.json
vendored
153
vendor/vendor.json
vendored
@ -1,153 +0,0 @@
|
||||
{
|
||||
"comment": "v1.4.0",
|
||||
"ignore": "test",
|
||||
"package": [
|
||||
{
|
||||
"checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=",
|
||||
"path": "github.com/davecgh/go-spew/spew",
|
||||
"revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73",
|
||||
"revisionTime": "2018-02-21T22:46:20Z",
|
||||
"version": "v1.1",
|
||||
"versionExact": "v1.1.1"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "qlEzrgKgIkh7y0ePm9BNo1cNdXo=",
|
||||
"path": "github.com/gin-contrib/sse",
|
||||
"revision": "54d8467d122d380a14768b6b4e5cd7ca4755938f",
|
||||
"revisionTime": "2019-06-02T15:02:53Z",
|
||||
"version": "v0.1",
|
||||
"versionExact": "v0.1.0"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "b4DmyMT9bicTRVJw1hJXHLhIH+0=",
|
||||
"path": "github.com/go-playground/locales",
|
||||
"revision": "f63010822830b6fe52288ee52d5a1151088ce039",
|
||||
"revisionTime": "2018-03-23T16:04:04Z",
|
||||
"version": "v0.12",
|
||||
"versionExact": "v0.12.1"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "JgF260rC9YpWyY5WEljjimWLUXs=",
|
||||
"path": "github.com/go-playground/locales/currency",
|
||||
"revision": "630ebbb602847eba93e75ae38bbc7bb7abcf1ff3",
|
||||
"revisionTime": "2019-04-30T15:33:29Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "9pKcUHBaVS+360X6h4IowhmOPjk=",
|
||||
"path": "github.com/go-playground/universal-translator",
|
||||
"revision": "b32fa301c9fe55953584134cb6853a13c87ec0a1",
|
||||
"revisionTime": "2017-02-09T16:11:52Z",
|
||||
"version": "v0.16",
|
||||
"versionExact": "v0.16.0"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=",
|
||||
"path": "github.com/golang/protobuf/proto",
|
||||
"revision": "c823c79ea1570fb5ff454033735a8e68575d1d0f",
|
||||
"revisionTime": "2019-02-05T22:20:52Z",
|
||||
"version": "v1.3",
|
||||
"versionExact": "v1.3.0"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "zNo6yGy/bCJuzkEcP70oEBtOB2M=",
|
||||
"path": "github.com/leodido/go-urn",
|
||||
"revision": "70078a794e8ea4b497ba7c19a78cd60f90ccf0f4",
|
||||
"revisionTime": "2018-05-24T03:26:21Z",
|
||||
"version": "v1.1",
|
||||
"versionExact": "v1.1.0"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "TB2vxux9xQbvsTHOVt4aRTuvSn4=",
|
||||
"path": "github.com/json-iterator/go",
|
||||
"revision": "0ff49de124c6f76f8494e194af75bde0f1a49a29",
|
||||
"revisionTime": "2019-03-06T14:29:09Z",
|
||||
"version": "v1.1",
|
||||
"versionExact": "v1.1.6"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Ya+baVBU/RkXXUWD3LGFmGJiiIg=",
|
||||
"path": "github.com/mattn/go-isatty",
|
||||
"revision": "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7",
|
||||
"revisionTime": "2019-03-12T13:58:54Z",
|
||||
"version": "v0.0",
|
||||
"versionExact": "v0.0.7"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=",
|
||||
"path": "github.com/modern-go/concurrent",
|
||||
"revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94",
|
||||
"revisionTime": "2018-03-06T01:26:44Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=",
|
||||
"path": "github.com/modern-go/reflect2",
|
||||
"revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8",
|
||||
"revisionTime": "2018-07-18T01:23:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
||||
"path": "github.com/pmezard/go-difflib/difflib",
|
||||
"revision": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc",
|
||||
"revisionTime": "2018-12-26T10:54:42Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "cpNsoLqBprpKh+VZTBOZNVXzBEk=",
|
||||
"path": "github.com/stretchr/objx",
|
||||
"revision": "c61a9dfcced1815e7d40e214d00d1a8669a9f58c",
|
||||
"revisionTime": "2019-02-11T16:23:28Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "DBdcVxnvaINHhWyyGgih/Mel6gE=",
|
||||
"path": "github.com/stretchr/testify",
|
||||
"revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053",
|
||||
"revisionTime": "2018-12-05T02:12:43Z",
|
||||
"version": "v1.3",
|
||||
"versionExact": "v1.3.0"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=",
|
||||
"path": "github.com/stretchr/testify/assert",
|
||||
"revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686",
|
||||
"revisionTime": "2018-05-06T18:05:49Z",
|
||||
"version": "v1.2",
|
||||
"versionExact": "v1.2.2"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "wnEANt4k5X/KGwoFyfSSnpxULm4=",
|
||||
"path": "github.com/stretchr/testify/require",
|
||||
"revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686",
|
||||
"revisionTime": "2018-05-06T18:05:49Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "S4ei9eSqVThDio0Jn2sav6yUbvg=",
|
||||
"path": "github.com/ugorji/go/codec",
|
||||
"revision": "82dbfaf494e3b01d2d481376f11f6a5c8cf9599f",
|
||||
"revisionTime": "2019-07-02T14:15:27Z",
|
||||
"version": "v1.1",
|
||||
"versionExact": "v1.1.6"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=",
|
||||
"path": "golang.org/x/sys/unix",
|
||||
"revision": "a43fa875dd822b81eb6d2ad538bc1f4caba169bd",
|
||||
"revisionTime": "2019-05-02T15:41:39Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ACzc7AkwLtNgKhqtj8V7SGUJgnw=",
|
||||
"path": "gopkg.in/go-playground/validator.v9",
|
||||
"revision": "46b4b1e301c24cac870ffcb4ba5c8a703d1ef475",
|
||||
"revisionTime": "2019-03-31T13:31:25Z",
|
||||
"version": "v9.28",
|
||||
"versionExact": "v9.28.0"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "QqDq2x8XOU7IoOR98Cx1eiV5QY8=",
|
||||
"path": "gopkg.in/yaml.v2",
|
||||
"revision": "51d6538a90f86fe93ac480b35f37b2be17fef232",
|
||||
"revisionTime": "2018-11-15T11:05:04Z",
|
||||
"version": "v2.2",
|
||||
"versionExact": "v2.2.2"
|
||||
}
|
||||
],
|
||||
"rootPath": "github.com/gin-gonic/gin"
|
||||
}
|
@ -5,4 +5,4 @@
|
||||
package gin
|
||||
|
||||
// Version is the current gin framework's version.
|
||||
const Version = "v1.4.0-dev"
|
||||
const Version = "v1.5.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user