1
0
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:
kataras
2017-11-02 05:54:33 +02:00
parent 666bcacf20
commit 15feaf0237
100 changed files with 13338 additions and 13155 deletions

76
core/host/interrupt.go Normal file
View 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()
}
}

View File

@@ -190,8 +190,6 @@ func (su *Supervisor) supervise(blockFunc func() error) error {
su.notifyServe(host)
tryStartInterruptNotifier()
err := blockFunc()
su.notifyErr(err)

View File

@@ -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()
}
}()
}
}

View 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)
}
}

View 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
}
}
}
}

View 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)
}

View File

@@ -0,0 +1,6 @@
package maintenance
// Start starts the maintenance process.
func Start() {
CheckForUpdates()
}

View 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.
*/

View 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
}

View 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
}

View File

@@ -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,

View File

@@ -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",

View File

@@ -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,