mirror of
https://github.com/kataras/iris.git
synced 2026-01-20 02:16:08 +00:00
Update to version 8.5.5 | Read HISTORY.md
Former-commit-id: dced7d472edabbab4f80c76051f13261928a8dea
This commit is contained in:
76
core/host/interrupt.go
Normal file
76
core/host/interrupt.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// RegisterOnInterrupt registers a global function to call when CTRL+C/CMD+C pressed or a unix kill command received.
|
||||
func RegisterOnInterrupt(cb func()) {
|
||||
Interrupt.Register(cb)
|
||||
}
|
||||
|
||||
// Interrupt watches the os.Signals for interruption signals
|
||||
// and fires the callbacks when those happens.
|
||||
// A call of its `FireNow` manually will fire and reset the registered interrupt handlers.
|
||||
var Interrupt = new(interruptListener)
|
||||
|
||||
type interruptListener struct {
|
||||
mu sync.Mutex
|
||||
once sync.Once
|
||||
// onInterrupt contains a list of the functions that should be called when CTRL+C/CMD+C or
|
||||
// a unix kill command received.
|
||||
onInterrupt []func()
|
||||
}
|
||||
|
||||
// Register registers a global function to call when CTRL+C/CMD+C pressed or a unix kill command received.
|
||||
func (i *interruptListener) Register(cb func()) {
|
||||
if cb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
i.listenOnce()
|
||||
i.mu.Lock()
|
||||
i.onInterrupt = append(i.onInterrupt, cb)
|
||||
i.mu.Unlock()
|
||||
}
|
||||
|
||||
// FireNow can be called more than one times from a Consumer in order to
|
||||
// execute all interrupt handlers manually.
|
||||
func (i *interruptListener) FireNow() {
|
||||
i.mu.Lock()
|
||||
for _, f := range i.onInterrupt {
|
||||
f()
|
||||
}
|
||||
i.onInterrupt = i.onInterrupt[0:0]
|
||||
i.mu.Unlock()
|
||||
}
|
||||
|
||||
// listenOnce fires a goroutine which calls the interrupt handlers when CTRL+C/CMD+C and e.t.c.
|
||||
// If `FireNow` called before then it does nothing when interrupt signal received,
|
||||
// so it's safe to be used side by side with `FireNow`.
|
||||
//
|
||||
// Btw this `listenOnce` is called automatically on first register, it's useless for outsiders.
|
||||
func (i *interruptListener) listenOnce() {
|
||||
i.once.Do(func() { go i.notifyAndFire() })
|
||||
}
|
||||
|
||||
func (i *interruptListener) notifyAndFire() {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch,
|
||||
// kill -SIGINT XXXX or Ctrl+c
|
||||
os.Interrupt,
|
||||
syscall.SIGINT, // register that too, it should be ok
|
||||
// os.Kill is equivalent with the syscall.SIGKILL
|
||||
os.Kill,
|
||||
syscall.SIGKILL, // register that too, it should be ok
|
||||
// kill -SIGTERM XXXX
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
select {
|
||||
case <-ch:
|
||||
i.FireNow()
|
||||
}
|
||||
}
|
||||
@@ -190,8 +190,6 @@ func (su *Supervisor) supervise(blockFunc func() error) error {
|
||||
|
||||
su.notifyServe(host)
|
||||
|
||||
tryStartInterruptNotifier()
|
||||
|
||||
err := blockFunc()
|
||||
su.notifyErr(err)
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// package-level interrupt notifier and event firing.
|
||||
|
||||
type world struct {
|
||||
mu sync.Mutex
|
||||
// onInterrupt contains a list of the functions that should be called when CTRL+C/CMD+C or
|
||||
// a unix kill command received.
|
||||
onInterrupt []func()
|
||||
}
|
||||
|
||||
var w = &world{}
|
||||
|
||||
// RegisterOnInterrupt registers a global function to call when CTRL+C/CMD+C pressed or a unix kill command received.
|
||||
func RegisterOnInterrupt(cb func()) {
|
||||
w.mu.Lock()
|
||||
w.onInterrupt = append(w.onInterrupt, cb)
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
func notifyInterrupt() {
|
||||
w.mu.Lock()
|
||||
for _, f := range w.onInterrupt {
|
||||
f()
|
||||
}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
func tryStartInterruptNotifier() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if len(w.onInterrupt) > 0 {
|
||||
// this can't be moved to the task interrupt's `Run` function
|
||||
// because it will not catch more than one ctrl/cmd+c, so
|
||||
// we do it here. These tasks are canceled already too.
|
||||
go func() {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch,
|
||||
// kill -SIGINT XXXX or Ctrl+c
|
||||
os.Interrupt,
|
||||
syscall.SIGINT, // register that too, it should be ok
|
||||
// os.Kill is equivalent with the syscall.SIGKILL
|
||||
os.Kill,
|
||||
syscall.SIGKILL, // register that too, it should be ok
|
||||
// kill -SIGTERM XXXX
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
select {
|
||||
case <-ch:
|
||||
notifyInterrupt()
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
93
core/maintenance/authenticate.go
Normal file
93
core/maintenance/authenticate.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package maintenance
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
|
||||
"github.com/kataras/iris/core/maintenance/client"
|
||||
"github.com/kataras/iris/core/maintenance/encoding"
|
||||
|
||||
"github.com/kataras/survey"
|
||||
)
|
||||
|
||||
// question describes the question which will be used
|
||||
// for the survey in order to authenticate the local iris.
|
||||
type question struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func hasInternetConnection() (bool, bool) {
|
||||
r, err := client.PostForm("", nil)
|
||||
if err != nil {
|
||||
// no internet connection
|
||||
return false, false
|
||||
}
|
||||
defer r.Body.Close()
|
||||
return true, r.StatusCode == 204
|
||||
}
|
||||
|
||||
func ask() bool {
|
||||
qs := fetchQuestions()
|
||||
var lastResponseUnsed string
|
||||
for _, q := range qs {
|
||||
survey.AskOne(&survey.Input{Message: q.Message}, &lastResponseUnsed, validate(q))
|
||||
}
|
||||
|
||||
return lastResponseUnsed != ""
|
||||
}
|
||||
|
||||
// fetchQuestions returns a list of questions
|
||||
// fetched by the authority server.
|
||||
func fetchQuestions() (qs []question) {
|
||||
r, err := client.PostForm("/survey/ask", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if err := encoding.UnmarshalBody(r.Body, &qs, json.Unmarshal); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func validate(q question) survey.Validator {
|
||||
return func(answer interface{}) error {
|
||||
if err := survey.Required(answer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ans, ok := answer.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("bug: expected string but got %v", answer)
|
||||
}
|
||||
data := url.Values{
|
||||
"q": []string{q.Message},
|
||||
"ans": []string{ans},
|
||||
"current_version": []string{Version},
|
||||
}
|
||||
|
||||
r, err := client.PostForm("/survey/submit", data)
|
||||
if err != nil {
|
||||
// error from server-side, allow.
|
||||
return nil
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if r.StatusCode == 200 {
|
||||
// read the whole thing, it has nothing.
|
||||
io.Copy(ioutil.Discard, r.Body)
|
||||
return nil // pass, no any errors.
|
||||
}
|
||||
// now, if invalid;
|
||||
got, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
errMsg := string(got)
|
||||
return fmt.Errorf(errMsg)
|
||||
}
|
||||
}
|
||||
41
core/maintenance/client/client.go
Normal file
41
core/maintenance/client/client.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
)
|
||||
|
||||
const host = "http://live.iris-go.com"
|
||||
|
||||
// PostForm performs the PostForm with a secure client.
|
||||
func PostForm(p string, data url.Values) (*http.Response, error) {
|
||||
client := netutil.Client(25 * time.Second)
|
||||
|
||||
if len(data) == 0 {
|
||||
data = make(url.Values, 1)
|
||||
}
|
||||
data.Set("X-Auth", a)
|
||||
|
||||
u := host + p
|
||||
r, err := client.PostForm(u, data)
|
||||
return r, err
|
||||
}
|
||||
|
||||
var a string
|
||||
|
||||
func init() {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err == nil {
|
||||
for _, f := range interfaces {
|
||||
if f.Flags&net.FlagUp != 0 && bytes.Compare(f.HardwareAddr, nil) != 0 {
|
||||
a = f.HardwareAddr.String()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
core/maintenance/encoding/unmarshaler.go
Normal file
33
core/maintenance/encoding/unmarshaler.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package encoding
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// UnmarshalerFunc is the Unmarshaler compatible type.
|
||||
//
|
||||
// See 'unmarshalBody' for more.
|
||||
type UnmarshalerFunc func(data []byte, v interface{}) error
|
||||
|
||||
// UnmarshalBody reads the request's body and binds it to a value or pointer of any type.
|
||||
func UnmarshalBody(body io.Reader, v interface{}, unmarshaler UnmarshalerFunc) error {
|
||||
if body == nil {
|
||||
return errors.New("unmarshal: empty body")
|
||||
}
|
||||
|
||||
rawData, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if v is already a pointer, if yes then pass as it's
|
||||
if reflect.TypeOf(v).Kind() == reflect.Ptr {
|
||||
return unmarshaler(rawData, v)
|
||||
}
|
||||
// finally, if the v doesn't contains a self-body decoder and it's not a pointer
|
||||
// use the custom unmarshaler to bind the body
|
||||
return unmarshaler(rawData, &v)
|
||||
}
|
||||
6
core/maintenance/maintenance.go
Normal file
6
core/maintenance/maintenance.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package maintenance
|
||||
|
||||
// Start starts the maintenance process.
|
||||
func Start() {
|
||||
CheckForUpdates()
|
||||
}
|
||||
83
core/maintenance/version.go
Normal file
83
core/maintenance/version.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package maintenance
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/kataras/iris/core/maintenance/version"
|
||||
|
||||
"github.com/kataras/golog"
|
||||
"github.com/kataras/survey"
|
||||
)
|
||||
|
||||
const (
|
||||
// Version is the string representation of the current local Iris Web Framework version.
|
||||
Version = "8.5.5"
|
||||
)
|
||||
|
||||
// CheckForUpdates checks for any available updates
|
||||
// and asks for the user if want to update now or not.
|
||||
func CheckForUpdates() {
|
||||
v := version.Acquire()
|
||||
updateAvailale := v.Compare(Version) == version.Smaller
|
||||
|
||||
if updateAvailale {
|
||||
has, ft := hasInternetConnection()
|
||||
canUpdate := (has && ft && ask()) || !has || !ft
|
||||
if canUpdate {
|
||||
installVersion(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func installVersion(v version.Version) {
|
||||
// on help? when asking for installing the new update
|
||||
// and when answering "No".
|
||||
ignoreUpdatesMsg := "Would you like to ignore future updates? Disable the version checker via:\napp.Run(..., iris.WithoutVersionChecker)"
|
||||
|
||||
// if update available ask for update action.
|
||||
shouldUpdateNowMsg :=
|
||||
fmt.Sprintf("A new version is available online[%s < %s].\nRelease notes: %s.\nUpdate now?",
|
||||
Version, v.String(),
|
||||
v.ChangelogURL)
|
||||
|
||||
var confirmUpdate bool
|
||||
survey.AskOne(&survey.Confirm{
|
||||
Message: shouldUpdateNowMsg,
|
||||
Help: ignoreUpdatesMsg,
|
||||
}, &confirmUpdate, nil)
|
||||
|
||||
// run the updater last, so the user can star the repo and at the same time
|
||||
// the app will update her/his local iris.
|
||||
if confirmUpdate { // it's true only when update was available and user typed "yes".
|
||||
repo := "github.com/kataras/iris/..."
|
||||
cmd := exec.Command("go", "get", "-u", "-v", repo)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stdout
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
golog.Warnf("unexpected message while trying to go get,\nif you edited the original source code then you've to remove the whole $GOPATH/src/github.com/kataras folder and execute `go get -u github.com/kataras/iris/...` manually\n%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
golog.Infof("Update process finished.\nManual rebuild and restart is required to apply the changes...\n")
|
||||
return
|
||||
}
|
||||
|
||||
// if update was available but chosen not to update then just continue...
|
||||
}
|
||||
|
||||
/* Author's note:
|
||||
We could use github webhooks to automatic notify for updates
|
||||
when a new update is pushed to the repository
|
||||
even when server is already started and running but this would expose
|
||||
a route which dev may don't know about, so let it for now but if
|
||||
they ask it then I should add an optional configuration field
|
||||
to "live/realtime update" and implement the idea (which is already implemented in the iris-go server).
|
||||
*/
|
||||
|
||||
/* Author's note:
|
||||
The old remote endpoint for version checker is still available on the server for backwards
|
||||
compatibility with older clients, it will stay there for a long period of time.
|
||||
*/
|
||||
59
core/maintenance/version/fetch.go
Normal file
59
core/maintenance/version/fetch.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/kataras/golog"
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
)
|
||||
|
||||
const (
|
||||
versionURL = "https://raw.githubusercontent.com/kataras/iris/master/VERSION"
|
||||
)
|
||||
|
||||
func fetch() (*version.Version, string) {
|
||||
client := netutil.Client(time.Duration(30 * time.Second))
|
||||
|
||||
r, err := client.Get(versionURL)
|
||||
if err != nil {
|
||||
golog.Debugf("err: %v\n", err)
|
||||
return nil, ""
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if r.StatusCode >= 400 {
|
||||
golog.Debugf("Internet connection is missing, updater is unable to fetch the latest Iris version\n", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
|
||||
if len(b) == 0 || err != nil {
|
||||
golog.Debugf("err: %v\n", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
var (
|
||||
fetchedVersion = string(b)
|
||||
changelogURL string
|
||||
)
|
||||
// Example output:
|
||||
// Version(8.5.5)
|
||||
// 8.5.5:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-02-november-2017--v855
|
||||
if idx := strings.IndexByte(fetchedVersion, ':'); idx > 0 {
|
||||
changelogURL = fetchedVersion[idx+1:]
|
||||
fetchedVersion = fetchedVersion[0:idx]
|
||||
}
|
||||
|
||||
latestVersion, err := version.NewVersion(fetchedVersion)
|
||||
if err != nil {
|
||||
golog.Debugf("while fetching and parsing latest version from github: %v\n", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
return latestVersion, changelogURL
|
||||
}
|
||||
64
core/maintenance/version/version.go
Normal file
64
core/maintenance/version/version.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// Version is a version wrapper which
|
||||
// contains some additional customized properties.
|
||||
type Version struct {
|
||||
version.Version
|
||||
WrittenAt time.Time
|
||||
ChangelogURL string
|
||||
}
|
||||
|
||||
// Result is the compare result type.
|
||||
// Available types are Invalid, Smaller, Equal or Larger.
|
||||
type Result int32
|
||||
|
||||
const (
|
||||
// Smaller when the compared version is smaller than the latest one.
|
||||
Smaller Result = -1
|
||||
// Equal when the compared version is equal with the latest one.
|
||||
Equal Result = 0
|
||||
// Larger when the compared version is larger than the latest one.
|
||||
Larger Result = 1
|
||||
// Invalid means that an error occurred when comparing the versions.
|
||||
Invalid Result = -2
|
||||
)
|
||||
|
||||
// Compare compares the "versionStr" with the latest Iris version,
|
||||
// opossite to the version package
|
||||
// it returns the result of the "versionStr" not the "v" itself.
|
||||
func (v *Version) Compare(versionStr string) Result {
|
||||
if len(v.Version.String()) == 0 {
|
||||
// if version not refreshed, by an internet connection lose,
|
||||
// then return Invalid.
|
||||
return Invalid
|
||||
}
|
||||
|
||||
other, err := version.NewVersion(versionStr)
|
||||
if err != nil {
|
||||
return Invalid
|
||||
}
|
||||
return Result(other.Compare(&v.Version))
|
||||
}
|
||||
|
||||
// Acquire returns the latest version info wrapper.
|
||||
// It calls the fetch.
|
||||
func Acquire() (v Version) {
|
||||
newVersion, changelogURL := fetch()
|
||||
if newVersion == nil { // if github was down then don't panic, just set it as the smallest version.
|
||||
newVersion, _ = version.NewVersion("0.0.1")
|
||||
}
|
||||
|
||||
v = Version{
|
||||
Version: *newVersion,
|
||||
WrittenAt: time.Now(),
|
||||
ChangelogURL: changelogURL,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -50,6 +51,40 @@ const (
|
||||
ParamTypePath
|
||||
)
|
||||
|
||||
// Not because for a single reason
|
||||
// a string may be a
|
||||
// ParamTypeString or a ParamTypeFile
|
||||
// or a ParamTypePath or ParamTypeAlphabetical.
|
||||
//
|
||||
// func ParamTypeFromStd(k reflect.Kind) ParamType {
|
||||
|
||||
// Kind returns the std kind of this param type.
|
||||
func (pt ParamType) Kind() reflect.Kind {
|
||||
switch pt {
|
||||
case ParamTypeAlphabetical:
|
||||
fallthrough
|
||||
case ParamTypeFile:
|
||||
fallthrough
|
||||
case ParamTypePath:
|
||||
fallthrough
|
||||
case ParamTypeString:
|
||||
return reflect.String
|
||||
case ParamTypeInt:
|
||||
return reflect.Int
|
||||
case ParamTypeLong:
|
||||
return reflect.Int64
|
||||
case ParamTypeBoolean:
|
||||
return reflect.Bool
|
||||
}
|
||||
return reflect.Invalid // 0
|
||||
}
|
||||
|
||||
// Assignable returns true if the "k" standard type
|
||||
// is assignabled to this ParamType.
|
||||
func (pt ParamType) Assignable(k reflect.Kind) bool {
|
||||
return pt.Kind() == k
|
||||
}
|
||||
|
||||
var paramTypes = map[string]ParamType{
|
||||
"string": ParamTypeString,
|
||||
"int": ParamTypeInt,
|
||||
|
||||
@@ -235,7 +235,7 @@ var types = map[string]string{
|
||||
".mov": "video/quicktime",
|
||||
".movie": "video/x-sgi-movie",
|
||||
".mp2": "audio/mpeg",
|
||||
".mp3": "audio/mpeg3",
|
||||
".mp3": "audio/mpeg",
|
||||
".mp4": "video/mp4",
|
||||
".mpa": "audio/mpeg",
|
||||
".mpc": "application/x-project",
|
||||
|
||||
@@ -102,7 +102,7 @@ func (nodes *Nodes) add(routeName, path string, paramNames []string, handlers co
|
||||
// set the wildcard param name to the root and its children.
|
||||
wildcardIdx := strings.IndexByte(path, '*')
|
||||
wildcardParamName := ""
|
||||
if wildcardIdx > 0 && len(paramNames) == 0 {
|
||||
if wildcardIdx > 0 && len(paramNames) == 0 { // 27 Oct comment: && len(paramNames) == 0 {
|
||||
wildcardParamName = path[wildcardIdx+1:]
|
||||
path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash
|
||||
|
||||
@@ -213,12 +213,14 @@ loop:
|
||||
handlers: handlers,
|
||||
root: root,
|
||||
}
|
||||
// println("3.5. nodes.Add path: " + n.s)
|
||||
// println("3.5. nodes.Add path: " + n.s)
|
||||
*nodes = append(*nodes, n)
|
||||
return
|
||||
}
|
||||
// println("4. nodes.Add path: " + path[len(n.s):])
|
||||
err = n.childrenNodes.add(routeName, path[len(n.s):], paramNames, handlers, false)
|
||||
|
||||
pathToAdd := path[len(n.s):]
|
||||
// println("4. nodes.Add path: " + pathToAdd)
|
||||
err = n.childrenNodes.add(routeName, pathToAdd, paramNames, handlers, false)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -231,10 +233,19 @@ loop:
|
||||
}
|
||||
n.paramNames = paramNames
|
||||
n.handlers = handlers
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// START
|
||||
// Author's note:
|
||||
// 27 Oct 2017; fixes s|i|l+static+p
|
||||
// without breaking the current tests.
|
||||
if wildcardIdx > 0 {
|
||||
wildcardParamName = path[wildcardIdx+1:]
|
||||
path = path[0:wildcardIdx-1] + "/"
|
||||
}
|
||||
// END
|
||||
|
||||
n := &node{
|
||||
s: path,
|
||||
routeName: routeName,
|
||||
|
||||
Reference in New Issue
Block a user