Update to 8.2.5 | Say Hello to Controllers. Read HISTORY.md

Former-commit-id: 70f8619440497d132362da86c5187bcc57f8687b
This commit is contained in:
kataras 2017-08-13 21:58:34 +03:00
parent 2786ca1960
commit 8cd07719a6
32 changed files with 1302 additions and 152 deletions

View File

@ -18,6 +18,86 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
# Su, 13 August 2017 | v8.2.5
Good news for devs that are used to write their web apps using the `MVC-style` app architecture.
Yesterday I wrote a [tutorial](tutorial/mvc-from-scratch) on how you can transform your raw `Handlers` to `Controllers` using the existing tools only ([Iris is the most modular web framework out there](https://medium.com/@corebreaker/iris-web-cd684b4685c7), we all have no doubt about this).
Today, I did implement the `Controller` idea as **built'n feature inside Iris**.
Our `Controller` supports many things among them are:
- all HTTP Methods are supported, for example if want to serve `GET` then the controller should have a function named `Get()`, you can define more than one method function to serve in the same Controller struct
- `persistence` data inside your Controller struct (share data between requests) via **`iris:"persistence"`** tag right to the field
- optional `Init` function to perform any initialization before the methods, useful to call middlewares or when many methods use the same collection of data
- access to the request path parameters via the `Params` field
- access to the template file that should be rendered via the `Tmpl` field
- access to the template data that should be rendered inside the template file via `Data` field
- access to the template layout via the `Layout` field
- access to the low-level `context.Context` via the `Ctx` field
- flow as you used to, `Controllers` can be registered to any `Party`, including Subdomains, the Party's begin and done handlers work as expected.
It's very easy to get started, the only function you need to call instead of `app.Get/Post/Put/Delete/Connect/Head/Patch/Options/Trace` is the `app.Controller`.
Example Code:
```go
// file: main.go
package main
import (
"github.com/kataras/iris"
"controllers"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
app.Controller("/", new(controllers.Index))
// http://localhost:8080/
app.Run(iris.Addr(":8080"))
}
```
```go
// file: controllers/index.go
package controllers
import (
"github.com/kataras/iris/core/router"
)
// Index is our index example controller.
type Index struct {
router.Controller
// if you're using go1.9:
// you can omit the /core/router import statement
// and just use the `iris.Controller` instead.
}
// will handle GET method on http://localhost:8080/
func (c *Index) Get() {
c.Tmpl = "index.html"
c.Data["title"] = "Index page"
c.Data["message"] = "Hello world!"
}
// will handle POST method on http://localhost:8080/
func (c *Index) Post() {}
```
> Tip: declare a func(c *Index) All() {} or Any() to register all HTTP Methods.
A full example can be found at the [_examples/routing/mvc](_examples/routing/mvc) folder.
# Sa, 12 August 2017 | v8.2.4
No API Changes.

View File

@ -26,11 +26,11 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a b
### 📑 Table of contents
* [Installation](#-installation)
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#th-12-august-2017--v824)
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#su-13-august-2017--v825)
* [Learn](#-learn)
* [HTTP Listening](_examples/#http-listening)
* [Configuration](_examples/#configuration)
* [Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context](_examples/#routing-grouping-dynamic-path-parameters-macros-and-custom-context)
* [Routing, Grouping, Controllers, Dynamic Path Parameters, "Macros" and Custom Context](_examples/#routing-grouping-dynamic-path-parameters-macros-and-custom-context)
* [Subdomains](_examples/#subdomains)
* [Wrap `http.Handler/HandlerFunc`](_examples/#convert-httphandlerhandlerfunc)
* [View](_examples/#view)
@ -47,7 +47,6 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a b
* [Tutorial: Online Visitors](_examples/tutorial/online-visitors)
* [Tutorial: URL Shortener using BoltDB](https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7)
* [Tutorial: How to turn your Android Device into a fully featured Web Server (**MUST**)](https://twitter.com/ThePracticalDev/status/892022594031017988)
* [Tutorial: Controllers from scratch (**Coming soon as built'n feature, probably at v8.3**)](_examples/tutorial/mvc)
* [POC: Convert the medium-sized project "Parrot" from native to Iris](https://github.com/iris-contrib/parrot)
* [Middleware](middleware/)
* [Dockerize](https://github.com/iris-contrib/cloud-native-go)
@ -139,17 +138,17 @@ func main() {
}
```
We expect Go version 1.9 to be released in August, however you can install Go 1.9 RC1 today.
We expect Go version 1.9 to be released in August, however you can install Go 1.9 RC2 today.
### Installing Go 1.9rc1
### Installing Go 1.9rc2
1. Go to https://golang.org/dl/#go1.9rc1
2. Download a compatible, with your OS, archive or executable, i.e `go1.9rc1.windows-amd64.zip`
3. Unzip the contents of `go1.9rc1.windows-amd64.zip` folder to your $GOROOT, i.e `C:\Go` or just execute the executable you've just download
4. Open a terminal and execute `go version`, it should output the go1.9rc1 version, i.e:
1. Go to https://golang.org/dl/#go1.9rc2
2. Download a compatible, with your OS, archive or executable, i.e `go1.9rc2.windows-amd64.zip`
3. Unzip the contents of `go1.9rc2.windows-amd64.zip` folder to your $GOROOT, i.e `C:\Go` or just execute the executable you've just download
4. Open a terminal and execute `go version`, it should output the go1.9rc2 version, i.e:
```sh
C:\Users\kataras>go version
go version go1.9rc1 windows/amd64
go version go1.9rc2 windows/amd64
```
</details>
@ -249,6 +248,7 @@ Compared to the rest open source projects, this one is very active and you get a
* Remove trailing slash from the URL with option to redirect
* Virtual hosts and subdomains made easy
* Group API's and static or even dynamic subdomains
* MVC [**NEW**](_examples/routing/mvc)
* `net/http` and `negroni-like` handlers are compatible via `iris.FromStd`
* Register custom handlers for any HTTP error
* Transactions and rollback when you need it

View File

@ -1 +1 @@
8.2.4:https://github.com/kataras/iris/blob/master/HISTORY.md#th-12-august-2017--v824
8.2.5:https://github.com/kataras/iris/blob/master/HISTORY.md#su-13-august-2017--v825

View File

@ -13,7 +13,6 @@ It doesn't always contain the "best ways" but it does cover each important featu
- [Tutorial: Online Visitors](tutorial/online-visitors/main.go)
- [Tutorial: URL Shortener using BoltDB](https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7)
- [Tutorial: How to turn your Android Device into a fully featured Web Server (**MUST**)](https://twitter.com/ThePracticalDev/status/892022594031017988)
- [Tutorial: Controllers from scratch (**Coming soon as built'n feature, probably at v8.3**)](tutorial/mvc)
### HTTP Listening
@ -83,6 +82,7 @@ Navigate through examples for a better understanding.
- [Overview](routing/overview/main.go)
- [Basic](routing/basic/main.go)
- [Controllers](routing/mvc)
- [Custom HTTP Errors](routing/http-errors/main.go)
- [Dynamic Path](routing/dynamic-path/main.go)
* [root level wildcard path](routing/dynamic-path/root-wildcard/main.go)

View File

@ -1,4 +1,4 @@
// +build go.1.8
// +build !go1.9
package main

View File

@ -0,0 +1,18 @@
// +build !go1.9
package controllers
import (
"github.com/kataras/iris/core/router"
)
// Index is our index example controller.
type Index struct {
router.Controller
}
func (c *Index) Get() {
c.Tmpl = "index.html"
c.Data["title"] = "Index page"
c.Data["message"] = "Hello world!"
}

View File

@ -0,0 +1,18 @@
// +build go1.9
package controllers
import (
"github.com/kataras/iris"
)
// Index is our index example controller.
type Index struct {
iris.Controller
}
func (c *Index) Get() {
c.Tmpl = "index.html"
c.Data["title"] = "Index page"
c.Data["message"] = "Hello world!"
}

View File

@ -0,0 +1,62 @@
// +build !go1.9
package controllers
import (
"time"
"github.com/kataras/iris/_examples/routing/mvc/persistence"
"github.com/kataras/iris/core/router"
)
// User is our user example controller.
type User struct {
router.Controller
// All fields with pointers(*) that are not nil
// and all fields that are tagged with iris:"persistence"`
// are being persistence and kept between the different requests,
// meaning that these data will not be reset-ed on each new request,
// they will be the same for all requests.
CreatedAt time.Time `iris:"persistence"`
Title string `iris:"persistence"`
DB *persistence.Database `iris:"persistence"`
}
func NewUserController(db *persistence.Database) *User {
return &User{
CreatedAt: time.Now(),
Title: "User page",
DB: db,
}
}
// Get serves using the User controller when HTTP Method is "GET".
func (c *User) Get() {
c.Tmpl = "user/index.html"
c.Data["title"] = c.Title
c.Data["username"] = "kataras " + c.Params.Get("userid")
c.Data["connstring"] = c.DB.Connstring
c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
}
/* Can use more than one, the factory will make sure
that the correct http methods are being registed for this
controller, uncommend these if you want:
func (c *User) Post() {}
func (c *User) Put() {}
func (c *User) Delete() {}
func (c *User) Connect() {}
func (c *User) Head() {}
func (c *User) Patch() {}
func (c *User) Options() {}
func (c *User) Trace() {}
*/
/*
func (c *User) All() {}
// OR
func (c *User) Any() {}
*/

View File

@ -0,0 +1,81 @@
// +build go1.9
package controllers
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/sessions"
)
// User is our user example controller.
type User struct {
iris.Controller
// All fields with pointers(*) that are not nil
// and all fields that are tagged with iris:"persistence"`
// are being persistence and kept between the different requests,
// meaning that these data will not be reset-ed on each new request,
// they will be the same for all requests.
CreatedAt time.Time `iris:"persistence"`
Title string `iris:"persistence"`
SessionManager *sessions.Sessions `iris:"persistence"`
Session *sessions.Session // not persistence
}
func NewUserController(sess *sessions.Sessions) *User {
return &User{
SessionManager: sess,
CreatedAt: time.Now(),
Title: "User page",
}
}
// Init can be used as a custom function
// to init the new instance of controller
// that is created on each new request.
//
// Useful when more than one methods are using the same
// request data.
func (c *User) Init(ctx iris.Context) {
c.Session = c.SessionManager.Start(ctx)
// println("session id: " + c.Session.ID())
}
// Get serves using the User controller when HTTP Method is "GET".
func (c *User) Get() {
c.Tmpl = "user/index.html"
c.Data["title"] = c.Title
c.Data["username"] = "kataras " + c.Params.Get("userid")
c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
visits, err := c.Session.GetInt("visit_count")
if err != nil {
visits = 0
}
visits++
c.Session.Set("visit_count", visits)
c.Data["visit_count"] = visits
}
/* Can use more than one, the factory will make sure
that the correct http methods are being registed for this
controller, uncommend these if you want:
func (c *User) Post() {}
func (c *User) Put() {}
func (c *User) Delete() {}
func (c *User) Connect() {}
func (c *User) Head() {}
func (c *User) Patch() {}
func (c *User) Options() {}
func (c *User) Trace() {}
*/
/*
func (c *User) All() {}
// OR
func (c *User) Any() {}
*/

View File

@ -0,0 +1,25 @@
// +build !go1.9
package main
import (
"github.com/kataras/iris/_examples/routing/mvc/controllers"
"github.com/kataras/iris/_examples/routing/mvc/persistence"
"github.com/kataras/iris"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
db := persistence.OpenDatabase("a fake db")
app.Controller("/", new(controllers.Index))
app.Controller("/user/{userid:int}", controllers.NewUserController(db))
// http://localhost:8080/
// http://localhost:8080/user/42
app.Run(iris.Addr(":8080"))
}

View File

@ -0,0 +1,28 @@
// +build go1.9
package main
import (
"github.com/kataras/iris/_examples/routing/mvc/controllers"
"github.com/kataras/iris"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/sessions/sessiondb/boltdb"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
sessionDb, _ := boltdb.New("./sessions/sessions.db", 0666, "users")
sess := sessions.New(sessions.Config{Cookie: "sessionscookieid"})
sess.UseDatabase(sessionDb.Async(true))
app.Controller("/", new(controllers.Index))
app.Controller("/user/{userid:int}", controllers.NewUserController(sess))
// http://localhost:8080/
// http://localhost:8080/user/42
app.Run(iris.Addr(":8080"))
}

View File

@ -0,0 +1,18 @@
<html>
<head>
<title>{{.title}}</title>
</head>
<body>
<h1> Hello {{.username}} </h1>
All fields inside a controller that are pointers or they tagged as `iris:"persistence"` are marked as persistence data, meaning
that they will not be reset-ed on each new request.
<h3>Persistence data from *DB.Connstring: {{.connstring}} </h3>
<h3>Persistence data from CreatedAt `iris:"persistence"`: {{.uptime}} seconds </h3>
<h3>{{.visit_count}}
</body>
</html>

View File

@ -0,0 +1,99 @@
# Controllers from scratch
This example folder shows how I started to develop
the Controller idea inside the Iris web framework itself.
Now it's built'n feature and can be used as:
```go
// +build go1.9
// file main.go
package main
import (
"github.com/kataras/iris/_examples/routing/mvc/persistence"
"github.com/kataras/iris"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
db := persistence.OpenDatabase("a fake db")
app.Controller("/user/{userid:int}", NewUserController(db))
// http://localhost:8080/
// http://localhost:8080/user/42
app.Run(iris.Addr(":8080"))
}
```
```go
// +build go1.9
// file user_controller.go
package main
import (
"time"
"github.com/kataras/iris/_examples/routing/mvc/persistence"
"github.com/kataras/iris"
)
// User is our user example controller.
type UserController struct {
iris.Controller
// All fields that are tagged with iris:"persistence"`
// are being persistence and kept between the different requests,
// meaning that these data will not be reset-ed on each new request,
// they will be the same for all requests.
CreatedAt time.Time `iris:"persistence"`
Title string `iris:"persistence"`
DB *persistence.Database `iris:"persistence"`
}
func NewUserController(db *persistence.Database) *User {
return &UserController{
CreatedAt: time.Now(),
Title: "User page",
DB: db,
}
}
// Get serves using the User controller when HTTP Method is "GET".
func (c *UserController) Get() {
c.Tmpl = "user/index.html"
c.Data["title"] = c.Title
c.Data["username"] = "kataras " + c.Params.Get("userid")
c.Data["connstring"] = c.DB.Connstring
c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
}
/* Can use more than one, the factory will make sure
that the correct http methods are being registed for this
controller, uncommend these if you want:
func (c *User) Post() {}
func (c *User) Put() {}
func (c *User) Delete() {}
func (c *User) Connect() {}
func (c *User) Head() {}
func (c *User) Patch() {}
func (c *User) Options() {}
func (c *User) Trace() {}
*/
/*
func (c *User) All() {}
// OR
func (c *User) Any() {}
*/
```
Example can be found at: [_examples/routing/mvc](https://github.com/kataras/iris/tree/master/_examples/routing/mvc).

View File

@ -3,7 +3,6 @@ package controllers
import (
"reflect"
"strings"
// "unsafe"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
@ -75,20 +74,14 @@ func RegisterController(app *iris.Application, path string, c interface{}) {
valF := val.Field(i)
// catch persistence data by tags, i.e:
// MyData string `iris:"persistence"`
// DB *DB `iris:"persistence"`
if t, ok := f.Tag.Lookup("iris"); ok {
if t == "persistence" {
persistenceFields[i] = reflect.ValueOf(val.Field(i).Interface())
persistenceFields[i] = reflect.ValueOf(valF.Interface())
continue
}
}
// catch persistence data by pointer, i.e:
// DB *Database
if f.Type.Kind() == reflect.Ptr {
if !valF.IsNil() {
persistenceFields[i] = reflect.ValueOf(val.Field(i).Interface())
}
}
}
}

View File

@ -3,24 +3,20 @@ package controllers
import (
"time"
"github.com/kataras/iris/_examples/tutorial/mvc/persistence"
"github.com/kataras/iris/_examples/tutorial/mvc-from-scratch/persistence"
)
// User is our user example controller.
type User struct {
Controller
// all fields with pointers(*)
// that are not nil
// and all fields with
// that are tagged with iris:"persistence"`
// are being persistence and kept
// between the requests, meaning that
// they will not be reset-ed on each new request,
// All fields that are tagged with iris:"persistence"`
// are being persistence and kept between the different requests,
// meaning that these data will not be reset-ed on each new request,
// they will be the same for all requests.
CreatedAt time.Time `iris:"persistence"`
Title string `iris:"persistence"`
DB *persistence.Database
CreatedAt time.Time `iris:"persistence"`
Title string `iris:"persistence"`
DB *persistence.Database `iris:"persistence"`
}
func NewUserController(db *persistence.Database) *User {

View File

@ -1,8 +1,8 @@
package main
import (
"github.com/kataras/iris/_examples/tutorial/mvc/controllers"
"github.com/kataras/iris/_examples/tutorial/mvc/persistence"
"github.com/kataras/iris/_examples/tutorial/mvc-from-scratch/controllers"
"github.com/kataras/iris/_examples/tutorial/mvc-from-scratch/persistence"
"github.com/kataras/iris"
)
@ -18,7 +18,7 @@ func main() {
controllers.RegisterController(app, "/user/{userid:int}",
controllers.NewUserController(db))
// http://localhost/
// http://localhost:8080/
// http://localhost:8080/user/42
app.Run(iris.Addr(":8080"))
}

View File

@ -0,0 +1,15 @@
package models
import (
"time"
)
// User is an example model.
type User struct {
ID int64
Username string
Firstname string
Lastname string
CreatedAt time.Time
UpdatedAt time.Time
}

View File

@ -0,0 +1,10 @@
package persistence
// Database is our imaginary storage.
type Database struct {
Connstring string
}
func OpenDatabase(connstring string) *Database {
return &Database{Connstring: connstring}
}

View File

@ -0,0 +1,11 @@
<html>
<head>
<title>{{.title}}</title>
</head>
<body>
<h1>{{.message}}</h1>
</body>
</html>

View File

@ -58,7 +58,7 @@ func main() {
})
// http://localhost:8080
// http://localhost/redirect/my-page1
// http://localhost:8080/redirect/my-page1
app.Run(iris.Addr(":8080"))
}

View File

@ -10,6 +10,16 @@ import (
// TODO: When go 1.9 will be released
// split this file in order to separate the concepts.
//
// Files should change after go1.9 final release:
// README.md: Hello World with Go 1.9
// core/host/supervisor.go
// context.go
// _examples/hello-world/main_go19.go
// _examples/routing/mvc/controllers/index_go19.go
// _examples/routing/mvc/controllers/user_go19.go
// _examples/routing/mvc/main_go19.go
// _examples/tutorial/mvc-from-scratch/README.md
type (
// Context is the midle-man server's "object" for the clients.
//
@ -49,4 +59,70 @@ type (
//
// A shortcut for the `core/router#Party`, useful when `PartyFunc` is being used.
Party = router.Party
// Controller is the base controller for the high level controllers instances.
//
// This base controller is used as an alternative way of building
// APIs, the controller can register all type of http methods.
//
// Keep note that controllers are bit slow
// because of the reflection use however it's as fast as possible because
// it does preparation before the serve-time handler but still
// remains slower than the low-level handlers
// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
//
//
// All fields that are tagged with iris:"persistence"`
// are being persistence and kept between the different requests,
// meaning that these data will not be reset-ed on each new request,
// they will be the same for all requests.
//
// An Example Controller can be:
//
// type IndexController struct {
// iris.Controller
// }
//
// func (c *IndexController) Get() {
// c.Tmpl = "index.html"
// c.Data["title"] = "Index page"
// c.Data["message"] = "Hello world!"
// }
//
// Usage: app.Controller("/", new(IndexController))
//
//
// Another example with persistence data:
//
// type UserController struct {
// iris.Controller
//
// CreatedAt time.Time `iris:"persistence"`
// Title string `iris:"persistence"`
// DB *DB `iris:"persistence"`
// }
//
// // Get serves using the User controller when HTTP Method is "GET".
// func (c *UserController) Get() {
// c.Tmpl = "user/index.html"
// c.Data["title"] = c.Title
// c.Data["username"] = "kataras " + c.Params.Get("userid")
// c.Data["connstring"] = c.DB.Connstring
// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
// }
//
// Usage: app.Controller("/user/{id:int}", &UserController{
// CreatedAt: time.Now(),
// Title: "User page",
// DB: yourDB,
// })
//
// Look `core/router#APIBuilder#Controller` method too.
//
// A shortcut for the `core/router#Controller`,
// useful when `app.Controller` is being used.
//
// A Controller can be declared by importing
// the "github.com/kataras/iris/core/router"
// package for machines that have not installed go1.9 yet.
Controller = router.Controller
)

View File

@ -100,7 +100,7 @@ var _ RoutesProvider = &APIBuilder{} // passed to the default request handler (r
// NewAPIBuilder creates & returns a new builder
// which is responsible to build the API and the router handler.
func NewAPIBuilder() *APIBuilder {
rb := &APIBuilder{
api := &APIBuilder{
macros: defaultMacros(),
errorCodeHandlers: defaultErrorCodeHandlers(),
reporter: errors.NewReporter(),
@ -108,74 +108,74 @@ func NewAPIBuilder() *APIBuilder {
routes: new(repository),
}
return rb
return api
}
// GetReport returns an error may caused by party's methods.
func (rb *APIBuilder) GetReport() error {
return rb.reporter.Return()
func (api *APIBuilder) GetReport() error {
return api.reporter.Return()
}
// GetReporter returns the reporter for adding errors
func (rb *APIBuilder) GetReporter() *errors.Reporter {
return rb.reporter
func (api *APIBuilder) GetReporter() *errors.Reporter {
return api.reporter
}
// Handle registers a route to the server's rb.
// Handle registers a route to the server's api.
// if empty method is passed then handler(s) are being registered to all methods, same as .Any.
//
// Returns a *Route, app will throw any errors later on.
func (rb *APIBuilder) Handle(method string, registeredPath string, handlers ...context.Handler) *Route {
// if registeredPath[0] != '/' {
func (api *APIBuilder) Handle(method string, relativePath string, handlers ...context.Handler) *Route {
// if relativePath[0] != '/' {
// return nil, errors.New("path should start with slash and should not be empty")
// }
if method == "" || method == "ALL" || method == "ANY" { // then use like it was .Any
return rb.Any(registeredPath, handlers...)[0]
return api.Any(relativePath, handlers...)[0]
}
// no clean path yet because of subdomain indicator/separator which contains a dot.
// but remove the first slash if the relative has already ending with a slash
// it's not needed because later on we do normalize/clean the path, but better do it here too
// for any future updates.
if rb.relativePath[len(rb.relativePath)-1] == '/' {
if registeredPath[0] == '/' {
registeredPath = registeredPath[1:]
if api.relativePath[len(api.relativePath)-1] == '/' {
if relativePath[0] == '/' {
relativePath = relativePath[1:]
}
}
fullpath := rb.relativePath + registeredPath // for now, keep the last "/" if any, "/xyz/"
fullpath := api.relativePath + relativePath // for now, keep the last "/" if any, "/xyz/"
// global begin handlers -> middleware that are registered before route registration
// -> handlers that are passed to this Handle function.
routeHandlers := joinHandlers(append(rb.beginGlobalHandlers, rb.middleware...), handlers)
routeHandlers := joinHandlers(append(api.beginGlobalHandlers, api.middleware...), handlers)
// -> done handlers after all
if len(rb.doneGlobalHandlers) > 0 {
routeHandlers = append(routeHandlers, rb.doneGlobalHandlers...) // register the done middleware, if any
if len(api.doneGlobalHandlers) > 0 {
routeHandlers = append(routeHandlers, api.doneGlobalHandlers...) // register the done middleware, if any
}
// here we separate the subdomain and relative path
subdomain, path := splitSubdomainAndPath(fullpath)
r, err := NewRoute(method, subdomain, path, routeHandlers, rb.macros)
r, err := NewRoute(method, subdomain, path, routeHandlers, api.macros)
if err != nil { // template path parser errors:
rb.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path)
api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path)
return nil
}
// global
rb.routes.register(r)
api.routes.register(r)
// per -party, used for done handlers
rb.apiRoutes = append(rb.apiRoutes, r)
api.apiRoutes = append(api.apiRoutes, r)
return r
}
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
// Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun.
func (rb *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {
parentPath := rb.relativePath
func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {
parentPath := api.relativePath
dot := string(SubdomainPrefix[0])
if len(parentPath) > 0 && parentPath[0] == '/' && strings.HasSuffix(relativePath, dot) {
// if ends with . , i.e admin., it's subdomain->
@ -183,12 +183,12 @@ func (rb *APIBuilder) Party(relativePath string, handlers ...context.Handler) Pa
}
// this is checked later on but for easier debug is better to do it here:
if rb.relativePath[0] == '/' && relativePath[0] == '/' {
if api.relativePath[0] == '/' && relativePath[0] == '/' {
parentPath = parentPath[1:] // remove first slash if parent ended with / and new one started with /.
}
// if it's subdomain then it has priority, i.e:
// rb.relativePath == "admin."
// api.relativePath == "admin."
// relativePath == "panel."
// then it should be panel.admin.
// instead of admin.panel.
@ -199,16 +199,16 @@ func (rb *APIBuilder) Party(relativePath string, handlers ...context.Handler) Pa
fullpath := parentPath + relativePath
// append the parent's + child's handlers
middleware := joinHandlers(rb.middleware, handlers)
middleware := joinHandlers(api.middleware, handlers)
return &APIBuilder{
// global/api builder
macros: rb.macros,
routes: rb.routes,
errorCodeHandlers: rb.errorCodeHandlers,
beginGlobalHandlers: rb.beginGlobalHandlers,
doneGlobalHandlers: rb.doneGlobalHandlers,
reporter: rb.reporter,
macros: api.macros,
routes: api.routes,
errorCodeHandlers: api.errorCodeHandlers,
beginGlobalHandlers: api.beginGlobalHandlers,
doneGlobalHandlers: api.doneGlobalHandlers,
reporter: api.reporter,
// per-party/children
middleware: middleware,
relativePath: fullpath,
@ -231,8 +231,8 @@ func (rb *APIBuilder) Party(relativePath string, handlers ...context.Handler) Pa
// })
//
// Look `Party` for more.
func (rb *APIBuilder) PartyFunc(relativePath string, partyBuilderFunc func(p Party)) Party {
p := rb.Party(relativePath)
func (api *APIBuilder) PartyFunc(relativePath string, partyBuilderFunc func(p Party)) Party {
p := api.Party(relativePath)
partyBuilderFunc(p)
return p
}
@ -242,50 +242,50 @@ func (rb *APIBuilder) PartyFunc(relativePath string, partyBuilderFunc func(p Par
//
// If called from a child party then the subdomain will be prepended to the path instead of appended.
// So if app.Subdomain("admin.").Subdomain("panel.") then the result is: "panel.admin.".
func (rb *APIBuilder) Subdomain(subdomain string, middleware ...context.Handler) Party {
if rb.relativePath == SubdomainWildcardIndicator {
func (api *APIBuilder) Subdomain(subdomain string, middleware ...context.Handler) Party {
if api.relativePath == SubdomainWildcardIndicator {
// cannot concat wildcard subdomain with something else
rb.reporter.Add("cannot concat parent wildcard subdomain with anything else -> %s , %s",
rb.relativePath, subdomain)
return rb
api.reporter.Add("cannot concat parent wildcard subdomain with anything else -> %s , %s",
api.relativePath, subdomain)
return api
}
return rb.Party(subdomain, middleware...)
return api.Party(subdomain, middleware...)
}
// WildcardSubdomain returns a new party which is responsible to register routes to
// a dynamic, wildcard(ed) subdomain. A dynamic subdomain is a subdomain which
// can reply to any subdomain requests. Server will accept any subdomain
// (if not static subdomain found) and it will search and execute the handlers of this party.
func (rb *APIBuilder) WildcardSubdomain(middleware ...context.Handler) Party {
if hasSubdomain(rb.relativePath) {
func (api *APIBuilder) WildcardSubdomain(middleware ...context.Handler) Party {
if hasSubdomain(api.relativePath) {
// cannot concat static subdomain with a dynamic one, wildcard should be at the root level
rb.reporter.Add("cannot concat static subdomain with a dynamic one. Dynamic subdomains should be at the root level -> %s",
rb.relativePath)
return rb
api.reporter.Add("cannot concat static subdomain with a dynamic one. Dynamic subdomains should be at the root level -> %s",
api.relativePath)
return api
}
return rb.Subdomain(SubdomainWildcardIndicator, middleware...)
return api.Subdomain(SubdomainWildcardIndicator, middleware...)
}
// Macros returns the macro map which is responsible
// to register custom macro functions for all routes.
//
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
func (rb *APIBuilder) Macros() *macro.Map {
return rb.macros
func (api *APIBuilder) Macros() *macro.Map {
return api.macros
}
// GetRoutes returns the routes information,
// some of them can be changed at runtime some others not.
//
// Needs refresh of the router to Method or Path or Handlers changes to take place.
func (rb *APIBuilder) GetRoutes() []*Route {
return rb.routes.getAll()
func (api *APIBuilder) GetRoutes() []*Route {
return api.routes.getAll()
}
// GetRoute returns the registered route based on its name, otherwise nil.
// One note: "routeName" should be case-sensitive.
func (rb *APIBuilder) GetRoute(routeName string) *Route {
return rb.routes.get(routeName)
func (api *APIBuilder) GetRoute(routeName string) *Route {
return api.routes.get(routeName)
}
// Use appends Handler(s) to the current Party's routes and child routes.
@ -296,18 +296,18 @@ func (rb *APIBuilder) GetRoute(routeName string) *Route {
// If it's called after the routes then these handlers will never be executed.
// Use `UseGlobal` if you want to register begin handlers(middleware)
// that should be always run before all application's routes.
func (rb *APIBuilder) Use(middleware ...context.Handler) {
rb.middleware = append(rb.middleware, middleware...)
func (api *APIBuilder) Use(handlers ...context.Handler) {
api.middleware = append(api.middleware, handlers...)
}
// Done appends to the very end, Handler(s) to the current Party's routes and child routes
// The difference from .Use is that this/or these Handler(s) are being always running last.
func (rb *APIBuilder) Done(handlers ...context.Handler) {
for _, r := range rb.routes.routes {
func (api *APIBuilder) Done(handlers ...context.Handler) {
for _, r := range api.routes.routes {
r.done(handlers) // append the handlers to the existing routes
}
// set as done handlers for the next routes as well.
rb.doneGlobalHandlers = append(rb.doneGlobalHandlers, handlers...)
api.doneGlobalHandlers = append(api.doneGlobalHandlers, handlers...)
}
// UseGlobal registers handlers that should run before all routes,
@ -317,12 +317,12 @@ func (rb *APIBuilder) Done(handlers ...context.Handler) {
// existing routes and the future routes that may being registered.
//
// It's always a good practise to call it right before the `Application#Run` function.
func (rb *APIBuilder) UseGlobal(handlers ...context.Handler) {
for _, r := range rb.routes.routes {
func (api *APIBuilder) UseGlobal(handlers ...context.Handler) {
for _, r := range api.routes.routes {
r.use(handlers) // prepend the handlers to the existing routes
}
// set as begin handlers for the next routes as well.
rb.beginGlobalHandlers = append(rb.beginGlobalHandlers, handlers...)
api.beginGlobalHandlers = append(api.beginGlobalHandlers, handlers...)
}
// None registers an "offline" route
@ -331,86 +331,148 @@ func (rb *APIBuilder) UseGlobal(handlers ...context.Handler) {
// Offline(handleResultRouteInfo)
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) None(path string, handlers ...context.Handler) *Route {
return rb.Handle(MethodNone, path, handlers...)
func (api *APIBuilder) None(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(MethodNone, relativePath, handlers...)
}
// Get registers a route for the Get http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Get(path string, handlers ...context.Handler) *Route {
return rb.Handle(http.MethodGet, path, handlers...)
func (api *APIBuilder) Get(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(http.MethodGet, relativePath, handlers...)
}
// Post registers a route for the Post http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Post(path string, handlers ...context.Handler) *Route {
return rb.Handle(http.MethodPost, path, handlers...)
func (api *APIBuilder) Post(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(http.MethodPost, relativePath, handlers...)
}
// Put registers a route for the Put http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Put(path string, handlers ...context.Handler) *Route {
return rb.Handle(http.MethodPut, path, handlers...)
func (api *APIBuilder) Put(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(http.MethodPut, relativePath, handlers...)
}
// Delete registers a route for the Delete http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Delete(path string, handlers ...context.Handler) *Route {
return rb.Handle(http.MethodDelete, path, handlers...)
func (api *APIBuilder) Delete(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(http.MethodDelete, relativePath, handlers...)
}
// Connect registers a route for the Connect http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Connect(path string, handlers ...context.Handler) *Route {
return rb.Handle(http.MethodConnect, path, handlers...)
func (api *APIBuilder) Connect(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(http.MethodConnect, relativePath, handlers...)
}
// Head registers a route for the Head http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Head(path string, handlers ...context.Handler) *Route {
return rb.Handle(http.MethodHead, path, handlers...)
func (api *APIBuilder) Head(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(http.MethodHead, relativePath, handlers...)
}
// Options registers a route for the Options http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Options(path string, handlers ...context.Handler) *Route {
return rb.Handle(http.MethodOptions, path, handlers...)
func (api *APIBuilder) Options(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(http.MethodOptions, relativePath, handlers...)
}
// Patch registers a route for the Patch http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Patch(path string, handlers ...context.Handler) *Route {
return rb.Handle(http.MethodPatch, path, handlers...)
func (api *APIBuilder) Patch(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(http.MethodPatch, relativePath, handlers...)
}
// Trace registers a route for the Trace http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Trace(path string, handlers ...context.Handler) *Route {
return rb.Handle(http.MethodTrace, path, handlers...)
func (api *APIBuilder) Trace(relativePath string, handlers ...context.Handler) *Route {
return api.Handle(http.MethodTrace, relativePath, handlers...)
}
// Any registers a route for ALL of the http methods
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
func (rb *APIBuilder) Any(registeredPath string, handlers ...context.Handler) []*Route {
func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) []*Route {
routes := make([]*Route, len(AllMethods), len(AllMethods))
for i, k := range AllMethods {
r := rb.Handle(k, registeredPath, handlers...)
r := api.Handle(k, relativePath, handlers...)
routes[i] = r
}
return routes
}
// Controller registers a `Controller` instance and returns the registered Routes.
// The "controller" receiver should embed a field of `Controller` in order
// to be compatible Iris `Controller`.
//
// It's just an alternative way of building an API for a specific
// path, the controller can register all type of http methods.
//
// Keep note that this method is a bit slow
// because of the reflection use however it's as fast as possible because
// it does preparation before the serve-time handler but still
// remains slower than the low-level handlers
// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch` .
//
// An Example Controller can be:
//
// type IndexController struct {
// Controller
// }
//
// func (c *IndexController) Get() {
// c.Tmpl = "index.html"
// c.Data["title"] = "Index page"
// c.Data["message"] = "Hello world!"
// }
//
// Usage: app.Controller("/", new(IndexController))
//
//
// Another example with persistence data:
//
// type UserController struct {
// Controller
//
// CreatedAt time.Time `iris:"persistence"`
// Title string `iris:"persistence"`
// DB *DB `iris:"persistence"`
// }
//
// // Get serves using the User controller when HTTP Method is "GET".
// func (c *UserController) Get() {
// c.Tmpl = "user/index.html"
// c.Data["title"] = c.Title
// c.Data["username"] = "kataras " + c.Params.Get("userid")
// c.Data["connstring"] = c.DB.Connstring
// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
// }
//
// Usage: app.Controller("/user/{id:int}", &UserController{
// CreatedAt: time.Now(),
// Title: "User page",
// DB: yourDB,
// })
//
// Read more at `router#Controller`.
func (api *APIBuilder) Controller(relativePath string, controller interface{}) []*Route {
routes, err := registerController(api, relativePath, controller)
if err != nil {
api.reporter.Add("%v for path: '%s'", err, relativePath)
}
return routes
}
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration
// which can be changed.
var StaticCacheDuration = 20 * time.Second
@ -428,9 +490,9 @@ const (
varyHeaderKey = "Vary"
)
func (rb *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *Route {
rb.Head(reqPath, h)
return rb.Get(reqPath, h)
func (api *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *Route {
api.Head(reqPath, h)
return api.Get(reqPath, h)
}
// StaticHandler returns a new Handler which is ready
@ -450,7 +512,7 @@ func (rb *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *
// mySubdomainFsServer.Get("/static", h)
// ...
//
func (rb *APIBuilder) StaticHandler(systemPath string, showList bool, gzip bool) context.Handler {
func (api *APIBuilder) StaticHandler(systemPath string, showList bool, gzip bool) context.Handler {
// Note: this doesn't need to be here but we'll keep it for consistently
return StaticHandler(systemPath, showList, gzip)
}
@ -464,7 +526,7 @@ func (rb *APIBuilder) StaticHandler(systemPath string, showList bool, gzip bool)
// it uses gzip compression (compression on each request, no file cache).
//
// Returns the GET *Route.
func (rb *APIBuilder) StaticServe(systemPath string, requestPath ...string) *Route {
func (api *APIBuilder) StaticServe(systemPath string, requestPath ...string) *Route {
var reqPath string
if len(requestPath) == 0 {
@ -475,7 +537,7 @@ func (rb *APIBuilder) StaticServe(systemPath string, requestPath ...string) *Rou
reqPath = requestPath[0]
}
return rb.Get(joinPath(reqPath, WildcardParam("file")), func(ctx context.Context) {
return api.Get(joinPath(reqPath, WildcardParam("file")), func(ctx context.Context) {
filepath := ctx.Params().Get("file")
spath := strings.Replace(filepath, "/", string(os.PathSeparator), -1)
@ -497,7 +559,7 @@ func (rb *APIBuilder) StaticServe(systemPath string, requestPath ...string) *Rou
// that are ready to serve raw static bytes, memory cached.
//
// Returns the GET *Route.
func (rb *APIBuilder) StaticContent(reqPath string, cType string, content []byte) *Route {
func (api *APIBuilder) StaticContent(reqPath string, cType string, content []byte) *Route {
modtime := time.Now()
h := func(ctx context.Context) {
ctx.ContentType(cType)
@ -507,7 +569,7 @@ func (rb *APIBuilder) StaticContent(reqPath string, cType string, content []byte
}
}
return rb.registerResourceRoute(reqPath, h)
return api.registerResourceRoute(reqPath, h)
}
// StaticEmbeddedHandler returns a Handler which can serve
@ -515,7 +577,7 @@ func (rb *APIBuilder) StaticContent(reqPath string, cType string, content []byte
//
//
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
func (rb *APIBuilder) StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) context.Handler {
func (api *APIBuilder) StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) context.Handler {
// Notes:
// This doesn't need to be APIBuilder's scope,
// but we'll keep it here for consistently.
@ -531,12 +593,12 @@ func (rb *APIBuilder) StaticEmbeddedHandler(vdir string, assetFn func(name strin
// Returns the GET *Route.
//
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
func (rb *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
fullpath := joinPath(rb.relativePath, requestPath)
func (api *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
fullpath := joinPath(api.relativePath, requestPath)
requestPath = joinPath(fullpath, WildcardParam("file"))
h := StripPrefix(fullpath, rb.StaticEmbeddedHandler(vdir, assetFn, namesFn))
return rb.registerResourceRoute(requestPath, h)
h := StripPrefix(fullpath, api.StaticEmbeddedHandler(vdir, assetFn, namesFn))
return api.registerResourceRoute(requestPath, h)
}
// errDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace'
@ -553,12 +615,12 @@ var errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found.
// Note that you have to call it on every favicon you have to serve automatically (desktop, mobile and so on).
//
// Returns the GET *Route.
func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
favPath = Abs(favPath)
f, err := os.Open(favPath)
if err != nil {
rb.reporter.AddErr(errDirectoryFileNotFound.Format(favPath, err.Error()))
api.reporter.AddErr(errDirectoryFileNotFound.Format(favPath, err.Error()))
return nil
}
@ -570,7 +632,7 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
f, err = os.Open(fav)
if err != nil {
//we try again with .png
return rb.Favicon(path.Join(favPath, "favicon.png"))
return api.Favicon(path.Join(favPath, "favicon.png"))
}
favPath = fav
fi, _ = f.Stat()
@ -584,7 +646,7 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
// So we could panic but we don't,
// we just interrupt with a message
// to the (user-defined) logger.
rb.reporter.AddErr(errDirectoryFileNotFound.
api.reporter.AddErr(errDirectoryFileNotFound.
Format(favPath, "favicon: couldn't read the data bytes for file: "+err.Error()))
return nil
}
@ -615,7 +677,7 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
reqPath = requestPath[0]
}
return rb.registerResourceRoute(reqPath, h)
return api.registerResourceRoute(reqPath, h)
}
// StaticWeb returns a handler that serves HTTP requests
@ -626,7 +688,7 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
//
// for more options look router.StaticHandler.
//
// rb.StaticWeb("/static", "./static")
// api.StaticWeb("/static", "./static")
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
@ -635,11 +697,11 @@ func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
// StaticWeb calls the `StripPrefix(fullpath, NewStaticHandlerBuilder(systemPath).Listing(false).Build())`.
//
// Returns the GET *Route.
func (rb *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
func (api *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
paramName := "file"
fullpath := joinPath(rb.relativePath, requestPath)
fullpath := joinPath(api.relativePath, requestPath)
h := StripPrefix(fullpath, NewStaticHandlerBuilder(systemPath).Listing(false).Build())
@ -659,7 +721,7 @@ func (rb *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
requestPath = joinPath(fullpath, WildcardParam(paramName))
// requestPath = fullpath + "/{file:path}"
return rb.registerResourceRoute(requestPath, handler)
return api.registerResourceRoute(requestPath, handler)
}
// OnErrorCode registers an error http status code
@ -669,14 +731,14 @@ func (rb *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
// the body if recorder was enabled
// and/or disable the gzip if gzip response recorder
// was active.
func (rb *APIBuilder) OnErrorCode(statusCode int, handlers ...context.Handler) {
rb.errorCodeHandlers.Register(statusCode, handlers...)
func (api *APIBuilder) OnErrorCode(statusCode int, handlers ...context.Handler) {
api.errorCodeHandlers.Register(statusCode, handlers...)
}
// OnAnyErrorCode registers a handler which called when error status code written.
// Same as `OnErrorCode` but registers all http error codes.
// See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
func (rb *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) {
func (api *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) {
// we could register all >=400 and <=511 but this way
// could override custom status codes that iris developers can register for their
// web apps whenever needed.
@ -722,7 +784,7 @@ func (rb *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) {
http.StatusNetworkAuthenticationRequired}
for _, statusCode := range errStatusCodes {
rb.OnErrorCode(statusCode, handlers...)
api.OnErrorCode(statusCode, handlers...)
}
}
@ -731,8 +793,8 @@ func (rb *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) {
//
// If a handler is not already registered,
// then it creates & registers a new trivial handler on the-fly.
func (rb *APIBuilder) FireErrorCode(ctx context.Context) {
rb.errorCodeHandlers.Fire(ctx)
func (api *APIBuilder) FireErrorCode(ctx context.Context) {
api.errorCodeHandlers.Fire(ctx)
}
// Layout oerrides the parent template layout with a more specific layout for this Party
@ -745,13 +807,13 @@ func (rb *APIBuilder) FireErrorCode(ctx context.Context) {
// ctx.MustRender("page1.html", nil)
// })
// }
func (rb *APIBuilder) Layout(tmplLayoutFile string) Party {
rb.Use(func(ctx context.Context) {
func (api *APIBuilder) Layout(tmplLayoutFile string) Party {
api.Use(func(ctx context.Context) {
ctx.ViewLayout(tmplLayoutFile)
ctx.Next()
})
return rb
return api
}
// joinHandlers uses to create a copy of all Handlers and return them in order to use inside the node

272
core/router/controller.go Normal file
View File

@ -0,0 +1,272 @@
package router
import (
"reflect"
"strings"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
)
// Controller is the base controller for the high level controllers instances.
//
// This base controller is used as an alternative way of building
// APIs, the controller can register all type of http methods.
//
// Keep note that controllers are bit slow
// because of the reflection use however it's as fast as possible because
// it does preparation before the serve-time handler but still
// remains slower than the low-level handlers
// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
//
//
// All fields that are tagged with iris:"persistence"`
// are being persistence and kept between the different requests,
// meaning that these data will not be reset-ed on each new request,
// they will be the same for all requests.
//
// An Example Controller can be:
//
// type IndexController struct {
// Controller
// }
//
// func (c *IndexController) Get() {
// c.Tmpl = "index.html"
// c.Data["title"] = "Index page"
// c.Data["message"] = "Hello world!"
// }
//
// Usage: app.Controller("/", new(IndexController))
//
//
// Another example with persistence data:
//
// type UserController struct {
// Controller
//
// CreatedAt time.Time `iris:"persistence"`
// Title string `iris:"persistence"`
// DB *DB `iris:"persistence"`
// }
//
// // Get serves using the User controller when HTTP Method is "GET".
// func (c *UserController) Get() {
// c.Tmpl = "user/index.html"
// c.Data["title"] = c.Title
// c.Data["username"] = "kataras " + c.Params.Get("userid")
// c.Data["connstring"] = c.DB.Connstring
// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
// }
//
// Usage: app.Controller("/user/{id:int}", &UserController{
// CreatedAt: time.Now(),
// Title: "User page",
// DB: yourDB,
// })
//
// Look `router#APIBuilder#Controller` method too.
type Controller struct {
// path params.
Params *context.RequestParams
// view properties.
Layout string
Tmpl string
Data map[string]interface{}
// give access to the request context itself.
Ctx context.Context
}
// all lowercase, so user can see only the fields
// that are necessary to him/her, do not confuse that
// with the optional custom `Init` of the higher-level Controller.
func (b *Controller) init(ctx context.Context) {
b.Ctx = ctx
b.Params = ctx.Params()
b.Data = make(map[string]interface{}, 0)
}
func (b *Controller) exec() {
if v := b.Tmpl; v != "" {
if l := b.Layout; l != "" {
b.Ctx.ViewLayout(l)
}
if d := b.Data; d != nil {
for key, value := range d {
b.Ctx.ViewData(key, value)
}
}
b.Ctx.View(v)
}
}
var (
// ErrInvalidControllerType is a static error which fired from `Controller` when
// the passed "c" instnace is not a valid type of `Controller`.
ErrInvalidControllerType = errors.New("controller should have a field of Controller type")
)
// get the field name at compile-time,
// will help us to catch any unexpected results on future versions.
var baseControllerName = reflect.TypeOf(Controller{}).Name()
// registers a controller to a specific `Party`.
// Consumed by `APIBuilder#Controller` function.
func registerController(p Party, path string, c interface{}) ([]*Route, error) {
typ := reflect.TypeOf(c)
if typ.Kind() != reflect.Ptr {
typ = reflect.PtrTo(typ)
}
elem := typ.Elem()
// check if "c" has the "Controller" typeof `Controller` field.
b, has := elem.FieldByName(baseControllerName)
if !has {
return nil, ErrInvalidControllerType
}
baseControllerFieldIndex := b.Index[0]
persistenceFields := make(map[int]reflect.Value, 0)
if numField := elem.NumField(); numField > 1 {
val := reflect.Indirect(reflect.ValueOf(c))
for i := 0; i < numField; i++ {
f := elem.Field(i)
valF := val.Field(i)
// catch persistence data by tags, i.e:
// MyData string `iris:"persistence"`
if t, ok := f.Tag.Lookup("iris"); ok {
if t == "persistence" {
persistenceFields[i] = reflect.ValueOf(valF.Interface())
continue
}
}
// no: , lets have only the tag
// even for pointers, this will make
// things clear
// so a *Session can be declared
// without having to introduce
// a new tag such as `iris:"omit_persistence"`
// old:
// catch persistence data by pointer, i.e:
// DB *Database
// if f.Type.Kind() == reflect.Ptr {
// if !valF.IsNil() {
// persistenceFields[i] = reflect.ValueOf(valF.Interface())
// }
// }
}
}
customInitFuncIndex, _ := getCustomInitFuncIndex(typ)
// check if has Any() or All()
// if yes, then register all http methods and
// exit.
m, has := typ.MethodByName("Any")
if !has {
m, has = typ.MethodByName("All")
}
if has {
routes := p.Any(path,
controllerToHandler(elem, persistenceFields,
baseControllerFieldIndex, m.Index, customInitFuncIndex))
return routes, nil
}
var routes []*Route
// else search the entire controller
// for any compatible method function
// and register that.
for _, method := range AllMethods {
httpMethodFuncName := strings.Title(strings.ToLower(method))
m, has := typ.MethodByName(httpMethodFuncName)
if !has {
continue
}
httpMethodIndex := m.Index
r := p.Handle(method, path,
controllerToHandler(elem, persistenceFields,
baseControllerFieldIndex, httpMethodIndex, customInitFuncIndex))
routes = append(routes, r)
}
return routes, nil
}
func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Value,
baseControllerFieldIndex, httpMethodIndex int, customInitFuncIndex int) context.Handler {
return func(ctx context.Context) {
// create a new controller instance of that type(>ptr).
c := reflect.New(elem)
// get the responsible method.
// Remember:
// To improve the performance
// we don't compare the ctx.Method()[HTTP Method]
// to the instance's Method, each handler is registered
// to a specific http method.
methodFunc := c.Method(httpMethodIndex)
// get the Controller embedded field.
b, _ := c.Elem().Field(baseControllerFieldIndex).Addr().Interface().(*Controller)
if len(persistenceFields) > 0 {
elem := c.Elem()
for index, value := range persistenceFields {
elem.Field(index).Set(value)
}
}
// init the new controller instance.
b.init(ctx)
// calls the higher "Init(ctx context.Context)",
// if exists.
if customInitFuncIndex > 0 {
callCustomInit(ctx, c, customInitFuncIndex)
}
// if custom Init didn't stop the execution of the
// context
if !ctx.IsStopped() {
// execute the responsible method for that handler.
methodFunc.Interface().(func())()
}
// finally, execute the controller.
b.exec()
}
}
// Init can be used as a custom function
// to init the new instance of controller
// that is created on each new request.
//
// Useful when more than one methods are using the same
// request data.
const customInitFuncName = "Init"
func getCustomInitFuncIndex(typ reflect.Type) (initFuncIndex int, has bool) {
if m, has := typ.MethodByName(customInitFuncName); has {
return m.Index, has
}
return -1, false
}
// the "cServeTime" is a new "c" instance
// which is being used at serve time, inside the Handler.
// it calls the custom "Init", the check of this
// function made at build time, so it's a safe a call.
func callCustomInit(ctx context.Context, cServeTime reflect.Value, initFuncIndex int) {
cServeTime.Method(initFuncIndex).Interface().(func(ctx context.Context))(ctx)
}

View File

@ -0,0 +1,142 @@
// black-box testing
package router_test
import (
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
"github.com/kataras/iris/httptest"
)
type testController struct {
router.Controller
}
var writeMethod = func(c router.Controller) {
c.Ctx.Writef(c.Ctx.Method())
}
func (c *testController) Get() {
writeMethod(c.Controller)
}
func (c *testController) Post() {
writeMethod(c.Controller)
}
func (c *testController) Put() {
writeMethod(c.Controller)
}
func (c *testController) Delete() {
writeMethod(c.Controller)
}
func (c *testController) Connect() {
writeMethod(c.Controller)
}
func (c *testController) Head() {
writeMethod(c.Controller)
}
func (c *testController) Patch() {
writeMethod(c.Controller)
}
func (c *testController) Options() {
writeMethod(c.Controller)
}
func (c *testController) Trace() {
writeMethod(c.Controller)
}
type (
testControllerAll struct{ router.Controller }
testControllerAny struct{ router.Controller } // exactly same as All
)
func (c *testControllerAll) All() {
writeMethod(c.Controller)
}
func (c *testControllerAny) All() {
writeMethod(c.Controller)
}
func TestControllerMethodFuncs(t *testing.T) {
app := iris.New()
app.Controller("/", new(testController))
app.Controller("/all", new(testControllerAll))
app.Controller("/any", new(testControllerAny))
e := httptest.New(t, app)
for _, method := range router.AllMethods {
e.Request(method, "/").Expect().Status(httptest.StatusOK).
Body().Equal(method)
e.Request(method, "/all").Expect().Status(httptest.StatusOK).
Body().Equal(method)
e.Request(method, "/any").Expect().Status(httptest.StatusOK).
Body().Equal(method)
}
}
type testControllerPersistence struct {
router.Controller
Data string `iris:"persistence"`
}
func (t *testControllerPersistence) Get() {
t.Ctx.WriteString(t.Data)
}
func TestControllerPersistenceFields(t *testing.T) {
data := "this remains the same for all requests"
app := iris.New()
app.Controller("/", &testControllerPersistence{Data: data})
e := httptest.New(t, app)
e.GET("/").Expect().Status(httptest.StatusOK).
Body().Equal(data)
}
type testControllerInitFunc struct {
router.Controller
Username string
}
// useful when more than one methods using the
// same request values or context's function calls.
func (t *testControllerInitFunc) Init(ctx context.Context) {
t.Username = ctx.Params().Get("username")
// or t.Params.Get("username") because the
// t.Ctx == ctx and is being initialized before this "Init"
}
func (t *testControllerInitFunc) Get() {
t.Ctx.Writef(t.Username)
}
func (t *testControllerInitFunc) Post() {
t.Ctx.Writef(t.Username)
}
func TestControllerInitFunc(t *testing.T) {
app := iris.New()
app.Controller("/profile/{username}", new(testControllerInitFunc))
e := httptest.New(t, app)
usernames := []string{
"kataras",
"makis",
"efi",
"rg",
"bill",
"whoisyourdaddy",
}
for _, username := range usernames {
e.GET("/profile/" + username).Expect().Status(httptest.StatusOK).
Body().Equal(username)
e.POST("/profile/" + username).Expect().Status(httptest.StatusOK).
Body().Equal(username)
}
}

View File

@ -100,6 +100,62 @@ type Party interface {
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
Any(registeredPath string, handlers ...context.Handler) []*Route
// Controller registers a `Controller` instance and returns the registered Routes.
// The "controller" receiver should embed a field of `Controller` in order
// to be compatible Iris `Controller`.
//
// It's just an alternative way of building an API for a specific
// path, the controller can register all type of http methods.
//
// Keep note that this method is a bit slow
// because of the reflection use however it's as fast as possible because
// it does preparation before the serve-time handler but still
// remains slower than the low-level handlers
// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch` .
//
// An Example Controller can be:
//
// type IndexController struct {
// Controller
// }
//
// func (c *IndexController) Get() {
// c.Tmpl = "index.html"
// c.Data["title"] = "Index page"
// c.Data["message"] = "Hello world!"
// }
//
// Usage: app.Controller("/", new(IndexController))
//
//
// Another example with persistence data:
//
// type UserController struct {
// Controller
//
// CreatedAt time.Time `iris:"persistence"`
// Title string `iris:"persistence"`
// DB *DB `iris:"persistence"`
// }
//
// // Get serves using the User controller when HTTP Method is "GET".
// func (c *UserController) Get() {
// c.Tmpl = "user/index.html"
// c.Data["title"] = c.Title
// c.Data["username"] = "kataras " + c.Params.Get("userid")
// c.Data["connstring"] = c.DB.Connstring
// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
// }
//
// Usage: app.Controller("/user/{id:int}", &UserController{
// CreatedAt: time.Now(),
// Title: "User page",
// DB: yourDB,
// })
//
// Read more at `router#Controller`.
Controller(relativePath string, controller interface{}) []*Route
// StaticHandler returns a new Handler which is ready
// to serve all kind of static files.
//

90
doc.go
View File

@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
Current Version
8.2.4
8.2.5
Installation
@ -680,6 +680,94 @@ Example code:
}
Controllers
It's very easy to get started, the only function you need to call
instead of `app.Get/Post/Put/Delete/Connect/Head/Patch/Options/Trace`
is the `app.Controller`.
Characteristics:
All HTTP Methods are supported, for example if want to serve `GET`
then the controller should have a function named `Get()`,
you can define more than one method function to serve in the same Controller struct.
Persistence data inside your Controller struct (share data between requests)
via `iris:"persistence"` tag right to the field.
Optional `Init` function to perform any initialization before the methods,
useful to call middlewares or when many methods use the same collection of data.
Access to the request path parameters via the `Params` field.
Access to the template file that should be rendered via the `Tmpl` field.
Access to the template data that should be rendered inside
the template file via `Data` field.
Access to the template layout via the `Layout` field.
Access to the low-level `context.Context` via the `Ctx` field.
Flow as you used to, `Controllers` can be registered to any `Party`,
including Subdomains, the Party's begin and done handlers work as expected.
Example Code:
// file: main.go
package main
import (
"github.com/kataras/iris"
"controllers"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
app.Controller("/", new(controllers.Index))
// http://localhost:8080/
app.Run(iris.Addr(":8080"))
}
// file: controllers/index.go
package controllers
import (
"github.com/kataras/iris/core/router"
)
// Index is our index example controller.
type Index struct {
router.Controller
// if you're using go1.9:
// you can omit the /core/router import statement
// and just use the `iris.Controller` instead.
}
// will handle GET method on http://localhost:8080/
func (c *Index) Get() {
c.Tmpl = "index.html"
c.Data["title"] = "Index page"
c.Data["message"] = "Hello world!"
}
// will handle POST method on http://localhost:8080/
func (c *Index) Post() {}
Tip: declare a func(c *Index) All() {} or Any() to register all HTTP Methods.
A full example can be found at: https://github.com/kataras/iris/tree/master/_examples/routing/mvc.
Parameterized Path
At the previous example,

View File

@ -32,7 +32,7 @@ import (
const (
// Version is the current version number of the Iris Web Framework.
Version = "8.2.4"
Version = "8.2.5"
)
// HTTP status codes as registered with IANA.