1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-18 02:17:05 +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

474
iris.go
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 iris
import (
@@ -14,16 +10,20 @@ import (
"sync"
"time"
"github.com/sirupsen/logrus"
// context for the handlers
"github.com/kataras/iris/context"
// core packages, needed to build the application
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/host"
"github.com/kataras/iris/core/logger"
"github.com/kataras/iris/core/nettools"
"github.com/kataras/iris/core/netutil"
"github.com/kataras/iris/core/router"
// sessions and view
"github.com/kataras/iris/sessions"
// handlerconv conversions
"github.com/kataras/iris/core/handlerconv"
// cache conversions
"github.com/kataras/iris/cache"
// view
"github.com/kataras/iris/view"
// middleware used in Default method
requestLogger "github.com/kataras/iris/middleware/logger"
@@ -31,34 +31,103 @@ import (
)
const (
banner = ` _____ _
|_ _| (_)
| | ____ _ ___
| | | __|| |/ __|
_| |_| | | |\__ \
|_____|_| |_||___/ `
// Version is the current version number of the Iris Web framework.
//
// Look https://github.com/kataras/iris#where-can-i-find-older-versions for older versions.
Version = "7.2.0"
// Version is the current version number of the Iris Web Framework.
Version = "8.0.0"
)
// HTTP status codes as registered with IANA.
// See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
// Raw Copy from the net/http std package in order to recude the import path of "net/http" for the users.
//
// Copied from `net/http` package.
const (
// MethodNone is a Virtual method
// to store the "offline" routes.
//
// Conversion for router.MethodNone.
MethodNone = router.MethodNone
// NoLayout to disable layout for a particular template file
// Conversion for view.NoLayout.
NoLayout = view.NoLayout
StatusContinue = 100 // RFC 7231, 6.2.1
StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
StatusProcessing = 102 // RFC 2518, 10.1
StatusOK = 200 // RFC 7231, 6.3.1
StatusCreated = 201 // RFC 7231, 6.3.2
StatusAccepted = 202 // RFC 7231, 6.3.3
StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
StatusNoContent = 204 // RFC 7231, 6.3.5
StatusResetContent = 205 // RFC 7231, 6.3.6
StatusPartialContent = 206 // RFC 7233, 4.1
StatusMultiStatus = 207 // RFC 4918, 11.1
StatusAlreadyReported = 208 // RFC 5842, 7.1
StatusIMUsed = 226 // RFC 3229, 10.4.1
StatusMultipleChoices = 300 // RFC 7231, 6.4.1
StatusMovedPermanently = 301 // RFC 7231, 6.4.2
StatusFound = 302 // RFC 7231, 6.4.3
StatusSeeOther = 303 // RFC 7231, 6.4.4
StatusNotModified = 304 // RFC 7232, 4.1
StatusUseProxy = 305 // RFC 7231, 6.4.5
_ = 306 // RFC 7231, 6.4.6 (Unused)
StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
StatusPermanentRedirect = 308 // RFC 7538, 3
StatusBadRequest = 400 // RFC 7231, 6.5.1
StatusUnauthorized = 401 // RFC 7235, 3.1
StatusPaymentRequired = 402 // RFC 7231, 6.5.2
StatusForbidden = 403 // RFC 7231, 6.5.3
StatusNotFound = 404 // RFC 7231, 6.5.4
StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5
StatusNotAcceptable = 406 // RFC 7231, 6.5.6
StatusProxyAuthRequired = 407 // RFC 7235, 3.2
StatusRequestTimeout = 408 // RFC 7231, 6.5.7
StatusConflict = 409 // RFC 7231, 6.5.8
StatusGone = 410 // RFC 7231, 6.5.9
StatusLengthRequired = 411 // RFC 7231, 6.5.10
StatusPreconditionFailed = 412 // RFC 7232, 4.2
StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11
StatusRequestURITooLong = 414 // RFC 7231, 6.5.12
StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13
StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
StatusExpectationFailed = 417 // RFC 7231, 6.5.14
StatusTeapot = 418 // RFC 7168, 2.3.3
StatusUnprocessableEntity = 422 // RFC 4918, 11.2
StatusLocked = 423 // RFC 4918, 11.3
StatusFailedDependency = 424 // RFC 4918, 11.4
StatusUpgradeRequired = 426 // RFC 7231, 6.5.15
StatusPreconditionRequired = 428 // RFC 6585, 3
StatusTooManyRequests = 429 // RFC 6585, 4
StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
StatusInternalServerError = 500 // RFC 7231, 6.6.1
StatusNotImplemented = 501 // RFC 7231, 6.6.2
StatusBadGateway = 502 // RFC 7231, 6.6.3
StatusServiceUnavailable = 503 // RFC 7231, 6.6.4
StatusGatewayTimeout = 504 // RFC 7231, 6.6.5
StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6
StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
StatusInsufficientStorage = 507 // RFC 4918, 11.5
StatusLoopDetected = 508 // RFC 5842, 7.2
StatusNotExtended = 510 // RFC 2774, 7
StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)
// HTTP Methods copied from `net/http`.
const (
MethodGet = "GET"
MethodPost = "POST"
MethodPut = "PUT"
MethodDelete = "DELETE"
MethodConnect = "CONNECT"
MethodHead = "HEAD"
MethodPatch = "PATCH"
MethodOptions = "OPTIONS"
MethodTrace = "TRACE"
)
// MethodNone is an iris-specific "virtual" method
// to store the "offline" routes.
const MethodNone = "NONE"
// Application is responsible to manage the state of the application.
// It contains and handles all the necessary parts to create a fast web server.
type Application struct {
Scheduler host.Scheduler
// routing embedded | exposing APIBuilder's and Router's public API.
*router.APIBuilder
*router.Router
@@ -68,30 +137,33 @@ type Application struct {
// all fields defaults to something that is working, developers don't have to set it.
config *Configuration
// logger logs to the defined logger.
// Use AttachLogger to change the default which prints messages to the os.Stdout.
// It's just an io.Writer, period.
logger io.Writer
// the logrus logger instance, defaults to "Info" level messages (all except "Debug")
logger *logrus.Logger
// view engine
view view.View
// sessions and flash messages
sessions sessions.Sessions
// used for build
once sync.Once
mu sync.Mutex
Shutdown func(stdContext.Context) error
mu sync.Mutex
// Hosts contains a list of all servers (Host Supervisors) that this app is running on.
//
// Hosts may be empty only if application ran(`app.Run`) with `iris.Raw` option runner,
// otherwise it contains a single host (`app.Hosts[0]`).
//
// Additional Host Supervisors can be added to that list by calling the `app.NewHost` manually.
//
// Hosts field is available after `Run` or `NewHost`.
Hosts []*host.Supervisor
}
// New creates and returns a fresh empty Iris *Application instance.
// New creates and returns a fresh empty iris *Application instance.
func New() *Application {
config := DefaultConfiguration()
app := &Application{
config: &config,
logger: logger.NewDevLogger(banner),
logger: logrus.New(),
APIBuilder: router.NewAPIBuilder(),
Router: router.NewRouter(),
}
@@ -103,20 +175,10 @@ func New() *Application {
return app
}
// Default returns a new Application instance.
// Unlike `New` this method prepares some things for you.
// std html templates from the "./templates" directory,
// session manager is attached with a default expiration of 7 days,
// recovery and (request) logger handlers(middleware) are being registered.
// Default returns a new Application instance which, unlike `New`,
// recovers on panics and logs the incoming http requests.
func Default() *Application {
app := New()
app.AttachView(view.HTML("./templates", ".html"))
app.AttachSessionManager(sessions.New(sessions.Config{
Cookie: "irissessionid",
Expires: 7 * (24 * time.Hour), // 1 week
}))
app.Use(recover.New())
app.Use(requestLogger.New())
@@ -128,7 +190,7 @@ func Default() *Application {
// and returns an error which if it's not nil it's printed to the logger.
// See configuration.go for more.
//
// Returns itself in order to be used like app:= New().Configure(...)
// Returns itself in order to be used like `app:= New().Configure(...)`
func (app *Application) Configure(configurators ...Configurator) *Application {
for _, cfg := range configurators {
cfg(app)
@@ -137,36 +199,119 @@ func (app *Application) Configure(configurators ...Configurator) *Application {
return app
}
// Build sets up, once, the framework.
// It builds the default router with its default macros
// and the template functions that are very-closed to Iris.
func (app *Application) Build() (err error) {
app.once.Do(func() {
// view engine
// here is where we declare the closed-relative framework functions.
// Each engine has their defaults, i.e yield,render,render_r,partial, params...
rv := router.NewRoutePathReverser(app.APIBuilder)
app.view.AddFunc("urlpath", rv.Path)
// app.view.AddFunc("url", rv.URL)
err = app.view.Load()
if err != nil {
return // if view engine loading failed then don't continue
}
// ConfigurationReadOnly returns an object which doesn't allow field writing.
func (app *Application) ConfigurationReadOnly() context.ConfigurationReadOnly {
return app.config
}
if !app.Router.Downgraded() {
var routerHandler router.RequestHandler
// router
// create the request handler, the default routing handler
routerHandler = router.NewDefaultHandler()
// These are the different logging levels. You can set the logging level to log
// on the application 's instance of logger, obtained with `app.Logger()`.
//
// These are conversions from logrus.
const (
// NoLog level, logs nothing.
// It's the logrus' `PanicLevel` but it never used inside iris so it will never log.
NoLog = logrus.PanicLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
ErrorLevel = logrus.ErrorLevel
// WarnLevel level. Non-critical entries that deserve eyes.
WarnLevel = logrus.WarnLevel
)
err = app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder)
// re-build of the router from outside can be done with;
// app.RefreshRouter()
}
// Logger returns the logrus logger instance(pointer) that is being used inside the "app".
func (app *Application) Logger() *logrus.Logger {
return app.logger
}
})
var (
// HTML view engine.
// Conversion for the view.HTML.
HTML = view.HTML
// Django view engine.
// Conversion for the view.Django.
Django = view.Django
// Handlebars view engine.
// Conversion for the view.Handlebars.
Handlebars = view.Handlebars
// Pug view engine.
// Conversion for the view.Pug.
Pug = view.Pug
// Amber view engine.
// Conversion for the view.Amber.
Amber = view.Amber
)
return
// NoLayout to disable layout for a particular template file
// A shortcut for the `view#NoLayout`.
const NoLayout = view.NoLayout
// RegisterView should be used to register view engines mapping to a root directory
// and the template file(s) extension.
func (app *Application) RegisterView(viewEngine view.Engine) {
app.view.Register(viewEngine)
}
// View executes and writes the result of a template file to the writer.
//
// First parameter is the writer to write the parsed template.
// Second parameter is the relative, to templates directory, template filename, including extension.
// Third parameter is the layout, can be empty string.
// Forth parameter is the bindable data to the template, can be nil.
//
// Use context.View to render templates to the client instead.
// Returns an error on failure, otherwise nil.
func (app *Application) View(writer io.Writer, filename string, layout string, bindingData interface{}) error {
if app.view.Len() == 0 {
err := errors.New("view engine is missing, use `RegisterView`")
app.Logger().Errorln(err)
return err
}
err := app.view.ExecuteWriter(writer, filename, layout, bindingData)
if err != nil {
app.Logger().Errorln(err)
}
return err
}
var (
// LimitRequestBodySize is a middleware which sets a request body size limit
// for all next handlers in the chain.
//
// A shortcut for the `context#LimitRequestBodySize`.
LimitRequestBodySize = context.LimitRequestBodySize
// FromStd converts native http.Handler, http.HandlerFunc & func(w, r, next) to context.Handler.
//
// Supported form types:
// .FromStd(h http.Handler)
// .FromStd(func(w http.ResponseWriter, r *http.Request))
// .FromStd(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc))
//
// A shortcut for the `handlerconv#FromStd`.
FromStd = handlerconv.FromStd
// Cache is a middleware providing cache functionalities
// to the next handlers, can be used as: `app.Get("/", iris.Cache, aboutHandler)`.
//
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/#caching
Cache = cache.Handler
)
// SPA accepts an "assetHandler" which can be the result of an
// app.StaticHandler or app.StaticEmbeddedHandler.
// It wraps the router and checks:
// if it;s an asset, if the request contains "." (this behavior can be changed via /core/router.NewSPABuilder),
// if the request is index, redirects back to the "/" in order to let the root handler to be executed,
// if it's not an asset then it executes the router, so the rest of registered routes can be
// executed without conflicts with the file server ("assetHandler").
//
// Use that instead of `StaticWeb` for root "/" file server.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/single-page-application
func (app *Application) SPA(assetHandler context.Handler) {
s := router.NewSPABuilder(assetHandler)
wrapper := s.BuildWrapper(app.ContextPool)
app.Router.WrapRouter(wrapper)
}
// NewHost accepts a standar *http.Server object,
@@ -183,7 +328,7 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
// check if different ErrorLog provided, if not bind it with the framework's logger
if srv.ErrorLog == nil {
srv.ErrorLog = log.New(app.logger, "[HTTP Server] ", 0)
srv.ErrorLog = log.New(app.logger.Out, "[HTTP Server] ", 0)
}
if srv.Addr == "" {
@@ -201,38 +346,42 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
// sub.mydomain.com -> valid
// sub.localhost -> valid
// we need the host (without port if 80 or 443) in order to validate these, so:
app.config.vhost = nettools.ResolveVHost(srv.Addr)
app.config.vhost = netutil.ResolveVHost(srv.Addr)
}
// the below schedules some tasks that will run among the server
// I was thinking to have them on Default or here and if user not wanted these, could use a custom core/host
// but that's too much for someone to just disable the banner for example,
// so I will bind them to a configuration field, although is not direct to the *Application,
// host is de-coupled from *Application as the other features too, it took me 2 months for this design.
// copy the registered schedule tasks from the scheduler, if any will be copied to this host supervisor's scheduler.
app.Scheduler.CopyTo(&su.Scheduler)
if !app.config.DisableBanner {
// show the banner and the available keys to exit from app.
su.Schedule(host.WriteBannerTask(app.logger, banner+"V"+Version))
if !app.config.DisableStartupLog {
// show the available info to exit from app.
su.RegisterOnServe(host.WriteStartupLogOnServe(app.logger.Out)) // app.logger.Writer -> Info
}
if !app.config.DisableInterruptHandler {
// give 5 seconds to the server to wait for the (idle) connections.
shutdownTimeout := 5 * time.Second
// when CTRL+C/CMD+C pressed.
su.Schedule(host.ShutdownOnInterruptTask(shutdownTimeout))
shutdownTimeout := 5 * time.Second
host.RegisterOnInterrupt(host.ShutdownOnInterrupt(su, shutdownTimeout))
}
if app.Shutdown == nil {
app.Shutdown = su.Shutdown
}
app.Hosts = append(app.Hosts, su)
return su
}
// RegisterOnInterrupt registers a global function to call when CTRL+C/CMD+C pressed or a unix kill command received.
//
// A shortcut for the `host#RegisterOnInterrupt`.
var RegisterOnInterrupt = host.RegisterOnInterrupt
// Shutdown gracefully terminates all the application's server hosts.
// Returns an error on the first failure, otherwise nil.
func (app *Application) Shutdown(ctx stdContext.Context) error {
for _, su := range app.Hosts {
if err := su.Shutdown(ctx); err != nil {
return err
}
}
return nil
}
// Runner is just an interface which accepts the framework instance
// and returns an error.
//
@@ -250,7 +399,7 @@ type Runner func(*Application) error
// See `Run` for more.
func Listener(l net.Listener) Runner {
return func(app *Application) error {
app.config.vhost = nettools.ResolveVHost(l.Addr().String())
app.config.vhost = netutil.ResolveVHost(l.Addr().String())
return app.NewHost(new(http.Server)).
Serve(l)
}
@@ -318,7 +467,7 @@ func AutoTLS(addr string) Runner {
// only when the server exited or a fatal error caused.
//
// With this option you're not limited to the servers
// that Iris can run by-default.
// that iris can run by-default.
//
// See `Run` for more.
func Raw(f func() error) Runner {
@@ -327,6 +476,45 @@ func Raw(f func() error) Runner {
}
}
// Build sets up, once, the framework.
// It builds the default router with its default macros
// and the template functions that are very-closed to iris.
func (app *Application) Build() error {
rp := errors.NewReporter()
app.once.Do(func() {
rp.Describe("api builder: %v", app.APIBuilder.GetReport())
if !app.Router.Downgraded() {
// router
// create the request handler, the default routing handler
routerHandler := router.NewDefaultHandler()
rp.Describe("router: %v", app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder))
// re-build of the router from outside can be done with;
// app.RefreshRouter()
}
if app.view.Len() > 0 {
// view engine
// here is where we declare the closed-relative framework functions.
// Each engine has their defaults, i.e yield,render,render_r,partial, params...
rv := router.NewRoutePathReverser(app.APIBuilder)
app.view.AddFunc("urlpath", rv.Path)
// app.view.AddFunc("url", rv.URL)
rp.Describe("view: %v", app.view.Load())
}
})
return rp.Return()
}
// ErrServerClosed is returned by the Server's Serve, ServeTLS, ListenAndServe,
// and ListenAndServeTLS methods after a call to Shutdown or Close.
//
// Conversion for the http.ErrServerClosed.
var ErrServerClosed = http.ErrServerClosed
// Run builds the framework and starts the desired `Runner` with or without configuration edits.
//
// Run should be called only once per Application instance, it blocks like http.Server.
@@ -335,7 +523,7 @@ func Raw(f func() error) Runner {
// then create a new host and run it manually by `go NewHost(*http.Server).Serve/ListenAndServe` etc...
// or use an already created host:
// h := NewHost(*http.Server)
// Run(Raw(h.ListenAndServe), WithoutBanner, WithCharset("UTF-8"))
// Run(Raw(h.ListenAndServe), WithCharset("UTF-8"), WithRemoteAddrHeader("CF-Connecting-IP"))
//
// The Application can go online with any type of server or iris's host with the help of
// the following runners:
@@ -344,91 +532,15 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error {
// first Build because it doesn't need anything from configuration,
// this give the user the chance to modify the router inside a configurator as well.
if err := app.Build(); err != nil {
return err
return errors.PrintAndReturnErrors(err, app.logger.Errorf)
}
app.Configure(withOrWithout...)
// this will block until an error(unless supervisor's DeferFlow called from a Task).
return serve(app)
}
// AttachLogger attachs a new logger to the framework.
func (app *Application) AttachLogger(logWriter io.Writer) {
if logWriter == nil {
// convert that to an empty writerFunc
logWriter = logger.NoOpLogger
err := serve(app)
if err != nil {
app.Logger().Errorln(err)
}
app.logger = logWriter
}
// Log sends a message to the defined 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 (app *Application) Log(format string, a ...interface{}) {
logger.Log(app.logger, format, a...)
}
// AttachView should be used to register view engines mapping to a root directory
// and the template file(s) extension.
// Returns an error on failure, otherwise nil.
func (app *Application) AttachView(viewEngine view.Engine) error {
return app.view.Register(viewEngine)
}
// View executes and writes the result of a template file to the writer.
//
// First parameter is the writer to write the parsed template.
// Second parameter is the relative, to templates directory, template filename, including extension.
// Third parameter is the layout, can be empty string.
// Forth parameter is the bindable data to the template, can be nil.
//
// Use context.View to render templates to the client instead.
// Returns an error on failure, otherwise nil.
func (app *Application) View(writer io.Writer, filename string, layout string, bindingData interface{}) error {
if app.view.Len() == 0 {
return errors.New("view engine is missing")
}
return app.view.ExecuteWriter(writer, filename, layout, bindingData)
}
// AttachSessionManager registers a session manager to the framework which is used for flash messages too.
//
// See context.Session too.
func (app *Application) AttachSessionManager(manager sessions.Sessions) {
app.sessions = manager
}
// SessionManager returns the session manager which contain a Start and Destroy methods
// used inside the context.Session().
//
// It's ready to use after the RegisterSessions.
func (app *Application) SessionManager() (sessions.Sessions, error) {
if app.sessions == nil {
return nil, errors.New("session manager is missing")
}
return app.sessions, nil
}
// SPA accepts an "assetHandler" which can be the result of an
// app.StaticHandler or app.StaticEmbeddedHandler.
// It wraps the router and checks:
// if it;s an asset, if the request contains "." (this behavior can be changed via /core/router.NewSPABuilder),
// if the request is index, redirects back to the "/" in order to let the root handler to be executed,
// if it's not an asset then it executes the router, so the rest of registered routes can be
// executed without conflicts with the file server ("assetHandler").
//
// Use that instead of `StaticWeb` for root "/" file server.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/beginner/file-server/single-page-application
func (app *Application) SPA(assetHandler context.Handler) {
s := router.NewSPABuilder(assetHandler)
wrapper := s.BuildWrapper(app.ContextPool)
app.Router.WrapRouter(wrapper)
}
// ConfigurationReadOnly returns a structure which doesn't allow writing.
func (app *Application) ConfigurationReadOnly() context.ConfigurationReadOnly {
return app.config
return err
}