From 8cd07719a6caf01e955d49b24f560f11fca0a628 Mon Sep 17 00:00:00 2001 From: kataras Date: Sun, 13 Aug 2017 21:58:34 +0300 Subject: [PATCH] Update to 8.2.5 | Say Hello to Controllers. Read HISTORY.md Former-commit-id: 70f8619440497d132362da86c5187bcc57f8687b --- HISTORY.md | 80 +++++ README.md | 20 +- VERSION | 2 +- _examples/README.md | 2 +- _examples/hello-world/main.go | 2 +- _examples/routing/mvc/controllers/index.go | 18 ++ .../routing/mvc/controllers/index_go19.go | 18 ++ _examples/routing/mvc/controllers/user.go | 62 ++++ .../routing/mvc/controllers/user_go19.go | 81 +++++ _examples/routing/mvc/main.go | 25 ++ _examples/routing/mvc/main_go19.go | 28 ++ .../{tutorial => routing}/mvc/models/user.go | 0 .../mvc/persistence/database.go | 0 .../mvc/views/index.html | 0 _examples/routing/mvc/views/user/index.html | 18 ++ _examples/tutorial/mvc-from-scratch/README.md | 99 ++++++ .../controllers/controller.go | 11 +- .../controllers/index.go | 0 .../controllers/user.go | 18 +- .../{mvc => mvc-from-scratch}/main.go | 6 +- .../tutorial/mvc-from-scratch/models/user.go | 15 + .../mvc-from-scratch/persistence/database.go | 10 + .../mvc-from-scratch/views/index.html | 11 + .../views/user/index.html | 0 _examples/view/template_html_3/main.go | 2 +- context.go | 76 +++++ core/router/api_builder.go | 288 +++++++++++------- core/router/controller.go | 272 +++++++++++++++++ core/router/controller_test.go | 142 +++++++++ core/router/party.go | 56 ++++ doc.go | 90 +++++- iris.go | 2 +- 32 files changed, 1302 insertions(+), 152 deletions(-) create mode 100644 _examples/routing/mvc/controllers/index.go create mode 100644 _examples/routing/mvc/controllers/index_go19.go create mode 100644 _examples/routing/mvc/controllers/user.go create mode 100644 _examples/routing/mvc/controllers/user_go19.go create mode 100644 _examples/routing/mvc/main.go create mode 100644 _examples/routing/mvc/main_go19.go rename _examples/{tutorial => routing}/mvc/models/user.go (100%) rename _examples/{tutorial => routing}/mvc/persistence/database.go (100%) rename _examples/{tutorial => routing}/mvc/views/index.html (100%) create mode 100644 _examples/routing/mvc/views/user/index.html create mode 100644 _examples/tutorial/mvc-from-scratch/README.md rename _examples/tutorial/{mvc => mvc-from-scratch}/controllers/controller.go (92%) rename _examples/tutorial/{mvc => mvc-from-scratch}/controllers/index.go (100%) rename _examples/tutorial/{mvc => mvc-from-scratch}/controllers/user.go (70%) rename _examples/tutorial/{mvc => mvc-from-scratch}/main.go (69%) create mode 100644 _examples/tutorial/mvc-from-scratch/models/user.go create mode 100644 _examples/tutorial/mvc-from-scratch/persistence/database.go create mode 100644 _examples/tutorial/mvc-from-scratch/views/index.html rename _examples/tutorial/{mvc => mvc-from-scratch}/views/user/index.html (100%) create mode 100644 core/router/controller.go create mode 100644 core/router/controller_test.go 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.