mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
929b00a24b
Former-commit-id: 32af4dd8ef18a5fb2fa88aa8b87e71a594faa6f2
869 lines
31 KiB
Go
869 lines
31 KiB
Go
// Package iris provides efficient and well-designed tools with robust set of features to
|
||
// create your own perfect high performance web application
|
||
// with unlimited potentials and portability.
|
||
//
|
||
// For middleware, template engines, response engines, sessions, websockets, mails, subdomains,
|
||
// dynamic subdomains, routes, party of subdomains & routes and more
|
||
//
|
||
// visit https://godoc.org/gopkg.in/kataras/iris.v6
|
||
package iris
|
||
|
||
import (
|
||
"context"
|
||
"crypto/tls"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net"
|
||
"net/http"
|
||
"net/url"
|
||
"os"
|
||
"os/signal"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/geekypanda/httpcache"
|
||
"github.com/kataras/go-errors"
|
||
"github.com/kataras/go-fs"
|
||
"github.com/kataras/go-serializer"
|
||
)
|
||
|
||
const (
|
||
// Version is the current version number of the Iris web framework
|
||
Version = "6.2.0"
|
||
|
||
codeName = `√Νεxτ`
|
||
|
||
banner = ` _____ _
|
||
|_ _| (_)
|
||
| | ____ _ ___
|
||
| | | __|| |/ __|
|
||
_| |_| | | |\__ \
|
||
|_____|_| |_||___/ ` + codeName
|
||
)
|
||
|
||
// 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(...)`
|
||
var (
|
||
Default *Framework
|
||
// ResetDefault resets the `.Default`
|
||
// to an empty *Framework with the default configuration.
|
||
//
|
||
// Note: ResetDefault field itself can be setted
|
||
// to custom function too.
|
||
ResetDefault = func() { Default = New() }
|
||
)
|
||
|
||
func init() {
|
||
ResetDefault()
|
||
}
|
||
|
||
// Framework is our God |\| Google.Search('Greek mythology Iris').
|
||
type Framework struct {
|
||
// 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.
|
||
*Router
|
||
|
||
// 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
|
||
// - URLPath
|
||
// - RouteContextLinker
|
||
// - RouterBuilderPolicy
|
||
// - RouterWrapperPolicy
|
||
// - RenderPolicy
|
||
// - TemplateFuncsPolicy
|
||
// - SessionsPolicy
|
||
//
|
||
// These are setted by user's call to .Adapt
|
||
policies Policies
|
||
|
||
ln net.Listener // setted on Listten/Serve funcions, available after 'Boot'
|
||
|
||
// 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.
|
||
TLSNextProto map[string]func(*http.Server, *tls.Conn, http.Handler) // same as http.Server.TLSNextProto
|
||
|
||
// ConnState specifies an optional callback function that is
|
||
// called when a client connection changes state. See the
|
||
// ConnState type and associated constants for details.
|
||
ConnState func(net.Conn, http.ConnState) // same as http.Server.ConnState
|
||
|
||
// 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
|
||
|
||
once sync.Once // used to 'Boot' once
|
||
}
|
||
|
||
var defaultGlobalLoggerOuput = log.New(os.Stdout, "[Iris] ", log.LstdFlags)
|
||
|
||
// DevLogger returns a new Logger which prints both ProdMode and DevMode messages
|
||
// to the default global logger printer.
|
||
//
|
||
// 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 {
|
||
return func(mode LogMode, logMessage string) {
|
||
defaultGlobalLoggerOuput.Println(logMessage)
|
||
}
|
||
}
|
||
|
||
// New creates and returns a fresh Iris *Framework instance
|
||
// with the default configuration if no 'setters' parameters passed.
|
||
func New(setters ...OptionSetter) *Framework {
|
||
cfg := DefaultConfiguration()
|
||
s := &Framework{Config: &cfg}
|
||
|
||
// +------------------------------------------------------------+
|
||
// | Set the config passed from setters |
|
||
// | or use the default one |
|
||
// +------------------------------------------------------------+
|
||
s.Set(setters...)
|
||
{
|
||
// +------------------------------------------------------------+
|
||
// | 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)
|
||
}
|
||
}))
|
||
|
||
}
|
||
|
||
// +------------------------------------------------------------+
|
||
// | |
|
||
// | 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
|
||
if s.ln != nil { // but user called .Serve
|
||
// then take the listener's addr
|
||
s.Config.VHost = s.ln.Addr().String()
|
||
} else {
|
||
// if no .Serve or .Listen called, then the user should set the VHost manually,
|
||
// however set it to a default value here for any case
|
||
s.Config.VHost = DefaultServerAddr
|
||
}
|
||
}
|
||
// if user didn't specified a scheme then get it from the VHost, which is already setted at before statements
|
||
if s.Config.VScheme == "" {
|
||
s.Config.VScheme = ParseScheme(s.Config.VHost)
|
||
}
|
||
}})
|
||
|
||
{
|
||
// +------------------------------------------------------------+
|
||
// | 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 |
|
||
// | On Init: Attach a new empty content-type serializers. |
|
||
// | Adapt one RenderPolicy which is responsible |
|
||
// | for json,jsonp,xml and markdown rendering |
|
||
// +------------------------------------------------------------+
|
||
|
||
// prepare the serializers,
|
||
// serializer content-types(json,jsonp,xml,markdown) the defaults are setted:
|
||
serializers := serializer.Serializers{}
|
||
serializer.RegisterDefaults(serializers)
|
||
|
||
//
|
||
// notes for me: Why not at the build state? in order to be overridable and not only them,
|
||
// these are easy to be overriden by external adaptors too, no matter the order,
|
||
// this is why the RenderPolicy last registration executing first and the first last.
|
||
//
|
||
|
||
// Adapt the RenderPolicy on the Build in order to be the last
|
||
// render policy, so the users can adapt their own before the default(= to override json,xml,jsonp renderer).
|
||
//
|
||
// Notes: the Renderer of the view system is managed by the
|
||
// adaptors because they are optional.
|
||
// If templates are binded to the RenderPolicy then
|
||
// If a key contains a dot('.') then is a template file
|
||
// otherwise try to find a serializer, if contains error then we return false and the error
|
||
// in order the renderer to continue to search for any other custom registerer RenderPolicy
|
||
// if no error then check if it has written anything, if yes write the content
|
||
// to the writer(which is the context.ResponseWriter or the gzip version of it)
|
||
// if no error but nothing written then we return false and the error
|
||
s.Adapt(RenderPolicy(func(out io.Writer, name string, bind interface{}, options ...map[string]interface{}) (error, bool) {
|
||
b, err := serializers.Serialize(name, bind, options...)
|
||
if err != nil {
|
||
return err, false // errors should be wrapped
|
||
}
|
||
if len(b) > 0 {
|
||
_, err = out.Write(b)
|
||
return err, true
|
||
}
|
||
// continue to the next if any or notice there is no available renderer for that name
|
||
return nil, false
|
||
}))
|
||
}
|
||
{
|
||
// +------------------------------------------------------------+
|
||
// | Module Name: Template engine's funcs |
|
||
// | On Init: Adapt the reverse routing tmpl funcs |
|
||
// | for any template engine that will be registered |
|
||
// +------------------------------------------------------------+
|
||
s.Adapt(TemplateFuncsPolicy{
|
||
"url": s.URL,
|
||
"urlpath": s.policies.RouterReversionPolicy.URLPath,
|
||
}) // the entire template registration logic lives inside the ./adaptors/view now.
|
||
|
||
}
|
||
|
||
}
|
||
|
||
{
|
||
// +------------------------------------------------------------+
|
||
// | 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) {
|
||
// 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 {
|
||
// 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)
|
||
}
|
||
}
|
||
|
||
if routerBuilder != nil {
|
||
// buid the router using user's selection build policy
|
||
s.Router.build(routerBuilder)
|
||
}
|
||
}
|
||
}})
|
||
|
||
}
|
||
|
||
{
|
||
// +------------------------------------------------------------+
|
||
// | Module Name: System |
|
||
// | On Build: Check for updates on Build, async |
|
||
// +------------------------------------------------------------+
|
||
|
||
// On Build: local repository updates
|
||
s.Adapt(EventPolicy{Build: func(*Framework) {
|
||
if s.Config.CheckForUpdates {
|
||
go s.CheckForUpdates(false)
|
||
}
|
||
}})
|
||
}
|
||
|
||
return s
|
||
}
|
||
|
||
// Set sets an option, configuration field to its Config
|
||
func (s *Framework) Set(setters ...OptionSetter) {
|
||
for _, setter := range setters {
|
||
setter.Set(s.Config)
|
||
}
|
||
}
|
||
|
||
// Log logs to the defined logger policy.
|
||
//
|
||
// The default outputs to the os.Stdout when EnvMode is 'ProductionEnv'
|
||
func (s *Framework) Log(mode LogMode, log string) {
|
||
s.policies.LoggerPolicy(mode, log)
|
||
}
|
||
|
||
// 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.
|
||
func (s *Framework) Must(err error) {
|
||
if err != nil {
|
||
s.handlePanic(err)
|
||
}
|
||
}
|
||
|
||
func (s *Framework) handlePanic(err error) {
|
||
// 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
|
||
}
|
||
|
||
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())
|
||
}
|
||
|
||
// 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) {
|
||
s.once.Do(func() {
|
||
// 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)
|
||
|
||
// 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
|
||
})
|
||
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 s.ln != nil {
|
||
return errors.New("server is already started and listening")
|
||
}
|
||
|
||
s.ln = ln
|
||
s.closedManually = false
|
||
s.Boot()
|
||
|
||
// post any panics to the user defined logger.
|
||
defer func() {
|
||
if rerr := recover(); rerr != nil {
|
||
if err, ok := rerr.(error); ok {
|
||
s.handlePanic(err)
|
||
}
|
||
}
|
||
}()
|
||
|
||
srv := &http.Server{
|
||
ReadTimeout: s.Config.ReadTimeout,
|
||
WriteTimeout: s.Config.WriteTimeout,
|
||
MaxHeaderBytes: s.Config.MaxHeaderBytes,
|
||
TLSNextProto: s.TLSNextProto,
|
||
ConnState: s.ConnState,
|
||
Addr: s.Config.VHost,
|
||
ErrorLog: s.policies.LoggerPolicy.ToLogger(log.LstdFlags),
|
||
Handler: s.Router,
|
||
}
|
||
// 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)
|
||
s.ln = nil
|
||
return err
|
||
}
|
||
|
||
// print the banner and wait for system channel interrupt
|
||
go s.postServe()
|
||
// finally return the error or block here, remember,
|
||
// until go1.8 these are our best options.
|
||
return srv.Serve(s.ln)
|
||
}
|
||
|
||
func (s *Framework) postServe() {
|
||
bannerMessage := fmt.Sprintf("| Running at %s\n\n%s\n", s.Config.VHost, banner)
|
||
s.Log(DevMode, bannerMessage)
|
||
|
||
ch := make(chan os.Signal, 1)
|
||
signal.Notify(ch, os.Interrupt)
|
||
<-ch
|
||
|
||
// 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)
|
||
|
||
s.Shutdown(context.Background())
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Listen starts the standalone http server
|
||
// which listens to the addr parameter which as the form of
|
||
// host:port
|
||
//
|
||
// If you need to manually monitor any error please use `.Serve` instead.
|
||
func (s *Framework) Listen(addr string) {
|
||
addr = ParseHost(addr)
|
||
if s.Config.VHost == "" {
|
||
s.Config.VHost = addr
|
||
// this will be set as the front-end listening addr
|
||
}
|
||
// only here, other Listen functions should throw an error if port is missing.
|
||
// User should know how to fix them on ListenUNIX/ListenTLS/ListenLETSENCRYPT/Serve,
|
||
// they are used by more 'advanced' devs, mostly.
|
||
|
||
if portIdx := strings.IndexByte(addr, ':'); portIdx < 0 {
|
||
// missing port part, add it
|
||
addr = addr + ":80"
|
||
}
|
||
|
||
ln, err := TCPKeepAlive(addr)
|
||
if err != nil {
|
||
s.handlePanic(err)
|
||
}
|
||
|
||
s.Must(s.Serve(ln))
|
||
}
|
||
|
||
// 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
|
||
//
|
||
//
|
||
// If you need to manually monitor any error please use `.Serve` instead.
|
||
func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
|
||
addr = ParseHost(addr)
|
||
if s.Config.VHost == "" {
|
||
s.Config.VHost = addr
|
||
// this will be set as the front-end listening addr
|
||
}
|
||
|
||
ln, err := TLS(addr, certFile, keyFile)
|
||
if err != nil {
|
||
s.handlePanic(err)
|
||
}
|
||
s.Must(s.Serve(ln))
|
||
}
|
||
|
||
// 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'
|
||
// 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"
|
||
// if you want to disable cache then simple pass as second argument an empty empty string ""
|
||
//
|
||
// example: https://github.com/iris-contrib/examples/blob/master/letsencrypt/main.go
|
||
//
|
||
// supports localhost domains for testing,
|
||
// NOTE: if you are ready for production then use `$app.Serve(iris.LETSENCRYPTPROD("mydomain.com"))` instead
|
||
func (s *Framework) ListenLETSENCRYPT(addr string, cacheFileOptional ...string) {
|
||
addr = ParseHost(addr)
|
||
if s.Config.VHost == "" {
|
||
s.Config.VHost = addr
|
||
// this will be set as the front-end listening addr
|
||
}
|
||
ln, err := LETSENCRYPT(addr, cacheFileOptional...)
|
||
if err != nil {
|
||
s.handlePanic(err)
|
||
}
|
||
|
||
// starts a second server which listening on HOST:80 to redirect all requests to the HTTPS://HOST:PORT
|
||
Proxy(ParseHostname(addr)+":80", "https://"+addr)
|
||
s.Must(s.Serve(ln))
|
||
}
|
||
|
||
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
|
||
//
|
||
//
|
||
// If you need to manually monitor any error please use `.Serve` instead.
|
||
func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
|
||
// *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 {
|
||
s.handlePanic(err)
|
||
}
|
||
|
||
s.Must(s.Serve(ln))
|
||
}
|
||
|
||
func (s *Framework) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
s.Router.ServeHTTP(w, r)
|
||
}
|
||
|
||
// global once because is not necessary to check for updates on more than one iris station*
|
||
var updateOnce sync.Once
|
||
|
||
const (
|
||
githubOwner = "kataras"
|
||
githubRepo = "iris"
|
||
)
|
||
|
||
// CheckForUpdates will try to search for newer version of Iris based on the https://github.com/kataras/iris/releases
|
||
// If a newer version found then the app will ask the he dev/user if want to update the 'x' version
|
||
// if 'y' is pressed then the updater will try to install the latest version
|
||
// the updater, will notify the dev/user that the update is finished and should restart the App manually.
|
||
// Note: exported func CheckForUpdates exists because of the reason that an update can be executed while Iris is running
|
||
func (s *Framework) CheckForUpdates(force bool) {
|
||
updated := false
|
||
checker := func() {
|
||
|
||
fs.DefaultUpdaterAlreadyInstalledMessage = "Updater: Running with the latest version(%s)\n"
|
||
updater, err := fs.GetUpdater(githubOwner, githubRepo, Version)
|
||
|
||
if err != nil {
|
||
// ignore writer's error
|
||
s.Log(DevMode, "update failed: "+err.Error())
|
||
return
|
||
}
|
||
|
||
updated = updater.Run(fs.Stdout(s.policies.LoggerPolicy), fs.Stderr(s.policies.LoggerPolicy), fs.Silent(false))
|
||
|
||
}
|
||
|
||
if force {
|
||
checker()
|
||
} else {
|
||
updateOnce.Do(checker)
|
||
}
|
||
|
||
if updated { // if updated, then do not run the web server
|
||
s.Log(DevMode, "exiting now...")
|
||
os.Exit(1)
|
||
}
|
||
|
||
}
|
||
|
||
// 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
|
||
//
|
||
// 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)
|
||
}
|
||
}
|
||
|
||
// 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
|
||
}
|
||
|
||
func newCachedMuxEntry(s *Framework, bodyHandler HandlerFunc, expiration time.Duration) *cachedMuxEntry {
|
||
httpHandler := ToNativeHandler(s, bodyHandler)
|
||
|
||
cachedHandler := httpcache.Cache(httpHandler, expiration)
|
||
return &cachedMuxEntry{
|
||
cachedHandler: cachedHandler,
|
||
}
|
||
}
|
||
|
||
func (c *cachedMuxEntry) Serve(ctx *Context) {
|
||
c.cachedHandler.ServeHTTP(ctx.ResponseWriter, ctx.Request)
|
||
}
|
||
|
||
// 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
|
||
//
|
||
// 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
|
||
}
|
||
|
||
// Path used to check arguments with the route's named parameters and return the correct url
|
||
// if parse failed returns empty string
|
||
func (s *Framework) Path(routeName string, args ...interface{}) string {
|
||
r := s.Router.Routes().Lookup(routeName)
|
||
if r == nil {
|
||
return ""
|
||
}
|
||
|
||
// 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
|
||
|
||
var argsString []string
|
||
if len(args) > 0 {
|
||
argsString = make([]string, len(args))
|
||
}
|
||
|
||
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:]...)
|
||
}
|
||
}
|
||
}
|
||
|
||
return s.policies.RouterReversionPolicy.URLPath(r, argsString...)
|
||
}
|
||
|
||
// 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()
|
||
}
|
||
|
||
// URL returns the subdomain+ host + Path(...optional named parameters if route is dynamic)
|
||
// returns an empty string if parse is failed
|
||
func (s *Framework) URL(routeName string, args ...interface{}) (url string) {
|
||
r := s.Router.Routes().Lookup(routeName)
|
||
if r == nil {
|
||
return
|
||
}
|
||
|
||
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
|
||
|
||
// 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 len(args) == 0 { // it's a wildcard subdomain but not arguments
|
||
return
|
||
}
|
||
|
||
if subdomain, ok := args[0].(string); ok {
|
||
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
|
||
}
|
||
args = args[1:] // remove the subdomain part for the arguments,
|
||
}
|
||
|
||
if parsedPath := s.Path(routeName, args...); parsedPath != "" {
|
||
url = scheme + host + parsedPath
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
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 (
|
||
"github.com/kataras/iris"
|
||
"github.com/kataras/iris/adaptors/httprouter" // or gorillamux
|
||
"github.com/kataras/iris/adaptors/view" // <--- this line
|
||
)
|
||
|
||
func main(){
|
||
app := iris.New()
|
||
// right below the iris.New():
|
||
app.Adapt(httprouter.New()) // or gorillamux.New()
|
||
|
||
app.Adapt(view.HTML("./templates", ".html")) // <--- and this line were missing.
|
||
|
||
// the rest of your source code...
|
||
// ...
|
||
|
||
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 {
|
||
err, ok := s.policies.RenderPolicy(w, name, bind, options...)
|
||
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
|
||
}
|