1
0
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:
kataras
2017-07-10 18:32:42 +03:00
parent 2d4c2779a7
commit 9f85b74fc9
344 changed files with 4842 additions and 5174 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -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...
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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