2017-02-16 23:09:04 +01:00
|
|
|
|
// Package iris provides efficient and well-designed tools with robust set of features to
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// create your own perfect high performance web application
|
2017-02-16 23:09:04 +01:00
|
|
|
|
// with unlimited potentials and portability.
|
2017-02-14 04:54:11 +01:00
|
|
|
|
//
|
|
|
|
|
// For middleware, template engines, response engines, sessions, websockets, mails, subdomains,
|
|
|
|
|
// dynamic subdomains, routes, party of subdomains & routes and more
|
|
|
|
|
//
|
2017-02-16 17:33:36 +01:00
|
|
|
|
// visit https://godoc.org/gopkg.in/kataras/iris.v6
|
2017-02-14 04:54:11 +01:00
|
|
|
|
package iris
|
2016-05-30 16:08:09 +02:00
|
|
|
|
|
|
|
|
|
import (
|
2017-02-17 03:46:33 +01:00
|
|
|
|
"context"
|
2017-02-16 04:00:08 +01:00
|
|
|
|
"crypto/tls"
|
2016-06-14 07:45:40 +02:00
|
|
|
|
"fmt"
|
2017-02-14 04:54:11 +01:00
|
|
|
|
"io"
|
2016-09-07 06:36:23 +02:00
|
|
|
|
"log"
|
2016-09-27 15:28:38 +02:00
|
|
|
|
"net"
|
2017-01-02 20:20:17 +01:00
|
|
|
|
"net/http"
|
2016-09-07 06:36:23 +02:00
|
|
|
|
"net/url"
|
|
|
|
|
"os"
|
2016-09-27 15:28:38 +02:00
|
|
|
|
"os/signal"
|
2017-02-28 14:01:18 +01:00
|
|
|
|
"regexp"
|
2016-09-07 06:36:23 +02:00
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
2016-10-02 02:30:37 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
"github.com/geekypanda/httpcache"
|
2016-10-02 02:30:37 +02:00
|
|
|
|
"github.com/kataras/go-errors"
|
2016-05-30 16:08:09 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2016-10-21 02:06:50 +02:00
|
|
|
|
// Version is the current version number of the Iris web framework
|
2017-02-14 04:54:11 +01:00
|
|
|
|
Version = "6.2.0"
|
2016-07-06 20:24:34 +02:00
|
|
|
|
|
2017-02-17 03:46:33 +01:00
|
|
|
|
codeName = `√Νεxτ`
|
|
|
|
|
|
2016-07-06 20:24:34 +02:00
|
|
|
|
banner = ` _____ _
|
2016-06-06 20:04:38 +02:00
|
|
|
|
|_ _| (_)
|
|
|
|
|
| | ____ _ ___
|
|
|
|
|
| | | __|| |/ __|
|
|
|
|
|
_| |_| | | |\__ \
|
2017-02-17 03:46:33 +01:00
|
|
|
|
|_____|_| |_||___/ ` + codeName
|
2016-07-06 20:24:34 +02:00
|
|
|
|
)
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Default is the field which keeps an empty `Framework`
|
|
|
|
|
// instance with its default configuration (config can change at runtime).
|
|
|
|
|
//
|
|
|
|
|
// Use that as `iris.Default.Handle(...)`
|
|
|
|
|
// or create a new, ex: `app := iris.New(); app.Handle(...)`
|
2016-07-06 20:24:34 +02:00
|
|
|
|
var (
|
2017-01-25 21:19:06 +01:00
|
|
|
|
Default *Framework
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// ResetDefault resets the `.Default`
|
|
|
|
|
// to an empty *Framework with the default configuration.
|
2017-01-14 04:17:46 +01:00
|
|
|
|
//
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Note: ResetDefault field itself can be setted
|
|
|
|
|
// to custom function too.
|
|
|
|
|
ResetDefault = func() { Default = New() }
|
2016-05-30 16:08:09 +02:00
|
|
|
|
)
|
|
|
|
|
|
2016-07-06 20:24:34 +02:00
|
|
|
|
func init() {
|
2016-10-13 16:25:01 +02:00
|
|
|
|
ResetDefault()
|
2016-07-06 20:24:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Framework is our God |\| Google.Search('Greek mythology Iris').
|
2017-01-02 20:20:17 +01:00
|
|
|
|
type Framework struct {
|
2017-02-17 01:55:26 +01:00
|
|
|
|
// Router is the Router API, REST Routing, Static files & favicon,
|
|
|
|
|
// Grouping, Custom HTTP Errors, Subdomains and more.
|
|
|
|
|
//
|
|
|
|
|
// This field is available before 'Boot' but the routes are actually registered after 'Boot'
|
|
|
|
|
// if no RouterBuilderPolicy was .Adapt(ed) by user then
|
|
|
|
|
// it throws a panic with detailed information of how-to-fix it.
|
2017-02-14 04:54:11 +01:00
|
|
|
|
*Router
|
2017-02-17 01:55:26 +01:00
|
|
|
|
|
|
|
|
|
// Config contains the configuration fields
|
|
|
|
|
// all fields defaults to something that is working, developers don't have to set it.
|
|
|
|
|
//
|
|
|
|
|
// can be setted via .New, .Set and .New(.YAML)
|
|
|
|
|
Config *Configuration
|
|
|
|
|
|
|
|
|
|
// policies contains the necessary information about the application's components.
|
|
|
|
|
// - LoggerPolicy
|
|
|
|
|
// - EventPolicy
|
|
|
|
|
// - Boot
|
|
|
|
|
// - Build
|
|
|
|
|
// - Interrupted
|
|
|
|
|
// - Recover
|
|
|
|
|
// - RouterReversionPolicy
|
|
|
|
|
// - StaticPath
|
|
|
|
|
// - WildcardPath
|
2017-02-28 14:01:18 +01:00
|
|
|
|
// - Param
|
2017-02-17 01:55:26 +01:00
|
|
|
|
// - URLPath
|
|
|
|
|
// - RouterBuilderPolicy
|
|
|
|
|
// - RouterWrapperPolicy
|
|
|
|
|
// - RenderPolicy
|
|
|
|
|
// - TemplateFuncsPolicy
|
|
|
|
|
// - SessionsPolicy
|
|
|
|
|
//
|
|
|
|
|
// These are setted by user's call to .Adapt
|
2017-02-14 04:54:11 +01:00
|
|
|
|
policies Policies
|
|
|
|
|
|
2017-02-16 04:00:08 +01:00
|
|
|
|
// TLSNextProto optionally specifies a function to take over
|
|
|
|
|
// ownership of the provided TLS connection when an NPN/ALPN
|
|
|
|
|
// protocol upgrade has occurred. The map key is the protocol
|
|
|
|
|
// name negotiated. The Handler argument should be used to
|
|
|
|
|
// handle HTTP requests and will initialize the Request's TLS
|
|
|
|
|
// and RemoteAddr if not already set. The connection is
|
|
|
|
|
// automatically closed when the function returns.
|
|
|
|
|
// If TLSNextProto is nil, HTTP/2 support is enabled automatically.
|
2017-02-17 01:55:26 +01:00
|
|
|
|
TLSNextProto map[string]func(*http.Server, *tls.Conn, http.Handler) // same as http.Server.TLSNextProto
|
|
|
|
|
|
2017-02-16 04:00:08 +01:00
|
|
|
|
// ConnState specifies an optional callback function that is
|
|
|
|
|
// called when a client connection changes state. See the
|
|
|
|
|
// ConnState type and associated constants for details.
|
2017-02-17 01:55:26 +01:00
|
|
|
|
ConnState func(net.Conn, http.ConnState) // same as http.Server.ConnState
|
2017-02-16 04:00:08 +01:00
|
|
|
|
|
2017-02-17 03:46:33 +01:00
|
|
|
|
// 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
|
|
|
|
|
// indefinitely for connections to return to idle and then shut down.
|
|
|
|
|
// If the provided context expires before the shutdown is complete,
|
|
|
|
|
// then the context's error is returned.
|
|
|
|
|
//
|
|
|
|
|
// Shutdown does not attempt to close nor wait for hijacked
|
|
|
|
|
// 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.
|
|
|
|
|
Shutdown func(context.Context) error
|
|
|
|
|
|
|
|
|
|
closedManually bool // true if closed via .Shutdown, used to not throw a panic on s.handlePanic when closing the app's server
|
2017-02-14 04:54:11 +01:00
|
|
|
|
|
2017-02-17 01:55:26 +01:00
|
|
|
|
once sync.Once // used to 'Boot' once
|
2017-01-02 20:20:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-28 04:37:53 +01:00
|
|
|
|
var defaultGlobalLoggerOuput = log.New(os.Stdout, "", log.LstdFlags)
|
2016-05-30 16:08:09 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// DevLogger returns a new Logger which prints both ProdMode and DevMode messages
|
|
|
|
|
// to the default global logger printer.
|
2016-09-09 07:09:03 +02:00
|
|
|
|
//
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Usage: app := iris.New()
|
|
|
|
|
// app.Adapt(iris.DevLogger())
|
|
|
|
|
//
|
|
|
|
|
// Users can always ignore that and adapt a custom LoggerPolicy,
|
|
|
|
|
// which will use your custom printer instead.
|
|
|
|
|
func DevLogger() LoggerPolicy {
|
2017-02-28 04:37:53 +01:00
|
|
|
|
lastLog := time.Now()
|
|
|
|
|
distanceDuration := 850 * time.Millisecond
|
2017-02-14 04:54:11 +01:00
|
|
|
|
return func(mode LogMode, logMessage string) {
|
2017-02-28 04:37:53 +01:00
|
|
|
|
if strings.Contains(logMessage, banner) {
|
|
|
|
|
fmt.Print(logMessage)
|
|
|
|
|
lastLog = time.Now()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
|
fmt.Println(logMessage)
|
|
|
|
|
lastLog = nowLog
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// begin with new line in order to have the time once at the top
|
|
|
|
|
// and the child logs below it.
|
|
|
|
|
defaultGlobalLoggerOuput.Println("\u2192\n" + logMessage)
|
|
|
|
|
lastLog = nowLog
|
2017-02-14 04:54:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-09-09 07:09:03 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// New creates and returns a fresh Iris *Framework instance
|
|
|
|
|
// with the default configuration if no 'setters' parameters passed.
|
|
|
|
|
func New(setters ...OptionSetter) *Framework {
|
2017-02-16 04:00:08 +01:00
|
|
|
|
cfg := DefaultConfiguration()
|
|
|
|
|
s := &Framework{Config: &cfg}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
// | Set the config passed from setters |
|
|
|
|
|
// | or use the default one |
|
|
|
|
|
// +------------------------------------------------------------+
|
2016-09-09 07:09:03 +02:00
|
|
|
|
s.Set(setters...)
|
|
|
|
|
{
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
// | Module Name: Logger |
|
|
|
|
|
// | On Init: If user didn't adapt a custom loggger then attach |
|
|
|
|
|
// | a new logger using log.Logger as printer with |
|
|
|
|
|
// | some default options |
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
// The logger policy is never nil and it doesn't defaults to an empty func,
|
|
|
|
|
// instead it defaults to a logger with os.Stdout as the print target which prints
|
|
|
|
|
// ONLY prodction level messages.
|
|
|
|
|
// While in ProdMode Iris logs only panics and fatal errors.
|
|
|
|
|
// You can override the default log policy with app.Adapt(iris.DevLogger())
|
|
|
|
|
// or app.Adapt(iris.LoggerPolicy(customLogger))
|
|
|
|
|
// to log both ProdMode and DevMode messages.
|
|
|
|
|
//
|
|
|
|
|
// Note:
|
|
|
|
|
// The decision to not log everything and use middleware for http requests instead of built'n
|
|
|
|
|
// is because I'm using Iris on production so I don't want many logs to my screens
|
|
|
|
|
// while server is running.
|
|
|
|
|
s.Adapt(LoggerPolicy(func(mode LogMode, logMessage string) {
|
|
|
|
|
if mode == ProdMode {
|
|
|
|
|
defaultGlobalLoggerOuput.Println(logMessage)
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
|
2017-02-28 04:37:53 +01:00
|
|
|
|
s.Adapt(EventPolicy{Boot: func(*Framework) {
|
|
|
|
|
// Print the banner first, even if we have to print errors later.
|
|
|
|
|
s.Log(DevMode, banner+"\n\n")
|
|
|
|
|
}})
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
// | |
|
|
|
|
|
// | Please take a look at the policy.go file first. |
|
|
|
|
|
// | The EventPolicy contains all the necessary information |
|
|
|
|
|
// | user should know about the framework's flow. |
|
|
|
|
|
// | |
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
// | On Boot: Set the VHost and VScheme config fields |
|
|
|
|
|
// | based on the net.Listener which (or not) |
|
|
|
|
|
// | setted on Serve and Listen funcs. |
|
|
|
|
|
// | |
|
|
|
|
|
// | It's the only pre-defined Boot event because of |
|
|
|
|
|
// | any user's custom 'Build' events should know |
|
|
|
|
|
// | the Host of the server. |
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
s.Adapt(EventPolicy{Boot: func(s *Framework) {
|
|
|
|
|
// set the host and scheme
|
|
|
|
|
if s.Config.VHost == "" { // if not setted by Listen functions
|
2017-02-17 05:49:54 +01:00
|
|
|
|
s.Config.VHost = DefaultServerAddr
|
2017-02-14 04:54:11 +01:00
|
|
|
|
}
|
2017-02-17 05:49:54 +01:00
|
|
|
|
// if user didn't specified a scheme then get it from the VHost,
|
|
|
|
|
// which is already setted at before statements
|
2017-02-14 04:54:11 +01:00
|
|
|
|
if s.Config.VScheme == "" {
|
2017-02-17 05:49:54 +01:00
|
|
|
|
// if :443 or :https then returns https:// otherwise http://
|
2017-02-14 04:54:11 +01:00
|
|
|
|
s.Config.VScheme = ParseScheme(s.Config.VHost)
|
|
|
|
|
}
|
2017-02-17 05:49:54 +01:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
}})
|
2016-09-09 07:09:03 +02:00
|
|
|
|
|
|
|
|
|
{
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
// | Module Name: Renderer |
|
|
|
|
|
// | On Init: set templates and serializers |
|
|
|
|
|
// | and adapt the RenderPolicy for both |
|
|
|
|
|
// | templates and content-type specific renderer (serializer) |
|
|
|
|
|
// | On Build: build the serializers and templates |
|
|
|
|
|
// | based on the user's calls |
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
// | Module Name: Rich Content-Type Renderer |
|
2017-02-16 02:26:02 +01:00
|
|
|
|
// | On Init: Attach a new empty content-type serializers. |
|
|
|
|
|
// | Adapt one RenderPolicy which is responsible |
|
|
|
|
|
// | for json,jsonp,xml and markdown rendering |
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// +------------------------------------------------------------+
|
2017-03-01 14:04:42 +01:00
|
|
|
|
s.Adapt(restRenderPolicy)
|
2017-02-14 04:54:11 +01:00
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
// | Module Name: Template engine's funcs |
|
2017-02-16 02:26:02 +01:00
|
|
|
|
// | On Init: Adapt the reverse routing tmpl funcs |
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// | for any template engine that will be registered |
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
s.Adapt(TemplateFuncsPolicy{
|
|
|
|
|
"url": s.URL,
|
|
|
|
|
"urlpath": s.policies.RouterReversionPolicy.URLPath,
|
2017-02-16 02:26:02 +01:00
|
|
|
|
}) // the entire template registration logic lives inside the ./adaptors/view now.
|
2017-02-14 04:54:11 +01:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-09 07:09:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
{
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
// | Module Name: Router |
|
|
|
|
|
// | On Init: Attach a new router, pass a new repository, |
|
|
|
|
|
// | an empty error handlers list, the context pool binded |
|
|
|
|
|
// | to the Framework and the root path "/" |
|
|
|
|
|
// | On Build: Use the policies to build the router's handler |
|
|
|
|
|
// | based on its route repository |
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
s.Router = &Router{
|
|
|
|
|
repository: new(routeRepository),
|
|
|
|
|
Errors: &ErrorHandlers{
|
|
|
|
|
handlers: make(map[int]Handler, 0),
|
|
|
|
|
},
|
|
|
|
|
Context: &contextPool{
|
|
|
|
|
sync.Pool{New: func() interface{} { return &Context{framework: s} }},
|
|
|
|
|
},
|
|
|
|
|
relativePath: "/",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.Adapt(EventPolicy{Build: func(*Framework) {
|
2017-02-17 01:55:26 +01:00
|
|
|
|
// Author's notes:
|
|
|
|
|
// Proxy for example has 0 routes registered but still uses the RouterBuilderPolicy
|
|
|
|
|
// so we can't check only for it, we can check if it's nil and it has more than one registered
|
|
|
|
|
// routes, then panic, if has no registered routes the user don't want to get errors about the router.
|
|
|
|
|
|
|
|
|
|
// first check if it's not setted already by any Boot event.
|
|
|
|
|
if s.Router.handler == nil {
|
|
|
|
|
hasRoutes := s.Router.repository.Len() > 0
|
|
|
|
|
routerBuilder := s.policies.RouterBuilderPolicy
|
|
|
|
|
// and most importantly, check if the user has provided a router adaptor
|
|
|
|
|
// at the same time has registered at least one route,
|
|
|
|
|
// if not then it should panic here, iris can't run without a router attached to it
|
|
|
|
|
// and default router not any more, user should select one from ./adaptors or
|
|
|
|
|
// any other third-party adaptor may done by community.
|
|
|
|
|
// I was coding the new iris version for more than 20 days(~200+ hours of code)
|
|
|
|
|
// and I hope that once per application the addition of +1 line users have to put,
|
|
|
|
|
// is not a big deal.
|
|
|
|
|
if hasRoutes {
|
|
|
|
|
if routerBuilder == nil {
|
2017-02-14 19:32:36 +01:00
|
|
|
|
// this is important panic and app can't continue as we said.
|
|
|
|
|
s.handlePanic(errRouterIsMissing.Format(s.Config.VHost))
|
|
|
|
|
// don't trace anything else,
|
|
|
|
|
// the detailed errRouterIsMissing message will tell the user what to do to fix that.
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
2017-02-17 01:55:26 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if routerBuilder != nil {
|
2017-02-14 19:32:36 +01:00
|
|
|
|
// buid the router using user's selection build policy
|
2017-02-17 01:55:26 +01:00
|
|
|
|
s.Router.build(routerBuilder)
|
2017-02-18 06:03:37 +01:00
|
|
|
|
|
|
|
|
|
s.Router.repository.OnMethodChanged(func(route RouteInfo, oldMethod string) {
|
|
|
|
|
// set service not available temporarily until the router completes the building
|
|
|
|
|
// this won't take more than 100ms, but we want to inform the user.
|
|
|
|
|
s.Router.handler = ToNativeHandler(s, HandlerFunc(func(ctx *Context) {
|
|
|
|
|
ctx.EmitError(StatusServiceUnavailable)
|
|
|
|
|
}))
|
|
|
|
|
// Re-build the whole router if state changed (from offline to online state mostly)
|
|
|
|
|
s.Router.build(routerBuilder)
|
|
|
|
|
})
|
2017-02-14 04:54:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}})
|
2016-10-02 02:30:37 +02:00
|
|
|
|
|
2016-07-06 20:24:34 +02:00
|
|
|
|
}
|
2016-05-30 16:08:09 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
{
|
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
// | Module Name: System |
|
2017-02-16 02:26:02 +01:00
|
|
|
|
// | On Build: Check for updates on Build, async |
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
// On Build: local repository updates
|
|
|
|
|
s.Adapt(EventPolicy{Build: func(*Framework) {
|
2017-02-16 02:26:02 +01:00
|
|
|
|
if s.Config.CheckForUpdates {
|
2017-03-01 18:17:32 +01:00
|
|
|
|
go CheckForUpdates(false)
|
2017-02-14 04:54:11 +01:00
|
|
|
|
}
|
|
|
|
|
}})
|
|
|
|
|
}
|
2016-06-14 19:29:01 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
return s
|
2016-09-27 15:28:38 +02:00
|
|
|
|
}
|
2016-08-17 11:57:54 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Set sets an option, configuration field to its Config
|
2016-09-27 15:28:38 +02:00
|
|
|
|
func (s *Framework) Set(setters ...OptionSetter) {
|
|
|
|
|
for _, setter := range setters {
|
|
|
|
|
setter.Set(s.Config)
|
2016-09-16 20:16:48 +02:00
|
|
|
|
}
|
2016-07-06 20:24:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Log logs to the defined logger policy.
|
|
|
|
|
//
|
2017-02-28 04:37:53 +01:00
|
|
|
|
// The default outputs to the os.Stdout when EnvMode is 'iris.ProdMode'
|
2017-02-14 04:54:11 +01:00
|
|
|
|
func (s *Framework) Log(mode LogMode, log string) {
|
|
|
|
|
s.policies.LoggerPolicy(mode, log)
|
2016-09-18 06:21:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-28 04:37:53 +01:00
|
|
|
|
// Author's note:
|
|
|
|
|
//
|
|
|
|
|
// I implemented this and worked for configuration fields logs but I'm not sure
|
|
|
|
|
// if users want to print adaptor's configuration, i.e: websocket, sessions
|
|
|
|
|
// so comment this feature, and
|
|
|
|
|
// if/when I get good feedback from the private beta testers I'll uncomment these on public repo too.
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
// Logf same as .Log but receives arguments like fmt.Printf.
|
|
|
|
|
// Note: '%#v' and '%+v' named arguments are being printed, pretty.
|
|
|
|
|
//
|
|
|
|
|
// The default outputs to the os.Stdout when EnvMode is 'iris.ProdMode'
|
|
|
|
|
// func (s *Framework) Logf(mode LogMode, format string, v ...interface{}) {
|
|
|
|
|
// if mode == DevMode {
|
|
|
|
|
// marker := 0
|
|
|
|
|
// for i := 0; i < len(format); i++ {
|
|
|
|
|
// if format[i] == '%' {
|
|
|
|
|
// if i < len(format)-2 {
|
|
|
|
|
// if part := format[i : i+3]; part == "%#v" || part == "%+v" {
|
|
|
|
|
// if len(v) > marker {
|
|
|
|
|
// spew.Config.Indent = "\t\t\t"
|
|
|
|
|
// spew.Config.DisablePointerAddresses = true
|
|
|
|
|
// spew.Config.DisableCapacities = true
|
|
|
|
|
// spew.Config.DisablePointerMethods = true
|
|
|
|
|
// spew.Config.DisableMethods = true
|
|
|
|
|
// arg := v[marker]
|
|
|
|
|
// format = strings.Replace(format, part, "%s", 1)
|
|
|
|
|
// v[marker] = spew.Sdump(arg)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// marker++
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// log := fmt.Sprintf(format, v...)
|
|
|
|
|
//
|
|
|
|
|
// s.policies.LoggerPolicy(mode, log)
|
|
|
|
|
// }
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Must checks if the error is not nil, if it isn't
|
|
|
|
|
// panics on registered iris' logger or
|
|
|
|
|
// to a recovery event handler, otherwise does nothing.
|
2016-09-27 15:28:38 +02:00
|
|
|
|
func (s *Framework) Must(err error) {
|
|
|
|
|
if err != nil {
|
2017-02-14 04:54:11 +01:00
|
|
|
|
s.handlePanic(err)
|
2016-09-27 15:28:38 +02:00
|
|
|
|
}
|
2016-07-06 20:24:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
func (s *Framework) handlePanic(err error) {
|
2017-02-17 03:46:33 +01:00
|
|
|
|
// if x, ok := err.(*net.OpError); ok && x.Op == "accept" {
|
|
|
|
|
// return
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
if err.Error() == http.ErrServerClosed.Error() && s.closedManually {
|
|
|
|
|
//.Shutdown was called, log to dev not in prod (prod is only for critical errors.)
|
|
|
|
|
// also do not try to recover from this error, remember, Shutdown was called manually here.
|
|
|
|
|
s.Log(DevMode, "HTTP Server closed manually")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
if recoveryHandler := s.policies.EventPolicy.Recover; recoveryHandler != nil {
|
|
|
|
|
recoveryHandler(s, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// if not a registered recovery event handler found
|
|
|
|
|
// then call the logger's Panic.
|
|
|
|
|
s.Log(ProdMode, err.Error())
|
2016-09-18 05:55:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Boot runs only once, automatically
|
|
|
|
|
// when 'Serve/Listen/ListenTLS/ListenUNIX/ListenLETSENCRYPT' called.
|
|
|
|
|
// It's exported because you may want to build the router
|
|
|
|
|
// and its components but not run the server.
|
|
|
|
|
//
|
|
|
|
|
// See ./httptest/httptest.go to understand its usage.
|
|
|
|
|
func (s *Framework) Boot() (firstTime bool) {
|
2016-09-27 15:28:38 +02:00
|
|
|
|
s.once.Do(func() {
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// here execute the boot events, before build events, if exists, here is
|
|
|
|
|
// where the user can make an event module to adapt custom routers and other things
|
|
|
|
|
// fire the before build event
|
|
|
|
|
s.policies.EventPolicy.Fire(s.policies.EventPolicy.Boot, s)
|
2016-07-06 20:24:34 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// here execute the build events if exists
|
|
|
|
|
// right before the Listen, all methods have been setted
|
|
|
|
|
// usually is used to adapt third-party servers or proxies or load balancer(s)
|
|
|
|
|
s.policies.EventPolicy.Fire(s.policies.EventPolicy.Build, s)
|
|
|
|
|
firstTime = true
|
2016-09-27 15:28:38 +02:00
|
|
|
|
})
|
2017-02-14 04:54:11 +01:00
|
|
|
|
return
|
2016-09-16 20:16:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-17 05:49:54 +01:00
|
|
|
|
func (s *Framework) setupServe() (srv *http.Server, deferFn func()) {
|
2017-02-17 03:57:51 +01:00
|
|
|
|
s.closedManually = false
|
2017-02-17 05:49:54 +01:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
s.Boot()
|
2016-09-16 20:16:48 +02:00
|
|
|
|
|
2017-02-17 05:49:54 +01:00
|
|
|
|
deferFn = func() {
|
|
|
|
|
// post any panics to the user defined logger.
|
2017-02-14 04:54:11 +01:00
|
|
|
|
if rerr := recover(); rerr != nil {
|
|
|
|
|
if err, ok := rerr.(error); ok {
|
|
|
|
|
s.handlePanic(err)
|
|
|
|
|
}
|
2016-09-16 20:16:48 +02:00
|
|
|
|
}
|
2017-02-17 05:49:54 +01:00
|
|
|
|
}
|
2017-01-11 15:23:38 +01:00
|
|
|
|
|
2017-02-17 05:49:54 +01:00
|
|
|
|
srv = &http.Server{
|
2017-02-14 04:54:11 +01:00
|
|
|
|
ReadTimeout: s.Config.ReadTimeout,
|
|
|
|
|
WriteTimeout: s.Config.WriteTimeout,
|
|
|
|
|
MaxHeaderBytes: s.Config.MaxHeaderBytes,
|
2017-02-16 04:00:08 +01:00
|
|
|
|
TLSNextProto: s.TLSNextProto,
|
|
|
|
|
ConnState: s.ConnState,
|
2017-02-14 04:54:11 +01:00
|
|
|
|
Addr: s.Config.VHost,
|
|
|
|
|
ErrorLog: s.policies.LoggerPolicy.ToLogger(log.LstdFlags),
|
|
|
|
|
Handler: s.Router,
|
|
|
|
|
}
|
2017-02-17 05:49:54 +01:00
|
|
|
|
|
2017-02-17 03:46:33 +01:00
|
|
|
|
// Set the grace shutdown, it's just a func no need to make things complicated
|
|
|
|
|
// all are managed by net/http now.
|
|
|
|
|
s.Shutdown = func(ctx context.Context) error {
|
|
|
|
|
// order matters, look s.handlePanic
|
|
|
|
|
s.closedManually = true
|
|
|
|
|
err := srv.Shutdown(ctx)
|
|
|
|
|
return err
|
|
|
|
|
}
|
2017-01-11 15:23:38 +01:00
|
|
|
|
|
2017-02-17 05:49:54 +01:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Serve serves incoming connections from the given listener.
|
|
|
|
|
//
|
|
|
|
|
// Serve blocks until the given listener returns permanent error.
|
|
|
|
|
func (s *Framework) Serve(ln net.Listener) error {
|
|
|
|
|
if ln == nil {
|
|
|
|
|
return errors.New("nil net.Listener on Serve")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if user called .Serve and doesn't uses any nginx-like balancers.
|
|
|
|
|
if s.Config.VHost == "" {
|
|
|
|
|
s.Config.VHost = ParseHost(ln.Addr().String())
|
|
|
|
|
} // Scheme will be checked from Boot state.
|
|
|
|
|
|
|
|
|
|
srv, fn := s.setupServe()
|
|
|
|
|
defer fn()
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// print the banner and wait for system channel interrupt
|
|
|
|
|
go s.postServe()
|
2017-02-17 05:49:54 +01:00
|
|
|
|
return srv.Serve(ln)
|
2017-01-11 15:23:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Framework) postServe() {
|
2017-02-28 04:37:53 +01:00
|
|
|
|
routerInfo := ""
|
|
|
|
|
if routerNameVal := s.Config.Other[RouterNameConfigKey]; routerNameVal != nil {
|
|
|
|
|
if routerName, ok := routerNameVal.(string); ok {
|
|
|
|
|
routerInfo = "using " + routerName
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bannerMessage := fmt.Sprintf("Serving HTTP on %s port %d %s", ParseHostname(s.Config.VHost), ParsePort(s.Config.VHost), routerInfo)
|
|
|
|
|
|
2017-02-17 03:46:33 +01:00
|
|
|
|
s.Log(DevMode, bannerMessage)
|
2016-09-16 20:16:48 +02:00
|
|
|
|
|
2016-10-04 00:18:17 +02:00
|
|
|
|
ch := make(chan os.Signal, 1)
|
|
|
|
|
signal.Notify(ch, os.Interrupt)
|
2016-09-27 15:28:38 +02:00
|
|
|
|
<-ch
|
2017-01-11 17:01:29 +01:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// fire any custom interrupted events and at the end close and exit
|
|
|
|
|
// if the custom event blocks then it decides what to do next.
|
|
|
|
|
s.policies.Fire(s.policies.Interrupted, s)
|
2017-02-17 03:46:33 +01:00
|
|
|
|
|
|
|
|
|
s.Shutdown(context.Background())
|
2017-02-14 04:54:11 +01:00
|
|
|
|
os.Exit(1)
|
2016-06-14 07:45:40 +02:00
|
|
|
|
}
|
2016-05-30 16:08:09 +02:00
|
|
|
|
|
2016-06-14 07:45:40 +02:00
|
|
|
|
// Listen starts the standalone http server
|
|
|
|
|
// which listens to the addr parameter which as the form of
|
|
|
|
|
// host:port
|
|
|
|
|
//
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// If you need to manually monitor any error please use `.Serve` instead.
|
2016-06-14 07:45:40 +02:00
|
|
|
|
func (s *Framework) Listen(addr string) {
|
2016-09-27 15:28:38 +02:00
|
|
|
|
addr = ParseHost(addr)
|
2017-02-17 05:49:54 +01:00
|
|
|
|
|
|
|
|
|
// if .Listen called normally and VHost is not setted,
|
|
|
|
|
// so it's Host is the Real listening addr and user-given
|
2016-09-27 15:28:38 +02:00
|
|
|
|
if s.Config.VHost == "" {
|
2017-02-17 05:49:54 +01:00
|
|
|
|
s.Config.VHost = addr // as it is
|
2016-09-27 15:28:38 +02:00
|
|
|
|
// this will be set as the front-end listening addr
|
2017-02-17 05:49:54 +01:00
|
|
|
|
} // VScheme will be checked on Boot.
|
2017-01-11 23:57:07 +01:00
|
|
|
|
|
2017-02-17 05:49:54 +01:00
|
|
|
|
// this check, only here, other Listen functions should throw an error if port is missing.
|
2017-01-11 23:57:07 +01:00
|
|
|
|
if portIdx := strings.IndexByte(addr, ':'); portIdx < 0 {
|
|
|
|
|
// missing port part, add it
|
|
|
|
|
addr = addr + ":80"
|
|
|
|
|
}
|
2016-09-27 15:28:38 +02:00
|
|
|
|
|
2017-01-04 14:16:53 +01:00
|
|
|
|
ln, err := TCPKeepAlive(addr)
|
2016-09-27 15:28:38 +02:00
|
|
|
|
if err != nil {
|
2017-02-14 04:54:11 +01:00
|
|
|
|
s.handlePanic(err)
|
2016-09-27 15:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.Must(s.Serve(ln))
|
2016-06-14 07:45:40 +02:00
|
|
|
|
}
|
2016-05-30 16:08:09 +02:00
|
|
|
|
|
2016-06-14 07:45:40 +02:00
|
|
|
|
// ListenTLS Starts a https server with certificates,
|
|
|
|
|
// if you use this method the requests of the form of 'http://' will fail
|
|
|
|
|
// only https:// connections are allowed
|
|
|
|
|
// which listens to the addr parameter which as the form of
|
|
|
|
|
// host:port
|
|
|
|
|
//
|
|
|
|
|
//
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// If you need to manually monitor any error please use `.Serve` instead.
|
2016-06-14 07:45:40 +02:00
|
|
|
|
func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
|
2016-09-27 15:28:38 +02:00
|
|
|
|
addr = ParseHost(addr)
|
|
|
|
|
|
2017-02-17 05:49:54 +01:00
|
|
|
|
{
|
|
|
|
|
// set it before Boot, be-careful VHost and VScheme are used by nginx users too
|
|
|
|
|
// we don't want to alt them.
|
|
|
|
|
if s.Config.VHost == "" {
|
|
|
|
|
s.Config.VHost = addr
|
|
|
|
|
// this will be set as the front-end listening addr
|
|
|
|
|
}
|
|
|
|
|
if s.Config.VScheme == "" {
|
|
|
|
|
s.Config.VScheme = SchemeHTTPS
|
|
|
|
|
}
|
2016-07-07 02:36:48 +02:00
|
|
|
|
}
|
2017-02-17 05:49:54 +01:00
|
|
|
|
|
|
|
|
|
srv, fn := s.setupServe()
|
|
|
|
|
// We are doing the same parts as .Serve does but instead we run srv.ListenAndServeTLS
|
|
|
|
|
// because of un-exported net/http.server.go:setupHTTP2_ListenAndServeTLS function which
|
|
|
|
|
// broke our previous flow but no problem :)
|
|
|
|
|
defer fn()
|
|
|
|
|
// print the banner and wait for system channel interrupt
|
|
|
|
|
go s.postServe()
|
|
|
|
|
s.Must(srv.ListenAndServeTLS(certFile, keyFile))
|
2016-06-14 07:45:40 +02:00
|
|
|
|
}
|
2016-06-06 12:25:09 +02:00
|
|
|
|
|
2016-09-27 15:28:38 +02:00
|
|
|
|
// ListenLETSENCRYPT starts a server listening at the specific nat address
|
|
|
|
|
// using key & certification taken from the letsencrypt.org 's servers
|
|
|
|
|
// it's also starts a second 'http' server to redirect all 'http://$ADDR_HOSTNAME:80' to the' https://$ADDR'
|
2016-10-28 20:21:57 +02:00
|
|
|
|
// it creates a cache file to store the certifications, for performance reasons, this file by-default is "./letsencrypt.cache"
|
|
|
|
|
// if you skip the second parameter then the cache file is "./letsencrypt.cache"
|
2016-10-28 20:55:00 +02:00
|
|
|
|
// if you want to disable cache then simple pass as second argument an empty empty string ""
|
2016-10-28 20:21:57 +02:00
|
|
|
|
//
|
2017-02-17 05:49:54 +01:00
|
|
|
|
// Note: HTTP/2 Push is not working with LETSENCRYPT, you have to use ListenTLS to enable HTTP/2
|
|
|
|
|
// Because net/http's author didn't exported the functions to tell the server that is using HTTP/2...
|
2016-10-28 20:21:57 +02:00
|
|
|
|
//
|
2017-02-17 05:49:54 +01:00
|
|
|
|
// example: https://github.com/iris-contrib/examples/blob/master/letsencrypt/main.go
|
2016-10-28 20:21:57 +02:00
|
|
|
|
func (s *Framework) ListenLETSENCRYPT(addr string, cacheFileOptional ...string) {
|
2016-09-27 15:28:38 +02:00
|
|
|
|
addr = ParseHost(addr)
|
2017-02-17 05:49:54 +01:00
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// set it before Boot, be-careful VHost and VScheme are used by nginx users too
|
|
|
|
|
// we don't want to alt them.
|
|
|
|
|
if s.Config.VHost == "" {
|
|
|
|
|
s.Config.VHost = addr
|
|
|
|
|
// this will be set as the front-end listening addr
|
|
|
|
|
}
|
|
|
|
|
if s.Config.VScheme == "" {
|
|
|
|
|
s.Config.VScheme = SchemeHTTPS
|
|
|
|
|
}
|
2016-09-27 15:28:38 +02:00
|
|
|
|
}
|
2017-02-17 05:49:54 +01:00
|
|
|
|
|
2016-10-28 20:21:57 +02:00
|
|
|
|
ln, err := LETSENCRYPT(addr, cacheFileOptional...)
|
2016-09-27 15:28:38 +02:00
|
|
|
|
if err != nil {
|
2017-02-14 04:54:11 +01:00
|
|
|
|
s.handlePanic(err)
|
2016-07-29 00:33:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-04 14:16:53 +01:00
|
|
|
|
// starts a second server which listening on HOST:80 to redirect all requests to the HTTPS://HOST:PORT
|
|
|
|
|
Proxy(ParseHostname(addr)+":80", "https://"+addr)
|
2016-09-27 15:28:38 +02:00
|
|
|
|
s.Must(s.Serve(ln))
|
2016-07-29 00:33:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-14 07:45:40 +02:00
|
|
|
|
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
|
2016-07-07 02:36:48 +02:00
|
|
|
|
//
|
2016-06-30 04:58:04 +02:00
|
|
|
|
//
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// If you need to manually monitor any error please use `.Serve` instead.
|
2016-07-07 02:36:48 +02:00
|
|
|
|
func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
|
2016-09-27 15:28:38 +02:00
|
|
|
|
// *on unix listen we don't parse the host, because sometimes it causes problems to the user
|
|
|
|
|
if s.Config.VHost == "" {
|
|
|
|
|
s.Config.VHost = addr
|
|
|
|
|
// this will be set as the front-end listening addr
|
|
|
|
|
}
|
|
|
|
|
ln, err := UNIX(addr, mode)
|
|
|
|
|
if err != nil {
|
2017-02-14 04:54:11 +01:00
|
|
|
|
s.handlePanic(err)
|
2016-09-27 15:28:38 +02:00
|
|
|
|
}
|
2016-07-05 14:29:32 +02:00
|
|
|
|
|
2016-09-27 15:28:38 +02:00
|
|
|
|
s.Must(s.Serve(ln))
|
2016-07-05 14:29:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
func (s *Framework) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
s.Router.ServeHTTP(w, r)
|
2016-09-27 15:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Adapt adapds a policy to the Framework.
|
|
|
|
|
// It accepts single or more objects that implements the iris.Policy.
|
|
|
|
|
// Iris provides some of them but you can build your own based on one or more of these:
|
|
|
|
|
// - iris.EventPolicy
|
|
|
|
|
// - iris.RouterReversionPolicy
|
|
|
|
|
// - iris.RouterBuilderPolicy
|
|
|
|
|
// - iris.RouterWrapperPolicy
|
|
|
|
|
// - iris.TemplateRenderPolicy
|
|
|
|
|
// - iris.TemplateFuncsPolicy
|
:rainbow: sessions were re-written, update to 4.0.0-alpha.2, read HISTORY.md
**Sessions were re-written **
- Developers can use more than one 'session database', at the same time,
to store the sessions
- Easy to develop a custom session database (only two functions are
required (Load & Update)), [learn
more](https://github.com/iris-contrib/sessiondb/blob/master/redis/database.go)
- Session databases are located
[here](https://github.com/iris-contrib/sessiondb), contributions are
welcome
- The only frontend deleted 'thing' is the: **config.Sessions.Provider**
- No need to register a database, the sessions works out-of-the-box
- No frontend/API changes except the
`context.Session().Set/Delete/Clear`, they doesn't return errors
anymore, btw they (errors) were always nil :)
- Examples (master branch) were updated.
```sh
$ go get github.com/iris-contrib/sessiondb/$DATABASE
```
```go
db := $DATABASE.New(configurationHere{})
iris.UseSessionDB(db)
```
> Note: Book is not updated yet, examples are up-to-date as always.
2016-07-15 19:50:36 +02:00
|
|
|
|
//
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// With a Policy you can change the behavior of almost each of the existing Iris' features.
|
|
|
|
|
// See policy.go for more.
|
|
|
|
|
func (s *Framework) Adapt(policies ...Policy) {
|
|
|
|
|
for i := range policies {
|
|
|
|
|
policies[i].Adapt(&s.policies)
|
|
|
|
|
}
|
:rainbow: sessions were re-written, update to 4.0.0-alpha.2, read HISTORY.md
**Sessions were re-written **
- Developers can use more than one 'session database', at the same time,
to store the sessions
- Easy to develop a custom session database (only two functions are
required (Load & Update)), [learn
more](https://github.com/iris-contrib/sessiondb/blob/master/redis/database.go)
- Session databases are located
[here](https://github.com/iris-contrib/sessiondb), contributions are
welcome
- The only frontend deleted 'thing' is the: **config.Sessions.Provider**
- No need to register a database, the sessions works out-of-the-box
- No frontend/API changes except the
`context.Session().Set/Delete/Clear`, they doesn't return errors
anymore, btw they (errors) were always nil :)
- Examples (master branch) were updated.
```sh
$ go get github.com/iris-contrib/sessiondb/$DATABASE
```
```go
db := $DATABASE.New(configurationHere{})
iris.UseSessionDB(db)
```
> Note: Book is not updated yet, examples are up-to-date as always.
2016-07-15 19:50:36 +02:00
|
|
|
|
}
|
2016-07-13 05:28:09 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// cachedMuxEntry is just a wrapper for the Cache functionality
|
|
|
|
|
// it seems useless but I prefer to keep the cached handler on its own memory stack,
|
|
|
|
|
// reason: no clojures hell in the Cache function
|
|
|
|
|
type cachedMuxEntry struct {
|
|
|
|
|
cachedHandler http.Handler
|
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
func newCachedMuxEntry(s *Framework, bodyHandler HandlerFunc, expiration time.Duration) *cachedMuxEntry {
|
|
|
|
|
httpHandler := ToNativeHandler(s, bodyHandler)
|
2016-09-29 16:05:22 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
cachedHandler := httpcache.Cache(httpHandler, expiration)
|
|
|
|
|
return &cachedMuxEntry{
|
|
|
|
|
cachedHandler: cachedHandler,
|
|
|
|
|
}
|
2016-09-29 16:05:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
func (c *cachedMuxEntry) Serve(ctx *Context) {
|
|
|
|
|
c.cachedHandler.ServeHTTP(ctx.ResponseWriter, ctx.Request)
|
2017-01-10 13:21:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Cache is just a wrapper for a route's handler which you want to enable body caching
|
|
|
|
|
// Usage: iris.Default.Get("/", iris.Cache(func(ctx *iris.Context){
|
|
|
|
|
// ctx.WriteString("Hello, world!") // or a template or anything else
|
|
|
|
|
// }, time.Duration(10*time.Second))) // duration of expiration
|
|
|
|
|
// if <=time.Second then it tries to find it though request header's "cache-control" maxage value
|
2017-01-10 13:21:49 +01:00
|
|
|
|
//
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Note that it depends on a station instance's cache service.
|
|
|
|
|
// Do not try to call it from default' station if you use the form of app := iris.New(),
|
|
|
|
|
// use the app.Cache instead of iris.Cache
|
|
|
|
|
func (s *Framework) Cache(bodyHandler HandlerFunc, expiration time.Duration) HandlerFunc {
|
|
|
|
|
ce := newCachedMuxEntry(s, bodyHandler, expiration)
|
|
|
|
|
return ce.Serve
|
2016-07-13 05:28:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// Path used to check arguments with the route's named parameters and return the correct url
|
2017-02-18 07:18:09 +01:00
|
|
|
|
// if parse failed returns empty string.
|
|
|
|
|
// Used for reverse routing, depends on router adaptor.
|
|
|
|
|
//
|
|
|
|
|
// Examples:
|
|
|
|
|
// https://github.com/kataras/iris/tree/v6/adaptors/view/_examples/template_html_3 (gorillamux)
|
|
|
|
|
// https://github.com/kataras/iris/tree/v6/adaptors/view/_examples/template_html_4 (httprouter)
|
2017-02-14 04:54:11 +01:00
|
|
|
|
func (s *Framework) Path(routeName string, args ...interface{}) string {
|
|
|
|
|
r := s.Router.Routes().Lookup(routeName)
|
|
|
|
|
if r == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
2016-07-13 05:28:09 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// why receive interface{}
|
|
|
|
|
// but need string?
|
|
|
|
|
// because the key:value are string for a route path
|
|
|
|
|
// but in the template functions all works fine with ...string
|
|
|
|
|
// except when the developer wants to pass that string from a binding
|
|
|
|
|
// via Render, then the template will fail to render
|
|
|
|
|
// because of expecting string; but got []string
|
2016-06-17 06:18:09 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
var argsString []string
|
|
|
|
|
if len(args) > 0 {
|
|
|
|
|
argsString = make([]string, len(args))
|
|
|
|
|
}
|
2016-06-17 06:18:09 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
for i, v := range args {
|
|
|
|
|
if s, ok := v.(string); ok {
|
|
|
|
|
argsString[i] = s
|
|
|
|
|
} else if num, ok := v.(int); ok {
|
|
|
|
|
argsString[i] = strconv.Itoa(num)
|
|
|
|
|
} else if b, ok := v.(bool); ok {
|
|
|
|
|
argsString[i] = strconv.FormatBool(b)
|
|
|
|
|
} else if arr, ok := v.([]string); ok {
|
|
|
|
|
if len(arr) > 0 {
|
|
|
|
|
argsString[i] = arr[0]
|
|
|
|
|
argsString = append(argsString, arr[1:]...)
|
|
|
|
|
}
|
2017-01-04 20:29:58 +01:00
|
|
|
|
}
|
2016-06-17 06:18:09 +02:00
|
|
|
|
}
|
2017-01-04 20:29:58 +01:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
return s.policies.RouterReversionPolicy.URLPath(r, argsString...)
|
2016-06-14 07:45:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-18 07:18:09 +01:00
|
|
|
|
// URL returns the subdomain + host + Path(...optional named parameters if route is dynamic)
|
|
|
|
|
// returns an empty string if parse is failed.
|
|
|
|
|
// Used for reverse routing, depends on router adaptor.
|
2016-07-21 19:33:00 +02:00
|
|
|
|
//
|
2017-02-18 07:18:09 +01:00
|
|
|
|
// Examples:
|
|
|
|
|
// https://github.com/kataras/iris/tree/v6/adaptors/view/_examples/template_html_3 (gorillamux)
|
|
|
|
|
// https://github.com/kataras/iris/tree/v6/adaptors/view/_examples/template_html_4 (httprouter)
|
2016-06-14 07:45:40 +02:00
|
|
|
|
func (s *Framework) URL(routeName string, args ...interface{}) (url string) {
|
2017-02-14 04:54:11 +01:00
|
|
|
|
r := s.Router.Routes().Lookup(routeName)
|
2016-06-14 07:45:40 +02:00
|
|
|
|
if r == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-27 15:28:38 +02:00
|
|
|
|
scheme := s.Config.VScheme // if s.Config.VScheme was setted, that will be used instead of the real, in order to make easy to run behind nginx
|
|
|
|
|
host := s.Config.VHost // if s.Config.VHost was setted, that will be used instead of the real, in order to make easy to run behind nginx
|
2016-05-30 16:08:09 +02:00
|
|
|
|
|
2016-06-14 07:45:40 +02:00
|
|
|
|
// if it's dynamic subdomain then the first argument is the subdomain part
|
2017-02-14 04:54:11 +01:00
|
|
|
|
// for this part we are responsible not the custom routers
|
|
|
|
|
if r.Subdomain() == DynamicSubdomainIndicator {
|
|
|
|
|
if len(args) == 0 { // it's a wildcard subdomain but not arguments
|
2016-06-14 07:45:40 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
2016-05-30 16:08:09 +02:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
if subdomain, ok := args[0].(string); ok {
|
2016-06-14 07:45:40 +02:00
|
|
|
|
host = subdomain + "." + host
|
|
|
|
|
} else {
|
|
|
|
|
// it is not array because we join them before. if not pass a string then this is not a subdomain part, return empty uri
|
|
|
|
|
return
|
2016-05-30 16:08:09 +02:00
|
|
|
|
}
|
2017-02-14 04:54:11 +01:00
|
|
|
|
args = args[1:] // remove the subdomain part for the arguments,
|
2016-05-30 16:08:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
if parsedPath := s.Path(routeName, args...); parsedPath != "" {
|
2016-06-14 07:45:40 +02:00
|
|
|
|
url = scheme + host + parsedPath
|
2016-06-06 20:04:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-14 07:45:40 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
2016-05-30 16:08:09 +02:00
|
|
|
|
|
2017-02-28 14:01:18 +01:00
|
|
|
|
// Regex takes pairs with the named path (without symbols) following by its expression
|
|
|
|
|
// and returns a middleware which will do a pure but effective validation using the regexp package.
|
|
|
|
|
//
|
|
|
|
|
// Note: '/adaptors/gorillamux' already supports regex path validation.
|
|
|
|
|
// It's useful while the developer uses the '/adaptors/httprouter' instead.
|
|
|
|
|
func (s *Framework) Regex(pairParamExpr ...string) HandlerFunc {
|
|
|
|
|
srvErr := func(ctx *Context) {
|
|
|
|
|
ctx.EmitError(StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp := s.policies.RouterReversionPolicy.WildcardPath
|
|
|
|
|
if wp == nil {
|
|
|
|
|
s.Log(ProdMode, "regex cannot be used when a router policy is missing\n"+errRouterIsMissing.Format(s.Config.VHost).Error())
|
|
|
|
|
return srvErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(pairParamExpr)%2 != 0 {
|
|
|
|
|
s.Log(ProdMode,
|
|
|
|
|
"regex pre-compile error: the correct format is paramName, expression"+
|
|
|
|
|
"paramName2, expression2. The len should be %2==0")
|
|
|
|
|
return srvErr
|
|
|
|
|
}
|
|
|
|
|
pairs := make(map[string]*regexp.Regexp, len(pairParamExpr)/2)
|
|
|
|
|
|
|
|
|
|
for i := 0; i < len(pairParamExpr)-1; i++ {
|
|
|
|
|
expr := pairParamExpr[i+1]
|
|
|
|
|
r, err := regexp.Compile(expr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.Log(ProdMode, "regex '"+expr+"' failed. Trace: "+err.Error())
|
|
|
|
|
return srvErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pairs[pairParamExpr[i]] = r
|
|
|
|
|
i++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return the middleware
|
|
|
|
|
return func(ctx *Context) {
|
|
|
|
|
for k, v := range pairs {
|
|
|
|
|
pathPart := ctx.Param(k)
|
|
|
|
|
if pathPart == "" {
|
|
|
|
|
// take care, the router already
|
|
|
|
|
// does the param validations
|
|
|
|
|
// so if it's empty here it means that
|
|
|
|
|
// the router has label it as optional.
|
|
|
|
|
// so we skip it, and continue to the next.
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// the improtant thing:
|
|
|
|
|
// if the path part didn't match with the relative exp, then fire status not found.
|
|
|
|
|
if !v.MatchString(pathPart) {
|
|
|
|
|
ctx.EmitError(StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// otherwise continue to the next handler...
|
|
|
|
|
ctx.Next()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RouteParam returns a named parameter as each router defines named path parameters.
|
|
|
|
|
// For example, with the httprouter(: as named param symbol):
|
|
|
|
|
// userid should return :userid.
|
|
|
|
|
// with gorillamux, userid should return {userid}
|
|
|
|
|
// or userid[1-9]+ should return {userid[1-9]+}.
|
|
|
|
|
// so basically we just wrap the raw parameter name
|
|
|
|
|
// with the start (and end) dynamic symbols of each router implementing the RouterReversionPolicy.
|
|
|
|
|
// It's an optional functionality but it can be used to create adaptors without even know the router
|
|
|
|
|
// that the user uses (which can be taken by app.Config.Other[iris.RouterNameConfigKey].
|
|
|
|
|
//
|
|
|
|
|
// Note: we don't need a function like ToWildcardParam because the developer
|
|
|
|
|
// can use the RouterParam with a combination with RouteWildcardPath.
|
|
|
|
|
//
|
|
|
|
|
// Example: https://github.com/iris-contrib/adaptors/blob/master/oauth/oauth.go
|
|
|
|
|
func (s *Framework) RouteParam(paramName string) string {
|
|
|
|
|
if s.policies.RouterReversionPolicy.Param == nil {
|
|
|
|
|
// all Iris' routers are implementing all features but third-parties may not, so make sure that the user
|
|
|
|
|
// will get a useful message back.
|
|
|
|
|
s.Log(DevMode, "cannot wrap a route named path parameter because the functionality was not implemented by the current router.")
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s.policies.RouterReversionPolicy.Param(paramName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RouteWildcardPath returns a path converted to a 'dynamic' path
|
|
|
|
|
// for example, with the httprouter(wildcard symbol: '*'):
|
|
|
|
|
// ("/static", "path") should return /static/*path
|
|
|
|
|
// ("/myfiles/assets", "anything") should return /myfiles/assets/*anything
|
|
|
|
|
func (s *Framework) RouteWildcardPath(path string, paramName string) string {
|
|
|
|
|
if s.policies.RouterReversionPolicy.WildcardPath == nil {
|
|
|
|
|
s.Log(DevMode, "please use WildcardPath after .Adapt(router).\n"+errRouterIsMissing.Format(s.Config.VHost).Error())
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
return s.policies.RouterReversionPolicy.WildcardPath(path, paramName)
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-18 07:18:09 +01:00
|
|
|
|
// DecodeQuery returns the uri parameter as url (string)
|
|
|
|
|
// useful when you want to pass something to a database and be valid to retrieve it via context.Param
|
|
|
|
|
// use it only for special cases, when the default behavior doesn't suits you.
|
|
|
|
|
//
|
|
|
|
|
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
|
|
|
|
|
// it uses just the url.QueryUnescape
|
|
|
|
|
func DecodeQuery(path string) string {
|
|
|
|
|
if path == "" {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
encodedPath, err := url.QueryUnescape(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return path
|
|
|
|
|
}
|
|
|
|
|
return encodedPath
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DecodeURL returns the decoded uri
|
|
|
|
|
// useful when you want to pass something to a database and be valid to retrieve it via context.Param
|
|
|
|
|
// use it only for special cases, when the default behavior doesn't suits you.
|
|
|
|
|
//
|
|
|
|
|
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
|
|
|
|
|
// it uses just the url.Parse
|
|
|
|
|
func DecodeURL(uri string) string {
|
|
|
|
|
u, err := url.Parse(uri)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return uri
|
|
|
|
|
}
|
|
|
|
|
return u.String()
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
var errTemplateRendererIsMissing = errors.New(
|
|
|
|
|
`
|
|
|
|
|
manually call of Render for a template: '%s' without specified RenderPolicy!
|
|
|
|
|
Please .Adapt one of the available view engines inside 'kataras/iris/adaptors/view'.
|
|
|
|
|
By-default Iris supports five template engines:
|
|
|
|
|
- standard html | view.HTML(...)
|
|
|
|
|
- django | view.Django(...)
|
|
|
|
|
- handlebars | view.Handlebars(...)
|
|
|
|
|
- pug(jade) | view.Pug(...)
|
|
|
|
|
- amber | view.Amber(...)
|
|
|
|
|
|
|
|
|
|
Edit your main .go source file to adapt one of these and restart your app.
|
|
|
|
|
i.e: lines (<---) were missing.
|
|
|
|
|
-------------------------------------------------------------------
|
|
|
|
|
import (
|
2017-02-18 06:22:57 +01:00
|
|
|
|
"gopkg.in/kataras/iris.v6"
|
|
|
|
|
"gopkg.in/kataras/iris.v6/adaptors/httprouter" // or gorillamux
|
|
|
|
|
"gopkg.in/kataras/iris.v6/adaptors/view" // <--- this line
|
2017-02-14 04:54:11 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main(){
|
|
|
|
|
app := iris.New()
|
2017-02-15 19:06:19 +01:00
|
|
|
|
// right below the iris.New():
|
2017-02-14 04:54:11 +01:00
|
|
|
|
app.Adapt(httprouter.New()) // or gorillamux.New()
|
2017-02-15 19:06:19 +01:00
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
app.Adapt(view.HTML("./templates", ".html")) // <--- and this line were missing.
|
|
|
|
|
|
2017-02-15 19:06:19 +01:00
|
|
|
|
// the rest of your source code...
|
|
|
|
|
// ...
|
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
|
app.Listen("%s")
|
|
|
|
|
}
|
|
|
|
|
-------------------------------------------------------------------
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
// RenderOptions is a helper type for the optional runtime options can be passed by user when Render called.
|
|
|
|
|
// I.e the "layout" or "gzip" option
|
|
|
|
|
// same as iris.Map but more specific name
|
|
|
|
|
type RenderOptions map[string]interface{}
|
|
|
|
|
|
|
|
|
|
// Render renders using the specific template or any other rich content renderer to the 'w'.
|
|
|
|
|
//
|
|
|
|
|
// Example of usage:
|
|
|
|
|
// - send an e-mail using a template engine that you already
|
|
|
|
|
// adapted via: app.Adapt(view.HTML("./templates", ".html")) or app.Adapt(iris.RenderPolicy(mycustomRenderer)).
|
|
|
|
|
//
|
|
|
|
|
// It can also render json,xml,jsonp and markdown by-default before or after .Build too.
|
|
|
|
|
func (s *Framework) Render(w io.Writer, name string, bind interface{}, options ...map[string]interface{}) error {
|
2017-03-01 14:04:42 +01:00
|
|
|
|
ok, err := s.policies.RenderPolicy(w, name, bind, options...)
|
2017-02-14 04:54:11 +01:00
|
|
|
|
if !ok {
|
|
|
|
|
// ok is false ONLY WHEN there is no registered render policy
|
|
|
|
|
// that is responsible for that 'name` (if contains dot '.' it's for templates).
|
|
|
|
|
// We don't use default template engines on the new version,
|
|
|
|
|
// so we should notice the user here, we could make it to panic but because that is on runtime
|
|
|
|
|
// we don't want to panic for that, let's give a message if the user adapted a logger for dev.
|
|
|
|
|
// And return that error in the case the user wasn't in dev mode, she/he can catch this error.
|
|
|
|
|
|
|
|
|
|
// Also on the README we will add the .Adapt(iris.DevLogger()) to mention that
|
|
|
|
|
// logging for any runtime info(except http server's panics and unexpected serious errors) is not enabled by-default.
|
|
|
|
|
if strings.Contains(name, ".") {
|
|
|
|
|
err = errTemplateRendererIsMissing.Format(name, s.Config.VHost)
|
|
|
|
|
s.Log(DevMode, err.Error())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return err
|
2016-08-14 04:44:36 +02:00
|
|
|
|
}
|