build(deps): bump github.com/labstack/echo/v4 from 4.1.11 to 4.5.0

Bumps [github.com/labstack/echo/v4](https://github.com/labstack/echo) from 4.1.11 to 4.5.0.
- [Release notes](https://github.com/labstack/echo/releases)
- [Changelog](https://github.com/labstack/echo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/labstack/echo/compare/v4.1.11...v4.5.0)

---
updated-dependencies:
- dependency-name: github.com/labstack/echo/v4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] 2021-09-04 11:47:11 +00:00 committed by Ondřej Budai
parent 636a713745
commit cad77a25d1
41 changed files with 3312 additions and 561 deletions

View file

@ -5,3 +5,4 @@ vendor
.idea
*.iml
*.out
.vscode

View file

@ -1,7 +1,11 @@
arch:
- amd64
- ppc64le
language: go
go:
- 1.11.x
- 1.12.x
- 1.14.x
- 1.15.x
- tip
env:
- GO111MODULE=on

167
vendor/github.com/labstack/echo/v4/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,167 @@
# Changelog
## v4.5.0 - 2021-08-01
**Important notes**
A **BREAKING CHANGE** is introduced for JWT middleware users.
The JWT library used for the JWT middleware had to be changed from [github.com/dgrijalva/jwt-go](https://github.com/dgrijalva/jwt-go) to
[github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) due former library being unmaintained and affected by security
issues.
The [github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) project is a drop-in replacement, but supports only the latest 2 Go versions.
So for JWT middleware users Go 1.15+ is required. For detailed information please read [#1940](https://github.com/labstack/echo/discussions/)
To change the library imports in all .go files in your project replace all occurrences of `dgrijalva/jwt-go` with `golang-jwt/jwt`.
For Linux CLI you can use:
```bash
find -type f -name "*.go" -exec sed -i "s/dgrijalva\/jwt-go/golang-jwt\/jwt/g" {} \;
go mod tidy
```
**Fixes**
* Change JWT library to `github.com/golang-jwt/jwt` [#1946](https://github.com/labstack/echo/pull/1946)
## v4.4.0 - 2021-07-12
**Fixes**
* Split HeaderXForwardedFor header only by comma [#1878](https://github.com/labstack/echo/pull/1878)
* Fix Timeout middleware Context propagation [#1910](https://github.com/labstack/echo/pull/1910)
**Enhancements**
* Bind data using headers as source [#1866](https://github.com/labstack/echo/pull/1866)
* Adds JWTConfig.ParseTokenFunc to JWT middleware to allow different libraries implementing JWT parsing. [#1887](https://github.com/labstack/echo/pull/1887)
* Adding tests for Echo#Host [#1895](https://github.com/labstack/echo/pull/1895)
* Adds RequestIDHandler function to RequestID middleware [#1898](https://github.com/labstack/echo/pull/1898)
* Allow for custom JSON encoding implementations [#1880](https://github.com/labstack/echo/pull/1880)
## v4.3.0 - 2021-05-08
**Important notes**
* Route matching has improvements for following cases:
1. Correctly match routes with parameter part as last part of route (with trailing backslash)
2. Considering handlers when resolving routes and search for matching http method handler
* Echo minimal Go version is now 1.13.
**Fixes**
* When url ends with slash first param route is the match [#1804](https://github.com/labstack/echo/pull/1812)
* Router should check if node is suitable as matching route by path+method and if not then continue search in tree [#1808](https://github.com/labstack/echo/issues/1808)
* Fix timeout middleware not writing response correctly when handler panics [#1864](https://github.com/labstack/echo/pull/1864)
* Fix binder not working with embedded pointer structs [#1861](https://github.com/labstack/echo/pull/1861)
* Add Go 1.16 to CI and drop 1.12 specific code [#1850](https://github.com/labstack/echo/pull/1850)
**Enhancements**
* Make KeyFunc public in JWT middleware [#1756](https://github.com/labstack/echo/pull/1756)
* Add support for optional filesystem to the static middleware [#1797](https://github.com/labstack/echo/pull/1797)
* Add a custom error handler to key-auth middleware [#1847](https://github.com/labstack/echo/pull/1847)
* Allow JWT token to be looked up from multiple sources [#1845](https://github.com/labstack/echo/pull/1845)
## v4.2.2 - 2021-04-07
**Fixes**
* Allow proxy middleware to use query part in rewrite (#1802)
* Fix timeout middleware not sending status code when handler returns an error (#1805)
* Fix Bind() when target is array/slice and path/query params complains bind target not being struct (#1835)
* Fix panic in redirect middleware on short host name (#1813)
* Fix timeout middleware docs (#1836)
## v4.2.1 - 2021-03-08
**Important notes**
Due to a datarace the config parameters for the newly added timeout middleware required a change.
See the [docs](https://echo.labstack.com/middleware/timeout).
A performance regression has been fixed, even bringing better performance than before for some routing scenarios.
**Fixes**
* Fix performance regression caused by path escaping (#1777, #1798, #1799, aldas)
* Avoid context canceled errors (#1789, clwluvw)
* Improve router to use on stack backtracking (#1791, aldas, stffabi)
* Fix panic in timeout middleware not being not recovered and cause application crash (#1794, aldas)
* Fix Echo.Serve() not serving on HTTP port correctly when TLSListener is used (#1785, #1793, aldas)
* Apply go fmt (#1788, Le0tk0k)
* Uses strings.Equalfold (#1790, rkilingr)
* Improve code quality (#1792, withshubh)
This release was made possible by our **contributors**:
aldas, clwluvw, lammel, Le0tk0k, maciej-jezierski, rkilingr, stffabi, withshubh
## v4.2.0 - 2021-02-11
**Important notes**
The behaviour for binding data has been reworked for compatibility with echo before v4.1.11 by
enforcing `explicit tagging` for processing parameters. This **may break** your code if you
expect combined handling of query/path/form params.
Please see the updated documentation for [request](https://echo.labstack.com/guide/request) and [binding](https://echo.labstack.com/guide/request)
The handling for rewrite rules has been slightly adjusted to expand `*` to a non-greedy `(.*?)` capture group. This is only relevant if multiple asterisks are used in your rules.
Please see [rewrite](https://echo.labstack.com/middleware/rewrite) and [proxy](https://echo.labstack.com/middleware/proxy) for details.
**Security**
* Fix directory traversal vulnerability for Windows (#1718, little-cui)
* Fix open redirect vulnerability with trailing slash (#1771,#1775 aldas,GeoffreyFrogeye)
**Enhancements**
* Add Echo#ListenerNetwork as configuration (#1667, pafuent)
* Add ability to change the status code using response beforeFuncs (#1706, RashadAnsari)
* Echo server startup to allow data race free access to listener address
* Binder: Restore pre v4.1.11 behaviour for c.Bind() to use query params only for GET or DELETE methods (#1727, aldas)
* Binder: Add separate methods to bind only query params, path params or request body (#1681, aldas)
* Binder: New fluent binder for query/path/form parameter binding (#1717, #1736, aldas)
* Router: Performance improvements for missed routes (#1689, pafuent)
* Router: Improve performance for Real-IP detection using IndexByte instead of Split (#1640, imxyb)
* Middleware: Support real regex rules for rewrite and proxy middleware (#1767)
* Middleware: New rate limiting middleware (#1724, iambenkay)
* Middleware: New timeout middleware implementation for go1.13+ (#1743, )
* Middleware: Allow regex pattern for CORS middleware (#1623, KlotzAndrew)
* Middleware: Add IgnoreBase parameter to static middleware (#1701, lnenad, iambenkay)
* Middleware: Add an optional custom function to CORS middleware to validate origin (#1651, curvegrid)
* Middleware: Support form fields in JWT middleware (#1704, rkfg)
* Middleware: Use sync.Pool for (de)compress middleware to improve performance (#1699, #1672, pafuent)
* Middleware: Add decompress middleware to support gzip compressed requests (#1687, arun0009)
* Middleware: Add ErrJWTInvalid for JWT middleware (#1627, juanbelieni)
* Middleware: Add SameSite mode for CSRF cookies to support iframes (#1524, pr0head)
**Fixes**
* Fix handling of special trailing slash case for partial prefix (#1741, stffabi)
* Fix handling of static routes with trailing slash (#1747)
* Fix Static files route not working (#1671, pwli0755, lammel)
* Fix use of caret(^) in regex for rewrite middleware (#1588, chotow)
* Fix Echo#Reverse for Any type routes (#1695, pafuent)
* Fix Router#Find panic with infinite loop (#1661, pafuent)
* Fix Router#Find panic fails on Param paths (#1659, pafuent)
* Fix DefaultHTTPErrorHandler with Debug=true (#1477, lammel)
* Fix incorrect CORS headers (#1669, ulasakdeniz)
* Fix proxy middleware rewritePath to use url with updated tests (#1630, arun0009)
* Fix rewritePath for proxy middleware to use escaped path in (#1628, arun0009)
* Remove unless defer (#1656, imxyb)
**General**
* New maintainers for Echo: Roland Lammel (@lammel) and Pablo Andres Fuente (@pafuent)
* Add GitHub action to compare benchmarks (#1702, pafuent)
* Binding query/path params and form fields to struct only works for explicit tags (#1729,#1734, aldas)
* Add support for Go 1.15 in CI (#1683, asahasrabuddhe)
* Add test for request id to remain unchanged if provided (#1719, iambenkay)
* Refactor echo instance listener access and startup to speed up testing (#1735, aldas)
* Refactor and improve various tests for binding and routing
* Run test workflow only for relevant changes (#1637, #1636, pofl)
* Update .travis.yml (#1662, santosh653)
* Update README.md with an recents framework benchmark (#1679, pafuent)
This release was made possible by **over 100 commits** from more than **20 contributors**:
asahasrabuddhe, aldas, AndrewKlotz, arun0009, chotow, curvegrid, iambenkay, imxyb,
juanbelieni, lammel, little-cui, lnenad, pafuent, pofl, pr0head, pwli, RashadAnsari,
rkfg, santosh653, segfiner, stffabi, ulasakdeniz

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017 LabStack
Copyright (c) 2021 LabStack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,3 +1,34 @@
PKG := "github.com/labstack/echo"
PKG_LIST := $(shell go list ${PKG}/...)
tag:
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
@git tag|grep -v ^v
.DEFAULT_GOAL := check
check: lint vet race ## Check project
init:
@go get -u golang.org/x/lint/golint
lint: ## Lint the files
@golint -set_exit_status ${PKG_LIST}
vet: ## Vet the files
@go vet ${PKG_LIST}
test: ## Run tests
@go test -short ${PKG_LIST}
race: ## Run tests with data race detector
@go test -race ${PKG_LIST}
benchmark: ## Run benchmarks
@go test -run="-" -bench=".*" ${PKG_LIST}
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
goversion ?= "1.15"
test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.15
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"

View file

@ -1,12 +1,12 @@
<a href="https://echo.labstack.com"><img height="80" src="https://cdn.labstack.com/images/echo-logo.svg"></a>
[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge)
[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo)
[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/labstack/echo/v4)
[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo)
[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo)
[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
[![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo)
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://forum.labstack.com)
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions)
[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
@ -17,7 +17,7 @@ Therefore a Go version capable of understanding /vN suffixed imports is required
- 1.9.7+
- 1.10.3+
- 1.11+
- 1.14+
Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
way of using Echo going forward.
@ -42,14 +42,24 @@ For older versions, please use the latest v3 tag.
## Benchmarks
Date: 2018/03/15<br>
Date: 2020/11/11<br>
Source: https://github.com/vishr/web-framework-benchmark<br>
Lower is better!
<img src="https://i.imgur.com/I32VdMJ.png">
<img src="https://i.imgur.com/qwPNQbl.png">
<img src="https://i.imgur.com/s8yKQjx.png">
The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
## [Guide](https://echo.labstack.com/guide)
### Installation
```sh
// go get github.com/labstack/echo/{version}
go get github.com/labstack/echo/v4
```
### Example
```go
@ -84,7 +94,7 @@ func hello(c echo.Context) error {
## Help
- [Forum](https://forum.labstack.com)
- [Forum](https://github.com/labstack/echo/discussions)
- [Chat](https://gitter.im/labstack/echo)
## Contribute

View file

@ -2,7 +2,6 @@ package echo
import (
"encoding"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
@ -30,10 +29,8 @@ type (
}
)
// Bind implements the `Binder#Bind` function.
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
req := c.Request()
// BindPathParams binds path params to bindable object
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
names := c.ParamNames()
values := c.ParamValues()
params := map[string][]string{}
@ -43,22 +40,38 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
if err := b.bindData(i, params, "param"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
return nil
}
// BindQueryParams binds query params to bindable object
func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error {
if err := b.bindData(i, c.QueryParams(), "query"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return nil
}
// BindBody binds request body contents to bindable object
// NB: then binding forms take note that this implementation uses standard library form parsing
// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm
// See non-MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseForm
// See MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseMultipartForm
func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
req := c.Request()
if req.ContentLength == 0 {
return
}
ctype := req.Header.Get(HeaderContentType)
switch {
case strings.HasPrefix(ctype, MIMEApplicationJSON):
if err = json.NewDecoder(req.Body).Decode(i); err != nil {
if ute, ok := err.(*json.UnmarshalTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
} else if se, ok := err.(*json.SyntaxError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
if err = c.Echo().JSONSerializer.Deserialize(c, i); err != nil {
switch err.(type) {
case *HTTPError:
return err
default:
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
@ -80,45 +93,89 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
default:
return ErrUnsupportedMediaType
}
return
return nil
}
func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
if ptr == nil || len(data) == 0 {
// BindHeaders binds HTTP headers to a bindable object
func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error {
if err := b.bindData(i, c.Request().Header, "header"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return nil
}
// Bind implements the `Binder#Bind` function.
// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous
// step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
if err := b.BindPathParams(c, i); err != nil {
return err
}
// Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH)
// Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that.
// i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding.
// This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body
if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {
if err = b.BindQueryParams(c, i); err != nil {
return err
}
}
return b.BindBody(c, i)
}
// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag
func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string) error {
if destination == nil || len(data) == 0 {
return nil
}
typ := reflect.TypeOf(ptr).Elem()
val := reflect.ValueOf(ptr).Elem()
typ := reflect.TypeOf(destination).Elem()
val := reflect.ValueOf(destination).Elem()
if m, ok := ptr.(*map[string]interface{}); ok {
// Map
if typ.Kind() == reflect.Map {
for k, v := range data {
(*m)[k] = v[0]
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
}
return nil
}
// !struct
if typ.Kind() != reflect.Struct {
if tag == "param" || tag == "query" || tag == "header" {
// incompatible type, data is probably to be found in the body
return nil
}
return errors.New("binding element must be a struct")
}
for i := 0; i < typ.NumField(); i++ {
typeField := typ.Field(i)
structField := val.Field(i)
if typeField.Anonymous {
if structField.Kind() == reflect.Ptr {
structField = structField.Elem()
}
}
if !structField.CanSet() {
continue
}
structFieldKind := structField.Kind()
inputFieldName := typeField.Tag.Get(tag)
if typeField.Anonymous && structField.Kind() == reflect.Struct && inputFieldName != "" {
// if anonymous struct with query/param/form tags, report an error
return errors.New("query/param/form tags are not allowed with anonymous struct field")
}
if inputFieldName == "" {
inputFieldName = typeField.Name
// If tag is nil, we inspect if the field is a struct.
if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).
// structs that implement BindUnmarshaler are binded only when they have explicit tag
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
return err
}
continue
}
// does not have explicit tag and is not an ordinary struct - so move to next field
continue
}
inputValue, exists := data[inputFieldName]
@ -127,9 +184,8 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
// url params are bound case sensitive which is inconsistent. To
// fix this we must check all of the map values in a
// case-insensitive search.
inputFieldName = strings.ToLower(inputFieldName)
for k, v := range data {
if strings.ToLower(k) == inputFieldName {
if strings.EqualFold(k, inputFieldName) {
inputValue = v
exists = true
break
@ -219,40 +275,13 @@ func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bo
}
}
// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler
func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) {
ptr := reflect.New(field.Type())
if ptr.CanInterface() {
iface := ptr.Interface()
if unmarshaler, ok := iface.(BindUnmarshaler); ok {
return unmarshaler, ok
}
}
return nil, false
}
// textUnmarshaler attempts to unmarshal a reflect.Value into a TextUnmarshaler
func textUnmarshaler(field reflect.Value) (encoding.TextUnmarshaler, bool) {
ptr := reflect.New(field.Type())
if ptr.CanInterface() {
iface := ptr.Interface()
if unmarshaler, ok := iface.(encoding.TextUnmarshaler); ok {
return unmarshaler, ok
}
}
return nil, false
}
func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
if unmarshaler, ok := bindUnmarshaler(field); ok {
err := unmarshaler.UnmarshalParam(value)
field.Set(reflect.ValueOf(unmarshaler).Elem())
return true, err
fieldIValue := field.Addr().Interface()
if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok {
return true, unmarshaler.UnmarshalParam(value)
}
if unmarshaler, ok := textUnmarshaler(field); ok {
err := unmarshaler.UnmarshalText([]byte(value))
field.Set(reflect.ValueOf(unmarshaler).Elem())
return true, err
if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
return true, unmarshaler.UnmarshalText([]byte(value))
}
return false, nil

1230
vendor/github.com/labstack/echo/v4/binder.go generated vendored Normal file

File diff suppressed because it is too large Load diff

11
vendor/github.com/labstack/echo/v4/codecov.yml generated vendored Normal file
View file

@ -0,0 +1,11 @@
coverage:
status:
project:
default:
threshold: 1%
patch:
default:
threshold: 1%
comment:
require_changes: true

View file

@ -2,7 +2,6 @@ package echo
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
@ -43,6 +42,7 @@ type (
// RealIP returns the client's network address based on `X-Forwarded-For`
// or `X-Real-IP` request header.
// The behavior can be configured using `Echo#IPExtractor`.
RealIP() string
// Path returns the registered path for the handler.
@ -183,6 +183,9 @@ type (
// Logger returns the `Logger` instance.
Logger() Logger
// Set the logger
SetLogger(l Logger)
// Echo returns the `Echo` instance.
Echo() *Echo
@ -202,6 +205,7 @@ type (
handler HandlerFunc
store Map
echo *Echo
logger Logger
lock sync.RWMutex
}
)
@ -241,7 +245,7 @@ func (c *context) IsTLS() bool {
func (c *context) IsWebSocket() bool {
upgrade := c.request.Header.Get(HeaderUpgrade)
return strings.ToLower(upgrade) == "websocket"
return strings.EqualFold(upgrade, "websocket")
}
func (c *context) Scheme() string {
@ -266,8 +270,16 @@ func (c *context) Scheme() string {
}
func (c *context) RealIP() string {
if c.echo != nil && c.echo.IPExtractor != nil {
return c.echo.IPExtractor(c.request)
}
// Fall back to legacy behavior
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
return strings.Split(ip, ", ")[0]
i := strings.IndexAny(ip, ",")
if i > 0 {
return strings.TrimSpace(ip[:i])
}
return ip
}
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
return ip
@ -301,6 +313,19 @@ func (c *context) ParamNames() []string {
func (c *context) SetParamNames(names ...string) {
c.pnames = names
l := len(names)
if *c.echo.maxParam < l {
*c.echo.maxParam = l
}
if len(c.pvalues) < l {
// Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them,
// probably those values will be overriden in a Context#SetParamValues
newPvalues := make([]string, l)
copy(newPvalues, c.pvalues)
c.pvalues = newPvalues
}
}
func (c *context) ParamValues() []string {
@ -308,7 +333,15 @@ func (c *context) ParamValues() []string {
}
func (c *context) SetParamValues(values ...string) {
c.pvalues = values
// NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam at all times
// It will brake the Router#Find code
limit := len(values)
if limit > *c.echo.maxParam {
limit = *c.echo.maxParam
}
for i := 0; i < limit; i++ {
c.pvalues[i] = values[i]
}
}
func (c *context) QueryParam(name string) string {
@ -347,8 +380,12 @@ func (c *context) FormParams() (url.Values, error) {
}
func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
_, fh, err := c.request.FormFile(name)
return fh, err
f, fh, err := c.request.FormFile(name)
if err != nil {
return nil, err
}
f.Close()
return fh, nil
}
func (c *context) MultipartForm() (*multipart.Form, error) {
@ -419,17 +456,16 @@ func (c *context) String(code int, s string) (err error) {
}
func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) {
enc := json.NewEncoder(c.response)
_, pretty := c.QueryParams()["pretty"]
if c.echo.Debug || pretty {
enc.SetIndent("", " ")
indent := ""
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
indent = defaultIndent
}
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
c.response.WriteHeader(code)
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
return
}
if err = enc.Encode(i); err != nil {
if err = c.echo.JSONSerializer.Serialize(c, i, indent); err != nil {
return
}
if _, err = c.response.Write([]byte(");")); err != nil {
@ -439,13 +475,9 @@ func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error
}
func (c *context) json(code int, i interface{}, indent string) error {
enc := json.NewEncoder(c.response)
if indent != "" {
enc.SetIndent("", indent)
}
c.writeContentType(MIMEApplicationJSONCharsetUTF8)
c.response.Status = code
return enc.Encode(i)
return c.echo.JSONSerializer.Serialize(c, i, indent)
}
func (c *context) JSON(code int, i interface{}) (err error) {
@ -597,9 +629,17 @@ func (c *context) SetHandler(h HandlerFunc) {
}
func (c *context) Logger() Logger {
res := c.logger
if res != nil {
return res
}
return c.echo.Logger
}
func (c *context) SetLogger(l Logger) {
c.logger = l
}
func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
c.request = r
c.response.reset(w)
@ -608,6 +648,9 @@ func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
c.store = nil
c.path = ""
c.pnames = nil
c.logger = nil
// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
// c.pvalues = nil
for i := 0; i < *c.echo.maxParam; i++ {
c.pvalues[i] = ""
}
}

View file

@ -48,7 +48,7 @@ import (
"net"
"net/http"
"net/url"
"path"
"os"
"path/filepath"
"reflect"
"runtime"
@ -59,12 +59,17 @@ import (
"github.com/labstack/gommon/log"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
type (
// Echo is the top-level framework instance.
Echo struct {
common
// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
// listener address info (on which interface/port was listener binded) without having data races.
startupMutex sync.RWMutex
StdLogger *stdLog.Logger
colorer *color.Color
premiddleware []MiddlewareFunc
@ -85,9 +90,12 @@ type (
HidePort bool
HTTPErrorHandler HTTPErrorHandler
Binder Binder
JSONSerializer JSONSerializer
Validator Validator
Renderer Renderer
Logger Logger
IPExtractor IPExtractor
ListenerNetwork string
}
// Route contains a handler and information for matching against requests.
@ -118,6 +126,12 @@ type (
Validate(i interface{}) error
}
// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
JSONSerializer interface {
Serialize(c Context, i interface{}, indent string) error
Deserialize(c Context, i interface{}) error
}
// Renderer is the interface that wraps the Render function.
Renderer interface {
Render(io.Writer, string, interface{}, Context) error
@ -227,7 +241,7 @@ const (
const (
// Version of Echo
Version = "4.1.11"
Version = "4.5.0"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
@ -277,6 +291,7 @@ var (
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
ErrCookieNotFound = errors.New("cookie not found")
ErrInvalidCertOrKeyType = errors.New("invalid cert or key type, must be string or []byte")
ErrInvalidListenerNetwork = errors.New("invalid listener network")
)
// Error handlers
@ -298,14 +313,16 @@ func New() (e *Echo) {
AutoTLSManager: autocert.Manager{
Prompt: autocert.AcceptTOS,
},
Logger: log.New("echo"),
colorer: color.New(),
maxParam: new(int),
Logger: log.New("echo"),
colorer: color.New(),
maxParam: new(int),
ListenerNetwork: "tcp",
}
e.Server.Handler = e
e.TLSServer.Handler = e
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
e.Binder = &DefaultBinder{}
e.JSONSerializer = &DefaultJSONSerializer{}
e.Logger.SetLevel(log.ERROR)
e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
e.pool.New = func() interface{} {
@ -354,10 +371,16 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
Message: http.StatusText(http.StatusInternalServerError),
}
}
if e.Debug {
he.Message = err.Error()
} else if m, ok := he.Message.(string); ok {
he.Message = Map{"message": m}
// Issue #1426
code := he.Code
message := he.Message
if m, ok := he.Message.(string); ok {
if e.Debug {
message = Map{"message": m, "error": err.Error()}
} else {
message = Map{"message": m}
}
}
// Send response
@ -365,7 +388,7 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
if c.Request().Method == http.MethodHead { // Issue #608
err = c.NoContent(he.Code)
} else {
err = c.JSON(he.Code, he.Message)
err = c.JSON(code, message)
}
if err != nil {
e.Logger.Error(err)
@ -472,11 +495,31 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl
if err != nil {
return err
}
name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
fi, err := os.Stat(name)
if err != nil {
// The access path does not exist
return NotFoundHandler(c)
}
// If the request is for a directory and does not end with "/"
p = c.Request().URL.Path // path must not be empty.
if fi.IsDir() && p[len(p)-1] != '/' {
// Redirect to ends with "/"
return c.Redirect(http.StatusMovedPermanently, p+"/")
}
return c.File(name)
}
if prefix == "/" {
return get(prefix+"*", h)
// Handle added routes based on trailing slash:
// /prefix => exact route "/prefix" + any route "/prefix/*"
// /prefix/ => only any route "/prefix/*"
if prefix != "" {
if prefix[len(prefix)-1] == '/' {
// Only add any route for intentional trailing slash
return get(prefix+"*", h)
}
get(prefix, h)
}
return get(prefix+"/*", h)
}
@ -497,11 +540,7 @@ func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ..
name := handlerName(handler)
router := e.findRouter(host)
router.Add(method, path, func(c Context) error {
h := handler
// Chain middleware
for i := len(middleware) - 1; i >= 0; i-- {
h = middleware[i](h)
}
h := applyMiddleware(handler, middleware...)
return h(c)
})
r := &Route{
@ -553,7 +592,7 @@ func (e *Echo) Reverse(name string, params ...interface{}) string {
for _, r := range e.router.routes {
if r.Name == name {
for i, l := 0, len(r.Path); i < l; i++ {
if r.Path[i] == ':' && n < ln {
if (r.Path[i] == ':' || r.Path[i] == '*') && n < ln {
for ; i < l && r.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))
@ -595,16 +634,15 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context
c := e.pool.Get().(*context)
c.Reset(r, w)
h := NotFoundHandler
if e.premiddleware == nil {
e.findRouter(r.Host).Find(r.Method, getPath(r), c)
e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
h = c.Handler()
h = applyMiddleware(h, e.middleware...)
} else {
h = func(c Context) error {
e.findRouter(r.Host).Find(r.Method, getPath(r), c)
e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
h := c.Handler()
h = applyMiddleware(h, e.middleware...)
return h(c)
@ -623,21 +661,30 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Start starts an HTTP server.
func (e *Echo) Start(address string) error {
e.startupMutex.Lock()
e.Server.Addr = address
return e.StartServer(e.Server)
if err := e.configureServer(e.Server); err != nil {
e.startupMutex.Unlock()
return err
}
e.startupMutex.Unlock()
return e.Server.Serve(e.Listener)
}
// StartTLS starts an HTTPS server.
// If `certFile` or `keyFile` is `string` the values are treated as file paths.
// If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is.
func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error) {
e.startupMutex.Lock()
var cert []byte
if cert, err = filepathOrContent(certFile); err != nil {
e.startupMutex.Unlock()
return
}
var key []byte
if key, err = filepathOrContent(keyFile); err != nil {
e.startupMutex.Unlock()
return
}
@ -645,10 +692,17 @@ func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err erro
s.TLSConfig = new(tls.Config)
s.TLSConfig.Certificates = make([]tls.Certificate, 1)
if s.TLSConfig.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil {
e.startupMutex.Unlock()
return
}
return e.startTLS(address)
e.configureTLS(address)
if err := e.configureServer(s); err != nil {
e.startupMutex.Unlock()
return err
}
e.startupMutex.Unlock()
return s.Serve(e.TLSListener)
}
func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
@ -664,24 +718,45 @@ func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
func (e *Echo) StartAutoTLS(address string) error {
e.startupMutex.Lock()
s := e.TLSServer
s.TLSConfig = new(tls.Config)
s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto)
return e.startTLS(address)
e.configureTLS(address)
if err := e.configureServer(s); err != nil {
e.startupMutex.Unlock()
return err
}
e.startupMutex.Unlock()
return s.Serve(e.TLSListener)
}
func (e *Echo) startTLS(address string) error {
func (e *Echo) configureTLS(address string) {
s := e.TLSServer
s.Addr = address
if !e.DisableHTTP2 {
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
}
return e.StartServer(e.TLSServer)
}
// StartServer starts a custom http server.
func (e *Echo) StartServer(s *http.Server) (err error) {
e.startupMutex.Lock()
if err := e.configureServer(s); err != nil {
e.startupMutex.Unlock()
return err
}
if s.TLSConfig != nil {
e.startupMutex.Unlock()
return s.Serve(e.TLSListener)
}
e.startupMutex.Unlock()
return s.Serve(e.Listener)
}
func (e *Echo) configureServer(s *http.Server) (err error) {
// Setup
e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger
@ -696,7 +771,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
if s.TLSConfig == nil {
if e.Listener == nil {
e.Listener, err = newListener(s.Addr)
e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
if err != nil {
return err
}
@ -704,10 +779,10 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
return s.Serve(e.Listener)
return nil
}
if e.TLSListener == nil {
l, err := newListener(s.Addr)
l, err := newListener(s.Addr, e.ListenerNetwork)
if err != nil {
return err
}
@ -716,12 +791,65 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
if !e.HidePort {
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
}
return s.Serve(e.TLSListener)
return nil
}
// ListenerAddr returns net.Addr for Listener
func (e *Echo) ListenerAddr() net.Addr {
e.startupMutex.RLock()
defer e.startupMutex.RUnlock()
if e.Listener == nil {
return nil
}
return e.Listener.Addr()
}
// TLSListenerAddr returns net.Addr for TLSListener
func (e *Echo) TLSListenerAddr() net.Addr {
e.startupMutex.RLock()
defer e.startupMutex.RUnlock()
if e.TLSListener == nil {
return nil
}
return e.TLSListener.Addr()
}
// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext).
func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
e.startupMutex.Lock()
// Setup
s := e.Server
s.Addr = address
e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger
s.Handler = h2c.NewHandler(e, h2s)
if e.Debug {
e.Logger.SetLevel(log.DEBUG)
}
if !e.HideBanner {
e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
}
if e.Listener == nil {
e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
if err != nil {
e.startupMutex.Unlock()
return err
}
}
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
e.startupMutex.Unlock()
return s.Serve(e.Listener)
}
// Close immediately stops the server.
// It internally calls `http.Server#Close()`.
func (e *Echo) Close() error {
e.startupMutex.Lock()
defer e.startupMutex.Unlock()
if err := e.TLSServer.Close(); err != nil {
return err
}
@ -731,6 +859,8 @@ func (e *Echo) Close() error {
// Shutdown stops the server gracefully.
// It internally calls `http.Server#Shutdown()`.
func (e *Echo) Shutdown(ctx stdContext.Context) error {
e.startupMutex.Lock()
defer e.startupMutex.Unlock()
if err := e.TLSServer.Shutdown(ctx); err != nil {
return err
}
@ -748,6 +878,9 @@ func NewHTTPError(code int, message ...interface{}) *HTTPError {
// Error makes it compatible with `error` interface.
func (he *HTTPError) Error() string {
if he.Internal == nil {
return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message)
}
return fmt.Sprintf("code=%d, message=%v, internal=%v", he.Code, he.Message, he.Internal)
}
@ -757,6 +890,11 @@ func (he *HTTPError) SetInternal(err error) *HTTPError {
return he
}
// Unwrap satisfies the Go 1.13 error wrapper interface.
func (he *HTTPError) Unwrap() error {
return he.Internal
}
// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
func WrapHandler(h http.Handler) HandlerFunc {
return func(c Context) error {
@ -779,7 +917,11 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
}
}
func getPath(r *http.Request) string {
// GetPath returns RawPath, if it's empty returns Path from URL
// Difference between RawPath and Path is:
// * Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/.
// * RawPath is an optional field which only gets set if the default encoding is different from Path.
func GetPath(r *http.Request) string {
path := r.URL.RawPath
if path == "" {
path = r.URL.Path
@ -822,14 +964,18 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
return
} else if err = c.(*net.TCPConn).SetKeepAlive(true); err != nil {
return
} else if err = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute); err != nil {
return
}
// Ignore error from setting the KeepAlivePeriod as some systems, such as
// OpenBSD, do not support setting TCP_USER_TIMEOUT on IPPROTO_TCP
_ = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute)
return
}
func newListener(address string) (*tcpKeepAliveListener, error) {
l, err := net.Listen("tcp", address)
func newListener(address, network string) (*tcpKeepAliveListener, error) {
if network != "tcp" && network != "tcp4" && network != "tcp6" {
return nil, ErrInvalidListenerNetwork
}
l, err := net.Listen(network, address)
if err != nil {
return nil, err
}

View file

@ -1,11 +1,16 @@
module github.com/labstack/echo/v4
go 1.12
go 1.15
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/labstack/gommon v0.3.0
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/stretchr/testify v1.4.0
github.com/valyala/fasttemplate v1.0.1
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
github.com/valyala/fasttemplate v1.2.1
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
)

View file

@ -1,14 +1,16 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
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/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -16,19 +18,30 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -109,7 +109,7 @@ func (g *Group) Static(prefix, root string) {
// File implements `Echo#File()` for sub-routes within the Group.
func (g *Group) File(path, file string) {
g.file(g.prefix+path, file, g.GET)
g.file(path, file, g.GET)
}
// Add implements `Echo#Add()` for sub-routes within the Group.

137
vendor/github.com/labstack/echo/v4/ip.go generated vendored Normal file
View file

@ -0,0 +1,137 @@
package echo
import (
"net"
"net/http"
"strings"
)
type ipChecker struct {
trustLoopback bool
trustLinkLocal bool
trustPrivateNet bool
trustExtraRanges []*net.IPNet
}
// TrustOption is config for which IP address to trust
type TrustOption func(*ipChecker)
// TrustLoopback configures if you trust loopback address (default: true).
func TrustLoopback(v bool) TrustOption {
return func(c *ipChecker) {
c.trustLoopback = v
}
}
// TrustLinkLocal configures if you trust link-local address (default: true).
func TrustLinkLocal(v bool) TrustOption {
return func(c *ipChecker) {
c.trustLinkLocal = v
}
}
// TrustPrivateNet configures if you trust private network address (default: true).
func TrustPrivateNet(v bool) TrustOption {
return func(c *ipChecker) {
c.trustPrivateNet = v
}
}
// TrustIPRange add trustable IP ranges using CIDR notation.
func TrustIPRange(ipRange *net.IPNet) TrustOption {
return func(c *ipChecker) {
c.trustExtraRanges = append(c.trustExtraRanges, ipRange)
}
}
func newIPChecker(configs []TrustOption) *ipChecker {
checker := &ipChecker{trustLoopback: true, trustLinkLocal: true, trustPrivateNet: true}
for _, configure := range configs {
configure(checker)
}
return checker
}
func isPrivateIPRange(ip net.IP) bool {
if ip4 := ip.To4(); ip4 != nil {
return ip4[0] == 10 ||
ip4[0] == 172 && ip4[1]&0xf0 == 16 ||
ip4[0] == 192 && ip4[1] == 168
}
return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
}
func (c *ipChecker) trust(ip net.IP) bool {
if c.trustLoopback && ip.IsLoopback() {
return true
}
if c.trustLinkLocal && ip.IsLinkLocalUnicast() {
return true
}
if c.trustPrivateNet && isPrivateIPRange(ip) {
return true
}
for _, trustedRange := range c.trustExtraRanges {
if trustedRange.Contains(ip) {
return true
}
}
return false
}
// IPExtractor is a function to extract IP addr from http.Request.
// Set appropriate one to Echo#IPExtractor.
// See https://echo.labstack.com/guide/ip-address for more details.
type IPExtractor func(*http.Request) string
// ExtractIPDirect extracts IP address using actual IP address.
// Use this if your server faces to internet directory (i.e.: uses no proxy).
func ExtractIPDirect() IPExtractor {
return func(req *http.Request) string {
ra, _, _ := net.SplitHostPort(req.RemoteAddr)
return ra
}
}
// ExtractIPFromRealIPHeader extracts IP address using x-real-ip header.
// Use this if you put proxy which uses this header.
func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
directIP := ExtractIPDirect()(req)
realIP := req.Header.Get(HeaderXRealIP)
if realIP != "" {
if ip := net.ParseIP(directIP); ip != nil && checker.trust(ip) {
return realIP
}
}
return directIP
}
}
// ExtractIPFromXFFHeader extracts IP address using x-forwarded-for header.
// Use this if you put proxy which uses this header.
// This returns nearest untrustable IP. If all IPs are trustable, returns furthest one (i.e.: XFF[0]).
func ExtractIPFromXFFHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
directIP := ExtractIPDirect()(req)
xffs := req.Header[HeaderXForwardedFor]
if len(xffs) == 0 {
return directIP
}
ips := append(strings.Split(strings.Join(xffs, ","), ","), directIP)
for i := len(ips) - 1; i >= 0; i-- {
ip := net.ParseIP(strings.TrimSpace(ips[i]))
if ip == nil {
// Unable to parse IP; cannot trust entire records
return directIP
}
if !checker.trust(ip) {
return ip.String()
}
}
// All of the IPs are trusted; return first element because it is furthest from server (best effort strategy).
return strings.TrimSpace(ips[0])
}
}

31
vendor/github.com/labstack/echo/v4/json.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
package echo
import (
"encoding/json"
"fmt"
"net/http"
)
// DefaultJSONSerializer implements JSON encoding using encoding/json.
type DefaultJSONSerializer struct{}
// Serialize converts an interface into a json and writes it to the response.
// You can optionally use the indent parameter to produce pretty JSONs.
func (d DefaultJSONSerializer) Serialize(c Context, i interface{}, indent string) error {
enc := json.NewEncoder(c.Response())
if indent != "" {
enc.SetIndent("", indent)
}
return enc.Encode(i)
}
// Deserialize reads a JSON from a request body and converts it into an interface.
func (d DefaultJSONSerializer) Deserialize(c Context, i interface{}) error {
err := json.NewDecoder(c.Request().Body).Decode(i)
if ute, ok := err.(*json.UnmarshalTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
} else if se, ok := err.(*json.SyntaxError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
}
return err
}

View file

@ -56,11 +56,11 @@ func (r *Response) WriteHeader(code int) {
r.echo.Logger.Warn("response already committed")
return
}
r.Status = code
for _, fn := range r.beforeFuncs {
fn()
}
r.Status = code
r.Writer.WriteHeader(code)
r.Writer.WriteHeader(r.Status)
r.Committed = true
}

View file

@ -1,6 +1,8 @@
package echo
import "net/http"
import (
"net/http"
)
type (
// Router is the registry of all registered routes for an `Echo` instance for
@ -11,17 +13,23 @@ type (
echo *Echo
}
node struct {
kind kind
label byte
prefix string
parent *node
children children
ppath string
pnames []string
methodHandler *methodHandler
kind kind
label byte
prefix string
parent *node
staticChildren children
ppath string
pnames []string
methodHandler *methodHandler
paramChild *node
anyChild *node
// isLeaf indicates that node does not have child routes
isLeaf bool
// isHandler indicates that node has at least one handler registered to it
isHandler bool
}
kind uint8
children []*node
kind uint8
children []*node
methodHandler struct {
connect HandlerFunc
delete HandlerFunc
@ -38,11 +46,28 @@ type (
)
const (
skind kind = iota
pkind
akind
staticKind kind = iota
paramKind
anyKind
paramLabel = byte(':')
anyLabel = byte('*')
)
func (m *methodHandler) isHandler() bool {
return m.connect != nil ||
m.delete != nil ||
m.get != nil ||
m.head != nil ||
m.options != nil ||
m.patch != nil ||
m.post != nil ||
m.propfind != nil ||
m.put != nil ||
m.trace != nil ||
m.report != nil
}
// NewRouter returns a new Router instance.
func NewRouter(e *Echo) *Router {
return &Router{
@ -66,115 +91,157 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
pnames := []string{} // Param names
ppath := path // Pristine path
for i, l := 0, len(path); i < l; i++ {
if h == nil && r.echo.Logger != nil {
// FIXME: in future we should return error
r.echo.Logger.Errorf("Adding route without handler function: %v:%v", method, path)
}
for i, lcpIndex := 0, len(path); i < lcpIndex; i++ {
if path[i] == ':' {
j := i + 1
r.insert(method, path[:i], nil, skind, "", nil)
for ; i < l && path[i] != '/'; i++ {
r.insert(method, path[:i], nil, staticKind, "", nil)
for ; i < lcpIndex && path[i] != '/'; i++ {
}
pnames = append(pnames, path[j:i])
path = path[:j] + path[i:]
i, l = j, len(path)
i, lcpIndex = j, len(path)
if i == l {
r.insert(method, path[:i], h, pkind, ppath, pnames)
if i == lcpIndex {
// path node is last fragment of route path. ie. `/users/:id`
r.insert(method, path[:i], h, paramKind, ppath, pnames)
} else {
r.insert(method, path[:i], nil, pkind, "", nil)
r.insert(method, path[:i], nil, paramKind, "", nil)
}
} else if path[i] == '*' {
r.insert(method, path[:i], nil, skind, "", nil)
r.insert(method, path[:i], nil, staticKind, "", nil)
pnames = append(pnames, "*")
r.insert(method, path[:i+1], h, akind, ppath, pnames)
r.insert(method, path[:i+1], h, anyKind, ppath, pnames)
}
}
r.insert(method, path, h, skind, ppath, pnames)
r.insert(method, path, h, staticKind, ppath, pnames)
}
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {
// Adjust max param
l := len(pnames)
if *r.echo.maxParam < l {
*r.echo.maxParam = l
paramLen := len(pnames)
if *r.echo.maxParam < paramLen {
*r.echo.maxParam = paramLen
}
cn := r.tree // Current node as root
if cn == nil {
currentNode := r.tree // Current node as root
if currentNode == nil {
panic("echo: invalid method")
}
search := path
for {
sl := len(search)
pl := len(cn.prefix)
l := 0
searchLen := len(search)
prefixLen := len(currentNode.prefix)
lcpLen := 0
// LCP
max := pl
if sl < max {
max = sl
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
max := prefixLen
if searchLen < max {
max = searchLen
}
for ; l < max && search[l] == cn.prefix[l]; l++ {
for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
}
if l == 0 {
if lcpLen == 0 {
// At root node
cn.label = search[0]
cn.prefix = search
currentNode.label = search[0]
currentNode.prefix = search
if h != nil {
cn.kind = t
cn.addHandler(method, h)
cn.ppath = ppath
cn.pnames = pnames
currentNode.kind = t
currentNode.addHandler(method, h)
currentNode.ppath = ppath
currentNode.pnames = pnames
}
} else if l < pl {
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
} else if lcpLen < prefixLen {
// Split node
n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
n := newNode(
currentNode.kind,
currentNode.prefix[lcpLen:],
currentNode,
currentNode.staticChildren,
currentNode.methodHandler,
currentNode.ppath,
currentNode.pnames,
currentNode.paramChild,
currentNode.anyChild,
)
// Update parent path for all children to new node
for _, child := range currentNode.staticChildren {
child.parent = n
}
if currentNode.paramChild != nil {
currentNode.paramChild.parent = n
}
if currentNode.anyChild != nil {
currentNode.anyChild.parent = n
}
// Reset parent node
cn.kind = skind
cn.label = cn.prefix[0]
cn.prefix = cn.prefix[:l]
cn.children = nil
cn.methodHandler = new(methodHandler)
cn.ppath = ""
cn.pnames = nil
currentNode.kind = staticKind
currentNode.label = currentNode.prefix[0]
currentNode.prefix = currentNode.prefix[:lcpLen]
currentNode.staticChildren = nil
currentNode.methodHandler = new(methodHandler)
currentNode.ppath = ""
currentNode.pnames = nil
currentNode.paramChild = nil
currentNode.anyChild = nil
currentNode.isLeaf = false
currentNode.isHandler = false
cn.addChild(n)
// Only Static children could reach here
currentNode.addStaticChild(n)
if l == sl {
if lcpLen == searchLen {
// At parent node
cn.kind = t
cn.addHandler(method, h)
cn.ppath = ppath
cn.pnames = pnames
currentNode.kind = t
currentNode.addHandler(method, h)
currentNode.ppath = ppath
currentNode.pnames = pnames
} else {
// Create child node
n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames)
n = newNode(t, search[lcpLen:], currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
n.addHandler(method, h)
cn.addChild(n)
// Only Static children could reach here
currentNode.addStaticChild(n)
}
} else if l < sl {
search = search[l:]
c := cn.findChildWithLabel(search[0])
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
} else if lcpLen < searchLen {
search = search[lcpLen:]
c := currentNode.findChildWithLabel(search[0])
if c != nil {
// Go deeper
cn = c
currentNode = c
continue
}
// Create child node
n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames)
n := newNode(t, search, currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
n.addHandler(method, h)
cn.addChild(n)
switch t {
case staticKind:
currentNode.addStaticChild(n)
case paramKind:
currentNode.paramChild = n
case anyKind:
currentNode.anyChild = n
}
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
} else {
// Node already exists
if h != nil {
cn.addHandler(method, h)
cn.ppath = ppath
if len(cn.pnames) == 0 { // Issue #729
cn.pnames = pnames
currentNode.addHandler(method, h)
currentNode.ppath = ppath
if len(currentNode.pnames) == 0 { // Issue #729
currentNode.pnames = pnames
}
}
}
@ -182,34 +249,29 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
}
}
func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node {
func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath string, pnames []string, paramChildren, anyChildren *node) *node {
return &node{
kind: t,
label: pre[0],
prefix: pre,
parent: p,
children: c,
ppath: ppath,
pnames: pnames,
methodHandler: mh,
kind: t,
label: pre[0],
prefix: pre,
parent: p,
staticChildren: sc,
ppath: ppath,
pnames: pnames,
methodHandler: mh,
paramChild: paramChildren,
anyChild: anyChildren,
isLeaf: sc == nil && paramChildren == nil && anyChildren == nil,
isHandler: mh.isHandler(),
}
}
func (n *node) addChild(c *node) {
n.children = append(n.children, c)
func (n *node) addStaticChild(c *node) {
n.staticChildren = append(n.staticChildren, c)
}
func (n *node) findChild(l byte, t kind) *node {
for _, c := range n.children {
if c.label == l && c.kind == t {
return c
}
}
return nil
}
func (n *node) findChildWithLabel(l byte) *node {
for _, c := range n.children {
func (n *node) findStaticChild(l byte) *node {
for _, c := range n.staticChildren {
if c.label == l {
return c
}
@ -217,12 +279,18 @@ func (n *node) findChildWithLabel(l byte) *node {
return nil
}
func (n *node) findChildByKind(t kind) *node {
for _, c := range n.children {
if c.kind == t {
func (n *node) findChildWithLabel(l byte) *node {
for _, c := range n.staticChildren {
if c.label == l {
return c
}
}
if l == paramLabel {
return n.paramChild
}
if l == anyLabel {
return n.anyChild
}
return nil
}
@ -251,6 +319,12 @@ func (n *node) addHandler(method string, h HandlerFunc) {
case REPORT:
n.methodHandler.report = h
}
if h != nil {
n.isHandler = true
} else {
n.isHandler = n.methodHandler.isHandler()
}
}
func (n *node) findHandler(method string) HandlerFunc {
@ -302,142 +376,191 @@ func (n *node) checkMethodNotAllowed() HandlerFunc {
func (r *Router) Find(method, path string, c Context) {
ctx := c.(*context)
ctx.path = path
cn := r.tree // Current node as root
currentNode := r.tree // Current node as root
var (
search = path
child *node // Child node
n int // Param counter
nk kind // Next kind
nn *node // Next node
ns string // Next search
pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
previousBestMatchNode *node
matchedHandler HandlerFunc
// search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
// and search value gets shorter and shorter.
search = path
searchIndex = 0
paramIndex int // Param counter
paramValues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
)
// Search order static > param > any
for {
if search == "" {
break
}
// Backtracking is needed when a dead end (leaf node) is reached in the router tree.
// To backtrack the current node will be changed to the parent node and the next kind for the
// router logic will be returned based on fromKind or kind of the dead end node (static > param > any).
// For example if there is no static node match we should check parent next sibling by kind (param).
// Backtracking itself does not check if there is a next sibling, this is done by the router logic.
backtrackToNextNodeKind := func(fromKind kind) (nextNodeKind kind, valid bool) {
previous := currentNode
currentNode = previous.parent
valid = currentNode != nil
pl := 0 // Prefix length
l := 0 // LCP length
if cn.label != ':' {
sl := len(search)
pl = len(cn.prefix)
// LCP
max := pl
if sl < max {
max = sl
}
for ; l < max && search[l] == cn.prefix[l]; l++ {
}
}
if l == pl {
// Continue search
search = search[l:]
// Next node type by priority
if previous.kind == anyKind {
nextNodeKind = staticKind
} else {
if nn == nil { // Issue #1348
return // Not found
nextNodeKind = previous.kind + 1
}
if fromKind == staticKind {
// when backtracking is done from static kind block we did not change search so nothing to restore
return
}
// restore search to value it was before we move to current node we are backtracking from.
if previous.kind == staticKind {
searchIndex -= len(previous.prefix)
} else {
paramIndex--
// for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
// for that index as it would also contain part of path we cut off before moving into node we are backtracking from
searchIndex -= len(paramValues[paramIndex])
paramValues[paramIndex] = ""
}
search = path[searchIndex:]
return
}
// Router tree is implemented by longest common prefix array (LCP array) https://en.wikipedia.org/wiki/LCP_array
// Tree search is implemented as for loop where one loop iteration is divided into 3 separate blocks
// Each of these blocks checks specific kind of node (static/param/any). Order of blocks reflex their priority in routing.
// Search order/priority is: static > param > any.
//
// Note: backtracking in tree is implemented by replacing/switching currentNode to previous node
// and hoping to (goto statement) next block by priority to check if it is the match.
for {
prefixLen := 0 // Prefix length
lcpLen := 0 // LCP (longest common prefix) length
if currentNode.kind == staticKind {
searchLen := len(search)
prefixLen = len(currentNode.prefix)
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
max := prefixLen
if searchLen < max {
max = searchLen
}
cn = nn
search = ns
if nk == pkind {
goto Param
} else if nk == akind {
goto Any
for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
}
}
if search == "" {
break
if lcpLen != prefixLen {
// No matching prefix, let's backtrack to the first possible alternative node of the decision path
nk, ok := backtrackToNextNodeKind(staticKind)
if !ok {
return // No other possibilities on the decision path
} else if nk == paramKind {
goto Param
// NOTE: this case (backtracking from static node to previous any node) can not happen by current any matching logic. Any node is end of search currently
//} else if nk == anyKind {
// goto Any
} else {
// Not found (this should never be possible for static node we are looking currently)
break
}
}
// The full prefix has matched, remove the prefix from the remaining search
search = search[lcpLen:]
searchIndex = searchIndex + lcpLen
// Finish routing if no remaining search and we are on a node with handler and matching method type
if search == "" && currentNode.isHandler {
// check if current node has handler registered for http method we are looking for. we store currentNode as
// best matching in case we do no find no more routes matching this path+method
if previousBestMatchNode == nil {
previousBestMatchNode = currentNode
}
if h := currentNode.findHandler(method); h != nil {
matchedHandler = h
break
}
}
// Static node
if child = cn.findChild(search[0], skind); child != nil {
// Save next
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
nk = pkind
nn = cn
ns = search
}
cn = child
continue
}
// Param node
Param:
if child = cn.findChildByKind(pkind); child != nil {
// Issue #378
if len(pvalues) == n {
if search != "" {
if child := currentNode.findStaticChild(search[0]); child != nil {
currentNode = child
continue
}
}
// Save next
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
nk = akind
nn = cn
ns = search
Param:
// Param node
if child := currentNode.paramChild; search != "" && child != nil {
currentNode = child
i := 0
l := len(search)
if currentNode.isLeaf {
// when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
i = l
} else {
for ; i < l && search[i] != '/'; i++ {
}
}
cn = child
i, l := 0, len(search)
for ; i < l && search[i] != '/'; i++ {
}
pvalues[n] = search[:i]
n++
paramValues[paramIndex] = search[:i]
paramIndex++
search = search[i:]
searchIndex = searchIndex + i
continue
}
// Any node
Any:
if cn = cn.findChildByKind(akind); cn == nil {
if nn != nil {
cn = nn
nn = cn.parent // Next (Issue #954)
if nn != nil {
nk = nn.kind
}
search = ns
if nk == pkind {
goto Param
} else if nk == akind {
goto Any
}
// Any node
if child := currentNode.anyChild; child != nil {
// If any node is found, use remaining path for paramValues
currentNode = child
paramValues[len(currentNode.pnames)-1] = search
// update indexes/search in case we need to backtrack when no handler match is found
paramIndex++
searchIndex += +len(search)
search = ""
// check if current node has handler registered for http method we are looking for. we store currentNode as
// best matching in case we do no find no more routes matching this path+method
if previousBestMatchNode == nil {
previousBestMatchNode = currentNode
}
if h := currentNode.findHandler(method); h != nil {
matchedHandler = h
break
}
return // Not found
}
pvalues[len(cn.pnames)-1] = search
break
}
ctx.handler = cn.findHandler(method)
ctx.path = cn.ppath
ctx.pnames = cn.pnames
// NOTE: Slow zone...
if ctx.handler == nil {
ctx.handler = cn.checkMethodNotAllowed()
// Dig further for any, might have an empty value for *, e.g.
// serving a directory. Issue #207.
if cn = cn.findChildByKind(akind); cn == nil {
return
}
if h := cn.findHandler(method); h != nil {
ctx.handler = h
// Let's backtrack to the first possible alternative node of the decision path
nk, ok := backtrackToNextNodeKind(anyKind)
if !ok {
break // No other possibilities on the decision path
} else if nk == paramKind {
goto Param
} else if nk == anyKind {
goto Any
} else {
ctx.handler = cn.checkMethodNotAllowed()
// Not found
break
}
ctx.path = cn.ppath
ctx.pnames = cn.pnames
pvalues[len(cn.pnames)-1] = ""
}
if currentNode == nil && previousBestMatchNode == nil {
return // nothing matched at all
}
if matchedHandler != nil {
ctx.handler = matchedHandler
} else {
// use previous match as basis. although we have no matching handler we have path match.
// so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404)
currentNode = previousBestMatchNode
ctx.handler = currentNode.checkMethodNotAllowed()
}
ctx.path = currentNode.ppath
ctx.pnames = currentNode.pnames
return
}