mirror of
https://github.com/kataras/iris.git
synced 2026-01-11 05:55:57 +00:00
Add notes for the new lead maintainer of the open-source iris project and align with @get-ion/ion by @hiveminded
Former-commit-id: da4f38eb9034daa49446df3ee529423b98f9b331
This commit is contained in:
@@ -1,40 +1,62 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
// Prefix the error prefix, applies to each error's message.
|
||||
Prefix = ""
|
||||
// NewLine adds a new line to the end of each error's message
|
||||
// defaults to true
|
||||
NewLine = true
|
||||
)
|
||||
|
||||
// Error holds the error message, this message never really changes
|
||||
type Error struct {
|
||||
message string
|
||||
appended bool
|
||||
// ID returns the unique id of the error, it's needed
|
||||
// when we want to check if a specific error returned
|
||||
// but the `Error() string` value is not the same because the error may be dynamic
|
||||
// by a `Format` call.
|
||||
ID string `json:"id"`
|
||||
// The message of the error.
|
||||
Message string `json:"message"`
|
||||
// Apennded is true whenever it's a child error.
|
||||
Appended bool `json:"appended"`
|
||||
// Stack returns the list of the errors that are shown at `Error() string`.
|
||||
Stack []Error `json:"stack"` // filled on AppendX.
|
||||
}
|
||||
|
||||
// New creates and returns an Error with a pre-defined user output message
|
||||
// all methods below that doesn't accept a pointer receiver because actually they are not changing the original message
|
||||
func New(errMsg string) *Error {
|
||||
if NewLine {
|
||||
errMsg += "\n"
|
||||
func New(errMsg string) Error {
|
||||
return Error{
|
||||
ID: uuid.NewV4().String(),
|
||||
Message: Prefix + errMsg,
|
||||
}
|
||||
return &Error{message: Prefix + errMsg}
|
||||
}
|
||||
|
||||
// Equal returns true if "e" and "e2" are matched, by their IDs.
|
||||
// It will always returns true if the "e2" is a children of "e"
|
||||
// or the error messages are exactly the same, otherwise false.
|
||||
func (e Error) Equal(e2 Error) bool {
|
||||
return e.ID == e2.ID || e.Error() == e2.Error()
|
||||
}
|
||||
|
||||
// Empty returns true if the "e" Error has no message on its stack.
|
||||
func (e Error) Empty() bool {
|
||||
return e.Message == ""
|
||||
}
|
||||
|
||||
// NotEmpty returns true if the "e" Error has got a non-empty message on its stack.
|
||||
func (e Error) NotEmpty() bool {
|
||||
return !e.Empty()
|
||||
}
|
||||
|
||||
// String returns the error message
|
||||
func (e Error) String() string {
|
||||
return e.message
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Error returns the message of the actual error
|
||||
@@ -46,34 +68,53 @@ func (e Error) Error() string {
|
||||
// Format returns a formatted new error based on the arguments
|
||||
// it does NOT change the original error's message
|
||||
func (e Error) Format(a ...interface{}) Error {
|
||||
e.message = fmt.Sprintf(e.message, a...)
|
||||
e.Message = fmt.Sprintf(e.Message, a...)
|
||||
return e
|
||||
}
|
||||
|
||||
func omitNewLine(message string) string {
|
||||
if strings.HasSuffix(message, "\n") {
|
||||
return message[0 : len(message)-2]
|
||||
} else if strings.HasSuffix(message, "\\n") {
|
||||
return message[0 : len(message)-3]
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
// AppendInline appends an error to the stack.
|
||||
// It doesn't try to append a new line if needed.
|
||||
func (e Error) AppendInline(format string, a ...interface{}) Error {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
e.Message += msg
|
||||
e.Appended = true
|
||||
e.Stack = append(e.Stack, New(omitNewLine(msg)))
|
||||
return e
|
||||
}
|
||||
|
||||
// Append adds a message to the predefined error message and returns a new error
|
||||
// it does NOT change the original error's message
|
||||
func (e Error) Append(format string, a ...interface{}) Error {
|
||||
// eCp := *e
|
||||
if NewLine {
|
||||
format += "\n"
|
||||
// if new line is false then append this error but first
|
||||
// we need to add a new line to the first, if it was true then it has the newline already.
|
||||
if e.Message != "" {
|
||||
e.Message += "\n"
|
||||
}
|
||||
e.message += fmt.Sprintf(format, a...)
|
||||
e.appended = true
|
||||
return e
|
||||
|
||||
return e.AppendInline(format, a...)
|
||||
}
|
||||
|
||||
// AppendErr adds an error's message to the predefined error message and returns a new error
|
||||
// AppendErr adds an error's message to the predefined error message and returns a new error.
|
||||
// it does NOT change the original error's message
|
||||
func (e Error) AppendErr(err error) Error {
|
||||
return e.Append(err.Error())
|
||||
}
|
||||
|
||||
// IsAppended returns true if the Error instance is created using original's Error.Append/AppendErr func
|
||||
func (e Error) IsAppended() bool {
|
||||
return e.appended
|
||||
// HasStack returns true if the Error instance is created using Append/AppendInline/AppendErr funcs.
|
||||
func (e Error) HasStack() bool {
|
||||
return len(e.Stack) > 0
|
||||
}
|
||||
|
||||
// With does the same thing as Format but it receives an error type which if it's nil it returns a nil error
|
||||
// With does the same thing as Format but it receives an error type which if it's nil it returns a nil error.
|
||||
func (e Error) With(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
@@ -82,15 +123,15 @@ func (e Error) With(err error) error {
|
||||
return e.Format(err.Error())
|
||||
}
|
||||
|
||||
// Panic output the message and after panics
|
||||
// Panic output the message and after panics.
|
||||
func (e Error) Panic() {
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
errMsg := e.message
|
||||
errMsg := e.Message
|
||||
errMsg += "\nCaller was: " + fmt.Sprintf("%s:%d", fn, line)
|
||||
panic(errMsg)
|
||||
}
|
||||
|
||||
// Panicf output the formatted message and after panics
|
||||
// Panicf output the formatted message and after panics.
|
||||
func (e Error) Panicf(args ...interface{}) {
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
errMsg := e.Format(args...).Error()
|
||||
|
||||
@@ -13,26 +13,16 @@ var errUserAlreadyExists = errors.New(errMessage)
|
||||
var userMail = "user1@mail.go"
|
||||
var expectedUserAlreadyExists = "User with mail: user1@mail.go already exists"
|
||||
|
||||
func getNewLine() string {
|
||||
if errors.NewLine {
|
||||
return "\n"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ExampleError() {
|
||||
fmt.Print(errUserAlreadyExists.Format(userMail))
|
||||
// first output first Output line
|
||||
|
||||
fmt.Print(errUserAlreadyExists.Format(userMail).Append("Please change your mail addr"))
|
||||
// second output second and third Output lines
|
||||
|
||||
// Output:
|
||||
// User with mail: user1@mail.go already exists
|
||||
// User with mail: user1@mail.go already exists
|
||||
// Please change your mail addr
|
||||
}
|
||||
|
||||
func do(method string, testErr *errors.Error, expectingMsg string, t *testing.T) {
|
||||
func do(method string, testErr errors.Error, expectingMsg string, t *testing.T) {
|
||||
formattedErr := func() error {
|
||||
return testErr.Format(userMail)
|
||||
}()
|
||||
@@ -43,56 +33,51 @@ func do(method string, testErr *errors.Error, expectingMsg string, t *testing.T)
|
||||
}
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
expected := errors.Prefix + expectedUserAlreadyExists + getNewLine()
|
||||
expected := errors.Prefix + expectedUserAlreadyExists
|
||||
do("Format Test", errUserAlreadyExists, expected, t)
|
||||
}
|
||||
|
||||
func TestAppendErr(t *testing.T) {
|
||||
errors.NewLine = true
|
||||
errors.Prefix = "error: "
|
||||
|
||||
errChangeMailMsg := "Please change your mail addr"
|
||||
errChangeMail := fmt.Errorf(errChangeMailMsg) // test go standard error
|
||||
expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + errChangeMailMsg + getNewLine() // first Prefix and last newline lives inside do
|
||||
errChangeMail := fmt.Errorf(errChangeMailMsg) // test go standard error
|
||||
errAppended := errUserAlreadyExists.AppendErr(errChangeMail)
|
||||
do("Append Test Standard error type", &errAppended, expectedErrorMessage, t)
|
||||
expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + "\n" + errChangeMailMsg
|
||||
|
||||
do("Append Test Standard error type", errAppended, expectedErrorMessage, t)
|
||||
}
|
||||
|
||||
func TestAppendError(t *testing.T) {
|
||||
errors.NewLine = true
|
||||
errors.Prefix = "error: "
|
||||
|
||||
errChangeMailMsg := "Please change your mail addr"
|
||||
errChangeMail := errors.New(errChangeMailMsg) // test Error struct
|
||||
expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + errChangeMail.Error() + getNewLine() // first Prefix and last newline lives inside do
|
||||
errChangeMail := errors.New(errChangeMailMsg)
|
||||
|
||||
errAppended := errUserAlreadyExists.AppendErr(errChangeMail)
|
||||
do("Append Test Error type", &errAppended, expectedErrorMessage, t)
|
||||
expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + "\n" + errChangeMail.Error()
|
||||
|
||||
do("Append Test Error type", errAppended, expectedErrorMessage, t)
|
||||
}
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
errors.NewLine = true
|
||||
errors.Prefix = "error: "
|
||||
|
||||
errChangeMailMsg := "Please change your mail addr"
|
||||
expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + errChangeMailMsg + getNewLine() // first Prefix and last newline lives inside do
|
||||
expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + "\n" + errChangeMailMsg
|
||||
errAppended := errUserAlreadyExists.Append(errChangeMailMsg)
|
||||
do("Append Test string Message", &errAppended, expectedErrorMessage, t)
|
||||
do("Append Test string Message", errAppended, expectedErrorMessage, t)
|
||||
}
|
||||
|
||||
func TestNewLine(t *testing.T) {
|
||||
errors.NewLine = false
|
||||
|
||||
errNoNewLine := errors.New(errMessage)
|
||||
err := errors.New(errMessage)
|
||||
expected := errors.Prefix + expectedUserAlreadyExists
|
||||
do("NewLine Test", errNoNewLine, expected, t)
|
||||
|
||||
errors.NewLine = true
|
||||
do("NewLine Test", err, expected, t)
|
||||
}
|
||||
|
||||
func TestPrefix(t *testing.T) {
|
||||
errors.Prefix = "MyPrefix: "
|
||||
|
||||
errUpdatedPrefix := errors.New(errMessage)
|
||||
expected := errors.Prefix + expectedUserAlreadyExists + "\n"
|
||||
expected := errors.Prefix + expectedUserAlreadyExists
|
||||
do("Prefix Test with "+errors.Prefix, errUpdatedPrefix, expected, t)
|
||||
}
|
||||
|
||||
141
core/errors/reporter.go
Normal file
141
core/errors/reporter.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// StackError contains the Stack method.
|
||||
type StackError interface {
|
||||
Stack() []Error
|
||||
Error() string
|
||||
}
|
||||
|
||||
// PrintAndReturnErrors prints the "err" to the given "printer",
|
||||
// printer will be called multiple times if the "err" is a StackError, where it contains more than one error.
|
||||
func PrintAndReturnErrors(err error, printer func(string, ...interface{})) error {
|
||||
if err == nil || err.Error() == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if stackErr, ok := err.(StackError); ok {
|
||||
if len(stackErr.Stack()) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
stack := stackErr.Stack()
|
||||
|
||||
for _, e := range stack {
|
||||
if e.HasStack() {
|
||||
for _, es := range e.Stack {
|
||||
printer("%v", es)
|
||||
}
|
||||
continue
|
||||
}
|
||||
printer("%v", e)
|
||||
}
|
||||
|
||||
return stackErr
|
||||
}
|
||||
|
||||
printer("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Reporter is a helper structure which can
|
||||
// stack errors and prints them to a printer of func(string).
|
||||
type Reporter struct {
|
||||
mu sync.Mutex
|
||||
wrapper Error
|
||||
}
|
||||
|
||||
// NewReporter returns a new empty error reporter.
|
||||
func NewReporter() *Reporter {
|
||||
return &Reporter{wrapper: New("")}
|
||||
}
|
||||
|
||||
// AddErr adds an error to the error stack.
|
||||
// if "err" is a StackError then
|
||||
// each of these errors will be printed as individual.
|
||||
func (r *Reporter) AddErr(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if stackErr, ok := err.(StackError); ok {
|
||||
r.addStack(stackErr.Stack())
|
||||
return
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
r.wrapper = r.wrapper.AppendErr(err)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
// Add adds a formatted message as an error to the error stack.
|
||||
func (r *Reporter) Add(format string, a ...interface{}) {
|
||||
// usually used as: "module: %v", err so
|
||||
// check if the first argument is error and if that error is empty then don't add it.
|
||||
if len(a) > 0 {
|
||||
f := a[0]
|
||||
if e, ok := f.(interface {
|
||||
Error() string
|
||||
}); ok {
|
||||
if e.Error() == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
r.wrapper = r.wrapper.Append(format, a...)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
// Describe same as `Add` but if "err" is nil then it does nothing.
|
||||
func (r *Reporter) Describe(format string, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if stackErr, ok := err.(StackError); ok {
|
||||
r.addStack(stackErr.Stack())
|
||||
return
|
||||
}
|
||||
|
||||
r.Add(format, err)
|
||||
}
|
||||
|
||||
// PrintStack prints all the errors to the given "printer".
|
||||
// Returns itself in order to be used as printer and return the full error in the same time.
|
||||
func (r Reporter) PrintStack(printer func(string, ...interface{})) error {
|
||||
return PrintAndReturnErrors(r, printer)
|
||||
}
|
||||
|
||||
// Stack returns the list of the errors in the stack.
|
||||
func (r Reporter) Stack() []Error {
|
||||
return r.wrapper.Stack
|
||||
}
|
||||
|
||||
func (r *Reporter) addStack(stack []Error) {
|
||||
for _, e := range stack {
|
||||
if e.Error() == "" {
|
||||
continue
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.wrapper = r.wrapper.AppendErr(e)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the error, returns the full error string.
|
||||
func (r Reporter) Error() string {
|
||||
return r.wrapper.Error()
|
||||
}
|
||||
|
||||
// Return returns nil if the error is empty, otherwise returns the full error.
|
||||
func (r Reporter) Return() error {
|
||||
if r.Error() == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
26
core/errors/reporter_test.go
Normal file
26
core/errors/reporter_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// black-box testing
|
||||
package errors_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
)
|
||||
|
||||
func TestReporterAdd(t *testing.T) {
|
||||
errors.Prefix = ""
|
||||
|
||||
r := errors.NewReporter()
|
||||
|
||||
tests := []string{"err1", "err3", "err4\nerr5"}
|
||||
for _, tt := range tests {
|
||||
r.Add(tt)
|
||||
}
|
||||
|
||||
for i, e := range r.Stack() {
|
||||
tt := tests[i]
|
||||
if expected, got := tt, e.Error(); expected != got {
|
||||
t.Fatalf("[%d] expected %s but got %s", i, expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package handlerconv
|
||||
|
||||
import (
|
||||
@@ -78,12 +74,12 @@ func FromStd(handler interface{}) context.Handler {
|
||||
func FromStdWithNext(h func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)) context.Handler {
|
||||
return func(ctx context.Context) {
|
||||
// take the next handler in route's chain
|
||||
nextIrisHandler := ctx.NextHandler()
|
||||
if nextIrisHandler != nil {
|
||||
nextIonHandler := ctx.NextHandler()
|
||||
if nextIonHandler != nil {
|
||||
executed := false // we need to watch this in order to StopExecution from all next handlers
|
||||
// if this next handler is not executed by the third-party net/http next-style Handlers.
|
||||
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextIrisHandler(ctx)
|
||||
nextIonHandler(ctx)
|
||||
executed = true
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package host
|
||||
|
||||
import (
|
||||
@@ -11,7 +7,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/core/nettools"
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
)
|
||||
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
@@ -52,7 +48,7 @@ func ProxyHandler(target *url.URL) *httputil.ReverseProxy {
|
||||
}
|
||||
p := &httputil.ReverseProxy{Director: director}
|
||||
|
||||
if nettools.IsLoopbackHost(target.Host) {
|
||||
if netutil.IsLoopbackHost(target.Host) {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package host
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type task struct {
|
||||
runner TaskRunner
|
||||
proc TaskProcess
|
||||
|
||||
// atomic-accessed, if != 0 means that is already
|
||||
// canceled before it ever ran, this happens to interrupt handlers too.
|
||||
alreadyCanceled int32
|
||||
Cancel func()
|
||||
}
|
||||
|
||||
func (t *task) isCanceled() bool {
|
||||
return atomic.LoadInt32(&t.alreadyCanceled) != 0
|
||||
}
|
||||
|
||||
// Scheduler is a type of an event emmiter.
|
||||
// Can register a specific task for a specific event
|
||||
// when host is starting the server or host is interrupted by CTRL+C/CMD+C.
|
||||
// It's being used internally on host supervisor.
|
||||
type Scheduler struct {
|
||||
onServeTasks []*task
|
||||
onInterruptTasks []*task
|
||||
}
|
||||
|
||||
// TaskCancelFunc cancels a Task when called.
|
||||
type TaskCancelFunc func()
|
||||
|
||||
// Schedule schedule/registers a Task,
|
||||
// it will be executed/run to when host starts the server
|
||||
// or when host is interrupted by CTRL+C/CMD+C based on the TaskRunner type.
|
||||
//
|
||||
// See `OnInterrupt` and `ScheduleFunc` too.
|
||||
func (s *Scheduler) Schedule(runner TaskRunner) TaskCancelFunc {
|
||||
|
||||
t := new(task)
|
||||
t.runner = runner
|
||||
t.Cancel = func() {
|
||||
// it's not running yet, so if canceled now
|
||||
// set to already canceled to not run it at all.
|
||||
atomic.StoreInt32(&t.alreadyCanceled, 1)
|
||||
}
|
||||
|
||||
if _, ok := runner.(OnInterrupt); ok {
|
||||
s.onInterruptTasks = append(s.onInterruptTasks, t)
|
||||
} else {
|
||||
s.onServeTasks = append(s.onServeTasks, t)
|
||||
}
|
||||
|
||||
return func() {
|
||||
t.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// ScheduleFunc schedule/registers a task function,
|
||||
// it will be executed/run to when host starts the server
|
||||
// or when host is interrupted by CTRL+C/CMD+C based on the TaskRunner type.
|
||||
//
|
||||
// See `OnInterrupt` and `ScheduleFunc` too.
|
||||
func (s *Scheduler) ScheduleFunc(runner func(TaskProcess)) TaskCancelFunc {
|
||||
return s.Schedule(TaskRunnerFunc(runner))
|
||||
}
|
||||
|
||||
func cancelTasks(tasks []*task) {
|
||||
for _, t := range tasks {
|
||||
if atomic.LoadInt32(&t.alreadyCanceled) != 0 {
|
||||
continue // canceled, don't run it
|
||||
}
|
||||
go t.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// CancelOnServeTasks cancels all tasks that are scheduled to run when
|
||||
// host is starting the server, when the server is alive and online.
|
||||
func (s *Scheduler) CancelOnServeTasks() {
|
||||
cancelTasks(s.onServeTasks)
|
||||
}
|
||||
|
||||
// CancelOnInterruptTasks cancels all tasks that are scheduled to run when
|
||||
// host is being interrupted by CTRL+C/CMD+C, when the server is alive and online as well.
|
||||
func (s *Scheduler) CancelOnInterruptTasks() {
|
||||
cancelTasks(s.onInterruptTasks)
|
||||
}
|
||||
|
||||
func runTaskNow(task *task, host TaskHost) {
|
||||
proc := newTaskProcess(host)
|
||||
task.proc = proc
|
||||
task.Cancel = func() {
|
||||
proc.canceledChan <- struct{}{}
|
||||
}
|
||||
|
||||
go task.runner.Run(proc)
|
||||
}
|
||||
|
||||
func runTasks(tasks []*task, host TaskHost) {
|
||||
for _, t := range tasks {
|
||||
if t.isCanceled() {
|
||||
continue
|
||||
}
|
||||
runTaskNow(t, host)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scheduler) runOnServe(host TaskHost) {
|
||||
runTasks(s.onServeTasks, host)
|
||||
}
|
||||
|
||||
func (s *Scheduler) runOnInterrupt(host TaskHost) {
|
||||
runTasks(s.onInterruptTasks, host)
|
||||
}
|
||||
|
||||
func (s *Scheduler) visit(visitor func(*task)) {
|
||||
for _, t := range s.onServeTasks {
|
||||
visitor(t)
|
||||
}
|
||||
|
||||
for _, t := range s.onInterruptTasks {
|
||||
visitor(t)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scheduler) notifyShutdown() {
|
||||
s.visit(func(t *task) {
|
||||
go func() {
|
||||
t.proc.Host().doneChan <- struct{}{}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Scheduler) notifyErr(err error) {
|
||||
s.visit(func(t *task) {
|
||||
go func() {
|
||||
t.proc.Host().errChan <- err
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// CopyTo copies all tasks from "s" to "to" Scheduler.
|
||||
// It doesn't care about anything else.
|
||||
func (s *Scheduler) CopyTo(to *Scheduler) {
|
||||
s.visit(func(t *task) {
|
||||
rnner := t.runner
|
||||
to.Schedule(rnner)
|
||||
})
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// white-box testing
|
||||
package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type myTestTask struct {
|
||||
delay time.Duration
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (m myTestTask) Run(proc TaskProcess) {
|
||||
ticker := time.NewTicker(m.delay)
|
||||
defer ticker.Stop()
|
||||
rans := 0
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-ticker.C:
|
||||
{
|
||||
if !ok {
|
||||
m.logger.Println("ticker issue, closed channel, exiting from this task...")
|
||||
return
|
||||
}
|
||||
rans++
|
||||
m.logger.Println(fmt.Sprintf("%d", rans))
|
||||
}
|
||||
case <-proc.Done():
|
||||
{
|
||||
m.logger.Println("canceled, exiting from task AND SHUTDOWN the server...")
|
||||
proc.Host().Shutdown(context.TODO())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SchedulerSchedule() {
|
||||
h := New(&http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}),
|
||||
})
|
||||
logger := log.New(os.Stdout, "Supervisor: ", 0)
|
||||
|
||||
delaySeconds := 2
|
||||
|
||||
mytask := myTestTask{
|
||||
delay: time.Duration(delaySeconds) * time.Second,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
cancel := h.Schedule(mytask)
|
||||
ln, err := net.Listen("tcp4", ":9090")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
logger.Println("server started...")
|
||||
logger.Println("we will cancel the task after 2 runs (the third will be canceled)")
|
||||
cancelAfterRuns := 2
|
||||
time.AfterFunc(time.Duration(delaySeconds*cancelAfterRuns+(delaySeconds/2))*time.Second, func() {
|
||||
cancel()
|
||||
logger.Println("cancel sent")
|
||||
})
|
||||
h.Serve(ln)
|
||||
|
||||
// Output:
|
||||
// Supervisor: server started...
|
||||
// Supervisor: we will cancel the task after 2 runs (the third will be canceled)
|
||||
// Supervisor: 1
|
||||
// Supervisor: 2
|
||||
// Supervisor: cancel sent
|
||||
// Supervisor: canceled, exiting from task AND SHUTDOWN the server...
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package host
|
||||
|
||||
import (
|
||||
@@ -9,14 +5,11 @@ import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/kataras/iris/core/nettools"
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
@@ -25,16 +18,17 @@ import (
|
||||
//
|
||||
// Interfaces are separated to return relative functionality to them.
|
||||
type Supervisor struct {
|
||||
Scheduler
|
||||
server *http.Server
|
||||
Server *http.Server
|
||||
closedManually int32 // future use, accessed atomically (non-zero means we've called the Shutdown)
|
||||
|
||||
shouldWait int32 // non-zero means that the host should wait for unblocking
|
||||
unblockChan chan struct{}
|
||||
shutdownChan chan struct{}
|
||||
errChan chan error
|
||||
manuallyTLS bool // we need that in order to determinate what to output on the console before the server begin.
|
||||
shouldWait int32 // non-zero means that the host should wait for unblocking
|
||||
unblockChan chan struct{}
|
||||
|
||||
mu sync.Mutex
|
||||
|
||||
onServe []func(TaskHost)
|
||||
onErr []func(error)
|
||||
onShutdown []func()
|
||||
}
|
||||
|
||||
// New returns a new host supervisor
|
||||
@@ -46,10 +40,8 @@ type Supervisor struct {
|
||||
// to return and exit and restore the flow too.
|
||||
func New(srv *http.Server) *Supervisor {
|
||||
return &Supervisor{
|
||||
server: srv,
|
||||
unblockChan: make(chan struct{}, 1),
|
||||
shutdownChan: make(chan struct{}),
|
||||
errChan: make(chan error),
|
||||
Server: srv,
|
||||
unblockChan: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,78 +75,73 @@ func (su *Supervisor) isWaiting() bool {
|
||||
return atomic.LoadInt32(&su.shouldWait) != 0
|
||||
}
|
||||
|
||||
// Done is being received when in server Shutdown.
|
||||
// This can be used to gracefully shutdown connections that have
|
||||
// undergone NPN/ALPN protocol upgrade or that have been hijacked.
|
||||
// This function should start protocol-specific graceful shutdown,
|
||||
// but should not wait for shutdown to complete.
|
||||
func (su *Supervisor) Done() <-chan struct{} {
|
||||
return su.shutdownChan
|
||||
func (su *Supervisor) newListener() (net.Listener, error) {
|
||||
// this will not work on "unix" as network
|
||||
// because UNIX doesn't supports the kind of
|
||||
// restarts we may want for the server.
|
||||
//
|
||||
// User still be able to call .Serve instead.
|
||||
l, err := netutil.TCPKeepAlive(su.Server.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// here we can check for sure, without the need of the supervisor's `manuallyTLS` field.
|
||||
if netutil.IsTLS(su.Server) {
|
||||
// means tls
|
||||
tlsl := tls.NewListener(l, su.Server.TLSConfig)
|
||||
return tlsl, nil
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Err refences to the return value of Server's .Serve, not the server's specific error logger.
|
||||
func (su *Supervisor) Err() <-chan error {
|
||||
return su.errChan
|
||||
}
|
||||
|
||||
func (su *Supervisor) notifyShutdown() {
|
||||
go func() {
|
||||
su.shutdownChan <- struct{}{}
|
||||
}()
|
||||
|
||||
su.Scheduler.notifyShutdown()
|
||||
// RegisterOnError registers a function to call when errors occured by the underline http server.
|
||||
func (su *Supervisor) RegisterOnError(cb func(error)) {
|
||||
su.mu.Lock()
|
||||
su.onErr = append(su.onErr, cb)
|
||||
su.mu.Unlock()
|
||||
}
|
||||
|
||||
func (su *Supervisor) notifyErr(err error) {
|
||||
// if err == http.ErrServerClosed {
|
||||
// su.notifyShutdown()
|
||||
// return
|
||||
// }
|
||||
|
||||
go func() {
|
||||
su.errChan <- err
|
||||
}()
|
||||
|
||||
su.Scheduler.notifyErr(err)
|
||||
su.mu.Lock()
|
||||
for _, f := range su.onErr {
|
||||
go f(err)
|
||||
}
|
||||
su.mu.Unlock()
|
||||
}
|
||||
|
||||
// RegisterOnServe registers a function to call on
|
||||
// Serve/ListenAndServe/ListenAndServeTLS/ListenAndServeAutoTLS.
|
||||
func (su *Supervisor) RegisterOnServe(cb func(TaskHost)) {
|
||||
su.mu.Lock()
|
||||
su.onServe = append(su.onServe, cb)
|
||||
su.mu.Unlock()
|
||||
}
|
||||
|
||||
func (su *Supervisor) notifyServe(host TaskHost) {
|
||||
su.mu.Lock()
|
||||
for _, f := range su.onServe {
|
||||
go f(host)
|
||||
}
|
||||
su.mu.Unlock()
|
||||
}
|
||||
|
||||
/// TODO:
|
||||
// Remove all channels, do it with events
|
||||
// or with channels but with a different channel on each task proc
|
||||
// I don't know channels are not so safe, when go func and race risk..
|
||||
// so better with callbacks....
|
||||
func (su *Supervisor) supervise(blockFunc func() error) error {
|
||||
// println("Running Serve from Supervisor")
|
||||
|
||||
// su.server: in order to Serve and Shutdown the underline server and no re-run the supervisors when .Shutdown -> .Serve.
|
||||
// su.GetBlocker: set the Block() and Unblock(), which are checked after a shutdown or error.
|
||||
// su.GetNotifier: only one supervisor is allowed to be notified about Close/Shutdown and Err.
|
||||
// su.log: set this builder's logger in order to supervisor to be able to share a common logger.
|
||||
host := createTaskHost(su)
|
||||
// run the list of supervisors in different go-tasks by-design.
|
||||
su.Scheduler.runOnServe(host)
|
||||
|
||||
if len(su.Scheduler.onInterruptTasks) > 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:
|
||||
su.Scheduler.runOnInterrupt(host)
|
||||
}
|
||||
}()
|
||||
}
|
||||
su.notifyServe(host)
|
||||
|
||||
tryStartInterruptNotifier()
|
||||
|
||||
err := blockFunc()
|
||||
su.notifyErr(err)
|
||||
@@ -172,26 +159,6 @@ func (su *Supervisor) supervise(blockFunc func() error) error {
|
||||
return err // start the server
|
||||
}
|
||||
|
||||
func (su *Supervisor) newListener() (net.Listener, error) {
|
||||
// this will not work on "unix" as network
|
||||
// because UNIX doesn't supports the kind of
|
||||
// restarts we may want for the server.
|
||||
//
|
||||
// User still be able to call .Serve instead.
|
||||
l, err := nettools.TCPKeepAlive(su.server.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nettools.IsTLS(su.server) {
|
||||
// means tls
|
||||
tlsl := tls.NewListener(l, su.server.TLSConfig)
|
||||
return tlsl, nil
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections on the Listener l, creating a
|
||||
// new service goroutine for each. The service goroutines read requests and
|
||||
// then call su.server.Handler to reply to them.
|
||||
@@ -204,7 +171,8 @@ func (su *Supervisor) newListener() (net.Listener, error) {
|
||||
// Serve always returns a non-nil error. After Shutdown or Close, the
|
||||
// returned error is http.ErrServerClosed.
|
||||
func (su *Supervisor) Serve(l net.Listener) error {
|
||||
return su.supervise(func() error { return su.server.Serve(l) })
|
||||
|
||||
return su.supervise(func() error { return su.Server.Serve(l) })
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address addr
|
||||
@@ -240,8 +208,8 @@ func (su *Supervisor) ListenAndServeTLS(certFile string, keyFile string) error {
|
||||
}
|
||||
|
||||
setupHTTP2(cfg)
|
||||
su.server.TLSConfig = cfg
|
||||
|
||||
su.Server.TLSConfig = cfg
|
||||
su.manuallyTLS = true
|
||||
return su.ListenAndServe()
|
||||
}
|
||||
|
||||
@@ -256,10 +224,33 @@ func (su *Supervisor) ListenAndServeAutoTLS() error {
|
||||
cfg := new(tls.Config)
|
||||
cfg.GetCertificate = autoTLSManager.GetCertificate
|
||||
setupHTTP2(cfg)
|
||||
su.server.TLSConfig = cfg
|
||||
su.Server.TLSConfig = cfg
|
||||
su.manuallyTLS = true
|
||||
return su.ListenAndServe()
|
||||
}
|
||||
|
||||
// RegisterOnShutdown registers a function to call on Shutdown.
|
||||
// This can be used to gracefully shutdown connections that have
|
||||
// undergone NPN/ALPN protocol upgrade or that have been hijacked.
|
||||
// This function should start protocol-specific graceful shutdown,
|
||||
// but should not wait for shutdown to complete.
|
||||
func (su *Supervisor) RegisterOnShutdown(cb func()) {
|
||||
// when go1.9: replace the following lines with su.Server.RegisterOnShutdown(f)
|
||||
su.mu.Lock()
|
||||
su.onShutdown = append(su.onShutdown, cb)
|
||||
su.mu.Unlock()
|
||||
}
|
||||
|
||||
func (su *Supervisor) notifyShutdown() {
|
||||
// when go1.9: remove the lines below
|
||||
su.mu.Lock()
|
||||
for _, f := range su.onShutdown {
|
||||
go f()
|
||||
}
|
||||
su.mu.Unlock()
|
||||
// end
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the server without interrupting any
|
||||
// active connections. Shutdown works by first closing all open
|
||||
// listeners, then closing all idle connections, and then waiting
|
||||
@@ -272,9 +263,7 @@ func (su *Supervisor) ListenAndServeAutoTLS() error {
|
||||
// separately notify such long-lived connections of shutdown and wait
|
||||
// for them to close, if desired.
|
||||
func (su *Supervisor) Shutdown(ctx context.Context) error {
|
||||
// println("Running Shutdown from Supervisor")
|
||||
|
||||
atomic.AddInt32(&su.closedManually, 1) // future-use
|
||||
su.notifyShutdown()
|
||||
return su.server.Shutdown(ctx)
|
||||
return su.Server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
118
core/host/supervisor_task_example_test.go
Normal file
118
core/host/supervisor_task_example_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// white-box testing
|
||||
package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleSupervisor_RegisterOnError() {
|
||||
su := New(&http.Server{Addr: ":8273", Handler: http.DefaultServeMux})
|
||||
|
||||
su.RegisterOnError(func(err error) {
|
||||
fmt.Println(err.Error())
|
||||
})
|
||||
|
||||
su.RegisterOnError(func(err error) {
|
||||
fmt.Println(err.Error())
|
||||
})
|
||||
|
||||
su.RegisterOnError(func(err error) {
|
||||
fmt.Println(err.Error())
|
||||
})
|
||||
|
||||
go su.ListenAndServe()
|
||||
time.Sleep(1 * time.Second)
|
||||
su.Shutdown(context.TODO())
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Output:
|
||||
// http: Server closed
|
||||
// http: Server closed
|
||||
// http: Server closed
|
||||
}
|
||||
|
||||
type myTestTask struct {
|
||||
restartEvery time.Duration
|
||||
maxRestarts int
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (m myTestTask) OnServe(host TaskHost) {
|
||||
host.Supervisor.DeferFlow() // don't exit on underline server's Shutdown.
|
||||
|
||||
ticker := time.NewTicker(m.restartEvery)
|
||||
defer ticker.Stop()
|
||||
rans := 0
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-ticker.C:
|
||||
{
|
||||
if !ok {
|
||||
m.logger.Println("ticker issue, closed channel, exiting from this task...")
|
||||
return
|
||||
}
|
||||
exitAfterXRestarts := m.maxRestarts
|
||||
if rans == exitAfterXRestarts {
|
||||
m.logger.Println("exit")
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
host.Supervisor.Shutdown(ctx) // total shutdown
|
||||
host.Supervisor.RestoreFlow() // free to exit (if shutdown)
|
||||
return
|
||||
}
|
||||
|
||||
rans++
|
||||
|
||||
m.logger.Println(fmt.Sprintf("closed %d times", rans))
|
||||
host.Shutdown(context.TODO())
|
||||
|
||||
startDelay := 2 * time.Second
|
||||
time.AfterFunc(startDelay, func() {
|
||||
m.logger.Println("restart")
|
||||
host.Serve() // restart
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSupervisor_RegisterOnServe() {
|
||||
h := New(&http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}),
|
||||
})
|
||||
|
||||
logger := log.New(os.Stdout, "Supervisor: ", 0)
|
||||
|
||||
mytask := myTestTask{
|
||||
restartEvery: 6 * time.Second,
|
||||
maxRestarts: 2,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
h.RegisterOnServe(mytask.OnServe)
|
||||
|
||||
ln, err := net.Listen("tcp4", ":9394")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
logger.Println("server started...")
|
||||
h.Serve(ln)
|
||||
|
||||
// Output:
|
||||
// Supervisor: server started...
|
||||
// Supervisor: closed 1 times
|
||||
// Supervisor: restart
|
||||
// Supervisor: closed 2 times
|
||||
// Supervisor: restart
|
||||
// Supervisor: exit
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func newTester(t *testing.T, baseURL string, handler http.Handler) *httpexpect.E
|
||||
return httpexpect.WithConfig(testConfiguration)
|
||||
}
|
||||
|
||||
func testSupervisor(t *testing.T, creator func(*http.Server, []TaskRunner) *Supervisor) {
|
||||
func testSupervisor(t *testing.T, creator func(*http.Server, []func(TaskHost)) *Supervisor) {
|
||||
loggerOutput := &bytes.Buffer{}
|
||||
logger := log.New(loggerOutput, "", 0)
|
||||
const (
|
||||
@@ -76,11 +76,11 @@ func testSupervisor(t *testing.T, creator func(*http.Server, []TaskRunner) *Supe
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
helloMe := TaskRunnerFunc(func(proc TaskProcess) {
|
||||
helloMe := func(_ TaskHost) {
|
||||
logger.Print(expectedHelloMessage)
|
||||
})
|
||||
}
|
||||
|
||||
host := creator(srv, []TaskRunner{helloMe})
|
||||
host := creator(srv, []func(TaskHost){helloMe})
|
||||
defer host.Shutdown(context.TODO())
|
||||
|
||||
go host.Serve(ln)
|
||||
@@ -99,10 +99,10 @@ func testSupervisor(t *testing.T, creator func(*http.Server, []TaskRunner) *Supe
|
||||
}
|
||||
}
|
||||
func TestSupervisor(t *testing.T) {
|
||||
testSupervisor(t, func(srv *http.Server, tasks []TaskRunner) *Supervisor {
|
||||
testSupervisor(t, func(srv *http.Server, tasks []func(TaskHost)) *Supervisor {
|
||||
su := New(srv)
|
||||
for _, t := range tasks {
|
||||
su.Schedule(t)
|
||||
su.RegisterOnServe(t)
|
||||
}
|
||||
|
||||
return su
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package host
|
||||
|
||||
// the 24hour name was "Supervisor" but it's not cover its usage
|
||||
@@ -10,57 +6,61 @@ package host
|
||||
// supervisor.
|
||||
import (
|
||||
"context"
|
||||
"github.com/kataras/iris/core/nettools"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
)
|
||||
|
||||
type (
|
||||
// FlowController exports the `DeferFlow`
|
||||
// and `RestoreFlow` capabilities.
|
||||
// Read more at Supervisor.
|
||||
FlowController interface {
|
||||
DeferFlow()
|
||||
RestoreFlow()
|
||||
// WriteStartupLogOnServe is a task which accepts a logger(io.Writer)
|
||||
// and logs the listening address
|
||||
// by a generated message based on the host supervisor's server and writes it to the "w".
|
||||
// This function should be registered on Serve.
|
||||
func WriteStartupLogOnServe(w io.Writer) func(TaskHost) {
|
||||
return func(h TaskHost) {
|
||||
guessScheme := netutil.ResolveScheme(h.Supervisor.manuallyTLS)
|
||||
listeningURI := netutil.ResolveURL(guessScheme, h.Supervisor.Server.Addr)
|
||||
interruptkey := "CTRL"
|
||||
if runtime.GOOS == "darwin" {
|
||||
interruptkey = "CMD"
|
||||
}
|
||||
w.Write([]byte(fmt.Sprintf("Now listening on: %s\nApplication started. Press %s+C to shut down.\n",
|
||||
listeningURI, interruptkey)))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ShutdownOnInterrupt terminates the supervisor and its underline server when CMD+C/CTRL+C pressed.
|
||||
// This function should be registerd on Interrupt.
|
||||
func ShutdownOnInterrupt(su *Supervisor, shutdownTimeout time.Duration) func() {
|
||||
return func() {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), shutdownTimeout)
|
||||
defer cancel()
|
||||
su.Shutdown(ctx)
|
||||
su.RestoreFlow()
|
||||
}
|
||||
}
|
||||
|
||||
// TaskHost contains all the necessary information
|
||||
// about the host supervisor, its server
|
||||
// and the exports the whole flow controller of it.
|
||||
type TaskHost struct {
|
||||
su *Supervisor
|
||||
// Supervisor with access fields when server is running, i.e restrict access to "Schedule"
|
||||
// Server that running, is active and open
|
||||
// Flow controller
|
||||
FlowController
|
||||
// Various
|
||||
pid int
|
||||
|
||||
doneChan chan struct{}
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// Done filled when server was shutdown.
|
||||
func (h TaskHost) Done() <-chan struct{} {
|
||||
return h.doneChan
|
||||
}
|
||||
|
||||
// Err filled when server received an error.
|
||||
func (h TaskHost) Err() <-chan error {
|
||||
return h.errChan
|
||||
Supervisor *Supervisor
|
||||
}
|
||||
|
||||
// Serve can (re)run the server with the latest known configuration.
|
||||
func (h TaskHost) Serve() error {
|
||||
// the underline server's serve, using the "latest known" listener from the supervisor.
|
||||
l, err := h.su.newListener()
|
||||
l, err := h.Supervisor.newListener()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if http.serverclosed ignroe the error, it will have this error
|
||||
// from the previous close
|
||||
if err := h.su.server.Serve(l); err != http.ErrServerClosed {
|
||||
if err := h.Supervisor.Server.Serve(l); err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -69,12 +69,12 @@ func (h TaskHost) Serve() error {
|
||||
// HostURL returns the listening full url (scheme+host)
|
||||
// based on the supervisor's server's address.
|
||||
func (h TaskHost) HostURL() string {
|
||||
return nettools.ResolveURLFromServer(h.su.server)
|
||||
return netutil.ResolveURLFromServer(h.Supervisor.Server)
|
||||
}
|
||||
|
||||
// Hostname returns the underline server's hostname.
|
||||
func (h TaskHost) Hostname() string {
|
||||
return nettools.ResolveHostname(h.su.server.Addr)
|
||||
return netutil.ResolveHostname(h.Supervisor.Server.Addr)
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the server without interrupting any
|
||||
@@ -88,77 +88,16 @@ func (h TaskHost) Hostname() string {
|
||||
// connections such as WebSockets. The caller of Shutdown should
|
||||
// separately notify such long-lived connections of shutdown and wait
|
||||
// for them to close, if desired.
|
||||
//
|
||||
// This Shutdown calls the underline's Server's Shutdown, in order to be able to re-start the server
|
||||
// from a task.
|
||||
func (h TaskHost) Shutdown(ctx context.Context) error {
|
||||
// the underline server's Shutdown (otherwise we will cancel all tasks and do cycles)
|
||||
return h.su.server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// TaskProcess is the context of the Task runner.
|
||||
// Contains the host's information and actions
|
||||
// and its self cancelation emmiter.
|
||||
type TaskProcess struct {
|
||||
canceledChan chan struct{}
|
||||
host TaskHost
|
||||
}
|
||||
|
||||
// Done filled when this task is canceled.
|
||||
func (p TaskProcess) Done() <-chan struct{} {
|
||||
return p.canceledChan
|
||||
}
|
||||
|
||||
// Host returns the TaskHost.
|
||||
//
|
||||
// TaskHost contains all the necessary information
|
||||
// about the host supervisor, its server
|
||||
// and the exports the whole flow controller of it.
|
||||
func (p TaskProcess) Host() TaskHost {
|
||||
return p.host
|
||||
return h.Supervisor.Server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func createTaskHost(su *Supervisor) TaskHost {
|
||||
host := TaskHost{
|
||||
su: su,
|
||||
FlowController: su,
|
||||
doneChan: make(chan struct{}),
|
||||
errChan: make(chan error),
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
func newTaskProcess(host TaskHost) TaskProcess {
|
||||
return TaskProcess{
|
||||
host: host,
|
||||
canceledChan: make(chan struct{}),
|
||||
return TaskHost{
|
||||
Supervisor: su,
|
||||
}
|
||||
}
|
||||
|
||||
// A TaskRunner is an independent stream of instructions in a Supervisor.
|
||||
// A routine is similar to a sequential program.
|
||||
// However, a routine itself is not a program,
|
||||
// it can't run on its own, instead it runs within a Supervisor's context.
|
||||
//
|
||||
// The real usage of a routine is not about a single sequential thread,
|
||||
// but rather using multiple tasks in a single Supervisor.
|
||||
// Multiple tasks running at the same time and performing various tasks is referred as Multithreading.
|
||||
// A Task is considered to be a lightweight process because it runs within the context of a Supervisor
|
||||
// and takes advantage of resources allocated for that Supervisor and its Server.
|
||||
type TaskRunner interface {
|
||||
// Run runs the task based on its TaskProcess which contains
|
||||
// all the necessary information and actions to control the host supervisor
|
||||
// and its server.
|
||||
Run(TaskProcess)
|
||||
}
|
||||
|
||||
// TaskRunnerFunc "converts" a func(TaskProcess) to a complete TaskRunner.
|
||||
// Its functionality is exactly the same as TaskRunner.
|
||||
//
|
||||
// See `TaskRunner` too.
|
||||
type TaskRunnerFunc func(TaskProcess)
|
||||
|
||||
// Run runs the task based on its TaskProcess which contains
|
||||
// all the necessary information and actions to control the host supervisor
|
||||
// and its server.
|
||||
func (s TaskRunnerFunc) Run(proc TaskProcess) {
|
||||
s(proc)
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package host
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// WriteBannerTask is a task which accepts a logger(io.Writer)
|
||||
// and a "banner" text to write to following
|
||||
// by a generated message based on the host supervisor's server and writes it to the "w".
|
||||
// This task runs on serve.
|
||||
func WriteBannerTask(w io.Writer, banner string) TaskRunnerFunc {
|
||||
return func(proc TaskProcess) {
|
||||
listeningURI := proc.Host().HostURL()
|
||||
interruptkey := "CTRL"
|
||||
if runtime.GOOS == "darwin" {
|
||||
interruptkey = "CMD"
|
||||
}
|
||||
w.Write([]byte(fmt.Sprintf("%s\n\nNow listening on: %s\nApplication started. Press %s+C to shut down.\n",
|
||||
banner, listeningURI, interruptkey)))
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// white-box testing
|
||||
package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TaskHostError() {
|
||||
su := New(&http.Server{Addr: ":8273", Handler: http.DefaultServeMux})
|
||||
|
||||
su.ScheduleFunc(func(proc TaskProcess) {
|
||||
select {
|
||||
case err := <-proc.Host().Err():
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
su.ScheduleFunc(func(proc TaskProcess) {
|
||||
select {
|
||||
case err := <-proc.Host().Err():
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
su.ScheduleFunc(func(proc TaskProcess) {
|
||||
select {
|
||||
case err := <-proc.Host().Err():
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
go su.ListenAndServe()
|
||||
time.Sleep(1 * time.Second)
|
||||
su.Shutdown(context.TODO())
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Output:
|
||||
// http: Server closed
|
||||
// http: Server closed
|
||||
// http: Server closed
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OnInterrupt is a built'n supervisor task type which fires its
|
||||
// value(Task) when an OS interrupt/kill signal received.
|
||||
type OnInterrupt TaskRunnerFunc
|
||||
|
||||
// Run runs the interrupt task and completes the TaskRunner interface.
|
||||
func (t OnInterrupt) Run(proc TaskProcess) {
|
||||
t(proc)
|
||||
}
|
||||
|
||||
// ShutdownOnInterruptTask returns a supervisor's built'n task which
|
||||
// shutdowns the server when InterruptSignalTask fire this task.
|
||||
func ShutdownOnInterruptTask(shutdownTimeout time.Duration) TaskRunner {
|
||||
return OnInterrupt(func(proc TaskProcess) {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), shutdownTimeout)
|
||||
defer cancel()
|
||||
proc.Host().Shutdown(ctx)
|
||||
proc.Host().RestoreFlow()
|
||||
})
|
||||
}
|
||||
61
core/host/world.go
Normal file
61
core/host/world.go
Normal file
@@ -0,0 +1,61 @@
|
||||
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 {
|
||||
go 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()
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewDevLogger returns a new logger of io.Writer which
|
||||
// formats its log message input and writes it
|
||||
// to the os.Stdout.
|
||||
func NewDevLogger(omitTimeFor ...string) io.Writer {
|
||||
mu := &sync.Mutex{} // for now and last log
|
||||
lastLog := time.Now()
|
||||
distanceDuration := 850 * time.Millisecond
|
||||
|
||||
return writerFunc(func(p []byte) (int, error) {
|
||||
logMessage := string(p)
|
||||
for _, s := range omitTimeFor {
|
||||
if strings.Contains(logMessage, s) {
|
||||
n, err := fmt.Print(logMessage)
|
||||
lastLog = time.Now()
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock() // "slow" but we don't care here.
|
||||
nowLog := time.Now()
|
||||
if nowLog.Before(lastLog.Add(distanceDuration)) {
|
||||
// don't use the log.Logger to print this message
|
||||
// if the last one was printed before some seconds.
|
||||
n, err := fmt.Println(logMessage) // fmt because we don't want the time, dev is dev so console.
|
||||
lastLog = nowLog
|
||||
return n, err
|
||||
}
|
||||
|
||||
// begin with new line in order to have the time once at the top
|
||||
// and the child logs below it.
|
||||
n, err := fmt.Printf("%s \u2192\n%s\n", nowLog.Format("01/02/2006 03:04:05"), logMessage)
|
||||
lastLog = nowLog
|
||||
return n, err
|
||||
})
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// writerFunc is just an "extended" io.Writer which provides
|
||||
// some "methods" which can help applications
|
||||
// to adapt their existing loggers inside Iris.
|
||||
type writerFunc func(p []byte) (n int, err error)
|
||||
|
||||
func (w writerFunc) Write(p []byte) (int, error) {
|
||||
return w(p)
|
||||
}
|
||||
|
||||
type formatPrintWriter interface {
|
||||
Printf(string, ...interface{})
|
||||
}
|
||||
|
||||
type stringWriter interface {
|
||||
WriteString(string) (int, error)
|
||||
}
|
||||
|
||||
// Log sends a message to the defined logger of io.Writer logger, it's
|
||||
// just a help function for internal use but it can be used to a cusotom middleware too.
|
||||
//
|
||||
// See AttachLogger too.
|
||||
func Log(w io.Writer, format string, a ...interface{}) {
|
||||
// check if the user's defined logger is one of the "high"
|
||||
// level printers, if yes then use their functions to print instead
|
||||
// of allocating new byte slices.
|
||||
|
||||
if fpw, ok := w.(formatPrintWriter); ok {
|
||||
fpw.Printf(format, a...)
|
||||
return
|
||||
}
|
||||
formattedMessage := fmt.Sprintf(format, a...)
|
||||
|
||||
if sWriter, ok := w.(stringWriter); ok {
|
||||
sWriter.WriteString(formattedMessage)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(formattedMessage))
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// black-box testing
|
||||
package logger_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/core/logger"
|
||||
)
|
||||
|
||||
func TestLog(t *testing.T) {
|
||||
msg := "Hello this is me"
|
||||
l := &bytes.Buffer{}
|
||||
logger.Log(l, msg)
|
||||
if expected, got := msg, l.String(); expected != got {
|
||||
t.Fatalf("expected %s but got %s", expected, got)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package logger
|
||||
|
||||
// NoOpLogger returns a new, non-operational logger of io.Writer,
|
||||
// it does nothing any form of input.
|
||||
var NoOpLogger = writerFunc(func([]byte) (int, error) { return -1, nil })
|
||||
@@ -1,13 +1,6 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package memstore contains a store which is just
|
||||
// a collection of key-value entries with immutability capabilities.
|
||||
//
|
||||
// Created after that proposal: https://github.com/iris-contrib/community-board/issues/5
|
||||
// and it's being used by session entries (session lifetime) and context's entries (per-request).
|
||||
//
|
||||
// Developers can use that storage to their own apps if they like its behavior.
|
||||
// It's fast and in the same time you get read-only access (safety) when you need it.
|
||||
package memstore
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package nettools
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -199,11 +195,26 @@ func ResolvePort(addr string) int {
|
||||
return 80
|
||||
}
|
||||
|
||||
// ResolveScheme returns the scheme based on the "vhost"
|
||||
func ResolveScheme(vhost string) string {
|
||||
// pure check
|
||||
if strings.HasPrefix(vhost, SchemeHTTPS) || ResolvePort(vhost) == 443 {
|
||||
// ResolveScheme returns "https://" if "isTLS" receiver is true,
|
||||
// otherwise "http://".
|
||||
func ResolveScheme(isTLS bool) string {
|
||||
if isTLS {
|
||||
return SchemeHTTPS
|
||||
}
|
||||
|
||||
return SchemeHTTP
|
||||
}
|
||||
|
||||
// ResolveSchemeFromVHost returns the scheme based on the "vhost".
|
||||
func ResolveSchemeFromVHost(vhost string) string {
|
||||
// pure check
|
||||
isTLS := strings.HasPrefix(vhost, SchemeHTTPS) || ResolvePort(vhost) == 443
|
||||
return ResolveScheme(isTLS)
|
||||
}
|
||||
|
||||
// ResolveURL takes the scheme and an address
|
||||
// and returns its URL, pure implementation but it does the job.
|
||||
func ResolveURL(scheme string, addr string) string {
|
||||
host := ResolveVHost(addr)
|
||||
return scheme + "://" + host
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package nettools
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,8 +1,4 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package nettools
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -26,11 +22,7 @@ func IsTLS(srv *http.Server) bool {
|
||||
// Returns "https" on secure server,
|
||||
// otherwise "http".
|
||||
func ResolveSchemeFromServer(srv *http.Server) string {
|
||||
if IsTLS(srv) {
|
||||
return SchemeHTTPS
|
||||
}
|
||||
|
||||
return SchemeHTTP
|
||||
return ResolveScheme(IsTLS(srv))
|
||||
}
|
||||
|
||||
// ResolveURLFromServer returns the scheme+host from a server.
|
||||
@@ -1,8 +1,4 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package nettools
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
@@ -62,13 +58,6 @@ func (r *repository) getAll() []*Route {
|
||||
return r.routes
|
||||
}
|
||||
|
||||
// RoutesProvider should be implemented by
|
||||
// iteral which contains the registered routes.
|
||||
type RoutesProvider interface { // api builder
|
||||
GetRoutes() []*Route
|
||||
GetRoute(routeName string) *Route
|
||||
}
|
||||
|
||||
// APIBuilder the visible API for constructing the router
|
||||
// and child routers.
|
||||
type APIBuilder struct {
|
||||
@@ -81,6 +70,11 @@ type APIBuilder struct {
|
||||
// the api builder global route path reverser object
|
||||
// used by the view engine but it can be used anywhere.
|
||||
reverser *RoutePathReverser
|
||||
// the api builder global errors, can be filled by the Subdomain, WildcardSubdomain, Handle...
|
||||
// the list of possible errors that can be
|
||||
// collected on the build state to log
|
||||
// to the end-user.
|
||||
reporter *errors.Reporter
|
||||
|
||||
// the per-party middleware
|
||||
middleware context.Handlers
|
||||
@@ -101,6 +95,7 @@ func NewAPIBuilder() *APIBuilder {
|
||||
rb := &APIBuilder{
|
||||
macros: defaultMacros(),
|
||||
errorCodeHandlers: defaultErrorCodeHandlers(),
|
||||
reporter: errors.NewReporter(),
|
||||
relativePath: "/",
|
||||
routes: new(repository),
|
||||
}
|
||||
@@ -108,17 +103,22 @@ func NewAPIBuilder() *APIBuilder {
|
||||
return rb
|
||||
}
|
||||
|
||||
// GetReport returns an error may caused by party's methods.
|
||||
func (rb *APIBuilder) GetReport() error {
|
||||
return rb.reporter.Return()
|
||||
}
|
||||
|
||||
// Handle registers a route to the server's rb.
|
||||
// if empty method is passed then handler(s) are being registered to all methods, same as .Any.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Handle(method string, registeredPath string, handlers ...context.Handler) (*Route, error) {
|
||||
// Returns a *Route, app will throw any errors later on.
|
||||
func (rb *APIBuilder) Handle(method string, registeredPath string, handlers ...context.Handler) *Route {
|
||||
// if registeredPath[0] != '/' {
|
||||
// return nil, errors.New("path should start with slash and should not be empty")
|
||||
// }
|
||||
|
||||
if method == "" || method == "ALL" || method == "ANY" { // then use like it was .Any
|
||||
return nil, rb.Any(registeredPath, handlers...)
|
||||
return rb.Any(registeredPath, handlers...)[0]
|
||||
}
|
||||
|
||||
// no clean path yet because of subdomain indicator/separator which contains a dot.
|
||||
@@ -136,31 +136,33 @@ func (rb *APIBuilder) Handle(method string, registeredPath string, handlers ...c
|
||||
routeHandlers := joinHandlers(rb.middleware, handlers)
|
||||
|
||||
// here we separate the subdomain and relative path
|
||||
subdomain, path := exctractSubdomain(fullpath)
|
||||
subdomain, path := splitSubdomainAndPath(fullpath)
|
||||
if len(rb.doneHandlers) > 0 {
|
||||
routeHandlers = append(routeHandlers, rb.doneHandlers...) // register the done middleware, if any
|
||||
}
|
||||
|
||||
r, err := NewRoute(method, subdomain, path, routeHandlers, rb.macros)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err != nil { // template path parser errors:
|
||||
rb.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
// global
|
||||
rb.routes.register(r)
|
||||
|
||||
// per -party
|
||||
// per -party, used for done handlers
|
||||
rb.apiRoutes = append(rb.apiRoutes, r)
|
||||
// should we remove the rb.apiRoutes on the .Party (new children party) ?, No, because the user maybe use this party later
|
||||
// should we add to the 'inheritance tree' the rb.apiRoutes, No, these are for this specific party only, because the user propably, will have unexpected behavior when using Use/Use, Done/DoneFunc
|
||||
return r, nil
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
|
||||
// Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun.
|
||||
func (rb *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {
|
||||
parentPath := rb.relativePath
|
||||
dot := string(SubdomainIndicator[0])
|
||||
if len(parentPath) > 0 && parentPath[0] == '/' && strings.HasSuffix(relativePath, dot) { // if ends with . , example: admin., it's subdomain->
|
||||
dot := string(SubdomainPrefix[0])
|
||||
if len(parentPath) > 0 && parentPath[0] == '/' && strings.HasSuffix(relativePath, dot) {
|
||||
// if ends with . , i.e admin., it's subdomain->
|
||||
parentPath = parentPath[1:] // remove first slash
|
||||
}
|
||||
|
||||
@@ -169,6 +171,16 @@ func (rb *APIBuilder) Party(relativePath string, handlers ...context.Handler) Pa
|
||||
parentPath = parentPath[1:] // remove first slash if parent ended with / and new one started with /.
|
||||
}
|
||||
|
||||
// if it's subdomain then it has priority, i.e:
|
||||
// rb.relativePath == "admin."
|
||||
// relativePath == "panel."
|
||||
// then it should be panel.admin.
|
||||
// instead of admin.panel.
|
||||
if hasSubdomain(parentPath) && hasSubdomain(relativePath) {
|
||||
relativePath = relativePath + parentPath
|
||||
parentPath = ""
|
||||
}
|
||||
|
||||
fullpath := parentPath + relativePath
|
||||
// append the parent's +child's handlers
|
||||
middleware := joinHandlers(rb.middleware, handlers)
|
||||
@@ -179,16 +191,46 @@ func (rb *APIBuilder) Party(relativePath string, handlers ...context.Handler) Pa
|
||||
routes: rb.routes,
|
||||
errorCodeHandlers: rb.errorCodeHandlers,
|
||||
doneHandlers: rb.doneHandlers,
|
||||
reporter: rb.reporter,
|
||||
// per-party/children
|
||||
middleware: middleware,
|
||||
relativePath: fullpath,
|
||||
}
|
||||
}
|
||||
|
||||
// Subdomain returns a new party which is responsible to register routes to
|
||||
// this specific "subdomain".
|
||||
//
|
||||
// If called from a child party then the subdomain will be prepended to the path instead of appended.
|
||||
// So if app.Subdomain("admin.").Subdomain("panel.") then the result is: "panel.admin.".
|
||||
func (rb *APIBuilder) Subdomain(subdomain string, middleware ...context.Handler) Party {
|
||||
if rb.relativePath == SubdomainWildcardIndicator {
|
||||
// cannot concat wildcard subdomain with something else
|
||||
rb.reporter.Add("cannot concat parent wildcard subdomain with anything else -> %s , %s",
|
||||
rb.relativePath, subdomain)
|
||||
return rb
|
||||
}
|
||||
return rb.Party(subdomain, middleware...)
|
||||
}
|
||||
|
||||
// WildcardSubdomain returns a new party which is responsible to register routes to
|
||||
// a dynamic, wildcard(ed) subdomain. A dynamic subdomain is a subdomain which
|
||||
// can reply to any subdomain requests. Server will accept any subdomain
|
||||
// (if not static subdomain found) and it will search and execute the handlers of this party.
|
||||
func (rb *APIBuilder) WildcardSubdomain(middleware ...context.Handler) Party {
|
||||
if hasSubdomain(rb.relativePath) {
|
||||
// cannot concat static subdomain with a dynamic one, wildcard should be at the root level
|
||||
rb.reporter.Add("cannot concat static subdomain with a dynamic one. Dynamic subdomains should be at the root level -> %s",
|
||||
rb.relativePath)
|
||||
return rb
|
||||
}
|
||||
return rb.Subdomain(SubdomainWildcardIndicator, middleware...)
|
||||
}
|
||||
|
||||
// Macros returns the macro map which is responsible
|
||||
// to register custom macro functions for all routes.
|
||||
//
|
||||
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/beginner/routing/dynamic-path
|
||||
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
|
||||
func (rb *APIBuilder) Macros() *macro.Map {
|
||||
return rb.macros
|
||||
}
|
||||
@@ -209,8 +251,8 @@ func (rb *APIBuilder) GetRoute(routeName string) *Route {
|
||||
|
||||
// Use appends Handler(s) to the current Party's routes and child routes.
|
||||
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
|
||||
func (rb *APIBuilder) Use(handlers ...context.Handler) {
|
||||
rb.middleware = append(rb.middleware, handlers...)
|
||||
func (rb *APIBuilder) Use(middleware ...context.Handler) {
|
||||
rb.middleware = append(rb.middleware, middleware...)
|
||||
}
|
||||
|
||||
// Done appends to the very end, Handler(s) to the current Party's routes and child routes
|
||||
@@ -245,83 +287,84 @@ func (rb *APIBuilder) UseGlobal(handlers ...context.Handler) {
|
||||
// Offline(handleResultRouteInfo)
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) None(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) None(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(MethodNone, path, handlers...)
|
||||
}
|
||||
|
||||
// Get registers a route for the Get http method.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Get(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) Get(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(http.MethodGet, path, handlers...)
|
||||
}
|
||||
|
||||
// Post registers a route for the Post http method.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Post(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) Post(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(http.MethodPost, path, handlers...)
|
||||
}
|
||||
|
||||
// Put registers a route for the Put http method.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Put(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) Put(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(http.MethodPut, path, handlers...)
|
||||
}
|
||||
|
||||
// Delete registers a route for the Delete http method.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Delete(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) Delete(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(http.MethodDelete, path, handlers...)
|
||||
}
|
||||
|
||||
// Connect registers a route for the Connect http method.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Connect(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) Connect(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(http.MethodConnect, path, handlers...)
|
||||
}
|
||||
|
||||
// Head registers a route for the Head http method.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Head(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) Head(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(http.MethodHead, path, handlers...)
|
||||
}
|
||||
|
||||
// Options registers a route for the Options http method.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Options(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) Options(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(http.MethodOptions, path, handlers...)
|
||||
}
|
||||
|
||||
// Patch registers a route for the Patch http method.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Patch(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) Patch(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(http.MethodPatch, path, handlers...)
|
||||
}
|
||||
|
||||
// Trace registers a route for the Trace http method.
|
||||
//
|
||||
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
|
||||
func (rb *APIBuilder) Trace(path string, handlers ...context.Handler) (*Route, error) {
|
||||
func (rb *APIBuilder) Trace(path string, handlers ...context.Handler) *Route {
|
||||
return rb.Handle(http.MethodTrace, path, handlers...)
|
||||
}
|
||||
|
||||
// Any registers a route for ALL of the http methods
|
||||
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
|
||||
func (rb *APIBuilder) Any(registeredPath string, handlers ...context.Handler) error {
|
||||
for _, k := range AllMethods {
|
||||
if _, err := rb.Handle(k, registeredPath, handlers...); err != nil {
|
||||
return err
|
||||
}
|
||||
func (rb *APIBuilder) Any(registeredPath string, handlers ...context.Handler) []*Route {
|
||||
routes := make([]*Route, len(AllMethods), len(AllMethods))
|
||||
|
||||
for i, k := range AllMethods {
|
||||
r := rb.Handle(k, registeredPath, handlers...)
|
||||
routes[i] = r
|
||||
}
|
||||
|
||||
return nil
|
||||
return routes
|
||||
}
|
||||
|
||||
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration
|
||||
@@ -341,10 +384,8 @@ const (
|
||||
varyHeaderKey = "Vary"
|
||||
)
|
||||
|
||||
func (rb *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) (*Route, error) {
|
||||
if _, err := rb.Head(reqPath, h); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (rb *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *Route {
|
||||
rb.Head(reqPath, h)
|
||||
return rb.Get(reqPath, h)
|
||||
}
|
||||
|
||||
@@ -365,7 +406,7 @@ func (rb *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) (
|
||||
// mySubdomainFsServer.Get("/static", h)
|
||||
// ...
|
||||
//
|
||||
func (rb *APIBuilder) StaticHandler(systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler {
|
||||
func (rb *APIBuilder) StaticHandler(systemPath string, showList bool, enableGzip bool) context.Handler {
|
||||
// Note: this doesn't need to be here but we'll keep it for consistently
|
||||
return StaticHandler(systemPath, showList, enableGzip)
|
||||
}
|
||||
@@ -379,7 +420,7 @@ func (rb *APIBuilder) StaticHandler(systemPath string, showList bool, enableGzip
|
||||
// it uses gzip compression (compression on each request, no file cache).
|
||||
//
|
||||
// Returns the GET *Route.
|
||||
func (rb *APIBuilder) StaticServe(systemPath string, requestPath ...string) (*Route, error) {
|
||||
func (rb *APIBuilder) StaticServe(systemPath string, requestPath ...string) *Route {
|
||||
var reqPath string
|
||||
|
||||
if len(requestPath) == 0 {
|
||||
@@ -411,12 +452,13 @@ func (rb *APIBuilder) StaticServe(systemPath string, requestPath ...string) (*Ro
|
||||
// that are ready to serve raw static bytes, memory cached.
|
||||
//
|
||||
// Returns the GET *Route.
|
||||
func (rb *APIBuilder) StaticContent(reqPath string, cType string, content []byte) (*Route, error) {
|
||||
func (rb *APIBuilder) StaticContent(reqPath string, cType string, content []byte) *Route {
|
||||
modtime := time.Now()
|
||||
h := func(ctx context.Context) {
|
||||
if err := ctx.WriteWithExpiration(content, cType, modtime); err != nil {
|
||||
ctx.NotFound()
|
||||
// ctx.Application().Log("error while serving []byte via StaticContent: %s", err.Error())
|
||||
ctx.ContentType(cType)
|
||||
if _, err := ctx.WriteWithExpiration(content, modtime); err != nil {
|
||||
ctx.StatusCode(http.StatusInternalServerError)
|
||||
// ctx.Application().Logger().Infof("error while serving []byte via StaticContent: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +486,7 @@ func (rb *APIBuilder) StaticEmbeddedHandler(vdir string, assetFn func(name strin
|
||||
// Returns the GET *Route.
|
||||
//
|
||||
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
|
||||
func (rb *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) (*Route, error) {
|
||||
func (rb *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
|
||||
fullpath := joinPath(rb.relativePath, requestPath)
|
||||
requestPath = joinPath(fullpath, WildcardParam("file"))
|
||||
|
||||
@@ -466,12 +508,13 @@ var errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found.
|
||||
// Note that you have to call it on every favicon you have to serve automatically (desktop, mobile and so on).
|
||||
//
|
||||
// Returns the GET *Route.
|
||||
func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) (*Route, error) {
|
||||
func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
|
||||
favPath = Abs(favPath)
|
||||
|
||||
f, err := os.Open(favPath)
|
||||
if err != nil {
|
||||
return nil, errDirectoryFileNotFound.Format(favPath, err.Error())
|
||||
rb.reporter.AddErr(errDirectoryFileNotFound.Format(favPath, err.Error()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ignore error f.Close()
|
||||
@@ -496,8 +539,9 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) (*Route, er
|
||||
// So we could panic but we don't,
|
||||
// we just interrupt with a message
|
||||
// to the (user-defined) logger.
|
||||
return nil, errDirectoryFileNotFound.
|
||||
Format(favPath, "favicon: couldn't read the data bytes for file: "+err.Error())
|
||||
rb.reporter.AddErr(errDirectoryFileNotFound.
|
||||
Format(favPath, "favicon: couldn't read the data bytes for file: "+err.Error()))
|
||||
return nil
|
||||
}
|
||||
modtime := ""
|
||||
h := func(ctx context.Context) {
|
||||
@@ -516,7 +560,7 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) (*Route, er
|
||||
ctx.ResponseWriter().Header().Set(lastModifiedHeaderKey, modtime)
|
||||
ctx.StatusCode(http.StatusOK)
|
||||
if _, err := ctx.Write(cacheFav); err != nil {
|
||||
// ctx.Application().Log("error while trying to serve the favicon: %s", err.Error())
|
||||
// ctx.Application().Logger().Infof("error while trying to serve the favicon: %s", err.Error())
|
||||
ctx.StatusCode(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
@@ -534,9 +578,8 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) (*Route, er
|
||||
//
|
||||
// first parameter: the route path
|
||||
// second parameter: the system directory
|
||||
// third OPTIONAL parameter: the exception routes
|
||||
// (= give priority to these routes instead of the static handler)
|
||||
// for more options look rb.StaticHandler.
|
||||
//
|
||||
// for more options look router.StaticHandler.
|
||||
//
|
||||
// rb.StaticWeb("/static", "./static")
|
||||
//
|
||||
@@ -547,13 +590,13 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) (*Route, er
|
||||
// StaticWeb calls the StaticHandler(systemPath, listingDirectories: false, gzip: false ).
|
||||
//
|
||||
// Returns the GET *Route.
|
||||
func (rb *APIBuilder) StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error) {
|
||||
func (rb *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
|
||||
|
||||
paramName := "file"
|
||||
|
||||
fullpath := joinPath(rb.relativePath, requestPath)
|
||||
|
||||
h := StripPrefix(fullpath, rb.StaticHandler(systemPath, false, false, exceptRoutes...))
|
||||
h := StripPrefix(fullpath, rb.StaticHandler(systemPath, false, false))
|
||||
|
||||
handler := func(ctx context.Context) {
|
||||
h(ctx)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ & Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
@@ -21,7 +18,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
)
|
||||
|
||||
// StaticEmbeddedHandler returns a Handler which can serve
|
||||
@@ -86,8 +82,8 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := ctx.WriteWithExpiration(buf, cType, modtime); err != nil {
|
||||
ctx.ContentType(cType)
|
||||
if _, err := ctx.WriteWithExpiration(buf, modtime); err != nil {
|
||||
ctx.StatusCode(http.StatusInternalServerError)
|
||||
ctx.StopExecution()
|
||||
}
|
||||
@@ -102,36 +98,6 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
|
||||
return h
|
||||
}
|
||||
|
||||
// Prioritize is a middleware which executes a route against this path
|
||||
// when the request's Path has a prefix of the route's STATIC PART
|
||||
// is not executing ExecRoute to determinate if it's valid, for performance reasons
|
||||
// if this function is not enough for you and you want to test more than one parameterized path
|
||||
// then use the: if c := ExecRoute(r); c == nil { /* move to the next, the route is not valid */ }
|
||||
//
|
||||
// You can find the Route by iris.Default.Routes().Lookup("theRouteName")
|
||||
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
|
||||
// that will set a name to the route and returns its iris.Route instance for further usage.
|
||||
//
|
||||
// if the route found then it executes that and don't continue to the next handler
|
||||
// if not found then continue to the next handler
|
||||
func Prioritize(r *Route) context.Handler {
|
||||
if r != nil {
|
||||
return func(ctx context.Context) {
|
||||
reqPath := ctx.Path()
|
||||
staticPath := ResolveStaticPath(reqPath)
|
||||
if strings.HasPrefix(reqPath, staticPath) {
|
||||
ctx.Exec(r.Method, reqPath) // execute the route based on this request path
|
||||
// we are done here.
|
||||
return
|
||||
}
|
||||
// execute the next handler if no prefix
|
||||
// here look, the only error we catch is the 404.
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
return func(ctx context.Context) { ctx.Next() }
|
||||
}
|
||||
|
||||
// StaticHandler returns a new Handler which is ready
|
||||
// to serve all kind of static files.
|
||||
//
|
||||
@@ -148,20 +114,18 @@ func Prioritize(r *Route) context.Handler {
|
||||
// app.Get("/static", h)
|
||||
// ...
|
||||
//
|
||||
func StaticHandler(systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler {
|
||||
func StaticHandler(systemPath string, showList bool, enableGzip bool) context.Handler {
|
||||
return NewStaticHandlerBuilder(systemPath).
|
||||
Listing(showList).
|
||||
Gzip(enableGzip).
|
||||
Except(exceptRoutes...).
|
||||
Build()
|
||||
}
|
||||
|
||||
// StaticHandlerBuilder is the web file system's Handler builder
|
||||
// use that or the iris.StaticHandler/StaticWeb methods
|
||||
// use that or the iris.StaticHandler/StaticWeb methods.
|
||||
type StaticHandlerBuilder interface {
|
||||
Gzip(enable bool) StaticHandlerBuilder
|
||||
Listing(listDirectoriesOnOff bool) StaticHandlerBuilder
|
||||
Except(r ...*Route) StaticHandlerBuilder
|
||||
Build() context.Handler
|
||||
}
|
||||
|
||||
@@ -179,7 +143,6 @@ type fsHandler struct {
|
||||
// these are init on the Build() call
|
||||
filesystem http.FileSystem
|
||||
once sync.Once
|
||||
exceptions []*Route
|
||||
handler context.Handler
|
||||
}
|
||||
|
||||
@@ -235,13 +198,6 @@ func (w *fsHandler) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
|
||||
return w
|
||||
}
|
||||
|
||||
// Except add a route exception,
|
||||
// gives priority to that Route over the static handler.
|
||||
func (w *fsHandler) Except(r ...*Route) StaticHandlerBuilder {
|
||||
w.exceptions = append(w.exceptions, r...)
|
||||
return w
|
||||
}
|
||||
|
||||
type (
|
||||
noListFile struct {
|
||||
http.File
|
||||
@@ -308,7 +264,7 @@ func (w *fsHandler) Build() context.Handler {
|
||||
// headers[contentEncodingHeader] = nil
|
||||
// headers[contentLength] = nil
|
||||
}
|
||||
// ctx.Application().Log(errMsg)
|
||||
// ctx.Application().Logger().Infof(errMsg)
|
||||
ctx.StatusCode(prevStatusCode)
|
||||
return
|
||||
}
|
||||
@@ -317,21 +273,6 @@ func (w *fsHandler) Build() context.Handler {
|
||||
ctx.Next()
|
||||
}
|
||||
|
||||
if len(w.exceptions) > 0 {
|
||||
middleware := make(context.Handlers, len(w.exceptions)+1)
|
||||
for i := range w.exceptions {
|
||||
middleware[i] = Prioritize(w.exceptions[i])
|
||||
}
|
||||
middleware[len(w.exceptions)] = fileserver
|
||||
|
||||
w.handler = func(ctx context.Context) {
|
||||
ctxHandlers := ctx.Handlers()
|
||||
ctx.SetHandlers(append(middleware, ctxHandlers...))
|
||||
ctx.Handlers()[0](ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.handler = fileserver
|
||||
|
||||
})
|
||||
@@ -357,7 +298,7 @@ func StripPrefix(prefix string, h context.Handler) context.Handler {
|
||||
// here we separate the path from the subdomain (if any), we care only for the path
|
||||
// fixes a bug when serving static files via a subdomain
|
||||
fixedPrefix := prefix
|
||||
if dotWSlashIdx := strings.Index(fixedPrefix, SubdomainIndicator); dotWSlashIdx > 0 {
|
||||
if dotWSlashIdx := strings.Index(fixedPrefix, SubdomainPrefix); dotWSlashIdx > 0 {
|
||||
fixedPrefix = fixedPrefix[dotWSlashIdx+1:]
|
||||
}
|
||||
fixedPrefix = toWebPath(fixedPrefix)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
@@ -11,7 +7,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/nettools"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
"github.com/kataras/iris/core/router/node"
|
||||
)
|
||||
|
||||
@@ -70,6 +68,13 @@ func NewDefaultHandler() RequestHandler {
|
||||
return h
|
||||
}
|
||||
|
||||
// RoutesProvider should be implemented by
|
||||
// iteral which contains the registered routes.
|
||||
type RoutesProvider interface { // api builder
|
||||
GetRoutes() []*Route
|
||||
GetRoute(routeName string) *Route
|
||||
}
|
||||
|
||||
func (h *routerHandler) Build(provider RoutesProvider) error {
|
||||
registeredRoutes := provider.GetRoutes()
|
||||
h.trees = h.trees[0:0] // reset, inneed when rebuilding.
|
||||
@@ -79,20 +84,24 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
|
||||
return len(registeredRoutes[i].Subdomain) >= len(registeredRoutes[j].Subdomain)
|
||||
})
|
||||
|
||||
rp := errors.NewReporter()
|
||||
|
||||
for _, r := range registeredRoutes {
|
||||
if r.Subdomain != "" {
|
||||
h.hosts = true
|
||||
}
|
||||
|
||||
// the only "bad" with this is if the user made an error
|
||||
// on route, it will be stacked shown in this build state
|
||||
// and no in the lines of the user's action, they should read
|
||||
// the docs better. Or TODO: add a link here in order to help new users.
|
||||
if err := h.addRoute(r.Method, r.Subdomain, r.Path, r.Handlers); err != nil {
|
||||
return err
|
||||
// node errors:
|
||||
rp.Add("%v -> %s", err, r.String())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return rp.Return()
|
||||
}
|
||||
|
||||
func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
@@ -134,14 +143,14 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
|
||||
if h.hosts && t.Subdomain != "" {
|
||||
requestHost := ctx.Host()
|
||||
if nettools.IsLoopbackSubdomain(requestHost) {
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
// 127.0.0.1:8080 for example
|
||||
// and have a wildcard subdomain and a route registered to root domain.
|
||||
continue // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||
}
|
||||
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||
if t.Subdomain == DynamicSubdomainIndicator {
|
||||
if t.Subdomain == SubdomainWildcardIndicator {
|
||||
// mydomain.com -> invalid
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Gerasimos Maropoulos nor the name of his
|
||||
username, kataras, may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lexer
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lexer
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package token
|
||||
|
||||
// Type is a specific type of int which describes the symbols.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package macro
|
||||
|
||||
import (
|
||||
@@ -239,7 +235,7 @@ type Map struct {
|
||||
// NewMap returns a new macro Map with default
|
||||
// type evaluators.
|
||||
//
|
||||
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/beginner/routing/dynamic-path
|
||||
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
|
||||
func NewMap() *Map {
|
||||
return &Map{
|
||||
// it allows everything, so no need for a regexp here.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package macro
|
||||
|
||||
import (
|
||||
@@ -179,7 +175,7 @@ func TestPathEvaluatorRaw(t *testing.T) {
|
||||
// }
|
||||
// })
|
||||
|
||||
// p, err := Parse("/user/@kataras")
|
||||
// p, err := Parse("/user/@iris")
|
||||
// if err != nil {
|
||||
// t.Fatalf(err)
|
||||
// }
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package macro
|
||||
|
||||
import (
|
||||
|
||||
@@ -20,8 +20,8 @@ type node struct {
|
||||
root bool
|
||||
}
|
||||
|
||||
// ErrDublicate returned from MakeChild when more than one routes have the same registered path.
|
||||
var ErrDublicate = errors.New("more than one routes have the same registered path")
|
||||
// ErrDublicate returnned from `Add` when two or more routes have the same registered path.
|
||||
var ErrDublicate = errors.New("two or more routes have the same registered path")
|
||||
|
||||
// Add adds a node to the tree, returns an ErrDublicate error on failure.
|
||||
func (nodes *Nodes) Add(path string, handlers context.Handlers) error {
|
||||
@@ -48,12 +48,22 @@ func (nodes *Nodes) Add(path string, handlers context.Handlers) error {
|
||||
paramEnd -= paramEnd - paramStart
|
||||
}
|
||||
|
||||
for _, idx := range paramsPos(path) {
|
||||
var p []int
|
||||
for i := 0; i < len(path); i++ {
|
||||
idx := strings.IndexByte(path[i:], ':')
|
||||
if idx == -1 {
|
||||
break
|
||||
}
|
||||
p = append(p, idx+i)
|
||||
i = idx + i
|
||||
}
|
||||
|
||||
if err := nodes.add(path[:idx], nil, nil, true); err != nil { // take the static path to its own node
|
||||
for _, idx := range p {
|
||||
|
||||
if err := nodes.add(path[:idx], nil, nil, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// create a second, empty, dynamic parameter node without the last slash
|
||||
|
||||
if nidx := idx + 1; len(path) > nidx {
|
||||
if err := nodes.add(path[:nidx], nil, nil, true); err != nil {
|
||||
return err
|
||||
@@ -61,13 +71,12 @@ func (nodes *Nodes) Add(path string, handlers context.Handlers) error {
|
||||
}
|
||||
}
|
||||
|
||||
// last, create the node filled by the full path, parameters and its handlers
|
||||
if err := nodes.add(path, params, handlers, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sort by static path, remember, they were already sorted by subdomains too.
|
||||
nodes.Sort()
|
||||
// prioritize by static path remember, they were already sorted by subdomains too.
|
||||
nodes.prioritize()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -153,7 +162,7 @@ loop:
|
||||
return nil
|
||||
}
|
||||
if len(n.handlers) > 0 { // n.handlers already setted
|
||||
return ErrDublicate.Append("for: %s", n.s)
|
||||
return ErrDublicate
|
||||
}
|
||||
n.paramNames = paramNames
|
||||
n.handlers = handlers
|
||||
@@ -216,7 +225,7 @@ func (nodes Nodes) findChild(path string, params []string) (*node, []string) {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(path) == len(n.s) { // Node matched until the end of path.
|
||||
if len(path) == len(n.s) {
|
||||
if len(n.handlers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -226,7 +235,7 @@ func (nodes Nodes) findChild(path string, params []string) (*node, []string) {
|
||||
child, childParamNames := n.children.findChild(path[len(n.s):], params)
|
||||
|
||||
if child == nil || len(child.handlers) == 0 {
|
||||
// is wildcard and it is not root neither has children
|
||||
|
||||
if n.s[len(n.s)-1] == '/' && !(n.root && (n.s == "/" || len(n.children) > 0)) {
|
||||
if len(n.handlers) == 0 {
|
||||
return nil, nil
|
||||
@@ -254,8 +263,8 @@ func (n *node) isDynamic() bool {
|
||||
return n.s == ":"
|
||||
}
|
||||
|
||||
// Sort sets the static paths first.
|
||||
func (nodes Nodes) Sort() {
|
||||
// prioritize sets the static paths first.
|
||||
func (nodes Nodes) prioritize() {
|
||||
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
if nodes[i].isDynamic() {
|
||||
@@ -268,18 +277,6 @@ func (nodes Nodes) Sort() {
|
||||
})
|
||||
|
||||
for _, n := range nodes {
|
||||
n.children.Sort()
|
||||
n.children.prioritize()
|
||||
}
|
||||
}
|
||||
|
||||
func paramsPos(s string) (pos []int) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
p := strings.IndexByte(s[i:], ':')
|
||||
if p == -1 {
|
||||
break
|
||||
}
|
||||
pos = append(pos, p+i)
|
||||
i = p + i
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
@@ -15,11 +11,17 @@ import (
|
||||
// Look the "APIBuilder" for its implementation.
|
||||
type Party interface {
|
||||
// Party creates and returns a new child Party with the following features.
|
||||
Party(relativePath string, handlers ...context.Handler) Party
|
||||
Party(relativePath string, middleware ...context.Handler) Party
|
||||
// Subdomain returns a new party which is responsible to register routes to
|
||||
// this specific "subdomain".
|
||||
//
|
||||
// If called from a child party then the subdomain will be prepended to the path instead of appended.
|
||||
// So if app.Subdomain("admin.").Subdomain("panel.") then the result is: "panel.admin.".
|
||||
Subdomain(subdomain string, middleware ...context.Handler) Party
|
||||
|
||||
// Use appends Handler(s) to the current Party's routes and child routes.
|
||||
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
|
||||
Use(handlers ...context.Handler)
|
||||
Use(middleware ...context.Handler)
|
||||
|
||||
// Done appends to the very end, Handler(s) to the current Party's routes and child routes
|
||||
// The difference from .Use is that this/or these Handler(s) are being always running last.
|
||||
@@ -29,7 +31,7 @@ type Party interface {
|
||||
// if empty method is passed then handler(s) are being registered to all methods, same as .Any.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Handle(method string, registeredPath string, handlers ...context.Handler) (*Route, error)
|
||||
Handle(method string, registeredPath string, handlers ...context.Handler) *Route
|
||||
|
||||
// None registers an "offline" route
|
||||
// see context.ExecRoute(routeName) and
|
||||
@@ -37,47 +39,47 @@ type Party interface {
|
||||
// Offline(handleResultregistry.*Route)
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
None(path string, handlers ...context.Handler) (*Route, error)
|
||||
None(path string, handlers ...context.Handler) *Route
|
||||
|
||||
// Get registers a route for the Get http method.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Get(path string, handlers ...context.Handler) (*Route, error)
|
||||
Get(path string, handlers ...context.Handler) *Route
|
||||
// Post registers a route for the Post http method.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Post(path string, handlers ...context.Handler) (*Route, error)
|
||||
Post(path string, handlers ...context.Handler) *Route
|
||||
// Put registers a route for the Put http method.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Put(path string, handlers ...context.Handler) (*Route, error)
|
||||
Put(path string, handlers ...context.Handler) *Route
|
||||
// Delete registers a route for the Delete http method.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Delete(path string, handlers ...context.Handler) (*Route, error)
|
||||
Delete(path string, handlers ...context.Handler) *Route
|
||||
// Connect registers a route for the Connect http method.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Connect(path string, handlers ...context.Handler) (*Route, error)
|
||||
Connect(path string, handlers ...context.Handler) *Route
|
||||
// Head registers a route for the Head http method.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Head(path string, handlers ...context.Handler) (*Route, error)
|
||||
Head(path string, handlers ...context.Handler) *Route
|
||||
// Options registers a route for the Options http method.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Options(path string, handlers ...context.Handler) (*Route, error)
|
||||
Options(path string, handlers ...context.Handler) *Route
|
||||
// Patch registers a route for the Patch http method.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Patch(path string, handlers ...context.Handler) (*Route, error)
|
||||
Patch(path string, handlers ...context.Handler) *Route
|
||||
// Trace registers a route for the Trace http method.
|
||||
//
|
||||
// Returns the read-only route information.
|
||||
Trace(path string, handlers ...context.Handler) (*Route, error)
|
||||
Trace(path string, handlers ...context.Handler) *Route
|
||||
// Any registers a route for ALL of the http methods
|
||||
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
|
||||
Any(registeredPath string, handlers ...context.Handler) error
|
||||
Any(registeredPath string, handlers ...context.Handler) []*Route
|
||||
|
||||
// StaticHandler returns a new Handler which is ready
|
||||
// to serve all kind of static files.
|
||||
@@ -96,7 +98,7 @@ type Party interface {
|
||||
// mySubdomainFsServer.Get("/static", h)
|
||||
// ...
|
||||
//
|
||||
StaticHandler(systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler
|
||||
StaticHandler(systemPath string, showList bool, enableGzip bool) context.Handler
|
||||
|
||||
// StaticServe serves a directory as web resource
|
||||
// it's the simpliest form of the Static* functions
|
||||
@@ -107,12 +109,12 @@ type Party interface {
|
||||
// it uses gzip compression (compression on each request, no file cache).
|
||||
//
|
||||
// Returns the GET *Route.
|
||||
StaticServe(systemPath string, requestPath ...string) (*Route, error)
|
||||
StaticServe(systemPath string, requestPath ...string) *Route
|
||||
// StaticContent registers a GET and HEAD method routes to the requestPath
|
||||
// that are ready to serve raw static bytes, memory cached.
|
||||
//
|
||||
// Returns the GET *Route.
|
||||
StaticContent(requestPath string, cType string, content []byte) (*Route, error)
|
||||
StaticContent(requestPath string, cType string, content []byte) *Route
|
||||
|
||||
// StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly
|
||||
// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
|
||||
@@ -122,8 +124,8 @@ type Party interface {
|
||||
//
|
||||
// Returns the GET *Route.
|
||||
//
|
||||
// Example: https://github.com/kataras/iris/tree/master/_examples/intermediate/serve-embedded-files
|
||||
StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) (*Route, error)
|
||||
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
|
||||
StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route
|
||||
|
||||
// Favicon serves static favicon
|
||||
// accepts 2 parameters, second is optional
|
||||
@@ -136,14 +138,13 @@ type Party interface {
|
||||
// Note that you have to call it on every favicon you have to serve automatically (desktop, mobile and so on).
|
||||
//
|
||||
// Returns the GET *Route.
|
||||
Favicon(favPath string, requestPath ...string) (*Route, error)
|
||||
Favicon(favPath string, requestPath ...string) *Route
|
||||
// StaticWeb returns a handler that serves HTTP requests
|
||||
// with the contents of the file system rooted at directory.
|
||||
//
|
||||
// first parameter: the route path
|
||||
// second parameter: the system directory
|
||||
// third OPTIONAL parameter: the exception routes
|
||||
// (= give priority to these routes instead of the static handler)
|
||||
//
|
||||
// for more options look router.StaticHandler.
|
||||
//
|
||||
// router.StaticWeb("/static", "./static")
|
||||
@@ -155,7 +156,7 @@ type Party interface {
|
||||
// StaticWeb calls the StaticHandler(systemPath, listingDirectories: false, gzip: false ).
|
||||
//
|
||||
// Returns the GET *Route.
|
||||
StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error)
|
||||
StaticWeb(requestPath string, systemPath string) *Route
|
||||
|
||||
// Layout oerrides the parent template layout with a more specific layout for this Party
|
||||
// returns this Party, to continue as normal
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
@@ -10,8 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/esemplastic/unis"
|
||||
"github.com/kataras/iris/core/nettools"
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -22,18 +17,6 @@ const (
|
||||
WildcardParamStart = "*"
|
||||
)
|
||||
|
||||
// ResolveStaticPath receives a (dynamic) path and tries to return its static path part.
|
||||
func ResolveStaticPath(original string) string {
|
||||
i := strings.Index(original, ParamStart)
|
||||
v := strings.Index(original, WildcardParamStart)
|
||||
|
||||
return unis.NewChain(
|
||||
unis.NewConditional(
|
||||
unis.NewRangeEnd(i),
|
||||
unis.NewRangeEnd(v)),
|
||||
cleanPath).Process(original)
|
||||
}
|
||||
|
||||
// Param receives a parameter name prefixed with the ParamStart symbol.
|
||||
func Param(name string) string {
|
||||
return prefix(name, ParamStart)
|
||||
@@ -47,8 +30,19 @@ func WildcardParam(name string) string {
|
||||
return prefix(name, WildcardParamStart)
|
||||
}
|
||||
|
||||
func prefix(str string, prefix string) string {
|
||||
return unis.NewExclusivePrepender(prefix).Process(str)
|
||||
func prefix(s string, prefix string) string {
|
||||
if !strings.HasPrefix(s, prefix) {
|
||||
return prefix + s
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func suffix(s string, suffix string) string {
|
||||
if !strings.HasSuffix(s, suffix) {
|
||||
return s + suffix
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func joinPath(path1 string, path2 string) string {
|
||||
@@ -65,59 +59,78 @@ func joinPath(path1 string, path2 string) string {
|
||||
// that is, replace "/.." by "/" at the beginning of a path.
|
||||
//
|
||||
// The returned path ends in a slash only if it is the root "/".
|
||||
var cleanPath = unis.NewChain(
|
||||
|
||||
unis.NewSuffixRemover("/"),
|
||||
unis.NewTargetedJoiner(0, '/'),
|
||||
unis.ProcessorFunc(path.Clean),
|
||||
unis.NewReplacer(map[string]string{
|
||||
"//": "/",
|
||||
"\\": "/",
|
||||
}),
|
||||
unis.ProcessorFunc(func(s string) string {
|
||||
if s == "" || s == "." {
|
||||
return "/"
|
||||
}
|
||||
return s
|
||||
}),
|
||||
)
|
||||
|
||||
const (
|
||||
// DynamicSubdomainIndicator where a registered path starts with '*.' then it contains a dynamic subdomain, if subdomain == "*." then its dynamic
|
||||
//
|
||||
// used internally by URLPath and the router serve.
|
||||
DynamicSubdomainIndicator = "*."
|
||||
// SubdomainIndicator where './' exists in a registered path then it contains subdomain
|
||||
//
|
||||
// used on router builder
|
||||
SubdomainIndicator = "./"
|
||||
)
|
||||
|
||||
func newSubdomainDivider(sep string) unis.DividerFunc {
|
||||
// invert if indiciator not found
|
||||
// because we need the first parameter to be the subdomain
|
||||
// even if empty, but the second parameter
|
||||
// should be the path, in order to normalize it
|
||||
// (because of the reason of subdomains shouldn't be normalized as path)
|
||||
subdomainDevider := unis.NewInvertOnFailureDivider(unis.NewDivider(sep))
|
||||
return func(fullpath string) (string, string) {
|
||||
subdomain, path := subdomainDevider.Divide(fullpath)
|
||||
if len(path) > 1 {
|
||||
if path[0] == '/' && path[1] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return subdomain, path //cleanPath(path)
|
||||
func cleanPath(s string) string {
|
||||
if s == "" || s == "." {
|
||||
return "/"
|
||||
}
|
||||
|
||||
// remove suffix "/"
|
||||
if lidx := len(s) - 1; s[lidx] == '/' {
|
||||
s = s[:lidx]
|
||||
}
|
||||
|
||||
// prefix with "/"
|
||||
s = prefix(s, "/")
|
||||
|
||||
// remove the os specific dir sep
|
||||
s = strings.Replace(s, "\\", "/", -1)
|
||||
|
||||
// use std path to clean the path
|
||||
s = path.Clean(s)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// ExctractSubdomain checks if the path has subdomain and if it's
|
||||
const (
|
||||
// SubdomainWildcardIndicator where a registered path starts with '*.'.
|
||||
// if subdomain == "*." then its wildcard.
|
||||
//
|
||||
// used internally by router and api builder.
|
||||
SubdomainWildcardIndicator = "*."
|
||||
|
||||
// SubdomainWildcardPrefix where a registered path starts with "*./",
|
||||
// then this route should accept any subdomain.
|
||||
SubdomainWildcardPrefix = SubdomainWildcardIndicator + "/"
|
||||
// SubdomainPrefix where './' exists in a registered path then it contains subdomain
|
||||
//
|
||||
// used on api builder.
|
||||
SubdomainPrefix = "./" // i.e subdomain./ -> Subdomain: subdomain. Path: /
|
||||
)
|
||||
|
||||
func hasSubdomain(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// subdomain./path
|
||||
// .*/path
|
||||
//
|
||||
// remember: path always starts with "/"
|
||||
// if not start with "/" then it should be something else,
|
||||
// we don't assume anything else but subdomain.
|
||||
slashIdx := strings.IndexByte(s, '/')
|
||||
return slashIdx > 0 || s[0] == SubdomainPrefix[0] || (len(s) >= 2 && s[0:2] == SubdomainWildcardIndicator)
|
||||
}
|
||||
|
||||
// splitSubdomainAndPath checks if the path has subdomain and if it's
|
||||
// it splits the subdomain and path and returns them, otherwise it returns
|
||||
// an empty subdomain and the given path.
|
||||
// an empty subdomain and the clean path.
|
||||
//
|
||||
// First return value is the subdomain, second is the path.
|
||||
var exctractSubdomain = newSubdomainDivider(SubdomainIndicator)
|
||||
func splitSubdomainAndPath(fullUnparsedPath string) (subdomain string, path string) {
|
||||
s := fullUnparsedPath
|
||||
if s == "" || s == "/" {
|
||||
return "", "/"
|
||||
}
|
||||
|
||||
slashIdx := strings.IndexByte(s, '/')
|
||||
if slashIdx == 0 {
|
||||
// no subdomain
|
||||
return "", cleanPath(s)
|
||||
}
|
||||
|
||||
return s[0:slashIdx], cleanPath(s[slashIdx:]) // return subdomain without slash, path with slash
|
||||
}
|
||||
|
||||
// RoutePathReverserOption option signature for the RoutePathReverser.
|
||||
type RoutePathReverserOption func(*RoutePathReverser)
|
||||
@@ -142,7 +155,7 @@ func WithHost(host string) RoutePathReverserOption {
|
||||
return func(ps *RoutePathReverser) {
|
||||
ps.vhost = host
|
||||
if ps.vscheme == "" {
|
||||
ps.vscheme = nettools.ResolveScheme(host)
|
||||
ps.vscheme = netutil.ResolveSchemeFromVHost(host)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,8 +165,8 @@ func WithHost(host string) RoutePathReverserOption {
|
||||
// a scheme and a host to be used in the URL function.
|
||||
func WithServer(srv *http.Server) RoutePathReverserOption {
|
||||
return func(ps *RoutePathReverser) {
|
||||
ps.vhost = nettools.ResolveVHost(srv.Addr)
|
||||
ps.vscheme = nettools.ResolveSchemeFromServer(srv)
|
||||
ps.vhost = netutil.ResolveVHost(srv.Addr)
|
||||
ps.vscheme = netutil.ResolveSchemeFromServer(srv)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +235,7 @@ func toStringSlice(args []interface{}) []string {
|
||||
// Remove the URL for now, it complicates things for the whole framework without a specific benefits,
|
||||
// developers can just concat the subdomain, (host can be auto-retrieve by browser using the Path).
|
||||
|
||||
// URL same as Path but returns the full uri, i.e https://mysubdomain.mydomain.com/hello/kataras
|
||||
// URL same as Path but returns the full uri, i.e https://mysubdomain.mydomain.com/hello/iris
|
||||
func (ps *RoutePathReverser) URL(routeName string, paramValues ...interface{}) (url string) {
|
||||
if ps.vhost == "" || ps.vscheme == "" {
|
||||
return "not supported"
|
||||
@@ -243,7 +256,7 @@ func (ps *RoutePathReverser) URL(routeName string, paramValues ...interface{}) (
|
||||
scheme := ps.vscheme
|
||||
// if it's dynamic subdomain then the first argument is the subdomain part
|
||||
// for this part we are responsible not the custom routers
|
||||
if r.Subdomain == DynamicSubdomainIndicator {
|
||||
if r.Subdomain == SubdomainWildcardIndicator {
|
||||
subdomain := args[0]
|
||||
host = subdomain + "." + host
|
||||
args = args[1:] // remove the subdomain part for the arguments,
|
||||
|
||||
29
core/router/path_test.go
Normal file
29
core/router/path_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSplitSubdomainAndPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
original string
|
||||
subdomain string
|
||||
path string
|
||||
}{
|
||||
{"admin./users/42", "admin.", "/users/42"},
|
||||
{"//api/users\\42", "", "/api/users/42"},
|
||||
{"admin./users/\\42", "admin.", "/users/42"},
|
||||
{"*./users/\\42", "*.", "/users/42"},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
subdomain, path := splitSubdomainAndPath(tt.original)
|
||||
|
||||
if expected, got := tt.subdomain, subdomain; expected != got {
|
||||
t.Fatalf("[%d] - expected subdomain '%s' but got '%s'", i, expected, got)
|
||||
}
|
||||
if expected, got := tt.path, path; expected != got {
|
||||
t.Fatalf("[%d] - expected path '%s' but got '%s'", i, expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
@@ -61,6 +57,12 @@ func NewRoute(method, subdomain, unparsedPath string,
|
||||
return route, nil
|
||||
}
|
||||
|
||||
// String returns the form of METHOD, SUBDOMAIN, TMPL PATH
|
||||
func (r Route) String() string {
|
||||
return fmt.Sprintf("%s %s%s",
|
||||
r.Method, r.Subdomain, r.Tmpl().Src)
|
||||
}
|
||||
|
||||
// Tmpl returns the path template, i
|
||||
// it contains the parsed template
|
||||
// for the route's path.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
|
||||
@@ -57,7 +57,7 @@ func (s *SPABuilder) isAsset(reqPath string) bool {
|
||||
// It should be passed to the router's `WrapRouter`:
|
||||
// https://godoc.org/github.com/kataras/iris/core/router#Router.WrapRouter
|
||||
//
|
||||
// Example: https://github.com/kataras/iris/tree/master/_examples/beginner/file-server/single-page-application-builder
|
||||
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/single-page-application-builder
|
||||
func (s *SPABuilder) BuildWrapper(cPool *context.Pool) WrapperFunc {
|
||||
|
||||
fileServer := s.AssetHandler
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
@@ -81,7 +77,7 @@ func defaultErrorCodeHandlers() *ErrorCodeHandlers {
|
||||
func statusText(statusCode int) context.Handler {
|
||||
return func(ctx context.Context) {
|
||||
if _, err := ctx.WriteString(http.StatusText(statusCode)); err != nil {
|
||||
// ctx.Application().Log("(status code: %d) %s",
|
||||
// ctx.Application().Logger().Infof("(status code: %d) %s",
|
||||
// err.Error(), statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user