mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
626 lines
22 KiB
Go
626 lines
22 KiB
Go
package iris
|
|
|
|
import (
|
|
"path"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
"github.com/kataras/iris/config"
|
|
"github.com/kataras/iris/context"
|
|
"github.com/kataras/iris/utils"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
type (
|
|
// IParty is the interface which implements the whole Party of routes
|
|
IParty interface {
|
|
Handle(string, string, ...Handler) IRoute
|
|
HandleFunc(string, string, ...HandlerFunc) IRoute
|
|
API(path string, controller HandlerAPI, middlewares ...HandlerFunc) error
|
|
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) []IRoute
|
|
Use(...Handler)
|
|
UseFunc(...HandlerFunc)
|
|
StaticHandlerFunc(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc
|
|
Static(string, string, int)
|
|
StaticFS(string, string, int)
|
|
StaticWeb(relative string, systemPath string, stripSlashes int)
|
|
StaticServe(systemPath string, requestPath ...string)
|
|
Party(string, ...HandlerFunc) IParty // Each party can have a party too
|
|
IsRoot() bool
|
|
}
|
|
|
|
// GardenParty is the struct which makes all the job for registering routes and middlewares
|
|
GardenParty struct {
|
|
relativePath string
|
|
station *Iris // this station is where the party is happening, this station's Garden is the same for all Parties per Station & Router instance
|
|
middleware Middleware
|
|
root bool
|
|
}
|
|
)
|
|
|
|
var _ IParty = &GardenParty{}
|
|
|
|
// IsRoot returns true if this is the root party ("/")
|
|
func (p *GardenParty) IsRoot() bool {
|
|
return p.root
|
|
}
|
|
|
|
// 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 (p *GardenParty) Handle(method string, registedPath string, handlers ...Handler) IRoute {
|
|
if method == "" { // then use like it was .Any
|
|
for _, k := range AllMethods {
|
|
p.Handle(k, registedPath, handlers...)
|
|
}
|
|
return nil
|
|
}
|
|
path := fixPath(p.relativePath + registedPath) // keep the last "/" as default ex: "/xyz/"
|
|
if !p.station.config.DisablePathCorrection {
|
|
// if we have path correction remove it with absPath
|
|
path = fixPath(absPath(p.relativePath, registedPath)) // "/xyz"
|
|
}
|
|
middleware := JoinMiddleware(p.middleware, handlers)
|
|
route := NewRoute(method, path, middleware)
|
|
p.station.plugins.DoPreHandle(route)
|
|
p.station.addRoute(route)
|
|
p.station.plugins.DoPostHandle(route)
|
|
return route
|
|
}
|
|
|
|
// HandleFunc registers and returns a route with a method string, path string and a handler
|
|
// registedPath is the relative url path
|
|
// handler is the iris.Handler which you can pass anything you want via iris.ToHandlerFunc(func(res,req){})... or just use func(c *iris.Context)
|
|
func (p *GardenParty) HandleFunc(method string, registedPath string, handlersFn ...HandlerFunc) IRoute {
|
|
return p.Handle(method, registedPath, ConvertToHandlers(handlersFn)...)
|
|
}
|
|
|
|
// 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 are the common middlewares, is optional parameter
|
|
//
|
|
// 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
|
|
//
|
|
// Usage:
|
|
// All the below methods are optional except the *iris.Context field,
|
|
// example with /users :
|
|
/*
|
|
|
|
package main
|
|
|
|
import (
|
|
"github.com/kataras/iris"
|
|
)
|
|
|
|
type UserAPI struct {
|
|
*iris.Context
|
|
}
|
|
|
|
// GET /users
|
|
func (u UserAPI) Get() {
|
|
u.Write("Get from /users")
|
|
// u.JSON(iris.StatusOK,myDb.AllUsers())
|
|
}
|
|
|
|
// GET /:param1 which its value passed to the id argument
|
|
func (u UserAPI) GetBy(id string) { // id equals to u.Param("param1")
|
|
u.Write("Get from /users/%s", id)
|
|
// u.JSON(iris.StatusOK, myDb.GetUserById(id))
|
|
|
|
}
|
|
|
|
// PUT /users
|
|
func (u UserAPI) Put() {
|
|
name := u.FormValue("name")
|
|
// myDb.InsertUser(...)
|
|
println(string(name))
|
|
println("Put from /users")
|
|
}
|
|
|
|
// POST /users/:param1
|
|
func (u UserAPI) PostBy(id string) {
|
|
name := u.FormValue("name") // you can still use the whole Context's features!
|
|
// myDb.UpdateUser(...)
|
|
println(string(name))
|
|
println("Post from /users/" + id)
|
|
}
|
|
|
|
// DELETE /users/:param1
|
|
func (u UserAPI) DeleteBy(id string) {
|
|
// myDb.DeleteUser(id)
|
|
println("Delete from /" + id)
|
|
}
|
|
|
|
func main() {
|
|
iris.API("/users", UserAPI{})
|
|
iris.Listen(":80")
|
|
}
|
|
*/
|
|
func (p *GardenParty) API(path string, controller HandlerAPI, middlewares ...HandlerFunc) error {
|
|
// 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(controller).Type()
|
|
contextField, found := typ.FieldByName("Context")
|
|
if !found {
|
|
return ErrControllerContextNotFound.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, middlewares...)
|
|
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
|
|
p.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, middlewares...)
|
|
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
|
|
p.HandleFunc(method, registedPath, handlersFn...)
|
|
}(registedPath, typ, contextField, methodFunc, numInLen-1, methodName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get registers a route for the Get http method
|
|
func (p *GardenParty) Get(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|
return p.HandleFunc(MethodGet, path, handlersFn...).Name
|
|
}
|
|
|
|
// Post registers a route for the Post http method
|
|
func (p *GardenParty) Post(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|
return p.HandleFunc(MethodPost, path, handlersFn...).Name
|
|
}
|
|
|
|
// Put registers a route for the Put http method
|
|
func (p *GardenParty) Put(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|
return p.HandleFunc(MethodPut, path, handlersFn...).Name
|
|
}
|
|
|
|
// Delete registers a route for the Delete http method
|
|
func (p *GardenParty) Delete(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|
return p.HandleFunc(MethodDelete, path, handlersFn...).Name
|
|
}
|
|
|
|
// Connect registers a route for the Connect http method
|
|
func (p *GardenParty) Connect(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|
return p.HandleFunc(MethodConnect, path, handlersFn...).Name
|
|
}
|
|
|
|
// Head registers a route for the Head http method
|
|
func (p *GardenParty) Head(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|
return p.HandleFunc(MethodHead, path, handlersFn...).Name
|
|
}
|
|
|
|
// Options registers a route for the Options http method
|
|
func (p *GardenParty) Options(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|
return p.HandleFunc(MethodOptions, path, handlersFn...).Name
|
|
}
|
|
|
|
// Patch registers a route for the Patch http method
|
|
func (p *GardenParty) Patch(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|
return p.HandleFunc(MethodPatch, path, handlersFn...).Name
|
|
}
|
|
|
|
// Trace registers a route for the Trace http method
|
|
func (p *GardenParty) Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|
return p.HandleFunc(MethodTrace, path, handlersFn...).Name
|
|
}
|
|
|
|
// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
|
|
func (p *GardenParty) Any(registedPath string, handlersFn ...HandlerFunc) []IRoute {
|
|
theRoutes := make([]IRoute, len(AllMethods), len(AllMethods))
|
|
for idx, k := range AllMethods {
|
|
r := p.HandleFunc(k, registedPath, handlersFn...)
|
|
theRoutes[idx] = r
|
|
}
|
|
|
|
return theRoutes
|
|
}
|
|
|
|
// 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 (p *GardenParty) H_(method string, registedPath string, fn func(context.IContext)) {
|
|
p.HandleFunc(method, registedPath, func(ctx *Context) {
|
|
fn(ctx)
|
|
})
|
|
}
|
|
|
|
// Use registers a Handler middleware
|
|
func (p *GardenParty) Use(handlers ...Handler) {
|
|
p.middleware = append(p.middleware, handlers...)
|
|
}
|
|
|
|
// UseFunc registers a HandlerFunc middleware
|
|
func (p *GardenParty) UseFunc(handlersFn ...HandlerFunc) {
|
|
p.Use(ConvertToHandlers(handlersFn)...)
|
|
}
|
|
|
|
// StaticHandlerFunc returns a HandlerFunc to serve static system directory
|
|
// Accepts 5 parameters
|
|
//
|
|
// 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 (p *GardenParty) StaticHandlerFunc(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)
|
|
}
|
|
|
|
// Create request handler for serving static files.
|
|
h := fs.NewRequestHandler()
|
|
return func(ctx *Context) {
|
|
h(ctx.RequestCtx)
|
|
errCode := ctx.RequestCtx.Response.StatusCode()
|
|
|
|
if errHandler := ctx.station.router.GetByCode(errCode); errHandler != nil {
|
|
ctx.RequestCtx.Response.ResetBody()
|
|
ctx.EmitError(errCode)
|
|
}
|
|
if ctx.pos < uint8(len(ctx.middleware))-1 {
|
|
ctx.Next() // for any case
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// 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 (p *GardenParty) Static(relative string, systemPath string, stripSlashes int) {
|
|
if relative[len(relative)-1] != SlashByte { // if / then /*filepath, if /something then /something/*filepath
|
|
relative += Slash
|
|
}
|
|
|
|
h := p.StaticHandlerFunc(systemPath, stripSlashes, false, false, nil)
|
|
|
|
p.Get(relative+"*filepath", h)
|
|
p.Head(relative+"*filepath", h)
|
|
}
|
|
|
|
// 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 (p *GardenParty) StaticFS(reqPath string, systemPath string, stripSlashes int) {
|
|
if reqPath[len(reqPath)-1] != SlashByte {
|
|
reqPath += "/"
|
|
}
|
|
|
|
h := p.StaticHandlerFunc(systemPath, stripSlashes, true, true, nil)
|
|
p.Get(reqPath+"*filepath", h)
|
|
p.Head(reqPath+"*filepath", h)
|
|
}
|
|
|
|
// 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 (p *GardenParty) StaticWeb(reqPath string, systemPath string, stripSlashes int) {
|
|
if reqPath[len(reqPath)-1] != SlashByte { // if / then /*filepath, if /something then /something/*filepath
|
|
reqPath += "/"
|
|
}
|
|
|
|
hasIndex := utils.Exists(systemPath + utils.PathSeparator + "index.html")
|
|
serveHandler := p.StaticHandlerFunc(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()
|
|
|
|
}
|
|
p.Get(reqPath+"*filepath", indexHandler, serveHandler)
|
|
p.Head(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 (p *GardenParty) StaticServe(systemPath string, requestPath ...string) {
|
|
var reqPath string
|
|
|
|
if len(reqPath) > 0 {
|
|
reqPath = 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)
|
|
|
|
p.Get(reqPath+"/*file", func(ctx *Context) {
|
|
filepath := ctx.Param("file")
|
|
|
|
path := strings.Replace(filepath, "/", utils.PathSeparator, -1)
|
|
path = absPath(systemPath, path)
|
|
|
|
if !utils.DirectoryExists(path) {
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
|
|
ctx.ServeFile(path, true)
|
|
})
|
|
}
|
|
|
|
/* here in order to the subdomains be able to change favicon also */
|
|
|
|
// Favicon serves static favicon
|
|
// accepts 2 parameters, second is optionally
|
|
// 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)
|
|
//
|
|
// returns an error if something goes bad
|
|
func (p *GardenParty) Favicon(favPath string, requestPath ...string) error {
|
|
f, err := os.Open(favPath)
|
|
if err != nil {
|
|
return 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 p.Favicon(path.Join(favPath, "favicon.png"))
|
|
}
|
|
favPath = fav
|
|
fi, _ = f.Stat()
|
|
}
|
|
modtime := fi.ModTime().UTC().Format(TimeFormat)
|
|
contentType := 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 {
|
|
return ErrDirectoryFileNotFound.Format(favPath, "Couldn't read the data bytes from ico: "+err.Error())
|
|
}
|
|
|
|
h := func(ctx *Context) {
|
|
if t, err := time.Parse(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, contentType)
|
|
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]
|
|
}
|
|
p.Get(reqPath, h)
|
|
p.Head(reqPath, h)
|
|
return nil
|
|
}
|
|
|
|
// StaticContent serves bytes, memory cached, on the reqPath
|
|
func (p *GardenParty) StaticContent(reqPath string, contentType string, content []byte) {
|
|
modtime := time.Now()
|
|
modtimeStr := modtime.UTC().Format(TimeFormat)
|
|
|
|
h := func(ctx *Context) {
|
|
if t, err := time.Parse(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, contentType)
|
|
ctx.Response.Header.Set(LastModified, modtimeStr)
|
|
ctx.SetStatusCode(StatusOK)
|
|
ctx.Response.SetBody(content)
|
|
}
|
|
|
|
p.Get(reqPath, h)
|
|
p.Head(reqPath, h)
|
|
}
|
|
|
|
/* */
|
|
|
|
// 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 (p *GardenParty) Party(path string, handlersFn ...HandlerFunc) IParty {
|
|
middleware := ConvertToHandlers(handlersFn)
|
|
if path[0] != SlashByte && strings.Contains(path, ".") {
|
|
//it's domain so no handlers share (even the global ) or path, nothing.
|
|
} else {
|
|
// set path to parent+child
|
|
path = absPath(p.relativePath, path)
|
|
// append the parent's +child's handlers
|
|
middleware = JoinMiddleware(p.middleware, middleware)
|
|
}
|
|
|
|
return &GardenParty{relativePath: path, station: p.station, middleware: middleware}
|
|
}
|
|
|
|
func absPath(rootPath string, relativePath string) (absPath string) {
|
|
|
|
if relativePath == "" {
|
|
absPath = rootPath
|
|
} else {
|
|
absPath = path.Join(rootPath, relativePath)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// fixPath fix the double slashes, (because of root,I just do that before the .Handle no need for anything else special)
|
|
func fixPath(str string) string {
|
|
|
|
strafter := strings.Replace(str, "//", Slash, -1)
|
|
|
|
if strafter[0] == SlashByte && strings.Count(strafter, ".") >= 2 {
|
|
//it's domain, remove the first slash
|
|
strafter = strafter[1:]
|
|
}
|
|
|
|
return strafter
|
|
}
|