diff --git a/HISTORY.md b/HISTORY.md
index 1889e750..8377e52e 100644
--- a/HISTORY.md
+++ b/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.
diff --git a/README.md b/README.md
index 243c07dc..b8db8f26 100644
--- a/README.md
+++ b/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
```
@@ -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
diff --git a/VERSION b/VERSION
index 77dd5e93..716db84a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.2.4:https://github.com/kataras/iris/blob/master/HISTORY.md#th-12-august-2017--v824
\ No newline at end of file
+8.2.5:https://github.com/kataras/iris/blob/master/HISTORY.md#su-13-august-2017--v825
\ No newline at end of file
diff --git a/_examples/README.md b/_examples/README.md
index f5a6add1..2d21adfa 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -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)
diff --git a/_examples/hello-world/main.go b/_examples/hello-world/main.go
index 49f2268c..b5b38874 100644
--- a/_examples/hello-world/main.go
+++ b/_examples/hello-world/main.go
@@ -1,4 +1,4 @@
-// +build go.1.8
+// +build !go1.9
package main
diff --git a/_examples/routing/mvc/controllers/index.go b/_examples/routing/mvc/controllers/index.go
new file mode 100644
index 00000000..5b27b013
--- /dev/null
+++ b/_examples/routing/mvc/controllers/index.go
@@ -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!"
+}
diff --git a/_examples/routing/mvc/controllers/index_go19.go b/_examples/routing/mvc/controllers/index_go19.go
new file mode 100644
index 00000000..057dff91
--- /dev/null
+++ b/_examples/routing/mvc/controllers/index_go19.go
@@ -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!"
+}
diff --git a/_examples/routing/mvc/controllers/user.go b/_examples/routing/mvc/controllers/user.go
new file mode 100644
index 00000000..0d44ba1b
--- /dev/null
+++ b/_examples/routing/mvc/controllers/user.go
@@ -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() {}
+*/
diff --git a/_examples/routing/mvc/controllers/user_go19.go b/_examples/routing/mvc/controllers/user_go19.go
new file mode 100644
index 00000000..ff710435
--- /dev/null
+++ b/_examples/routing/mvc/controllers/user_go19.go
@@ -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() {}
+*/
diff --git a/_examples/routing/mvc/main.go b/_examples/routing/mvc/main.go
new file mode 100644
index 00000000..2da23935
--- /dev/null
+++ b/_examples/routing/mvc/main.go
@@ -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"))
+}
diff --git a/_examples/routing/mvc/main_go19.go b/_examples/routing/mvc/main_go19.go
new file mode 100644
index 00000000..433d3d71
--- /dev/null
+++ b/_examples/routing/mvc/main_go19.go
@@ -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"))
+}
diff --git a/_examples/tutorial/mvc/models/user.go b/_examples/routing/mvc/models/user.go
similarity index 100%
rename from _examples/tutorial/mvc/models/user.go
rename to _examples/routing/mvc/models/user.go
diff --git a/_examples/tutorial/mvc/persistence/database.go b/_examples/routing/mvc/persistence/database.go
similarity index 100%
rename from _examples/tutorial/mvc/persistence/database.go
rename to _examples/routing/mvc/persistence/database.go
diff --git a/_examples/tutorial/mvc/views/index.html b/_examples/routing/mvc/views/index.html
similarity index 100%
rename from _examples/tutorial/mvc/views/index.html
rename to _examples/routing/mvc/views/index.html
diff --git a/_examples/routing/mvc/views/user/index.html b/_examples/routing/mvc/views/user/index.html
new file mode 100644
index 00000000..783a3629
--- /dev/null
+++ b/_examples/routing/mvc/views/user/index.html
@@ -0,0 +1,18 @@
+
+
+
+ {{.title}}
+
+
+
+ Hello {{.username}}
+
+
+ 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.
+ Persistence data from *DB.Connstring: {{.connstring}}
+ Persistence data from CreatedAt `iris:"persistence"`: {{.uptime}} seconds
+ {{.visit_count}}
+
+
+
\ No newline at end of file
diff --git a/_examples/tutorial/mvc-from-scratch/README.md b/_examples/tutorial/mvc-from-scratch/README.md
new file mode 100644
index 00000000..6d0f506a
--- /dev/null
+++ b/_examples/tutorial/mvc-from-scratch/README.md
@@ -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).
\ No newline at end of file
diff --git a/_examples/tutorial/mvc/controllers/controller.go b/_examples/tutorial/mvc-from-scratch/controllers/controller.go
similarity index 92%
rename from _examples/tutorial/mvc/controllers/controller.go
rename to _examples/tutorial/mvc-from-scratch/controllers/controller.go
index 4e9a7aa1..29c51161 100644
--- a/_examples/tutorial/mvc/controllers/controller.go
+++ b/_examples/tutorial/mvc-from-scratch/controllers/controller.go
@@ -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())
- }
- }
}
}
diff --git a/_examples/tutorial/mvc/controllers/index.go b/_examples/tutorial/mvc-from-scratch/controllers/index.go
similarity index 100%
rename from _examples/tutorial/mvc/controllers/index.go
rename to _examples/tutorial/mvc-from-scratch/controllers/index.go
diff --git a/_examples/tutorial/mvc/controllers/user.go b/_examples/tutorial/mvc-from-scratch/controllers/user.go
similarity index 70%
rename from _examples/tutorial/mvc/controllers/user.go
rename to _examples/tutorial/mvc-from-scratch/controllers/user.go
index 4dfd359e..95b26454 100644
--- a/_examples/tutorial/mvc/controllers/user.go
+++ b/_examples/tutorial/mvc-from-scratch/controllers/user.go
@@ -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 {
diff --git a/_examples/tutorial/mvc/main.go b/_examples/tutorial/mvc-from-scratch/main.go
similarity index 69%
rename from _examples/tutorial/mvc/main.go
rename to _examples/tutorial/mvc-from-scratch/main.go
index 105c4069..60cfaedb 100644
--- a/_examples/tutorial/mvc/main.go
+++ b/_examples/tutorial/mvc-from-scratch/main.go
@@ -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"))
}
diff --git a/_examples/tutorial/mvc-from-scratch/models/user.go b/_examples/tutorial/mvc-from-scratch/models/user.go
new file mode 100644
index 00000000..ab09ede0
--- /dev/null
+++ b/_examples/tutorial/mvc-from-scratch/models/user.go
@@ -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
+}
diff --git a/_examples/tutorial/mvc-from-scratch/persistence/database.go b/_examples/tutorial/mvc-from-scratch/persistence/database.go
new file mode 100644
index 00000000..8de23178
--- /dev/null
+++ b/_examples/tutorial/mvc-from-scratch/persistence/database.go
@@ -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}
+}
diff --git a/_examples/tutorial/mvc-from-scratch/views/index.html b/_examples/tutorial/mvc-from-scratch/views/index.html
new file mode 100644
index 00000000..34826bf1
--- /dev/null
+++ b/_examples/tutorial/mvc-from-scratch/views/index.html
@@ -0,0 +1,11 @@
+
+
+
+ {{.title}}
+
+
+
+ {{.message}}
+
+
+
\ No newline at end of file
diff --git a/_examples/tutorial/mvc/views/user/index.html b/_examples/tutorial/mvc-from-scratch/views/user/index.html
similarity index 100%
rename from _examples/tutorial/mvc/views/user/index.html
rename to _examples/tutorial/mvc-from-scratch/views/user/index.html
diff --git a/_examples/view/template_html_3/main.go b/_examples/view/template_html_3/main.go
index 650ddb87..88d22e16 100644
--- a/_examples/view/template_html_3/main.go
+++ b/_examples/view/template_html_3/main.go
@@ -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"))
}
diff --git a/context.go b/context.go
index e66b4916..b428ab83 100644
--- a/context.go
+++ b/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
)
diff --git a/core/router/api_builder.go b/core/router/api_builder.go
index 9f7e597f..28c1bc07 100644
--- a/core/router/api_builder.go
+++ b/core/router/api_builder.go
@@ -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
diff --git a/core/router/controller.go b/core/router/controller.go
new file mode 100644
index 00000000..5c387f12
--- /dev/null
+++ b/core/router/controller.go
@@ -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)
+}
diff --git a/core/router/controller_test.go b/core/router/controller_test.go
new file mode 100644
index 00000000..9f557e92
--- /dev/null
+++ b/core/router/controller_test.go
@@ -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)
+ }
+
+}
diff --git a/core/router/party.go b/core/router/party.go
index 4284cb3a..4e4c42f4 100644
--- a/core/router/party.go
+++ b/core/router/party.go
@@ -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.
//
diff --git a/doc.go b/doc.go
index 29db2f53..6b3fe232 100644
--- a/doc.go
+++ b/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,
diff --git a/iris.go b/iris.go
index e076ef6e..0d6cc4a8 100644
--- a/iris.go
+++ b/iris.go
@@ -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.