iris/iris.go

838 lines
30 KiB
Go
Raw Normal View History

// Package iris provides efficient and well-designed toolbox with robust set of features to
// create your own perfect high performance web application
// with unlimited portability using the Go Programming Language.
//
// 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
2016-05-30 16:08:09 +02:00
import (
"crypto/tls"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"time"
2016-10-02 02:30:37 +02:00
"github.com/geekypanda/httpcache"
2016-10-02 02:30:37 +02:00
"github.com/kataras/go-errors"
"github.com/kataras/go-fs"
"github.com/kataras/go-serializer"
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
Version = "6.2.0"
banner = ` _____ _
|_ _| (_)
| | ____ _ ___
| | | __|| |/ __|
_| |_| | | |\__ \
|_____|_| |_||___/ ` + Version + ` `
)
// 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 (
2017-01-25 21:19:06 +01:00
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() }
2016-05-30 16:08:09 +02:00
)
func init() {
ResetDefault()
}
// Framework is our God |\| Google.Search('Greek mythology Iris').
type Framework struct {
*Router
policies Policies
// HTTP Server runtime fields is the iris' defined main server, developer can use unlimited number of servers
// note: they're available after .Build, and .Serve/Listen/ListenTLS/ListenLETSENCRYPT/ListenUNIX
ln net.Listener
// 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)
// 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)
closedManually bool
once sync.Once
Config *Configuration
}
var defaultGlobalLoggerOuput = log.New(os.Stdout, "[iris] ", log.LstdFlags)
2016-05-30 16:08:09 +02:00
// 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) {
// user has registered routes
if s.Router.repository.Len() > 0 {
// first check if it's not setted already by any Boot event.
if s.Router.handler == nil {
// and most importantly, check if the user has provided a router
// adaptor, 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 s.policies.RouterBuilderPolicy == 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)
}
// buid the router using user's selection build policy
s.Router.build(s.policies.RouterBuilderPolicy)
}
}
}})
2016-10-02 02:30:37 +02:00
}
2016-05-30 16:08:09 +02:00
{
// +------------------------------------------------------------+
// | 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)
2016-09-16 20:16:48 +02:00
}
}
// 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 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
2016-09-16 20:16:48 +02:00
}
// 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.isRunning() {
return errors.New("Server is already started and listening")
}
// maybe a 'race' here but user should not call .Serve more than one time especially in more than one go routines...
s.ln = ln
s.Boot()
2016-09-16 20:16:48 +02:00
// post any panics to the user defined logger.
defer func() {
if rerr := recover(); rerr != nil {
if x, ok := rerr.(*net.OpError); ok && x.Op == "accept" && s.closedManually {
///TODO:
// here we don't report it back because the user called .Close manually.
// NOTES:
//
// I know that the best option to actual Close a server is
// by using a custom net.Listener and do it via channels on its Accept.
// BUT I am not doing this right now because as I'm learning the new go v1.8 will have a shutdown
// options by-default and we will use that instead.
// println("iris.go:355:recover but closed manually so we don't run the handler")
return
}
if err, ok := rerr.(error); ok {
s.handlePanic(err)
}
2016-09-16 20:16:48 +02:00
}
}()
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,
}
// 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() {
if !s.Config.DisableBanner {
bannerMessage := fmt.Sprintf("%s: Running at %s", time.Now().Format(s.Config.TimeFormat), s.Config.VHost)
// we don't print it via Logger because:
// 1. The banner is only 'useful' when the developer logs to terminal and no file
// 2. Prefix & LstdFlags options of the default s.Logger
2016-09-16 20:16:48 +02:00
fmt.Printf("%s\n\n%s\n", banner, 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)
<-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)
// .Close doesn't really closes but it releases the ip:port, wait for go1.8 and see comments on IsRunning
s.Close()
os.Exit(1)
}
2016-05-30 16:08:09 +02:00
// 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
}
2017-01-11 23:57:07 +01:00
// 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))
}
2016-05-30 16:08:09 +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
//
//
// 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))
}
// IsRunning returns true if server is running
func (s *Framework) isRunning() bool {
///TODO: this will change on gov1.8,
// Reseve or Restart and Close will be re-added again when 1.8 final release.
return s != nil && s.ln != nil && s.ln.Addr() != nil && s.ln.Addr().String() != ""
}
// Close is not working propetly but it releases the host:port.
func (s *Framework) Close() error {
if s.isRunning() {
s.closedManually = true
///TODO:
// This code below doesn't works without custom net listener which will work in a stop channel which will cost us performance.
// This will work on go v1.8 BUT FOR NOW make unexported reserve/reboot/restart in order to be non confusual for the user.
// Close need to be exported because whitebox tests are using this method to release the port.
return s.ln.Close()
}
return nil
}
// restart re-starts the server using the last .Serve's listener
// func (s *Framework) restart() error {
// ///TODO: See .close() notes
// return s.Serve(s.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)
}
}
2016-07-13 05:28:09 +02: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
}
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
2016-07-13 05:28:09 +02:00
}
// 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 ""
}
2016-07-13 05:28:09 +02: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
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 {
2017-01-10 07:54:39 +01:00
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
2016-05-30 16:08:09 +02:00
// 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
}
2016-05-30 16:08:09 +02:00
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
2016-05-30 16:08:09 +02:00
}
args = args[1:] // remove the subdomain part for the arguments,
2016-05-30 16:08:09 +02:00
}
if parsedPath := s.Path(routeName, args...); parsedPath != "" {
url = scheme + host + parsedPath
}
return
}
2016-05-30 16:08:09 +02: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 (
"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
}