mirror of
https://github.com/kataras/iris.git
synced 2025-03-13 21:36:28 +01:00
Update to 8.2.5 | Say Hello to Controllers. Read HISTORY.md
Former-commit-id: 70f8619440497d132362da86c5187bcc57f8687b
This commit is contained in:
parent
2786ca1960
commit
8cd07719a6
80
HISTORY.md
80
HISTORY.md
|
@ -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.
|
||||
|
|
20
README.md
20
README.md
|
@ -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
|
||||
|
|
2
VERSION
2
VERSION
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build go.1.8
|
||||
// +build !go1.9
|
||||
|
||||
package main
|
||||
|
||||
|
|
18
_examples/routing/mvc/controllers/index.go
Normal file
18
_examples/routing/mvc/controllers/index.go
Normal 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!"
|
||||
}
|
18
_examples/routing/mvc/controllers/index_go19.go
Normal file
18
_examples/routing/mvc/controllers/index_go19.go
Normal 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!"
|
||||
}
|
62
_examples/routing/mvc/controllers/user.go
Normal file
62
_examples/routing/mvc/controllers/user.go
Normal 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() {}
|
||||
*/
|
81
_examples/routing/mvc/controllers/user_go19.go
Normal file
81
_examples/routing/mvc/controllers/user_go19.go
Normal 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() {}
|
||||
*/
|
25
_examples/routing/mvc/main.go
Normal file
25
_examples/routing/mvc/main.go
Normal 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"))
|
||||
}
|
28
_examples/routing/mvc/main_go19.go
Normal file
28
_examples/routing/mvc/main_go19.go
Normal 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"))
|
||||
}
|
18
_examples/routing/mvc/views/user/index.html
Normal file
18
_examples/routing/mvc/views/user/index.html
Normal 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>
|
99
_examples/tutorial/mvc-from-scratch/README.md
Normal file
99
_examples/tutorial/mvc-from-scratch/README.md
Normal 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).
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
|
@ -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"))
|
||||
}
|
15
_examples/tutorial/mvc-from-scratch/models/user.go
Normal file
15
_examples/tutorial/mvc-from-scratch/models/user.go
Normal 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
|
||||
}
|
10
_examples/tutorial/mvc-from-scratch/persistence/database.go
Normal file
10
_examples/tutorial/mvc-from-scratch/persistence/database.go
Normal 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}
|
||||
}
|
11
_examples/tutorial/mvc-from-scratch/views/index.html
Normal file
11
_examples/tutorial/mvc-from-scratch/views/index.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{.message}}</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -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"))
|
||||
|
||||
}
|
||||
|
|
76
context.go
76
context.go
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
272
core/router/controller.go
Normal 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)
|
||||
}
|
142
core/router/controller_test.go
Normal file
142
core/router/controller_test.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
90
doc.go
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue
Block a user