iris/iris.go

1232 lines
45 KiB
Go
Raw Normal View History

//Package iris the fastest go web framework in (this) Earth.
///NOTE: When you see 'framework' or 'station' we mean the Iris web framework's main implementation.
2016-05-30 16:08:09 +02:00
//
//
// Basic usage
// ----------------------------------------------------------------------
//
// package main
//
// import "github.com/kataras/iris"
//
// func main() {
// iris.Get("/hi_json", func(c *iris.Context) {
// c.JSON(200, iris.Map{
// "Name": "Iris",
// "Age": 2,
// })
// })
// iris.Listen(":8080")
// }
//
// ----------------------------------------------------------------------
//
// package main
//
// import "github.com/kataras/iris"
//
// func main() {
// s1 := iris.New()
// s1.Get("/hi_json", func(c *iris.Context) {
// c.JSON(200, iris.Map{
// "Name": "Iris",
// "Age": 2,
// })
// })
//
// s2 := iris.New()
// s2.Get("/hi_raw_html", func(c *iris.Context) {
// c.HTML(iris.StatusOK, "<b> Iris </b> welcomes <h1>you!</h1>")
// })
//
// go s1.Listen(":8080")
// s2.Listen(":1993")
// }
//
// -----------------------------DOCUMENTATION----------------------------
// ----------------------------_______________---------------------------
// For middleware, templates, sessions, websockets, mails, subdomains,
// dynamic subdomains, routes, party of subdomains & routes and much more
// visit https://www.gitbook.com/book/kataras/iris/details
2016-05-30 16:08:09 +02:00
package iris
import (
"fmt"
2016-05-30 16:08:09 +02:00
"os"
"path"
"reflect"
"strconv"
"strings"
"time"
2016-05-30 16:08:09 +02:00
"github.com/kataras/iris/errors"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
2016-05-30 16:08:09 +02:00
)
const (
2016-05-31 10:05:42 +02:00
// Version of the iris
Version = "3.0.0-rc.1"
banner = ` _____ _
|_ _| (_)
| | ____ _ ___
| | | __|| |/ __|
_| |_| | | |\__ \
|_____|_| |_||___/ ` + Version + `
`
2016-05-30 16:08:09 +02:00
)
type (
// FrameworkAPI contains the main Iris Public API
FrameworkAPI interface {
MuxAPI
Must(error)
ListenWithErr(string) error
Listen(string)
ListenTLSWithErr(string, string, string) error
ListenTLS(string, string, string)
ListenUNIXWithErr(string, os.FileMode) error
ListenUNIX(string, os.FileMode)
NoListen() *Server
Close()
OnError(int, HandlerFunc)
EmitError(int, *Context)
Lookup(string) Route
Lookups() []Route
Path(string, ...interface{}) string
URL(string, ...interface{}) string
SendMail(string, string, ...string) error
TemplateString(string, interface{}, ...string) string
}
2016-05-30 16:08:09 +02:00
// RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route)
RouteNameFunc func(string)
// MuxAPI the visible api for the serveMux
MuxAPI interface {
Party(string, ...HandlerFunc) MuxAPI
2016-05-30 16:08:09 +02:00
Use(...Handler)
UseFunc(...HandlerFunc)
Handle(string, string, ...Handler) RouteNameFunc
HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
// H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
H_(string, string, func(context.IContext)) func(string)
API(string, HandlerAPI, ...HandlerFunc)
2016-05-30 16:08:09 +02:00
Get(string, ...HandlerFunc) RouteNameFunc
Post(string, ...HandlerFunc) RouteNameFunc
Put(string, ...HandlerFunc) RouteNameFunc
Delete(string, ...HandlerFunc) RouteNameFunc
Connect(string, ...HandlerFunc) RouteNameFunc
Head(string, ...HandlerFunc) RouteNameFunc
Options(string, ...HandlerFunc) RouteNameFunc
Patch(string, ...HandlerFunc) RouteNameFunc
Trace(string, ...HandlerFunc) RouteNameFunc
Any(string, ...HandlerFunc)
2016-05-30 16:08:09 +02:00
StaticHandler(string, int, bool, bool, []string) HandlerFunc
Static(string, string, int) RouteNameFunc
StaticFS(string, string, int) RouteNameFunc
StaticWeb(string, string, int) RouteNameFunc
StaticServe(string, ...string) RouteNameFunc
StaticContent(string, string, []byte) func(string)
Favicon(string, ...string) RouteNameFunc
2016-05-30 16:08:09 +02:00
}
)
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// --------------------------------Framework implementation-----------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
var _ FrameworkAPI = &Framework{}
// Must panics on error, it panics on registed iris' logger
func Must(err error) {
Default.Must(err)
}
// Must panics on error, it panics on registed iris' logger
func (s *Framework) Must(err error) {
if err != nil {
s.Logger.Panic(err.Error())
}
}
// ListenWithErr starts the standalone http server
// which listens to the addr parameter which as the form of
// host:port
2016-05-30 16:08:09 +02:00
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the Listen
// ex: log.Fatal(iris.ListenWithErr(":8080"))
func ListenWithErr(addr string) error {
return Default.ListenWithErr(addr)
}
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
//
// It panics on error if you need a func to return an error use the ListenWithErr
// ex: iris.Listen(":8080")
func Listen(addr string) {
Default.Listen(addr)
}
2016-05-30 16:08:09 +02:00
// ListenWithErr starts the standalone http server
// which listens to the addr parameter which as the form of
// host:port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the Listen
// ex: log.Fatal(iris.ListenWithErr(":8080"))
func (s *Framework) ListenWithErr(addr string) error {
s.Config.Server.ListeningAddr = addr
return s.openServer()
}
// Listen starts the standalone http server
// which listens to the addr parameter which as the form of
// host:port
//
// It panics on error if you need a func to return an error use the ListenWithErr
// ex: iris.Listen(":8080")
func (s *Framework) Listen(addr string) {
s.Must(s.ListenWithErr(addr))
}
2016-05-30 16:08:09 +02:00
// ListenTLSWithErr 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
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the ListenTLS
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
func ListenTLSWithErr(addr string, certFile string, keyFile string) error {
return Default.ListenTLSWithErr(addr, certFile, keyFile)
}
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
//
// It panics on error if you need a func to return an error use the ListenTLSWithErr
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
func ListenTLS(addr string, certFile string, keyFile string) {
Default.ListenTLS(addr, certFile, keyFile)
}
// ListenTLSWithErr 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
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the ListenTLS
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile string) error {
s.Config.Server.ListeningAddr = addr
s.Config.Server.CertFile = certFile
s.Config.Server.KeyFile = keyFile
return s.openServer()
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
//
// It panics on error if you need a func to return an error use the ListenTLSWithErr
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
s.Must(s.ListenTLSWithErr(addr, certFile, keyFile))
2016-05-30 16:08:09 +02:00
}
// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix
// returns an error if something bad happens when trying to listen to
func ListenUNIXWithErr(addr string, mode os.FileMode) error {
return Default.ListenUNIXWithErr(addr, mode)
}
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
// panics on error
func ListenUNIX(addr string, mode os.FileMode) {
Default.ListenUNIX(addr, mode)
}
// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix
// returns an error if something bad happens when trying to listen to
func (s *Framework) ListenUNIXWithErr(addr string, mode os.FileMode) error {
s.Config.Server.ListeningAddr = addr
s.Config.Server.Mode = mode
return s.openServer()
}
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
// panics on error
func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
s.Must(s.ListenUNIXWithErr(addr, mode))
}
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
func NoListen() *Server {
return Default.NoListen()
}
// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
func (s *Framework) NoListen() *Server {
s.initialize()
s.Plugins.DoPreListen(s)
s.Plugins.DoPostListen(s)
return s.HTTPServer
}
//Close terminates the server and panic if error occurs
func Close() {
Default.Close()
}
//Close terminates the server and panic if error occurs
func (s *Framework) Close() {
s.Must(s.closeServer())
}
// OnError registers a custom http error handler
func OnError(statusCode int, handlerFn HandlerFunc) {
Default.OnError(statusCode, handlerFn)
}
// EmitError fires a custom http error handler to the client
//
// if no custom error defined with this statuscode, then iris creates one, and once at runtime
func EmitError(statusCode int, ctx *Context) {
Default.EmitError(statusCode, ctx)
}
// OnError registers a custom http error handler
func (s *Framework) OnError(statusCode int, handlerFn HandlerFunc) {
s.mux.registerError(statusCode, handlerFn)
}
// EmitError fires a custom http error handler to the client
//
// if no custom error defined with this statuscode, then iris creates one, and once at runtime
func (s *Framework) EmitError(statusCode int, ctx *Context) {
s.mux.fireError(statusCode, ctx)
}
// Lookup returns a registed route by its name
func Lookup(routeName string) Route {
return Default.Lookup(routeName)
}
// Lookups returns all registed routes
func Lookups() []Route {
return Default.Lookups()
}
// Lookup returns a registed route by its name
func (s *Framework) Lookup(routeName string) Route {
return s.mux.lookup(routeName)
}
// Lookups returns all registed routes
func (s *Framework) Lookups() (routes []Route) {
// silly but...
for i := range s.mux.lookups {
routes = append(routes, s.mux.lookups[i])
2016-05-30 16:08:09 +02:00
}
return
}
2016-05-30 16:08: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 Path(routeName string, args ...interface{}) string {
return Default.Path(routeName, args...)
2016-05-30 16:08: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.mux.lookup(routeName)
if r == nil {
return ""
}
argsLen := len(args)
// we have named parameters but arguments not given
if argsLen == 0 && r.formattedParts > 0 {
return ""
} else if argsLen == 0 && r.formattedParts == 0 {
// it's static then just return the path
return r.path
}
// we have arguments but they are much more than the named parameters
// 1 check if we have /*, if yes then join all arguments to one as path and pass that as parameter
if argsLen > r.formattedParts {
if r.path[len(r.path)-1] == matchEverythingByte {
// we have to convert each argument to a string in this case
argsString := make([]string, argsLen, argsLen)
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:]...)
}
}
}
parameter := strings.Join(argsString, slash)
result := fmt.Sprintf(r.formattedPath, parameter)
return result
2016-05-30 16:08:09 +02:00
}
// 2 if !1 return false
return ""
2016-05-30 16:08:09 +02:00
}
arguments := args[0:]
// check for arrays
for i, v := range arguments {
if arr, ok := v.([]string); ok {
if len(arr) > 0 {
interfaceArr := make([]interface{}, len(arr))
for j, sv := range arr {
interfaceArr[j] = sv
}
arguments[i] = interfaceArr[0]
arguments = append(arguments, interfaceArr[1:]...)
}
2016-06-01 14:29:38 +02:00
}
}
return fmt.Sprintf(r.formattedPath, arguments...)
}
// URL returns the subdomain+ host + Path(...optional named parameters if route is dynamic)
// returns an empty string if parse is failed
func URL(routeName string, args ...interface{}) (url string) {
return Default.URL(routeName, args...)
2016-06-01 14:29:38 +02:00
}
// 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.mux.lookup(routeName)
if r == nil {
return
}
scheme := "http://"
if s.HTTPServer.IsSecure() {
scheme = "https://"
}
host := s.HTTPServer.VirtualHost()
arguments := args[0:]
// join arrays as arguments
for i, v := range arguments {
if arr, ok := v.([]string); ok {
if len(arr) > 0 {
interfaceArr := make([]interface{}, len(arr))
for j, sv := range arr {
interfaceArr[j] = sv
}
arguments[i] = interfaceArr[0]
arguments = append(arguments, interfaceArr[1:]...)
}
}
}
2016-05-30 16:08:09 +02:00
// if it's dynamic subdomain then the first argument is the subdomain part
if r.subdomain == dynamicSubdomainIndicator {
if len(arguments) == 0 { // it's a wildcard subdomain but not arguments
return
}
2016-05-30 16:08:09 +02:00
if subdomain, ok := arguments[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
}
arguments = arguments[1:]
2016-05-30 16:08:09 +02:00
}
if parsedPath := Path(routeName, arguments...); parsedPath != "" {
url = scheme + host + parsedPath
}
return
}
2016-05-30 16:08:09 +02:00
// SendMail sends a mail to recipients
// the body can be html also
func SendMail(subject string, body string, to ...string) error {
return Default.SendMail(subject, body, to...)
2016-05-30 16:08:09 +02:00
}
// SendMail sends a mail to recipients
// the body can be html also
func (s *Framework) SendMail(subject string, body string, to ...string) error {
s.prepareMailer()
return s.mailer.Send(subject, body, to...)
}
// TemplateString executes a template and returns its result as string, useful when you want it for sending rich e-mails
// returns empty string on error
func TemplateString(templateFile string, pageContext interface{}, layout ...string) string {
return Default.TemplateString(templateFile, pageContext, layout...)
}
2016-05-30 16:08:09 +02:00
// TemplateString executes a template and returns its result as string, useful when you want it for sending rich e-mails
// returns empty string on error
func (s *Framework) TemplateString(templateFile string, pageContext interface{}, layout ...string) string {
s.prepareTemplates()
res, err := s.templates.RenderString(templateFile, pageContext, layout...)
if err != nil {
return ""
2016-05-30 16:08:09 +02:00
}
return res
}
2016-05-30 16:08:09 +02:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// ----------------------------------MuxAPI implementation------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2016-05-30 16:08:09 +02:00
type muxAPI struct {
mux *serveMux
relativePath string
middleware Middleware
2016-05-30 16:08:09 +02:00
}
var (
// errAPIContextNotFound returns an error with message: 'From .API: "Context *iris.Context could not be found..'
errAPIContextNotFound = errors.New("From .API: Context *iris.Context could not be found.")
// errDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace'
errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")
)
var _ MuxAPI = &muxAPI{}
2016-05-30 16:08:09 +02:00
func pathIsSubdomain(s string) bool {
return strings.Index(s, subdomainIndicator) != -1
}
2016-05-30 16:08:09 +02:00
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
func Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI {
return Default.Party(relativePath, handlersFn...)
}
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
func (api *muxAPI) Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI {
parentPath := api.relativePath
dot := string(subdomainIndicator[0])
if len(parentPath) > 0 && parentPath[0] == slashByte && strings.HasSuffix(relativePath, dot) { // if ends with . , example: admin., it's subdomain->
parentPath = parentPath[1:] // remove first slash
2016-05-30 16:08:09 +02:00
}
fullpath := parentPath + relativePath
middleware := convertToHandlers(handlersFn)
// append the parent's +child's handlers
middleware = joinMiddleware(api.middleware, middleware)
return &muxAPI{relativePath: fullpath, mux: api.mux, middleware: middleware}
2016-05-30 16:08:09 +02:00
}
// Use registers a Handler middleware
func Use(handlers ...Handler) {
Default.Use(handlers...)
}
// UseFunc registers a HandlerFunc middleware
func UseFunc(handlersFn ...HandlerFunc) {
Default.UseFunc(handlersFn...)
}
// Use registers a Handler middleware
func (api *muxAPI) Use(handlers ...Handler) {
api.middleware = append(api.middleware, handlers...)
}
// UseFunc registers a HandlerFunc middleware
func (api *muxAPI) UseFunc(handlersFn ...HandlerFunc) {
api.Use(convertToHandlers(handlersFn)...)
}
// Handle registers a route to the server's router
// if empty method is passed then registers handler(s) for all methods, same as .Any, but returns nil as result
func Handle(method string, registedPath string, handlers ...Handler) RouteNameFunc {
return Default.Handle(method, registedPath, handlers...)
}
// HandleFunc registers and returns a route with a method string, path string and a handler
// registedPath is the relative url path
func HandleFunc(method string, registedPath string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.HandleFunc(method, registedPath, handlersFn...)
}
// Handle registers a route to the server's router
// if empty method is passed then registers handler(s) for all methods, same as .Any, but returns nil as result
func (api *muxAPI) Handle(method string, registedPath string, handlers ...Handler) RouteNameFunc {
if method == "" { // then use like it was .Any
for _, k := range AllMethods {
api.Handle(k, registedPath, handlers...)
}
return nil
}
fullpath := api.relativePath + registedPath // keep the last "/" if any, "/xyz/"
middleware := joinMiddleware(api.middleware, handlers)
// here we separate the subdomain and relative path
subdomain := ""
path := fullpath
if dotWSlashIdx := strings.Index(path, subdomainIndicator); dotWSlashIdx > 0 {
subdomain = fullpath[0 : dotWSlashIdx+1] // admin.
path = fullpath[dotWSlashIdx+1:] // /
}
path = strings.Replace(path, "//", "/", -1) // fix the path if double //
return api.mux.register([]byte(method), subdomain, path, middleware).setName
}
// HandleFunc registers and returns a route with a method string, path string and a handler
// registedPath is the relative url path
func (api *muxAPI) HandleFunc(method string, registedPath string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.Handle(method, registedPath, convertToHandlers(handlersFn)...)
}
// H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
func (api *muxAPI) H_(method string, registedPath string, fn func(context.IContext)) func(string) {
return api.HandleFunc(method, registedPath, func(ctx *Context) {
fn(ctx)
})
}
// API converts & registers a custom struct to the router
// receives two parameters
// first is the request path (string)
// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field.
// third is the common middlewares, it's optional
2016-05-30 16:08:09 +02:00
//
// Note that API's routes have their default-name to the full registed path,
// no need to give a special name for it, because it's not supposed to be used inside your templates.
//
// Recommend to use when you retrieve data from an external database,
// and the router-performance is not the (only) thing which slows the server's overall performance.
//
// This is a slow method, if you care about router-performance use the Handle/HandleFunc/Get/Post/Put/Delete/Trace/Options... instead
func API(path string, restAPI HandlerAPI, middleware ...HandlerFunc) {
Default.API(path, restAPI, middleware...)
2016-05-30 16:08:09 +02:00
}
// API converts & registers a custom struct to the router
// receives two parameters
// first is the request path (string)
// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field.
// third is the common middleware, it's optional
2016-05-30 16:08:09 +02:00
//
// Note that API's routes have their default-name to the full registed path,
// no need to give a special name for it, because it's not supposed to be used inside your templates.
//
// Recommend to use when you retrieve data from an external database,
// and the router-performance is not the (only) thing which slows the server's overall performance.
//
// This is a slow method, if you care about router-performance use the Handle/HandleFunc/Get/Post/Put/Delete/Trace/Options... instead
func (api *muxAPI) API(path string, restAPI HandlerAPI, middleware ...HandlerFunc) {
// here we need to find the registed methods and convert them to handler funcs
// methods are collected by method naming: Get(),GetBy(...), Post(),PostBy(...), Put() and so on
typ := reflect.ValueOf(restAPI).Type()
contextField, found := typ.FieldByName("Context")
if !found {
panic(errAPIContextNotFound.Return())
}
// check & register the Get(),Post(),Put(),Delete() and so on
for _, methodName := range AllMethods {
methodCapitalName := strings.Title(strings.ToLower(methodName))
if method, found := typ.MethodByName(methodCapitalName); found {
methodFunc := method.Func
if !methodFunc.IsValid() || methodFunc.Type().NumIn() > 1 { // for any case
continue
}
func(path string, typ reflect.Type, contextField reflect.StructField, methodFunc reflect.Value, method string) {
var handlersFn []HandlerFunc
handlersFn = append(handlersFn, middleware...)
handlersFn = append(handlersFn, func(ctx *Context) {
newController := reflect.New(typ).Elem()
newController.FieldByName("Context").Set(reflect.ValueOf(ctx))
methodFunc.Call([]reflect.Value{newController})
})
// register route
api.HandleFunc(method, path, handlersFn...)
}(path, typ, contextField, methodFunc, methodName)
}
}
// check for GetBy/PostBy(id string, something_else string) , these must be requested by the same order.
// (we could do this in the same top loop but I don't want)
// GET, DELETE -> with url named parameters (/users/:id/:secondArgumentIfExists)
// POST, PUT -> with post values (form)
// all other with URL Parameters (?something=this&else=other
//
// or no, I changed my mind, let all be named parameters and let users to decide what info they need,
// using the Context to take more values (post form,url params and so on).-
for _, methodName := range AllMethods {
methodWithBy := strings.Title(strings.ToLower(methodName)) + "By"
if method, found := typ.MethodByName(methodWithBy); found {
methodFunc := method.Func
if !methodFunc.IsValid() || methodFunc.Type().NumIn() < 2 { //it's By but it has not receive any arguments so its not api's
continue
}
methodFuncType := methodFunc.Type()
numInLen := methodFuncType.NumIn() // how much data we should receive from the request
registedPath := path
for i := 1; i < numInLen; i++ { // from 1 because the first is the 'object'
if registedPath[len(registedPath)-1] == slashByte {
registedPath += ":param" + strconv.Itoa(i)
} else {
registedPath += "/:param" + strconv.Itoa(i)
}
}
func(registedPath string, typ reflect.Type, contextField reflect.StructField, methodFunc reflect.Value, paramsLen int, method string) {
var handlersFn []HandlerFunc
handlersFn = append(handlersFn, middleware...)
handlersFn = append(handlersFn, func(ctx *Context) {
newController := reflect.New(typ).Elem()
newController.FieldByName("Context").Set(reflect.ValueOf(ctx))
args := make([]reflect.Value, paramsLen+1, paramsLen+1)
args[0] = newController
for i := 0; i < paramsLen; i++ {
args[i+1] = reflect.ValueOf(ctx.Params[i].Value)
}
methodFunc.Call(args)
})
// register route
api.HandleFunc(method, registedPath, handlersFn...)
}(registedPath, typ, contextField, methodFunc, numInLen-1, methodName)
}
2016-05-30 16:08:09 +02:00
}
2016-05-30 16:08:09 +02:00
}
// Get registers a route for the Get http method
func Get(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.Get(path, handlersFn...)
}
// Post registers a route for the Post http method
func Post(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.Post(path, handlersFn...)
}
// Put registers a route for the Put http method
func Put(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.Put(path, handlersFn...)
}
// Delete registers a route for the Delete http method
func Delete(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.Delete(path, handlersFn...)
}
// Connect registers a route for the Connect http method
func Connect(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.Connect(path, handlersFn...)
}
// Head registers a route for the Head http method
func Head(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.Head(path, handlersFn...)
}
// Options registers a route for the Options http method
func Options(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.Options(path, handlersFn...)
}
// Patch registers a route for the Patch http method
func Patch(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.Patch(path, handlersFn...)
}
// Trace registers a route for the Trace http method
func Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return Default.Trace(path, handlersFn...)
}
// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
func Any(registedPath string, handlersFn ...HandlerFunc) {
Default.Any(registedPath, handlersFn...)
}
// Get registers a route for the Get http method
func (api *muxAPI) Get(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.HandleFunc(MethodGet, path, handlersFn...)
}
// Post registers a route for the Post http method
func (api *muxAPI) Post(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.HandleFunc(MethodPost, path, handlersFn...)
}
// Put registers a route for the Put http method
func (api *muxAPI) Put(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.HandleFunc(MethodPut, path, handlersFn...)
}
// Delete registers a route for the Delete http method
func (api *muxAPI) Delete(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.HandleFunc(MethodDelete, path, handlersFn...)
}
// Connect registers a route for the Connect http method
func (api *muxAPI) Connect(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.HandleFunc(MethodConnect, path, handlersFn...)
}
// Head registers a route for the Head http method
func (api *muxAPI) Head(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.HandleFunc(MethodHead, path, handlersFn...)
}
// Options registers a route for the Options http method
func (api *muxAPI) Options(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.HandleFunc(MethodOptions, path, handlersFn...)
}
// Patch registers a route for the Patch http method
func (api *muxAPI) Patch(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.HandleFunc(MethodPatch, path, handlersFn...)
}
// Trace registers a route for the Trace http method
func (api *muxAPI) Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc {
return api.HandleFunc(MethodTrace, path, handlersFn...)
}
// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
func (api *muxAPI) Any(registedPath string, handlersFn ...HandlerFunc) {
for _, k := range AllMethods {
api.HandleFunc(k, registedPath, handlersFn...)
}
}
// StaticHandler returns a Handler to serve static system directory
// Accepts 5 parameters
2016-05-30 16:08:09 +02:00
//
// first is the systemPath (string)
// Path to the root directory to serve files from.
//
// second is the stripSlashes (int) level
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
//
// third is the compress (bool)
// Transparently compresses responses if set to true.
//
// The server tries minimizing CPU usage by caching compressed files.
// It adds fasthttp.FSCompressedFileSuffix suffix to the original file name and
// tries saving the resulting compressed file under the new file name.
// So it is advisable to give the server write access to Root
// and to all inner folders in order to minimze CPU usage when serving
// compressed responses.
//
// fourth is the generateIndexPages (bool)
// Index pages for directories without files matching IndexNames
// are automatically generated if set.
//
// Directory index generation may be quite slow for directories
// with many files (more than 1K), so it is discouraged enabling
// index pages' generation for such directories.
//
// fifth is the indexNames ([]string)
// List of index file names to try opening during directory access.
//
// For example:
//
// * index.html
// * index.htm
// * my-super-index.xml
//
func StaticHandler(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc {
return Default.StaticHandler(systemPath, stripSlashes, compress, generateIndexPages, indexNames)
2016-05-30 16:08:09 +02:00
}
// StaticHandler returns a Handler to serve static system directory
// Accepts 5 parameters
2016-05-30 16:08:09 +02:00
//
// first is the systemPath (string)
// Path to the root directory to serve files from.
//
// second is the stripSlashes (int) level
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
//
// third is the compress (bool)
// Transparently compresses responses if set to true.
//
// The server tries minimizing CPU usage by caching compressed files.
// It adds fasthttp.FSCompressedFileSuffix suffix to the original file name and
// tries saving the resulting compressed file under the new file name.
// So it is advisable to give the server write access to Root
// and to all inner folders in order to minimze CPU usage when serving
// compressed responses.
//
// fourth is the generateIndexPages (bool)
// Index pages for directories without files matching IndexNames
// are automatically generated if set.
//
// Directory index generation may be quite slow for directories
// with many files (more than 1K), so it is discouraged enabling
// index pages' generation for such directories.
//
// fifth is the indexNames ([]string)
// List of index file names to try opening during directory access.
//
// For example:
//
// * index.html
// * index.htm
// * my-super-index.xml
//
func (api *muxAPI) StaticHandler(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc {
if indexNames == nil {
indexNames = []string{}
}
fs := &fasthttp.FS{
// Path to directory to serve.
Root: systemPath,
IndexNames: indexNames,
// Generate index pages if client requests directory contents.
GenerateIndexPages: generateIndexPages,
// Enable transparent compression to save network traffic.
Compress: compress,
CacheDuration: config.StaticCacheDuration,
CompressedFileSuffix: config.CompressedFileSuffix,
}
if stripSlashes > 0 {
fs.PathRewrite = fasthttp.NewPathSlashesStripper(stripSlashes)
2016-05-30 16:08:09 +02:00
}
// Create request handler for serving static files.
h := fs.NewRequestHandler()
return HandlerFunc(func(ctx *Context) {
h(ctx.RequestCtx)
errCode := ctx.RequestCtx.Response.StatusCode()
if errCode == StatusNotFound || errCode == StatusBadRequest || errCode == StatusInternalServerError {
api.mux.fireError(errCode, ctx)
}
if ctx.pos < uint8(len(ctx.middleware))-1 {
ctx.Next() // for any case
}
})
2016-05-30 16:08:09 +02:00
}
// Static registers a route which serves a system directory
// this doesn't generates an index page which list all files
// no compression is used also, for these features look at StaticFS func
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
func Static(relative string, systemPath string, stripSlashes int) RouteNameFunc {
return Default.Static(relative, systemPath, stripSlashes)
2016-05-30 16:08:09 +02:00
}
// Static registers a route which serves a system directory
// this doesn't generates an index page which list all files
// no compression is used also, for these features look at StaticFS func
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
func (api *muxAPI) Static(relative string, systemPath string, stripSlashes int) RouteNameFunc {
if relative[len(relative)-1] != slashByte { // if / then /*filepath, if /something then /something/*filepath
relative += slash
2016-05-30 16:08:09 +02:00
}
h := api.StaticHandler(systemPath, stripSlashes, false, false, nil)
api.Head(relative+"*filepath", h)
return api.Get(relative+"*filepath", h)
2016-05-30 16:08:09 +02:00
}
// StaticFS registers a route which serves a system directory
// this is the fastest method to serve static files
// generates an index page which list all files
// if you use this method it will generate compressed files also
// think this function as small fileserver with http
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
func StaticFS(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
return Default.StaticFS(reqPath, systemPath, stripSlashes)
2016-05-30 16:08:09 +02:00
}
// StaticFS registers a route which serves a system directory
// this is the fastest method to serve static files
// generates an index page which list all files
// if you use this method it will generate compressed files also
// think this function as small fileserver with http
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
func (api *muxAPI) StaticFS(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
if reqPath[len(reqPath)-1] != slashByte {
reqPath += "/"
}
h := api.StaticHandler(systemPath, stripSlashes, true, true, nil)
api.Head(reqPath+"*filepath", h)
return api.Get(reqPath+"*filepath", h)
2016-05-30 16:08:09 +02:00
}
// StaticWeb same as Static but if index.html exists and request uri is '/' then display the index.html's contents
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
// * if you don't know what to put on stripSlashes just 1
func StaticWeb(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
return Default.StaticWeb(reqPath, systemPath, stripSlashes)
2016-05-30 16:08:09 +02:00
}
// StaticWeb same as Static but if index.html exists and request uri is '/' then display the index.html's contents
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
// * if you don't know what to put on stripSlashes just 1
func (api *muxAPI) StaticWeb(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
if reqPath[len(reqPath)-1] != slashByte { // if / then /*filepath, if /something then /something/*filepath
reqPath += "/"
}
hasIndex := utils.Exists(systemPath + utils.PathSeparator + "index.html")
serveHandler := api.StaticHandler(systemPath, stripSlashes, false, !hasIndex, nil) // if not index.html exists then generate index.html which shows the list of files
indexHandler := func(ctx *Context) {
if len(ctx.Param("filepath")) < 2 && hasIndex {
ctx.Request.SetRequestURI("index.html")
}
ctx.Next()
}
api.Head(reqPath+"*filepath", indexHandler, serveHandler)
return api.Get(reqPath+"*filepath", indexHandler, serveHandler)
}
// StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions
// Almost same usage as StaticWeb
// accepts only one required parameter which is the systemPath ( the same path will be used to register the GET&HEAD routes)
// if second parameter is empty, otherwise the requestPath is the second parameter
// it uses gzip compression (compression on each request, no file cache)
func StaticServe(systemPath string, requestPath ...string) RouteNameFunc {
return Default.StaticServe(systemPath, requestPath...)
}
// StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions
// Almost same usage as StaticWeb
// accepts only one required parameter which is the systemPath ( the same path will be used to register the GET&HEAD routes)
// if second parameter is empty, otherwise the requestPath is the second parameter
// it uses gzip compression (compression on each request, no file cache)
func (api *muxAPI) StaticServe(systemPath string, requestPath ...string) RouteNameFunc {
var reqPath string
if len(requestPath) == 0 {
reqPath = strings.Replace(systemPath, utils.PathSeparator, slash, -1) // replaces any \ to /
reqPath = strings.Replace(reqPath, "//", slash, -1) // for any case, replaces // to /
reqPath = strings.Replace(reqPath, ".", "", -1) // replace any dots (./mypath -> /mypath)
} else {
reqPath = requestPath[0]
}
return api.Get(reqPath+"/*file", func(ctx *Context) {
filepath := ctx.Param("file")
spath := strings.Replace(filepath, "/", utils.PathSeparator, -1)
spath = path.Join(systemPath, spath)
if !utils.DirectoryExists(spath) {
ctx.NotFound()
return
}
ctx.ServeFile(spath, true)
})
2016-05-30 16:08:09 +02:00
}
// StaticContent serves bytes, memory cached, on the reqPath
// a good example of this is how the websocket server uses that to auto-register the /iris-ws.js
func StaticContent(reqPath string, contentType string, content []byte) RouteNameFunc {
return Default.StaticContent(reqPath, contentType, content)
2016-05-30 16:08:09 +02:00
}
// StaticContent serves bytes, memory cached, on the reqPath
// a good example of this is how the websocket server uses that to auto-register the /iris-ws.js
func (api *muxAPI) StaticContent(reqPath string, cType string, content []byte) func(string) { // func(string) because we use that on websockets
modtime := time.Now()
modtimeStr := modtime.UTC().Format(config.TimeFormat)
h := func(ctx *Context) {
if t, err := time.Parse(config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(config.StaticCacheDuration)) {
ctx.Response.Header.Del(contentType)
ctx.Response.Header.Del(contentLength)
ctx.SetStatusCode(StatusNotModified)
return
}
ctx.Response.Header.Set(contentType, cType)
ctx.Response.Header.Set(lastModified, modtimeStr)
ctx.SetStatusCode(StatusOK)
ctx.Response.SetBody(content)
}
api.Head(reqPath, h)
return api.Get(reqPath, h)
2016-05-30 16:08:09 +02:00
}
// Favicon serves static favicon
// accepts 2 parameters, second is optional
// favPath (string), declare the system directory path of the __.ico
// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
//
// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
//
// panics on error
func Favicon(favPath string, requestPath ...string) RouteNameFunc {
return Default.Favicon(favPath, requestPath...)
2016-05-30 16:08:09 +02:00
}
2016-06-01 14:29:38 +02:00
// Favicon serves static favicon
// accepts 2 parameters, second is optional
// favPath (string), declare the system directory path of the __.ico
// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
//
// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
//
// panics on error
func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc {
f, err := os.Open(favPath)
if err != nil {
panic(errDirectoryFileNotFound.Format(favPath, err.Error()))
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() { // if it's dir the try to get the favicon.ico
fav := path.Join(favPath, "favicon.ico")
f, err = os.Open(fav)
if err != nil {
//we try again with .png
return api.Favicon(path.Join(favPath, "favicon.png"))
}
favPath = fav
fi, _ = f.Stat()
}
modtime := fi.ModTime().UTC().Format(config.TimeFormat)
cType := utils.TypeByExtension(favPath)
// copy the bytes here in order to cache and not read the ico on each request.
cacheFav := make([]byte, fi.Size())
if _, err = f.Read(cacheFav); err != nil {
panic(errDirectoryFileNotFound.Format(favPath, "Couldn't read the data bytes for Favicon: "+err.Error()))
}
h := func(ctx *Context) {
if t, err := time.Parse(config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && fi.ModTime().Before(t.Add(config.StaticCacheDuration)) {
ctx.Response.Header.Del(contentType)
ctx.Response.Header.Del(contentLength)
ctx.SetStatusCode(StatusNotModified)
return
}
ctx.Response.Header.Set(contentType, cType)
ctx.Response.Header.Set(lastModified, modtime)
ctx.SetStatusCode(StatusOK)
ctx.Response.SetBody(cacheFav)
}
reqPath := "/favicon" + path.Ext(fi.Name()) //we could use the filename, but because standards is /favicon.ico/.png.
if len(requestPath) > 0 {
reqPath = requestPath[0]
}
api.Head(reqPath, h)
return api.Get(reqPath, h)
2016-06-01 14:29:38 +02:00
}