Update to 8.3.0 | MVC Models and Bindings and fix of #723 , read HISTORY.md

Former-commit-id: d8f66d8d370c583a288333df2a14c6ee2dc56466
This commit is contained in:
kataras 2017-08-18 17:09:18 +03:00
parent 398d1e816c
commit b96476d100
53 changed files with 12642 additions and 1046 deletions

View File

@ -1,89 +0,0 @@
# 10 July 2017
## 📈 One and a half years with Iris and You...
- 7070 github stars
- 749 github forks
- 1m total views at its documentation
- ~800$ at donations (there're a lot for a golang open-source project, thanks to you)
- ~550 reported bugs fixed
- ~30 community feature requests have been implemented
## 🔥 Reborn
As you may have heard I have huge responsibilities on my new position at Dubai nowadays, therefore I don't have the needed time to work on this project anymore.
After almost a month of negotiations and searching I succeed to find a decent software engineer to continue my work on the open source community.
The leadership of this, open-source, repository was transferred to [hiveminded](https://github.com/hiveminded).
These types of projects need heart and sacrifices to continue offer the best developer experience like a paid software, please do support him as you did with me!
# 02 July 2017
### DEPRECATED
Iris has been acquired so development is up to the community, there are two active iris-based communities so far.
Use one of these projects instead:
https://github.com/get-ion/ion
**Ion is a fast, simple and efficient micro web framework for Go. It provides a beautifully expressive and easy to use foundation for your next website, API, or distributed app.**
- a bit faster than Iris version 7, based on `ab`
- stable api
- more examples
- sessions, websockets, typescript and cloud editor(fixed) on different packages
- test cov, including examples
- slack bot for support automation
- has a FAQ page which is part of the gitbook.com beta program
- central issue portal
- HuHu supported
https://github.com/go-siris/siris
**A fast, cross-platform and efficient web framework with robust set of well-designed features, written entirely in Go.**
- three maintainers
- plan to stabilize api, no unneeded changes
- plan to increase test-coverage
- plan to add more middlewares and examples
> If your team's project is missing from this list, please contact with me.
# 17 June 2017
### IRIS HAS BEEN ACQUIRED
Iris project has been acquired by a Dubai-based startup.
Both sides agree that every related public data should remain open for at least 30 days.
After the period of 30 days, company has the proprietary rights to delete or transfer this repository and all its related public data forever without any warnings.
The company may or may not reveal its true identity to the public.
Transaction of the public domains still in-progress:
- http://iris-go.com
- https://kataras.rocket.chat/channel/iris
View-accessed users can clone the current state of the project's public repositories and use without any warranties.
From now on, Original Author owns a high position to the company's table.
At any circumstances,
Original Author keeps the creation rights.
### About the future of Iris
Clone the repository today because if I can't find a new lead maintainer for the [v7.2](https://github.com/kataras/iris-v7-29d) you, as community, will have to find a way to communicate about its future, the name "iris go" was taken by the company too, so it will be nice if the future main contributor change its name too, if you don't do it I will not beat you but I don't know the full company's law-plan for this, yet.
All donators, without any exception, will have my support for at least 6 months (for all iris versions), we have a private room at the [chat](https://kataras.rocket.chat/channel/iris).
Don't worry **I will not let you down**, we're trying to find a decent open-source contributor to continue the Iris' open-source codebase. I'm already in touch with some good gophers but **If you're willing to maintain this project** please [send](#contact) me details about your experience, general bio and your github username.
**I am really thankful for all of your support to me and the community, all donations, all bug reports, all comments without any exception. I did proceeded with all my physical abilities so far but unfortunately there weren't enough for my survivor. I'm really sorry if the latest news made iris open-source community disappointed but you have to see things from my point view, I was one step before bankruptcy, I had no other choice but accept the offer.**

View File

@ -18,6 +18,223 @@ 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`. **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
# Fr, 18 August 2017 | v8.3.0
Good news for devs that are used to write their web apps using the `MVC` architecture pattern.
Implement a whole new `mvc` package with additional support for models and easy binding.
@kataras started to develop that feature by version 8.2.5, back then it didn't seem
to be a large feature and maybe a game-changer, so it lived inside the `kataras/iris/core/router/controller.go` file.
However with this version, so many things are implemented for the MVC and we needed a new whole package,
this new package is the `kataras/iris/mvc`, but if you used go 1.9 to build then you don't have to do any refactor, you could use the `iris.Controller` type alias.
People who used the mvc from its baby steps(v8.2.5) the only syntactic change you'll have to do is to rename the `router.Controller` to `mvc.Controller`:
Before:
```go
import "github.com/kataras/iris/core/router"
type MyController struct {
router.Controller
}
```
Now:
```go
import "github.com/kataras/iris/mvc"
type MyController struct {
mvc.Controller
// if you build with go1.9 you can omit the import of mvc package
// and just use `iris.Controller` instead.
}
```
### MVC (Model View Controller)
![](_examples/mvc/web_mvc_diagram.png)
From version 8.3 and after Iris has **first-class support for the MVC pattern**, you'll not find
these stuff anywhere else in the Go world.
Example Code
```go
package main
import (
"sync"
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
// when we have a path separated by spaces
// then the Controller is registered to all of them one by one.
//
// myDB is binded to the controller's `*DB` field: use only structs and pointers.
app.Controller("/profile /profile/browse /profile/{id:int} /profile/me",
new(ProfileController), myDB) // IMPORTANT
app.Run(iris.Addr(":8080"))
}
// UserModel our example model which will render on the template.
type UserModel struct {
ID int64
Username string
}
// DB is our example database.
type DB struct {
usersTable map[int64]UserModel
mu sync.RWMutex
}
// GetUserByID imaginary database lookup based on user id.
func (db *DB) GetUserByID(id int64) (u UserModel, found bool) {
db.mu.RLock()
u, found = db.usersTable[id]
db.mu.RUnlock()
return
}
var myDB = &DB{
usersTable: map[int64]UserModel{
1: {1, "kataras"},
2: {2, "makis"},
42: {42, "jdoe"},
},
}
// ProfileController our example user controller which controls
// the paths of "/profile" "/profile/{id:int}" and "/profile/me".
type ProfileController struct {
mvc.Controller // IMPORTANT
User UserModel `iris:"model"`
// we will bind it but you can also tag it with`iris:"persistence"`
// and init the controller with manual &PorifleController{DB: myDB}.
DB *DB
}
// Get method handles all "GET" HTTP Method requests of the controller's paths.
func (pc *ProfileController) Get() { // IMPORTANT
path := pc.Path
// requested: /profile path
if path == "/profile" {
pc.Tmpl = "profile/index.html"
return
}
// requested: /profile/browse
// this exists only to proof the concept of changing the path:
// it will result to a redirection.
if path == "/profile/browse" {
pc.Path = "/profile"
return
}
// requested: /profile/me path
if path == "/profile/me" {
pc.Tmpl = "profile/me.html"
return
}
// requested: /profile/$ID
id, _ := pc.Params.GetInt64("id")
user, found := pc.DB.GetUserByID(id)
if !found {
pc.Status = iris.StatusNotFound
pc.Tmpl = "profile/notfound.html"
pc.Data["ID"] = id
return
}
pc.Tmpl = "profile/profile.html"
pc.User = user
}
/*
func (pc *ProfileController) Post() {}
func (pc *ProfileController) Put() {}
func (pc *ProfileController) Delete() {}
func (pc *ProfileController) Connect() {}
func (pc *ProfileController) Head() {}
func (pc *ProfileController) Patch() {}
func (pc *ProfileController) Options() {}
func (pc *ProfileController) Trace() {}
*/
/*
func (pc *ProfileController) All() {}
// OR
func (pc *ProfileController) Any() {}
*/
```
Iris web framework supports Request data, Models, Persistence Data and Binding
with the fastest possible execution.
**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 or Bind using `app.Controller("/" , new(myController), theBindValue)`.
Models inside your Controller struct (set-ed at the Method function and rendered by the View)
via `iris:"model"` tag right to the field, i.e ```User UserModel `iris:"model" name:"user"` ``` view will recognise it as `{{.user}}`.
If `name` tag is missing then it takes the field's name, in this case the `"User"`.
Access to the request path and its parameters via the `Path and Params` fields.
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.
Optional `BeginRequest(ctx)` function to perform any initialization before the method execution,
useful to call middlewares or when many methods use the same collection of data.
Optional `EndRequest(ctx)` function to perform any finalization after any method executed.
Inheritance, see for example our `mvc.SessionController`, it has the `mvc.Controller` as an embedded field
and it adds its logic to its `BeginRequest`, [here](https://github.com/kataras/iris/blob/master/mvc/session_controller.go).
**Using Iris MVC for code reuse**
By creating components that are independent of one another, developers are able to reuse components quickly and easily in other applications. The same (or similar) view for one application can be refactored for another application with different data because the view is simply handling how the data is being displayed to the user.
If you're new to back-end web development read about the MVC architectural pattern first, a good start is that [wikipedia article](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
Follow the examples below,
- [Hello world](_examples/mvc/hello-world/main.go)
- [Session Controller](_examples/mvc/session-controller/main.go)
- [A simple but featured Controller with model and views](_examples/mvc/controller-with-model-and-view).
### Bugs
Fix [#723](https://github.com/kataras/iris/issues/723) reported by @speedwheel.
# Mo, 14 August 2017 | v8.2.6 # Mo, 14 August 2017 | v8.2.6
Able to call done/end handlers inside a `Controller`, via optional `EndRequest(ctx context.Context)` function inside the controller struct. Able to call done/end handlers inside a `Controller`, via optional `EndRequest(ctx context.Context)` function inside the controller struct.
@ -49,8 +266,8 @@ 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 - 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 - `persistence` data inside your Controller struct (share data between requests) via **`iris:"persistence"`** tag right to the field
- optional `Init(ctx) or BeginRequest(ctx)` function to perform any initialization before the methods, useful to call middlewares or when many methods use the same collection of data - optional `BeginRequest(ctx)` function to perform any initialization before the methods, useful to call middlewares or when many methods use the same collection of data
- optional `Done(ctx) or EndRequest(ctx)` function to perform any finalization after the methods executed - optional `EndRequest(ctx)` function to perform any finalization after the methods executed
- access to the request path parameters via the `Params` field - 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 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 data that should be rendered inside the template file via `Data` field
@ -96,7 +313,7 @@ import (
// Index is our index example controller. // Index is our index example controller.
type Index struct { type Index struct {
router.Controller mvc.Controller
// if you're using go1.9: // if you're using go1.9:
// you can omit the /core/router import statement // you can omit the /core/router import statement
// and just use the `iris.Controller` instead. // and just use the `iris.Controller` instead.
@ -116,7 +333,7 @@ func (c *Index) Post() {}
> Tip: declare a func(c *Index) All() {} or Any() to register all HTTP Methods. > 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. A full example can be found at the [_examples/mvc](_examples/mvc) folder.
# Sa, 12 August 2017 | v8.2.4 # Sa, 12 August 2017 | v8.2.4

View File

@ -4,9 +4,22 @@
<img src="iris_the_new_machine.gif"> <img src="iris_the_new_machine.gif">
</p> </p>
**Powered by [KeyCDN](https://www.keycdn.com/)**, A Simple, Fast and Reliable CDN.
Iris is a fast, simple and efficient micro web framework for Go. It provides a beautifully expressive and easy to use foundation for your next website, API, or distributed app. Iris is a fast, simple and efficient micro web framework for Go. It provides a beautifully expressive and easy to use foundation for your next website, API, or distributed app.
[Star or watch](https://github.com/kataras/iris/stargazers) this repository, it is still in **active development mode**. We have no doubt you will able to find other web frameworks written in Golang
and even put a real fight to learn and use them for quite some time but
make no mistake, sooner or later you'll be using Iris, no because of the ergonomic high-performant solution that it provides but its well-documented unique features are these will transform you to a real rockstar geek.
No matter what you're trying to build, Iris takes cover
every type of applications, from micro services to large monolithic web applications.
It's actually the best piece of software for back-end web developers
you could ever find online.
Iris may have reached version 8, but we're not stopping there. We have many feature ideas on our board that we're anxious to add and other innovative web development solutions that we're planning to build into Iris.
[Star or watch](https://github.com/kataras/iris/stargazers) this repository to stay updated at general technology progress. It's the only thing we ask from you, it's our sign.
<!-- [![total used by](http://iris-go.com/graph?style=flat-square)](http://iris-go.com/graph) --> <!-- [![total used by](http://iris-go.com/graph?style=flat-square)](http://iris-go.com/graph) -->
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)
@ -18,7 +31,6 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a b
<!-- has issues, the img.shields.io's API is problematic for this resource: [![issue stats](https://img.shields.io/issuestats/i/github/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed) --> <!-- has issues, the img.shields.io's API is problematic for this resource: [![issue stats](https://img.shields.io/issuestats/i/github/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed) -->
<!-- [![godocs](https://img.shields.io/badge/godocs-8.x.x-0366d6.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris) --> <!-- [![godocs](https://img.shields.io/badge/godocs-8.x.x-0366d6.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris) -->
<p> <p>
<img src="https://raw.githubusercontent.com/smallnest/go-web-framework-benchmark/4db507a22c964c9bc9774c5b31afdc199a0fe8b7/benchmark.png" alt="Third-party source for transparency." /> <img src="https://raw.githubusercontent.com/smallnest/go-web-framework-benchmark/4db507a22c964c9bc9774c5b31afdc199a0fe8b7/benchmark.png" alt="Third-party source for transparency." />
</p> </p>
@ -30,7 +42,8 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a b
* [Learn](#-learn) * [Learn](#-learn)
* [HTTP Listening](_examples/#http-listening) * [HTTP Listening](_examples/#http-listening)
* [Configuration](_examples/#configuration) * [Configuration](_examples/#configuration)
* [Routing, Grouping, Controllers, Dynamic Path Parameters, "Macros" and Custom Context](_examples/#routing-grouping-dynamic-path-parameters-macros-and-custom-context) * [Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context](_examples/#routing-grouping-dynamic-path-parameters-macros-and-custom-context)
* [MVC (Model View Controller)](_examples/#mvc)
* [Subdomains](_examples/#subdomains) * [Subdomains](_examples/#subdomains)
* [Wrap `http.Handler/HandlerFunc`](_examples/#convert-httphandlerhandlerfunc) * [Wrap `http.Handler/HandlerFunc`](_examples/#convert-httphandlerhandlerfunc)
* [View](_examples/#view) * [View](_examples/#view)
@ -248,7 +261,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 * Remove trailing slash from the URL with option to redirect
* Virtual hosts and subdomains made easy * Virtual hosts and subdomains made easy
* Group API's and static or even dynamic subdomains * Group API's and static or even dynamic subdomains
* MVC [**NEW**](_examples/routing/mvc) * MVC [**NEW**](_examples/mvc)
* `net/http` and `negroni-like` handlers are compatible via `iris.FromStd` * `net/http` and `negroni-like` handlers are compatible via `iris.FromStd`
* Register custom handlers for any HTTP error * Register custom handlers for any HTTP error
* Transactions and rollback when you need it * Transactions and rollback when you need it
@ -313,17 +326,19 @@ The most useful community repository for _iris_ developers is the
$ go get -u github.com/iris-contrib/middleware/... $ go get -u github.com/iris-contrib/middleware/...
``` ```
#### 📈 One and a half years with You... #### 📈 One and a half years...
- 7210 github stars [![total used by](http://iris-go.com/graph?style=flat-square)](http://iris-go.com/graph)
Iris exceeded all expectations, started as one-man project.
- 7288 github stars
- 766 github forks - 766 github forks
- 1m total views at its documentation - 1m total views at its documentation
- ~800$ at donations (there're a lot for a golang open-source project, thanks to you) - ~800$ at donations, small amount for the work we put here but it's a good start
- ~554 reported bugs fixed - ~554 reported bugs fixed
- ~30 community feature requests have been implemented - ~30 community feature requests have been implemented
Thank You for your trust!
### 📌 Version ### 📌 Version
Current: [VERSION](VERSION) Current: [VERSION](VERSION)
@ -354,12 +369,23 @@ Employers that are looking for brilliant Software Engineers with good experience
### 🥇 People ### 🥇 People
The original author of _iris_ is [Gerasimos Maropoulos](https://medium.com/@kataras) The original author of _Iris_ is [@kataras](https://github.com/kataras), you can reach him via
- [Medium](https://medium.com/@kataras)
- [Twitter](https://twitter.com/makismaropoulos)
- [Dev.to](https://dev.to/@kataras)
- [Facebook](https://facebook.com/kataras.gopher)
- [Mail](mailto:kataras2006@hotmail.com?subject=Iris%20I%20need%20some%20help%20please)
The current lead maintainer is [Bill Qeras, Jr.](https://github.com/hiveminded) [List of all Authors](AUTHORS)
[List of all contributors](https://github.com/kataras/iris/graphs/contributors) [List of all Contributors](https://github.com/kataras/iris/graphs/contributors)
Help this project to continue deliver awesome and unique features with the higher code quality as possible Help this project to continue deliver awesome and unique features with the higher code quality as possible by donating any amount
[![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) [![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
## License
This software is licensed under the open-source 3-Clause BSD.
You can find the license file [here](LICENSE), for any questions regarding the license please [contact](mailto:kataras2006@hotmail.com?subject=Iris%20License) us.

View File

@ -1 +1 @@
8.2.6:https://github.com/kataras/iris/blob/master/HISTORY.md#mo-14-august-2017--v826 8.3.0:https://github.com/kataras/iris/blob/master/HISTORY.md#fr-18-august-2017--v830

View File

@ -82,7 +82,7 @@ Navigate through examples for a better understanding.
- [Overview](routing/overview/main.go) - [Overview](routing/overview/main.go)
- [Basic](routing/basic/main.go) - [Basic](routing/basic/main.go)
- [Controllers](routing/mvc) - [Controllers](mvc)
- [Custom HTTP Errors](routing/http-errors/main.go) - [Custom HTTP Errors](routing/http-errors/main.go)
- [Dynamic Path](routing/dynamic-path/main.go) - [Dynamic Path](routing/dynamic-path/main.go)
* [root level wildcard path](routing/dynamic-path/root-wildcard/main.go) * [root level wildcard path](routing/dynamic-path/root-wildcard/main.go)
@ -93,6 +93,66 @@ Navigate through examples for a better understanding.
* [new implementation](routing/custom-context/new-implementation/main.go) * [new implementation](routing/custom-context/new-implementation/main.go)
- [Route State](routing/route-state/main.go) - [Route State](routing/route-state/main.go)
### MVC
![](mvc/web_mvc_diagram.png)
Iris has **first-class support for the MVC (Model View Controller) pattern**, you'll not find
these stuff anywhere else in the Go world.
Iris web framework supports Request data, Models, Persistence Data and Binding
with the fastest possible execution.
**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 or Bind using `app.Controller("/" , new(myController), theBindValue)`.
Models inside your Controller struct (set-ed at the Method function and rendered by the View)
via `iris:"model"` tag right to the field, i.e ```User UserModel `iris:"model" name:"user"` ``` view will recognise it as `{{.user}}`.
If `name` tag is missing then it takes the field's name, in this case the `"User"`.
Access to the request path and its parameters via the `Path and Params` fields.
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.
Optional `BeginRequest(ctx)` function to perform any initialization before the method execution,
useful to call middlewares or when many methods use the same collection of data.
Optional `EndRequest(ctx)` function to perform any finalization after any method executed.
Inheritance, see for example our `mvc.SessionController`, it has the `mvc.Controller` as an embedded field
and it adds its logic to its `BeginRequest`, [here](https://github.com/kataras/iris/blob/master/mvc/session_controller.go).
**Using Iris MVC for code reuse**
By creating components that are independent of one another, developers are able to reuse components quickly and easily in other applications. The same (or similar) view for one application can be refactored for another application with different data because the view is simply handling how the data is being displayed to the user.
If you're new to back-end web development read about the MVC architectural pattern first, a good start is that [wikipedia article](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
Follow the examples below,
- [Hello world](mvc/hello-world/main.go)
- [Session Controller](mvc/session-controller/main.go)
- [A simple but featured Controller with model and views](mvc/controller-with-model-and-view).
### Subdomains ### Subdomains
- [Single](subdomains/single/main.go) - [Single](subdomains/single/main.go)
@ -213,11 +273,14 @@ iris session manager lives on its own [package](https://github.com/kataras/iris/
iris websocket library lives on its own [package](https://github.com/kataras/iris/tree/master/websocket). iris websocket library lives on its own [package](https://github.com/kataras/iris/tree/master/websocket).
The package is designed to work with raw websockets although its API is similar to the famous [socket.io](https://socket.io). I have read an article recently and I felt very contented about my decision to design a **fast** websocket-**only** package for Iris and not a backwards socket.io-like package. You can read that article by following this link: https://medium.com/@ivanderbyl/why-you-don-t-need-socket-io-6848f1c871cd.
- [Chat](websocket/chat/main.go) - [Chat](websocket/chat/main.go)
- [Native Messages](websocket/native-messages/main.go) - [Native Messages](websocket/native-messages/main.go)
- [Connection List](websocket/connectionlist/main.go) - [Connection List](websocket/connectionlist/main.go)
- [TLS Enabled](websocket/secure/main.go) - [TLS Enabled](websocket/secure/main.go)
- [Custom Raw Go Client](websocket/custom-go-client/main.go) - [Custom Raw Go Client](websocket/custom-go-client/main.go)
- [Third-Party socket.io](websocket/third-party-socketio/main.go)
> You're free to use your own favourite websockets package if you'd like so. > You're free to use your own favourite websockets package if you'd like so.

View File

@ -5,13 +5,13 @@ package main
import ( import (
"github.com/kataras/iris" "github.com/kataras/iris"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/middleware/logger" "github.com/kataras/iris/middleware/logger"
"github.com/kataras/iris/middleware/recover" "github.com/kataras/iris/middleware/recover"
) )
func main() { func main() {
app := iris.New() app := iris.New()
// Optionally, add two built'n handlers // Optionally, add two built'n handlers
// that can recover from any http-relative panics // that can recover from any http-relative panics
// and log the requests to the terminal. // and log the requests to the terminal.
@ -21,7 +21,7 @@ func main() {
// Method: GET // Method: GET
// Resource: http://localhost:8080/ // Resource: http://localhost:8080/
app.Handle("GET", "/", func(ctx context.Context) { app.Handle("GET", "/", func(ctx context.Context) {
ctx.HTML("<b>Hello world!</b>") ctx.HTML("<b>Welcome!</b>")
}) })
// same as app.Handle("GET", "/ping", [...]) // same as app.Handle("GET", "/ping", [...])

View File

@ -0,0 +1,99 @@
package main
import (
"sync"
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
// when we have a path separated by spaces
// then the Controller is registered to all of them one by one.
//
// myDB is binded to the controller's `*DB` field: use only structs and pointers.
app.Controller("/profile /profile/browse /profile/{id:int} /profile/me",
new(ProfileController), myDB) // IMPORTANT
app.Run(iris.Addr(":8080"))
}
// UserModel our example model which will render on the template.
type UserModel struct {
ID int64
Username string
}
// DB is our example database.
type DB struct {
usersTable map[int64]UserModel
mu sync.RWMutex
}
// GetUserByID imaginary database lookup based on user id.
func (db *DB) GetUserByID(id int64) (u UserModel, found bool) {
db.mu.RLock()
u, found = db.usersTable[id]
db.mu.RUnlock()
return
}
var myDB = &DB{
usersTable: map[int64]UserModel{
1: {1, "kataras"},
2: {2, "makis"},
42: {42, "jdoe"},
},
}
// ProfileController our example user controller which controls
// the paths of "/profile" "/profile/{id:int}" and "/profile/me".
type ProfileController struct {
mvc.Controller // IMPORTANT
User UserModel `iris:"model"`
// we will bind it but you can also tag it with`iris:"persistence"`
// and init the controller with manual &PorifleController{DB: myDB}.
DB *DB
}
// Get method handles all "GET" HTTP Method requests of the controller's paths.
func (pc *ProfileController) Get() { // IMPORTANT
path := pc.Path
// requested: /profile path
if path == "/profile" {
pc.Tmpl = "profile/index.html"
return
}
// requested: /profile/browse
// this exists only to proof the concept of changing the path:
// it will result to a redirection.
if path == "/profile/browse" {
pc.Path = "/profile"
return
}
// requested: /profile/me path
if path == "/profile/me" {
pc.Tmpl = "profile/me.html"
return
}
// requested: /profile/$ID
id, _ := pc.Params.GetInt64("id")
user, found := pc.DB.GetUserByID(id)
if !found {
pc.Status = iris.StatusNotFound
pc.Tmpl = "profile/notfound.html"
pc.Data["ID"] = id
return
}
pc.Tmpl = "profile/profile.html"
pc.User = user
}

View File

@ -0,0 +1,13 @@
<html>
<head>
<title>Profile Browser</title>
</head>
<body>
<p>
This is the main page of the <b>/profile</b> path, we'd use that to browser profiles.
</p>
</body>
</html>

View File

@ -0,0 +1,13 @@
<html>
<head>
<title>My Profile</title>
</head>
<body>
<p>
This is the current's user imaginary profile space.
</p>
</body>
</html>

View File

@ -0,0 +1,13 @@
<html>
<head>
<title>Not Found</title>
</head>
<body>
<p>
User with <b>{{.ID}}</b> doesn't exist!</b>
</p>
</body>
</html>

View File

@ -0,0 +1,13 @@
<html>
<head>
<title>Profile of {{.User.Username}}</title>
</head>
<body>
<p>
This is the profile of a user with ID: <b>{{.User.ID}}</b> and Username: <b>{{.User.Username}}</b>
</p>
</body>
</html>

View File

@ -0,0 +1,105 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
"github.com/kataras/iris/middleware/logger"
"github.com/kataras/iris/middleware/recover"
)
// This example is equivalent to the
// https://github.com/kataras/iris/blob/master/_examples/hello-world/main.go
//
// It seems that additional code you
// have to write doesn't worth it
// but remember that, this example
// does not make use of iris mvc features like
// the Model, Persistence or the View engine neither the Session,
// it's very simple for learning purposes,
// probably you'll never use such
// as simple controller anywhere in your app.
//
// The cost we have on this example for using MVC
// on the "/hello" path which serves JSON
// is ~2MB per 20MB throughput on my personal laptop,
// it's tolerated for the majority of the applications
// but you can choose
// what suits you best with Iris, low-level handlers: performance
// or high-level controllers: easier to maintain and smaller codebase on large applications.
func main() {
app := iris.New()
// Optionally, add two built'n handlers
// that can recover from any http-relative panics
// and log the requests to the terminal.
app.Use(recover.New())
app.Use(logger.New())
app.Controller("/", new(IndexController))
app.Controller("/ping", new(PingController))
app.Controller("/hello", new(HelloController))
// http://localhost:8080
// http://localhost:8080/ping
// http://localhost:8080/hello
app.Run(iris.Addr(":8080"))
}
// IndexController serves the "/".
type IndexController struct {
// if you build with go1.9 you can omit the import of mvc package
// and just use `iris.Controller` instead.
mvc.Controller
}
// Get serves
// Method: GET
// Resource: http://localhost:8080/
func (c *IndexController) Get() {
c.Ctx.HTML("<b>Welcome!</b>")
}
// PingController serves the "/ping".
type PingController struct {
mvc.Controller
}
// Get serves
// Method: GET
// Resource: http://context:8080/ping
func (c *PingController) Get() {
c.Ctx.WriteString("pong")
}
// HelloController serves the "/hello".
type HelloController struct {
mvc.Controller
}
// Get serves
// Method: GET
// Resource: http://localhost:8080/hello
func (c *HelloController) Get() {
c.Ctx.JSON(iris.Map{"message": "Hello iris web framework."})
}
/* Can use more than one, the factory will make sure
that the correct http methods are being registered for each route
for this controller, uncomment these if you want:
func (c *HelloController) Post() {}
func (c *HelloController) Put() {}
func (c *HelloController) Delete() {}
func (c *HelloController) Connect() {}
func (c *HelloController) Head() {}
func (c *HelloController) Patch() {}
func (c *HelloController) Options() {}
func (c *HelloController) Trace() {}
*/
/*
func (c *HelloController) All() {}
// OR
func (c *HelloController) Any() {}
*/

View File

@ -0,0 +1,47 @@
package main
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
"github.com/kataras/iris/sessions"
)
type VisitController struct {
// if you build with go1.9 you can omit the import of mvc package
// and just use `iris.Controller` instead.
mvc.SessionController
StartTime time.Time
}
func (u *VisitController) Get() {
// get the visits, before calcuate this new one.
visits, _ := u.Session.GetIntDefault("visits", 0)
// increment the visits counter and set them to the session.
visits++
u.Session.Set("visits", visits)
// write the current, updated visits
u.Ctx.Writef("%d visits in %0.1f seconds", visits, time.Now().Sub(u.StartTime).Seconds())
}
func main() {
mySessionManager := sessions.New(sessions.Config{Cookie: "mysession_cookie_name"})
app := iris.New()
// bind our session manager, which is required, to the `VisitController.SessionManager.Manager`
// and the time.Now() to the `VisitController.StartTime`.
app.Controller("/", new(VisitController), mySessionManager, time.Now())
// 1. open the browser (no in private mode)
// 2. navigate to http://localhost:8080
// 3. refresh the page some times
// 4. close the browser
// 5. re-open the browser and re-play 2.
app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

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

View File

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

View File

@ -1,62 +0,0 @@
// +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 registered for this
controller, uncommend these if you want:
func (c *User) Post() {}
func (c *User) Put() {}
func (c *User) Delete() {}
func (c *User) Connect() {}
func (c *User) Head() {}
func (c *User) Patch() {}
func (c *User) Options() {}
func (c *User) Trace() {}
*/
/*
func (c *User) All() {}
// OR
func (c *User) Any() {}
*/

View File

@ -1,81 +0,0 @@
// +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 registered for this
controller, uncommend these if you want:
func (c *User) Post() {}
func (c *User) Put() {}
func (c *User) Delete() {}
func (c *User) Connect() {}
func (c *User) Head() {}
func (c *User) Patch() {}
func (c *User) Options() {}
func (c *User) Trace() {}
*/
/*
func (c *User) All() {}
// OR
func (c *User) Any() {}
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,99 +1,130 @@
# Controllers from scratch # Controllers from scratch
This example folder shows how I started to develop This folder shows how [@kataras](https://github.com/kataras) started to develop
the Controller idea inside the Iris web framework itself. the MVC idea inside the Iris web framework itself.
Now it's built'n feature and can be used as: **Now** it has been enhanced and it's a **built'n** feature and can be used as:
```go ```go
// +build go1.9
// file main.go
package main package main
import ( import (
"github.com/kataras/iris/_examples/routing/mvc/persistence" "sync"
"github.com/kataras/iris" "github.com/kataras/iris"
"github.com/kataras/iris/mvc"
) )
func main() { func main() {
app := iris.New() app := iris.New()
app.RegisterView(iris.HTML("./views", ".html")) app.RegisterView(iris.HTML("./views", ".html"))
db := persistence.OpenDatabase("a fake db") // when we have a path separated by spaces
// then the Controller is registered to all of them one by one.
//
// myDB is binded to the controller's `*DB` field: use only structs and pointers.
app.Controller("/profile /profile/browse /profile/{id:int} /profile/me",
new(ProfileController), myDB) // IMPORTANT
app.Controller("/user/{userid:int}", NewUserController(db))
// http://localhost:8080/
// http://localhost:8080/user/42
app.Run(iris.Addr(":8080")) app.Run(iris.Addr(":8080"))
} }
```
```go // UserModel our example model which will render on the template.
// +build go1.9 type UserModel struct {
ID int64
// file user_controller.go Username string
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 { // DB is our example database.
return &UserController{ type DB struct {
CreatedAt: time.Now(), usersTable map[int64]UserModel
Title: "User page", mu sync.RWMutex
DB: db, }
// GetUserByID imaginary database lookup based on user id.
func (db *DB) GetUserByID(id int64) (u UserModel, found bool) {
db.mu.RLock()
u, found = db.usersTable[id]
db.mu.RUnlock()
return
}
var myDB = &DB{
usersTable: map[int64]UserModel{
1: {1, "kataras"},
2: {2, "makis"},
42: {42, "jdoe"},
},
}
// ProfileController our example user controller which controls
// the paths of "/profile" "/profile/{id:int}" and "/profile/me".
type ProfileController struct {
mvc.Controller // IMPORTANT
User UserModel `iris:"model"`
// we will bind it but you can also tag it with`iris:"persistence"`
// and init the controller with manual &PorifleController{DB: myDB}.
DB *DB
}
// Get method handles all "GET" HTTP Method requests of the controller's paths.
func (pc *ProfileController) Get() { // IMPORTANT
path := pc.Path
// requested: /profile path
if path == "/profile" {
pc.Tmpl = "profile/index.html"
return
}
// requested: /profile/browse
// this exists only to proof the concept of changing the path:
// it will result to a redirection.
if path == "/profile/browse" {
pc.Path = "/profile"
return
} }
}
// Get serves using the User controller when HTTP Method is "GET". // requested: /profile/me path
func (c *UserController) Get() { if path == "/profile/me" {
c.Tmpl = "user/index.html" pc.Tmpl = "profile/me.html"
c.Data["title"] = c.Title return
c.Data["username"] = "kataras " + c.Params.Get("userid") }
c.Data["connstring"] = c.DB.Connstring
c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds() // requested: /profile/$ID
id, _ := pc.Params.GetInt64("id")
user, found := pc.DB.GetUserByID(id)
if !found {
pc.Status = iris.StatusNotFound
pc.Tmpl = "profile/notfound.html"
pc.Data["ID"] = id
return
}
pc.Tmpl = "profile/profile.html"
pc.User = user
} }
/* Can use more than one, the factory will make sure /* Can use more than one, the factory will make sure
that the correct http methods are being registered for this that the correct http methods are being registered for each route
controller, uncommend these if you want: for this controller, uncomment these if you want:
func (c *User) Post() {} func (pc *ProfileController) Post() {}
func (c *User) Put() {} func (pc *ProfileController) Put() {}
func (c *User) Delete() {} func (pc *ProfileController) Delete() {}
func (c *User) Connect() {} func (pc *ProfileController) Connect() {}
func (c *User) Head() {} func (pc *ProfileController) Head() {}
func (c *User) Patch() {} func (pc *ProfileController) Patch() {}
func (c *User) Options() {} func (pc *ProfileController) Options() {}
func (c *User) Trace() {} func (pc *ProfileController) Trace() {}
*/ */
/* /*
func (c *User) All() {} func (c *ProfileController) All() {}
// OR // OR
func (c *User) Any() {} func (c *ProfileController) Any() {}
*/ */
``` ```
Example can be found at: [_examples/routing/mvc](https://github.com/kataras/iris/tree/master/_examples/routing/mvc). Example can be found at: [_examples/mvc](https://github.com/kataras/iris/tree/master/_examples/mvc).

View File

@ -0,0 +1,44 @@
package main
import (
"github.com/kataras/iris"
"github.com/googollee/go-socket.io"
)
/*
go get -u github.com/googollee/go-socket.io
*/
func main() {
app := iris.New()
server, err := socketio.NewServer(nil)
if err != nil {
app.Logger().Fatal(err)
}
server.On("connection", func(so socketio.Socket) {
app.Logger().Infof("on connection")
so.Join("chat")
so.On("chat message", func(msg string) {
app.Logger().Infof("emit: %v", so.Emit("chat message", msg))
so.BroadcastTo("chat", "chat message", msg)
})
so.On("disconnection", func() {
app.Logger().Infof("on disconnect")
})
})
server.On("error", func(so socketio.Socket, err error) {
app.Logger().Errorf("error: %v", err)
})
// serve the socket.io endpoint.
app.Any("/socket.io/{p:path}", iris.FromStd(server))
// serve the index.html and the javascript libraries at
// http://localhost:8080
app.StaticWeb("/", "./public")
app.Run(iris.Addr("localhost:8080"))
}

View File

@ -0,0 +1,38 @@
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io-1.3.7.js"></script>
<script src="/jquery-1.11.1.js"></script>
<script>
var socket = io();
$('form').submit(function(){
socket.emit('chat message with ack', $('#m').val(), function(data){
$('#messages').append($('<li>').text('ACK CALLBACK: ' + data));
});
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -111,7 +111,7 @@ func WithoutServerError(errors ...error) Configurator {
return return
} }
errorsAsString := make([]string, len(errors), len(errors)) errorsAsString := make([]string, len(errors))
for i, e := range errors { for i, e := range errors {
errorsAsString[i] = e.Error() errorsAsString[i] = e.Error()
} }
@ -213,7 +213,7 @@ func WithCharset(charset string) Configurator {
func WithRemoteAddrHeader(headerName string) Configurator { func WithRemoteAddrHeader(headerName string) Configurator {
return func(app *Application) { return func(app *Application) {
if app.config.RemoteAddrHeaders == nil { if app.config.RemoteAddrHeaders == nil {
app.config.RemoteAddrHeaders = make(map[string]bool, 0) app.config.RemoteAddrHeaders = make(map[string]bool)
} }
app.config.RemoteAddrHeaders[headerName] = true app.config.RemoteAddrHeaders[headerName] = true
} }
@ -231,7 +231,7 @@ func WithRemoteAddrHeader(headerName string) Configurator {
func WithoutRemoteAddrHeader(headerName string) Configurator { func WithoutRemoteAddrHeader(headerName string) Configurator {
return func(app *Application) { return func(app *Application) {
if app.config.RemoteAddrHeaders == nil { if app.config.RemoteAddrHeaders == nil {
app.config.RemoteAddrHeaders = make(map[string]bool, 0) app.config.RemoteAddrHeaders = make(map[string]bool)
} }
app.config.RemoteAddrHeaders[headerName] = false app.config.RemoteAddrHeaders[headerName] = false
} }
@ -243,7 +243,7 @@ func WithoutRemoteAddrHeader(headerName string) Configurator {
func WithOtherValue(key string, val interface{}) Configurator { func WithOtherValue(key string, val interface{}) Configurator {
return func(app *Application) { return func(app *Application) {
if app.config.Other == nil { if app.config.Other == nil {
app.config.Other = make(map[string]interface{}, 0) app.config.Other = make(map[string]interface{})
} }
app.config.Other[key] = val app.config.Other[key] = val
} }
@ -584,7 +584,7 @@ func WithConfiguration(c Configuration) Configurator {
if v := c.RemoteAddrHeaders; len(v) > 0 { if v := c.RemoteAddrHeaders; len(v) > 0 {
if main.RemoteAddrHeaders == nil { if main.RemoteAddrHeaders == nil {
main.RemoteAddrHeaders = make(map[string]bool, 0) main.RemoteAddrHeaders = make(map[string]bool)
} }
for key, value := range v { for key, value := range v {
main.RemoteAddrHeaders[key] = value main.RemoteAddrHeaders[key] = value
@ -593,7 +593,7 @@ func WithConfiguration(c Configuration) Configurator {
if v := c.Other; len(v) > 0 { if v := c.Other; len(v) > 0 {
if main.Other == nil { if main.Other == nil {
main.Other = make(map[string]interface{}, 0) main.Other = make(map[string]interface{})
} }
for key, value := range v { for key, value := range v {
main.Other[key] = value main.Other[key] = value
@ -625,6 +625,6 @@ func DefaultConfiguration() Configuration {
"CF-Connecting-IP": false, "CF-Connecting-IP": false,
}, },
EnableOptimizations: false, EnableOptimizations: false,
Other: make(map[string]interface{}, 0), Other: make(map[string]interface{}),
} }
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/host" "github.com/kataras/iris/core/host"
"github.com/kataras/iris/core/router" "github.com/kataras/iris/core/router"
"github.com/kataras/iris/mvc"
) )
// TODO: When go 1.9 will be released // TODO: When go 1.9 will be released
@ -16,9 +17,6 @@ import (
// core/host/supervisor.go // core/host/supervisor.go
// context.go // context.go
// _examples/hello-world/main_go19.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 // _examples/tutorial/mvc-from-scratch/README.md
type ( type (
// Context is the midle-man server's "object" for the clients. // Context is the midle-man server's "object" for the clients.
@ -59,6 +57,7 @@ type (
// //
// A shortcut for the `core/router#Party`, useful when `PartyFunc` is being used. // A shortcut for the `core/router#Party`, useful when `PartyFunc` is being used.
Party = router.Party Party = router.Party
// Controller is the base controller for the high level controllers instances. // Controller is the base controller for the high level controllers instances.
// //
// This base controller is used as an alternative way of building // This base controller is used as an alternative way of building
@ -118,11 +117,11 @@ type (
// //
// Look `core/router#APIBuilder#Controller` method too. // Look `core/router#APIBuilder#Controller` method too.
// //
// A shortcut for the `core/router#Controller`, // A shortcut for the `mvc#Controller`,
// useful when `app.Controller` is being used. // useful when `app.Controller` is being used.
// //
// A Controller can be declared by importing // A Controller can be declared by importing
// the "github.com/kataras/iris/core/router" // the "github.com/kataras/iris/mvc"
// package for machines that have not installed go1.9 yet. // package for machines that have not installed go1.9 yet.
Controller = router.Controller Controller = mvc.Controller
) )

View File

@ -887,8 +887,7 @@ func (ctx *context) Request() *http.Request {
// It's used by the router, developers may use that // It's used by the router, developers may use that
// to replace and execute handlers immediately. // to replace and execute handlers immediately.
func (ctx *context) Do(handlers Handlers) { func (ctx *context) Do(handlers Handlers) {
ctx.handlers = handlers Do(ctx, handlers)
ctx.handlers[0](ctx)
} }
// AddHandler can add handler(s) // AddHandler can add handler(s)
@ -1453,6 +1452,7 @@ func (ctx *context) Write(rawBody []byte) (int, error) {
// //
// Returns the number of bytes written and any write error encountered. // Returns the number of bytes written and any write error encountered.
func (ctx *context) Writef(format string, a ...interface{}) (n int, err error) { func (ctx *context) Writef(format string, a ...interface{}) (n int, err error) {
ctx.ContentType(contentTextHeaderValue)
return ctx.writer.Writef(format, a...) return ctx.writer.Writef(format, a...)
} }

View File

@ -1,6 +1,7 @@
package context package context
import ( import (
"fmt"
"io" "io"
"sync" "sync"
@ -50,6 +51,11 @@ func releaseGzipWriter(gzipWriter *gzip.Writer) {
func writeGzip(w io.Writer, b []byte) (int, error) { func writeGzip(w io.Writer, b []byte) (int, error) {
gzipWriter := acquireGzipWriter(w) gzipWriter := acquireGzipWriter(w)
n, err := gzipWriter.Write(b) n, err := gzipWriter.Write(b)
if err != nil {
releaseGzipWriter(gzipWriter)
return -1, err
}
err = gzipWriter.Flush()
releaseGzipWriter(gzipWriter) releaseGzipWriter(gzipWriter)
return n, err return n, err
} }
@ -64,7 +70,6 @@ func AcquireGzipResponseWriter() *GzipResponseWriter {
} }
func releaseGzipResponseWriter(w *GzipResponseWriter) { func releaseGzipResponseWriter(w *GzipResponseWriter) {
releaseGzipWriter(w.gzipWriter)
gzpool.Put(w) gzpool.Put(w)
} }
@ -74,7 +79,6 @@ func releaseGzipResponseWriter(w *GzipResponseWriter) {
// went wrong with the response, and write http errors in plain form instead. // went wrong with the response, and write http errors in plain form instead.
type GzipResponseWriter struct { type GzipResponseWriter struct {
ResponseWriter ResponseWriter
gzipWriter *gzip.Writer
chunks []byte chunks []byte
disabled bool disabled bool
} }
@ -87,7 +91,7 @@ var _ ResponseWriter = &GzipResponseWriter{}
// to change the response writer type. // to change the response writer type.
func (w *GzipResponseWriter) BeginGzipResponse(underline ResponseWriter) { func (w *GzipResponseWriter) BeginGzipResponse(underline ResponseWriter) {
w.ResponseWriter = underline w.ResponseWriter = underline
w.gzipWriter = acquireGzipWriter(w.ResponseWriter)
w.chunks = w.chunks[0:0] w.chunks = w.chunks[0:0]
w.disabled = false w.disabled = false
} }
@ -107,6 +111,19 @@ func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
return len(w.chunks), nil return len(w.chunks), nil
} }
// Writef formats according to a format specifier and writes to the response.
//
// Returns the number of bytes written and any write error encountered.
func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, a...)
}
// WriteString prepares the string data write to the gzip writer and finally to its
// underline response writer, returns the uncompressed len(contents).
func (w *GzipResponseWriter) WriteString(s string) (int, error) {
return w.Write([]byte(s))
}
// WriteNow compresses and writes that data to the underline response writer, // WriteNow compresses and writes that data to the underline response writer,
// returns the compressed written len. // returns the compressed written len.
// //
@ -116,17 +133,35 @@ func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
// after that, so that information is not closed to the handler anymore. // after that, so that information is not closed to the handler anymore.
func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) { func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) {
if w.disabled { if w.disabled {
// type noOp struct{}
//
// func (n noOp) Write([]byte) (int, error) {
// return 0, nil
// }
//
// var noop = noOp{}
// problem solved with w.gzipWriter.Reset(noop):
//
// the below Write called multiple times but not from here,
// the gzip writer does something to the writer, even if we don't call the
// w.gzipWriter.Write it does call the underline http.ResponseWriter
// multiple times, and therefore it changes the content-length
// the problem that results to the #723.
//
// Or a better idea, acquire and adapt the gzip writer on-time when is not disabled.
// So that is not needed any more:
// w.gzipWriter.Reset(noop)
return w.ResponseWriter.Write(contents) return w.ResponseWriter.Write(contents)
} }
w.ResponseWriter.Header().Add(varyHeaderKey, "Accept-Encoding") w.ResponseWriter.Header().Add(varyHeaderKey, "Accept-Encoding")
w.ResponseWriter.Header().Set(contentEncodingHeaderKey, "gzip") w.ResponseWriter.Header().Set(contentEncodingHeaderKey, "gzip")
// if not `WriteNow` but "Content-Length" header // if not `WriteNow` but "Content-Length" header
// is exists, then delete it before `.Write` // is exists, then delete it before `.Write`
// Content-Length should not be there. // Content-Length should not be there.
// no, for now at least: w.ResponseWriter.Header().Del(contentLengthHeaderKey) // no, for now at least: w.ResponseWriter.Header().Del(contentLengthHeaderKey)
return writeGzip(w.ResponseWriter, contents)
return w.gzipWriter.Write(contents)
} }
// FlushResponse validates the response headers in order to be compatible with the gzip written data // FlushResponse validates the response headers in order to be compatible with the gzip written data

View File

@ -10,11 +10,12 @@ import (
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/router/macro" "github.com/kataras/iris/core/router/macro"
"github.com/kataras/iris/mvc/activator"
) )
const ( const (
// MethodNone is a Virtual method // MethodNone is a Virtual method
// to store the "offline" routes // to store the "offline" routes.
MethodNone = "NONE" MethodNone = "NONE"
) )
@ -172,6 +173,36 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
return r return r
} }
// HandleMany works like `Handle` but can receive more than one
// paths separated by spaces and returns always a slice of *Route instead of a single instance of Route.
//
// It's useful only if the same handler can handle more than one request paths,
// otherwise use `Party` which can handle many paths with different handlers and middlewares.
//
// Usage:
// app.HandleMany(iris.MethodGet, "/user /user/{id:int} /user/me", userHandler)
// At the other side, with `Handle` we've had to write:
// app.Handle(iris.MethodGet, "/user", userHandler)
// app.Handle(iris.MethodGet, "/user/{id:int}", userByIDHandler)
// app.Handle(iris.MethodGet, "/user/me", userMeHandler)
//
// This method is used behind the scenes at the `Controller` function
// in order to handle more than one paths for the same controller instance.
func (api *APIBuilder) HandleMany(method string, relativePath string, handlers ...context.Handler) (routes []*Route) {
trimmedPath := strings.Trim(relativePath, " ")
// at least slash
// a space
// at least one other slash for the next path
// app.Controller("/user /user{id}", new(UserController))
paths := strings.Split(trimmedPath, " ")
for _, p := range paths {
if p != "" {
routes = append(routes, api.Handle(method, p, handlers...))
}
}
return
}
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also. // 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. // Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun.
func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party { func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {
@ -400,15 +431,13 @@ func (api *APIBuilder) Trace(relativePath string, handlers ...context.Handler) *
// Any registers a route for ALL of the http methods // Any registers a route for ALL of the http methods
// (Get,Post,Put,Head,Patch,Options,Connect,Delete). // (Get,Post,Put,Head,Patch,Options,Connect,Delete).
func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) []*Route { func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (routes []*Route) {
routes := make([]*Route, len(AllMethods), len(AllMethods)) for _, m := range AllMethods {
r := api.HandleMany(m, relativePath, handlers...)
for i, k := range AllMethods { routes = append(routes, r...)
r := api.Handle(k, relativePath, handlers...)
routes[i] = r
} }
return routes return
} }
// Controller registers a `Controller` instance and returns the registered Routes. // Controller registers a `Controller` instance and returns the registered Routes.
@ -418,11 +447,15 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) []*
// It's just an alternative way of building an API for a specific // It's just an alternative way of building an API for a specific
// path, the controller can register all type of http methods. // path, the controller can register all type of http methods.
// //
// Keep note that this method is a bit slow // Keep note that controllers are bit slow
// because of the reflection use however it's as fast as possible because // because of the reflection use however it's as fast as possible because
// it does preparation before the serve-time handler but still // it does preparation before the serve-time handler but still
// remains slower than the low-level handlers // remains slower than the low-level handlers
// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch` . // such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
//
//
// All fields that are tagged with iris:"persistence"` or binded
// are being persistence and kept the same between the different requests.
// //
// An Example Controller can be: // An Example Controller can be:
// //
@ -439,38 +472,53 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) []*
// Usage: app.Controller("/", new(IndexController)) // Usage: app.Controller("/", new(IndexController))
// //
// //
// Another example with persistence data: // Another example with bind:
// //
// type UserController struct { // type UserController struct {
// Controller // Controller
// //
// CreatedAt time.Time `iris:"persistence"` // DB *DB
// Title string `iris:"persistence"` // CreatedAt time.Time
// DB *DB `iris:"persistence"` //
// } // }
// //
// // Get serves using the User controller when HTTP Method is "GET". // // Get serves using the User controller when HTTP Method is "GET".
// func (c *UserController) Get() { // func (c *UserController) Get() {
// c.Tmpl = "user/index.html" // c.Tmpl = "user/index.html"
// c.Data["title"] = c.Title // c.Data["title"] = "User Page"
// c.Data["username"] = "kataras " + c.Params.Get("userid") // c.Data["username"] = "kataras " + c.Params.Get("userid")
// c.Data["connstring"] = c.DB.Connstring // c.Data["connstring"] = c.DB.Connstring
// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds() // c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
// } // }
// //
// Usage: app.Controller("/user/{id:int}", &UserController{ // Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now())
// CreatedAt: time.Now(),
// Title: "User page",
// DB: yourDB,
// })
// //
// Read more at `router#Controller`. // Read more at `/mvc#Controller`.
func (api *APIBuilder) Controller(relativePath string, controller interface{}) []*Route { func (api *APIBuilder) Controller(relativePath string, controller activator.BaseController,
routes, err := registerController(api, relativePath, controller) bindValues ...interface{}) (routes []*Route) {
registerFunc := func(method string, handler context.Handler) {
if method == "ANY" || method == "ALL" {
routes = api.Any(relativePath, handler)
} else {
routes = append(routes, api.HandleMany(method, relativePath, handler)...)
}
}
// bind any values to the controller's relative fields
// and set them on each new request controller,
// binder is an alternative method
// of the persistence data control which requires the
// user already set the values manually to controller's fields
// and tag them with `iris:"persistence"`.
//
// don't worry it will never be handled if empty values.
err := activator.Register(controller, bindValues, nil, registerFunc)
if err != nil { if err != nil {
api.reporter.Add("%v for path: '%s'", err, relativePath) api.reporter.Add("%v for path: '%s'", err, relativePath)
} }
return routes
return
} }
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration // StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration

View File

@ -1,290 +0,0 @@
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, _ := getCustomFuncIndex(typ, customInitFuncNames...)
customEndFuncIndex, _ := getCustomFuncIndex(typ, customEndFuncNames...)
// 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, customEndFuncIndex))
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, customEndFuncIndex))
routes = append(routes, r)
}
return routes, nil
}
func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Value,
baseControllerFieldIndex, httpMethodIndex, customInitFuncIndex, customEndFuncIndex 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)
// call the higher "Init/BeginRequest(ctx context.Context)",
// if exists.
if customInitFuncIndex >= 0 {
callCustomFuncHandler(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())()
}
if !ctx.IsStopped() {
// call the higher "Done/EndRequest(ctx context.Context)",
// if exists.
if customEndFuncIndex >= 0 {
callCustomFuncHandler(ctx, c, customEndFuncIndex)
}
}
// finally, execute the controller.
b.exec()
}
}
// Useful when more than one methods are using the same
// request data.
var (
// customInitFuncNames can be used as custom functions
// to init the new instance of controller
// that is created on each new request.
// One of these is valid, no both.
customInitFuncNames = []string{"Init", "BeginRequest"}
// customEndFuncNames can be used as custom functions
// to action when the method handler has been finished,
// this is the last step before server send the response to the client.
// One of these is valid, no both.
customEndFuncNames = []string{"Done", "EndRequest"}
)
func getCustomFuncIndex(typ reflect.Type, funcNames ...string) (initFuncIndex int, has bool) {
for _, customInitFuncName := range funcNames {
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 function (can be "Init", "BeginRequest", "End" and "EndRequest"),
// the check of this function made at build time, so it's a safe a call.
func callCustomFuncHandler(ctx context.Context, cServeTime reflect.Value, initFuncIndex int) {
cServeTime.Method(initFuncIndex).Interface().(func(ctx context.Context))(ctx)
}

View File

@ -1,152 +0,0 @@
// 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 testControllerBeginAndEndRequestFunc struct {
router.Controller
Username string
}
// called before of every method (Get() or Post()).
//
// useful when more than one methods using the
// same request values or context's function calls.
func (t *testControllerBeginAndEndRequestFunc) BeginRequest(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 "BeginRequest"
}
// called after every method (Get() or Post()).
func (t *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) {
ctx.Writef("done") // append "done" to the response
}
func (t *testControllerBeginAndEndRequestFunc) Get() {
t.Ctx.Writef(t.Username)
}
func (t *testControllerBeginAndEndRequestFunc) Post() {
t.Ctx.Writef(t.Username)
}
func TestControllerBeginAndEndRequestFunc(t *testing.T) {
app := iris.New()
app.Controller("/profile/{username}", new(testControllerBeginAndEndRequestFunc))
e := httptest.New(t, app)
usernames := []string{
"kataras",
"makis",
"efi",
"rg",
"bill",
"whoisyourdaddy",
}
doneResponse := "done"
for _, username := range usernames {
e.GET("/profile/" + username).Expect().Status(httptest.StatusOK).
Body().Equal(username + doneResponse)
e.POST("/profile/" + username).Expect().Status(httptest.StatusOK).
Body().Equal(username + doneResponse)
}
}

View File

@ -2,6 +2,7 @@ package router
import ( import (
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/mvc/activator"
) )
// Party is here to separate the concept of // Party is here to separate the concept of
@ -51,6 +52,22 @@ type Party interface {
// //
// Returns the read-only route information. // Returns the read-only route information.
Handle(method string, registeredPath string, handlers ...context.Handler) *Route Handle(method string, registeredPath string, handlers ...context.Handler) *Route
// HandleMany works like `Handle` but can receive more than one
// paths separated by spaces and returns always a slice of *Route instead of a single instance of Route.
//
// It's useful only if the same handler can handle more than one request paths,
// otherwise use `Party` which can handle many paths with different handlers and middlewares.
//
// Usage:
// app.HandleMany(iris.MethodGet, "/user /user/{id:int} /user/me", userHandler)
// At the other side, with `Handle` we've had to write:
// app.Handle(iris.MethodGet, "/user", userHandler)
// app.Handle(iris.MethodGet, "/user/{id:int}", userByIDHandler)
// app.Handle(iris.MethodGet, "/user/me", userMeHandler)
//
// This method is used behind the scenes at the `Controller` function
// in order to handle more than one paths for the same controller instance.
HandleMany(method string, relativePath string, handlers ...context.Handler) []*Route
// None registers an "offline" route // None registers an "offline" route
// see context.ExecRoute(routeName) and // see context.ExecRoute(routeName) and
@ -107,11 +124,15 @@ type Party interface {
// It's just an alternative way of building an API for a specific // It's just an alternative way of building an API for a specific
// path, the controller can register all type of http methods. // path, the controller can register all type of http methods.
// //
// Keep note that this method is a bit slow // Keep note that controllers are bit slow
// because of the reflection use however it's as fast as possible because // because of the reflection use however it's as fast as possible because
// it does preparation before the serve-time handler but still // it does preparation before the serve-time handler but still
// remains slower than the low-level handlers // remains slower than the low-level handlers
// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch` . // such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
//
//
// All fields that are tagged with iris:"persistence"` or binded
// are being persistence and kept the same between the different requests.
// //
// An Example Controller can be: // An Example Controller can be:
// //
@ -128,33 +149,29 @@ type Party interface {
// Usage: app.Controller("/", new(IndexController)) // Usage: app.Controller("/", new(IndexController))
// //
// //
// Another example with persistence data: // Another example with bind:
// //
// type UserController struct { // type UserController struct {
// Controller // Controller
// //
// CreatedAt time.Time `iris:"persistence"` // DB *DB
// Title string `iris:"persistence"` // CreatedAt time.Time
// DB *DB `iris:"persistence"` //
// } // }
// //
// // Get serves using the User controller when HTTP Method is "GET". // // Get serves using the User controller when HTTP Method is "GET".
// func (c *UserController) Get() { // func (c *UserController) Get() {
// c.Tmpl = "user/index.html" // c.Tmpl = "user/index.html"
// c.Data["title"] = c.Title // c.Data["title"] = "User Page"
// c.Data["username"] = "kataras " + c.Params.Get("userid") // c.Data["username"] = "kataras " + c.Params.Get("userid")
// c.Data["connstring"] = c.DB.Connstring // c.Data["connstring"] = c.DB.Connstring
// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds() // c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
// } // }
// //
// Usage: app.Controller("/user/{id:int}", &UserController{ // Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now())
// CreatedAt: time.Now(),
// Title: "User page",
// DB: yourDB,
// })
// //
// Read more at `router#Controller`. // Read more at `/mvc#Controller`.
Controller(relativePath string, controller interface{}) []*Route Controller(relativePath string, controller activator.BaseController, bindValues ...interface{}) []*Route
// StaticHandler returns a new Handler which is ready // StaticHandler returns a new Handler which is ready
// to serve all kind of static files. // to serve all kind of static files.

View File

@ -39,6 +39,7 @@ func (ch *ErrorCodeHandler) Fire(ctx context.Context) {
return return
} }
} }
// ctx.StopExecution() // not uncomment this, is here to remember why to. // ctx.StopExecution() // not uncomment this, is here to remember why to.
// note for me: I don't stopping the execution of the other handlers // note for me: I don't stopping the execution of the other handlers
// because may the user want to add a fallback error code // because may the user want to add a fallback error code

180
doc.go
View File

@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
Current Version Current Version
8.2.6 8.3.0
Installation Installation
@ -680,11 +680,108 @@ Example code:
} }
Controllers MVC - Model View Controller
It's very easy to get started, the only function you need to call Iris has first-class support for the MVC pattern, you'll not find
instead of `app.Get/Post/Put/Delete/Connect/Head/Patch/Options/Trace` these stuff anywhere else in the Go world.
is the `app.Controller`.
Example Code
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"
"github.com/kataras/iris/middleware/logger"
"github.com/kataras/iris/middleware/recover"
)
// This example is equivalent to the
// https://github.com/kataras/iris/blob/master/_examples/hello-world/main.go
//
// It seems that additional code you
// have to write doesn't worth it
// but remember that, this example
// does not make use of iris mvc features like
// the Model, Persistence or the View engine neither the Session,
// it's very simple for learning purposes,
// probably you'll never use such
// as simple controller anywhere in your app.
func main() {
app := iris.New()
// Optionally, add two built'n handlers
// that can recover from any http-relative panics
// and log the requests to the terminal.
app.Use(recover.New())
app.Use(logger.New())
app.Controller("/", new(IndexController))
app.Controller("/ping", new(PingController))
app.Controller("/hello", new(HelloController))
// http://localhost:8080
// http://localhost:8080/ping
// http://localhost:8080/hello
app.Run(iris.Addr(":8080"))
}
// IndexController serves the "/".
type IndexController struct {
// if you build with go1.9 you can omit the import of mvc package
// and just use `iris.Controller` instead.
mvc.Controller
}
// Get serves
// Method: GET
// Resource: http://localhost:8080/
func (c *IndexController) Get() {
c.Ctx.HTML("<b>Welcome!</b>")
}
// PingController serves the "/ping".
type PingController struct {
mvc.Controller
}
// Get serves
// Method: GET
// Resource: http://context:8080/ping
func (c *PingController) Get() {
c.Ctx.WriteString("pong")
}
// HelloController serves the "/hello".
type HelloController struct {
mvc.Controller
}
// Get serves
// Method: GET
// Resource: http://localhost:8080/hello
func (c *HelloController) Get() {
c.Ctx.JSON(iris.Map{"message": "Hello iris web framework."})
}
// Can use more than one, the factory will make sure
// that the correct http methods are being registered for each route
// for this controller, uncomment these if you want:
// func (c *HelloController) Post() {}
// func (c *HelloController) Put() {}
// func (c *HelloController) Delete() {}
// func (c *HelloController) Connect() {}
// func (c *HelloController) Head() {}
// func (c *HelloController) Patch() {}
// func (c *HelloController) Options() {}
// func (c *HelloController) Trace() {}
// or All() or Any() to catch all http methods.
Iris web framework supports Request data, Models, Persistence Data and Binding
with the fastest possible execution.
Characteristics: Characteristics:
@ -693,9 +790,14 @@ then the controller should have a function named `Get()`,
you can define more than one method function to serve in the same Controller struct. 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) Persistence data inside your Controller struct (share data between requests)
via `iris:"persistence"` tag right to the field. via `iris:"persistence"` tag right to the field or Bind using `app.Controller("/" , new(myController), theBindValue)`.
Access to the request path parameters via the `Params` field. Models inside your Controller struct (set-ed at the Method function and rendered by the View)
via `iris:"model"` tag right to the field, i.e User UserModel `iris:"model" name:"user"`
view will recognise it as `{{.user}}`.
If `name` tag is missing then it takes the field's name, in this case the `"User"`.
Access to the request path and its parameters via the `Path and Params` fields.
Access to the template file that should be rendered via the `Tmpl` field. Access to the template file that should be rendered via the `Tmpl` field.
@ -709,65 +811,31 @@ Access to the low-level `context.Context` via the `Ctx` field.
Flow as you used to, `Controllers` can be registered to any `Party`, Flow as you used to, `Controllers` can be registered to any `Party`,
including Subdomains, the Party's begin and done handlers work as expected. including Subdomains, the Party's begin and done handlers work as expected.
Optional `Init(ctx) or BeginRequest(ctx)` function to perform any initialization before the methods, Optional `BeginRequest(ctx)` function to perform any initialization before the method execution,
useful to call middlewares or when many methods use the same collection of data. useful to call middlewares or when many methods use the same collection of data.
Optional `Done(ctx) or EndRequest(ctx)` function to perform any finalization after the methods executed. Optional `EndRequest(ctx)` function to perform any finalization after any method executed.
Example Code: Inheritance, see for example our `mvc.SessionController`, it has the `mvc.Controller` as an embedded field
and it adds its logic to its `BeginRequest`. Source file: https://github.com/kataras/iris/blob/master/mvc/session_controller.go.
Using Iris MVC for code reuse
// file: main.go By creating components that are independent of one another,
developers are able to reuse components quickly and easily in other applications.
The same (or similar) view for one application can be refactored for another application with
different data because the view is simply handling how the data is being displayed to the user.
package main If you're new to back-end web development read about the MVC architectural pattern first,
a good start is that wikipedia article: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller.
import ( Follow the examples below,
"github.com/kataras/iris"
"controllers" - Hello world: https://github.com/kataras/iris/blob/master/_examples/mvc/hello-world/main.go
)
func main() { - Session Controller usage: https://github.com/kataras/iris/blob/master/_examples/mvc/session-controller/main.go
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
app.Controller("/", new(controllers.Index)) - A simple but featured Controller with model and views: https://github.com/kataras/iris/tree/master/_examples/mvc/controller-with-model-and-view
// 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 Parameterized Path

38
faq.md Normal file
View File

@ -0,0 +1,38 @@
## How to upgrade
```sh
go get -u github.com/kataras/iris
```
## Active development mode
Many ideas to implement but no breaking changes.
https://github.com/kataras/iris/issues/722
## Can I found a job if I learn how to use Iris?
Yes, not only because you will learn Golang in the same time, but there are some positions
open for Iris-specific developers the time we speak.
- https://glints.id/opportunities/jobs/5553
## Can Iris be used in production after Dubai purchase?
Yes, now more than ever.
https://github.com/kataras/iris/issues/711
## Do we have a community Chat?
Yes, https://kataras.rocket.chat/channel/iris.
https://github.com/kataras/iris/issues/646
## How this open-source project still active and shine?
By normal people like you, who help us by donating small or larger amounts of money.
Help this project to continue deliver awesome and unique features with the higher code quality as possible by donating any amount.
[![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)

14
iris.go
View File

@ -32,7 +32,7 @@ import (
const ( const (
// Version is the current version number of the Iris Web Framework. // Version is the current version number of the Iris Web Framework.
Version = "8.2.6" Version = "8.3.0"
) )
// HTTP status codes as registered with IANA. // HTTP status codes as registered with IANA.
@ -383,7 +383,7 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
if srv.Addr == "" { if srv.Addr == "" {
srv.Addr = ":8080" srv.Addr = ":8080"
} }
app.logger.Debugf("HTTP Server Addr: %s", srv.Addr) app.logger.Debugf("Host: addr is %s", srv.Addr)
// create the new host supervisor // create the new host supervisor
// bind the constructed server and return it // bind the constructed server and return it
@ -399,25 +399,25 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
app.config.vhost = netutil.ResolveVHost(srv.Addr) app.config.vhost = netutil.ResolveVHost(srv.Addr)
} }
app.logger.Debugf("VHost: %s", app.config.vhost) app.logger.Debugf("Host: virtual host is %s", app.config.vhost)
// the below schedules some tasks that will run among the server // the below schedules some tasks that will run among the server
if !app.config.DisableStartupLog { if !app.config.DisableStartupLog {
// show the available info to exit from app. // show the available info to exit from app.
su.RegisterOnServe(host.WriteStartupLogOnServe(app.logger.Printer.Output)) // app.logger.Writer -> Info su.RegisterOnServe(host.WriteStartupLogOnServe(app.logger.Printer.Output)) // app.logger.Writer -> Info
app.logger.Debugf("Host: Register startup notifier") app.logger.Debugf("Host: register startup notifier")
} }
if !app.config.DisableInterruptHandler { if !app.config.DisableInterruptHandler {
// when CTRL+C/CMD+C pressed. // when CTRL+C/CMD+C pressed.
shutdownTimeout := 5 * time.Second shutdownTimeout := 5 * time.Second
host.RegisterOnInterrupt(host.ShutdownOnInterrupt(su, shutdownTimeout)) host.RegisterOnInterrupt(host.ShutdownOnInterrupt(su, shutdownTimeout))
app.logger.Debugf("Host: Register server shutdown on interrupt(CTRL+C/CMD+C)") app.logger.Debugf("Host: register server shutdown on interrupt(CTRL+C/CMD+C)")
} }
su.IgnoredErrors = append(su.IgnoredErrors, app.config.IgnoreServerErrors...) su.IgnoredErrors = append(su.IgnoredErrors, app.config.IgnoreServerErrors...)
app.logger.Debugf("Host: Server will ignore the following errors: %s", su.IgnoredErrors) app.logger.Debugf("Host: server will ignore the following errors: %s", su.IgnoredErrors)
su.Configure(app.hostConfigurators...) su.Configure(app.hostConfigurators...)
app.Hosts = append(app.Hosts, su) app.Hosts = append(app.Hosts, su)
@ -608,7 +608,7 @@ func (app *Application) Build() error {
} }
if app.view.Len() > 0 { if app.view.Len() > 0 {
app.logger.Debugf("%d registered view engine(s)", app.view.Len()) app.logger.Debugf("Application: %d registered view engine(s)", app.view.Len())
// view engine // view engine
// here is where we declare the closed-relative framework functions. // here is where we declare the closed-relative framework functions.
// Each engine has their defaults, i.e yield,render,render_r,partial, params... // Each engine has their defaults, i.e yield,render,render_r,partial, params...

312
mvc/activator/activator.go Normal file
View File

@ -0,0 +1,312 @@
package activator
import (
"reflect"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
)
type (
// TController is the type of the controller,
// it contains all the necessary information to load
// and serve the controller to the outside world,
// think it as a "supervisor" of your Controller which
// cares about you.
TController struct {
// the type of the user/dev's "c" controller (interface{})
Type reflect.Type
// it's the first passed value of the controller instance,
// we need this to collect and save the persistence fields' values.
Value reflect.Value
binder *binder // executed even before the BeginRequest if not nil.
controls []TControl // executed on request, after the BeginRequest and before the EndRequest.
// the actual method functions
// i.e for "GET" it's the `Get()`
//
// Here we have a strange relation by-design.
// It contains the methods
// but we have different handlers
// for each of these methods,
// while in the same time all of these
// are depend from this TypeInfo struct.
// So we have TypeInfo -> Methods -> Each(TypeInfo, Method.Index)
// -> Handler for X HTTPMethod, see `Register`.
Methods []MethodFunc
}
// MethodFunc is part of the `TController`,
// it contains the index for a specific http method,
// taken from user's controller struct.
MethodFunc struct {
Index int
HTTPMethod string
}
)
// ErrControlSkip never shows up, used to determinate
// if a control's Load return error is critical or not,
// `ErrControlSkip` means that activation can continue
// and skip this control.
var ErrControlSkip = errors.New("skip control")
// TControl is an optional feature that an app can benefit
// by using its own custom controls to control the flow
// inside a controller, they are being registered per controller.
//
// Naming:
// I could find better name such as 'Control',
// but I can imagine the user's confusion about `Controller`
// and `Control` types, they are different but they may
// use that as embedded, so it can not start with the world "C..".
// The best name that shows the relation between this
// and the controller type info struct(TController) is the "TControl",
// `TController` is prepended with "T" for the same reasons, it's different
// than `Controller`, the TController is the "description" of the user's
// `Controller` embedded field.
type TControl interface { // or CoreControl?
// Load should returns nil if its `Handle`
// should be called on serve time.
//
// if error is filled then controller info
// is not created and that error is returned to the
// high-level caller, but the `ErrControlSkip` can be used
// to skip the control without breaking the rest of the registration.
Load(t *TController) error
// Handle executes the control.
// It accepts the context, the new controller instance
// and the specific methodFunc based on the request.
Handle(ctx context.Context, controller reflect.Value, methodFunc func())
}
func isControlErr(err error) bool {
if err != nil {
if isSkipper(err) {
return false
}
return true
}
return false
}
func isSkipper(err error) bool {
if err != nil {
if err.Error() == ErrControlSkip.Error() {
return true
}
}
return false
}
// the parent package should complete this "interface"
// it's not exported, so their functions
// but reflect doesn't care about it, so we are ok
// to compare the type of the base controller field
// with this "ctrl", see `buildTypeInfo` and `buildMethodHandler`.
var (
// ErrMissingControllerInstance is a static error which fired from `Controller` when
// the passed "c" instnace is not a valid type of `Controller`.
ErrMissingControllerInstance = errors.New("controller should have a field of Controller type")
// ErrInvalidControllerType fired when the "Controller" field is not
// the correct type.
ErrInvalidControllerType = errors.New("controller instance is not a valid implementation")
)
// BaseController is the controller interface,
// which the main request `Controller` will implement automatically.
// End-User doesn't need to have any knowledge of this if she/he doesn't want to implement
// a new Controller type.
type BaseController interface {
BeginRequest(ctx context.Context)
EndRequest(ctx context.Context)
}
// ActivateController returns a new controller type info description.
// A TController is not useful for the end-developer
// but it can be used for debugging.
func ActivateController(base BaseController, bindValues []interface{},
controls []TControl) (TController, error) {
// get and save the type.
typ := reflect.TypeOf(base)
if typ.Kind() != reflect.Ptr {
typ = reflect.PtrTo(typ)
}
// first instance value, needed to validate
// the actual type of the controller field
// and to collect and save the instance's persistence fields'
// values later on.
val := reflect.Indirect(reflect.ValueOf(base))
ctrlName := val.Type().Name()
// set the binder, can be nil this check at made at runtime.
binder := newBinder(typ.Elem(), bindValues)
if binder != nil {
for _, bf := range binder.fields {
golog.Debugf("MVC %s: binder loaded for '%s' with field index of: %d",
ctrlName, bf.Name, bf.Index)
}
}
t := TController{
Type: typ,
Value: val,
binder: binder,
}
// first the custom controls,
// after these, the persistence,
// the method control
// which can set the model and
// last the model control.
controls = append(controls, []TControl{
// PersistenceDataControl stores the optional data
// that will be shared among all requests.
PersistenceDataControl(),
// MethodControl is the actual method function
// i.e for "GET" it's the `Get()` that will be
// fired.
MethodControl(),
// ModelControl stores the optional models from
// the struct's fields values that
// are being setted by the method function
// and set them as ViewData.
ModelControl()}...)
for _, control := range controls {
err := control.Load(&t)
// fail on first control error if not ErrControlSkip.
if isControlErr(err) {
return t, err
}
if isSkipper(err) {
continue
}
golog.Debugf("MVC %s: succeed load of the %#v", ctrlName, control)
t.controls = append(t.controls, control)
}
return t, nil
}
// builds the handler for a type based on the method index (i.e Get() -> [0], Post() -> [1]).
func buildMethodHandler(t TController, methodFuncIndex int) context.Handler {
elem := t.Type.Elem()
/*
// good idea, it speeds up the whole thing by ~1MB per 20MB at my personal
// laptop but this way the Model for example which is not a persistence
// variable can stay for the next request
// (if pointer receiver but if not then variables like `Tmpl` cannot stay)
// and that will have unexpected results.
// however we keep it here I want to see it every day in order to find a better way.
type runtimeC struct {
method func()
c reflect.Value
elem reflect.Value
b BaseController
}
pool := sync.Pool{
New: func() interface{} {
c := reflect.New(elem)
methodFunc := c.Method(methodFuncIndex).Interface().(func())
b, _ := c.Interface().(BaseController)
elem := c.Elem()
if t.binder != nil {
t.binder.handle(elem)
}
rc := runtimeC{
c: c,
elem: elem,
b: b,
method: methodFunc,
}
return rc
},
}
*/
return func(ctx context.Context) {
// // create a new controller instance of that type(>ptr).
c := reflect.New(elem)
if t.binder != nil {
t.binder.handle(c)
if ctx.IsStopped() {
return
}
}
// get the Controller embedded field's addr.
// it should never be invalid here because we made that checks on activation.
// but if somone tries to "crack" that, then just stop the world in order to be notified,
// we don't want to go away from that type of mistake.
b := c.Interface().(BaseController)
// init the request.
b.BeginRequest(ctx)
methodFunc := c.Method(methodFuncIndex).Interface().(func())
// execute the controls by order, including the method control.
for _, control := range t.controls {
if ctx.IsStopped() {
break
}
control.Handle(ctx, c, methodFunc)
}
// finally, execute the controller, don't check for IsStopped.
b.EndRequest(ctx)
}
}
// RegisterFunc used by the caller to register the result routes.
type RegisterFunc func(httpMethod string, handler context.Handler)
// RegisterMethodHandlers receives a `TController`, description of the
// user's controller, and calls the "registerFunc" for each of its
// method handlers.
//
// Not useful for the end-developer, but may needed for debugging
// at the future.
func RegisterMethodHandlers(t TController, registerFunc RegisterFunc) {
// range over the type info's method funcs,
// build a new handler for each of these
// methods and register them to their
// http methods using the registerFunc, which is
// responsible to convert these into routes
// and add them to router via the APIBuilder.
for _, m := range t.Methods {
registerFunc(m.HTTPMethod, buildMethodHandler(t, m.Index))
}
}
// Register receives a "controller",
// a pointer of an instance which embeds the `Controller`,
// the value of "baseControllerFieldName" should be `Controller`
// if embedded and "controls" that can intercept on controller
// activation and on the controller's handler, at serve-time.
func Register(controller BaseController, bindValues []interface{}, controls []TControl,
registerFunc RegisterFunc) error {
t, err := ActivateController(controller, bindValues, controls)
if err != nil {
return err
}
RegisterMethodHandlers(t, registerFunc)
return nil
}

118
mvc/activator/binder.go Normal file
View File

@ -0,0 +1,118 @@
package activator
import (
"reflect"
)
type binder struct {
values []interface{}
fields []field
}
// binder accepts a value of something
// and tries to find its equalivent type
// inside the controller and sets that to it,
// after that each new instance of the controller will have
// this value on the specific field, like persistence data control does.
//
// returns a nil binder if values are not valid bindable data to the controller type.
func newBinder(elemType reflect.Type, values []interface{}) *binder {
if len(values) == 0 {
return nil
}
b := &binder{values: values}
b.fields = b.lookup(elemType)
// if nothing valid found return nil, so the caller
// can omit the binder.
if len(b.fields) == 0 {
return nil
}
return b
}
func (b *binder) lookup(elem reflect.Type) (fields []field) {
for _, v := range b.values {
value := reflect.ValueOf(v)
for i, n := 0, elem.NumField(); i < n; i++ {
elemField := elem.Field(i)
if elemField.Type == value.Type() {
// we area inside the correct type
// println("[0] prepare bind filed for " + elemField.Name)
fields = append(fields, field{
Index: i,
Name: elemField.Name,
Type: elemField.Type,
Value: value,
})
continue
}
f := lookupStruct(elemField.Type, value)
if f != nil {
fields = append(fields, field{
Index: i,
Name: elemField.Name,
Type: elemField.Type,
embedded: f,
})
}
}
}
return
}
func lookupStruct(elem reflect.Type, value reflect.Value) *field {
// ignore if that field is not a struct
if elem.Kind() != reflect.Struct {
// and it's not a controller because we don't want to accidentally
// set fields to other user fields. Or no?
// ||
// (elem.Name() != "" && !strings.HasSuffix(elem.Name(), "Controller")) {
return nil
}
// search by fields.
for i, n := 0, elem.NumField(); i < n; i++ {
elemField := elem.Field(i)
if elemField.Type == value.Type() {
// println("Types are equal of: " + elemField.Type.Name() + " " + elemField.Name + " and " + value.Type().Name())
// we area inside the correct type.
return &field{
Index: i,
Name: elemField.Name,
Type: elemField.Type,
Value: value,
}
}
// if field is struct and the value is struct
// then try inside its fields for a compatible
// field type.
if elemField.Type.Kind() == reflect.Struct && value.Type().Kind() == reflect.Struct {
elemFieldEmb := elem.Field(i)
f := lookupStruct(elemFieldEmb.Type, value)
if f != nil {
fp := &field{
Index: i,
Name: elemFieldEmb.Name,
Type: elemFieldEmb.Type,
embedded: f,
}
return fp
}
}
}
return nil
}
func (b *binder) handle(c reflect.Value) {
elem := c.Elem() // controller should always be a pointer at this state
for _, f := range b.fields {
f.sendTo(elem)
}
}

View File

@ -0,0 +1,53 @@
package activator
import (
"reflect"
"github.com/kataras/iris/context"
)
func getCustomFuncIndex(t *TController, funcNames ...string) (funcIndex int, has bool) {
val := t.Value
for _, funcName := range funcNames {
if m, has := t.Type.MethodByName(funcName); has {
if _, isRequestFunc := val.Method(m.Index).Interface().(func(ctx context.Context)); isRequestFunc {
return m.Index, has
}
}
}
return -1, false
}
type callableControl struct {
Functions []string
index int
}
func (cc *callableControl) Load(t *TController) error {
funcIndex, has := getCustomFuncIndex(t, cc.Functions...)
if !has {
return ErrControlSkip
}
cc.index = funcIndex
return nil
}
// the "c" is a new "c" instance
// which is being used at serve time, inside the Handler.
// it calls the custom function (can be "Init", "BeginRequest", "End" and "EndRequest"),
// the check of this function made at build time, so it's a safe a call.
func (cc *callableControl) Handle(ctx context.Context, c reflect.Value, methodFunc func()) {
c.Method(cc.index).Interface().(func(ctx context.Context))(ctx)
}
// CallableControl is a generic-propose `TControl`
// which finds one function in the user's controller's struct
// based on the possible "funcName(s)" and executes
// that inside the handler, at serve-time, by passing
// the current request's `iris/context/#Context`.
func CallableControl(funcName ...string) TControl {
return &callableControl{Functions: funcName}
}

View File

@ -0,0 +1,81 @@
package activator
import (
"reflect"
"strings"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
)
var availableMethods = [...]string{
"ANY", // will be registered using the `core/router#APIBuilder#Any`
"ALL", // same as ANY
"NONE", // offline route
// valid http methods
"GET",
"POST",
"PUT",
"DELETE",
"CONNECT",
"HEAD",
"PATCH",
"OPTIONS",
"TRACE",
}
type methodControl struct{}
// ErrMissingHTTPMethodFunc fired when the controller doesn't handle any valid HTTP method.
var ErrMissingHTTPMethodFunc = errors.New(`controller can not be activated,
missing a compatible HTTP method function, i.e Get()`)
func (mc *methodControl) Load(t *TController) error {
// search the entire controller
// for any compatible method function
// and register that.
for _, method := range availableMethods {
if m, ok := t.Type.MethodByName(getMethodName(method)); ok {
t.Methods = append(t.Methods, MethodFunc{
HTTPMethod: method,
Index: m.Index,
})
// check if method was Any() or All()
// if yes, then break to skip any conflict with the rest of the method functions.
// (this will be registered to all valid http methods by the APIBuilder)
if method == "ANY" || method == "ALL" {
break
}
}
}
if len(t.Methods) == 0 {
// no compatible method found, fire an error and stop everything.
return ErrMissingHTTPMethodFunc
}
return nil
}
func getMethodName(httpMethod string) string {
httpMethodFuncName := strings.Title(strings.ToLower(httpMethod))
return httpMethodFuncName
}
func (mc *methodControl) Handle(ctx context.Context, c reflect.Value, methodFunc func()) {
// execute the responsible method for that handler.
// 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()
}
// MethodControl loads and serve the main functionality of the controllers,
// which is to run a function based on the http method (pre-computed).
func MethodControl() TControl {
return &methodControl{}
}

View File

@ -0,0 +1,54 @@
package activator
import (
"reflect"
"github.com/kataras/iris/context"
)
type modelControl struct {
fields []field
}
func (mc *modelControl) Load(t *TController) error {
fields := lookupFields(t, func(f reflect.StructField) bool {
if tag, ok := f.Tag.Lookup("iris"); ok {
if tag == "model" {
return true
}
}
return false
})
if len(fields) == 0 {
// first is the `Controller` so we need to
// check the second and after that.
return ErrControlSkip
}
mc.fields = fields
return nil
}
func (mc *modelControl) Handle(ctx context.Context, c reflect.Value, methodFunc func()) {
elem := c.Elem() // controller should always be a pointer at this state
for _, f := range mc.fields {
elemField := elem.Field(f.Index)
// check if current controller's element field
// is valid, is not nil and it's type is the same (should be but make that check to be sure).
if !elemField.IsValid() || (elemField.Kind() == reflect.Ptr && elemField.IsNil()) || elemField.Type() != f.Type {
continue
}
fieldValue := elemField.Interface()
// fmt.Printf("setting %s to %#v", f.Name, fieldValue)
ctx.ViewData(f.Name, fieldValue)
}
}
// ModelControl returns a TControl which is responsible
// to load and handle the `Model(s)` inside a controller struct
// via the `iris:"model"` tag field.
func ModelControl() TControl {
return &modelControl{}
}

View File

@ -0,0 +1,103 @@
package activator
import (
"reflect"
"github.com/kataras/iris/context"
)
type field struct {
Name string // by-defaultis the field's name but if `name: "other"` then it's overridden.
Index int
Type reflect.Type
Value reflect.Value
embedded *field
}
func (ff field) sendTo(elem reflect.Value) {
if embedded := ff.embedded; embedded != nil {
if ff.Index >= 0 {
embedded.sendTo(elem.Field(ff.Index))
}
return
}
elemField := elem.Field(ff.Index)
if elemField.Kind() == reflect.Ptr && !elemField.IsNil() {
return
}
elemField.Set(ff.Value)
}
func lookupFields(t *TController, validator func(reflect.StructField) bool) (fields []field) {
elem := t.Type.Elem()
for i, n := 0, elem.NumField(); i < n; i++ {
elemField := elem.Field(i)
valF := t.Value.Field(i)
// catch persistence data by tags, i.e:
// MyData string `iris:"persistence"`
if validator(elemField) {
name := elemField.Name
if nameTag, ok := elemField.Tag.Lookup("name"); ok {
name = nameTag
}
f := field{
Name: name,
Index: i,
Type: elemField.Type,
}
if valF.IsValid() || (valF.Kind() == reflect.Ptr && !valF.IsNil()) {
val := reflect.ValueOf(valF.Interface())
if val.IsValid() || (val.Kind() == reflect.Ptr && !val.IsNil()) {
f.Value = val
}
}
fields = append(fields, f)
}
}
return
}
type persistenceDataControl struct {
fields []field
}
func (d *persistenceDataControl) Load(t *TController) error {
fields := lookupFields(t, func(f reflect.StructField) bool {
if tag, ok := f.Tag.Lookup("iris"); ok {
if tag == "persistence" {
return true
}
}
return false
})
if len(fields) == 0 {
// first is the `Controller` so we need to
// check the second and after that.
return ErrControlSkip
}
d.fields = fields
return nil
}
func (d *persistenceDataControl) Handle(ctx context.Context, c reflect.Value, methodFunc func()) {
elem := c.Elem() // controller should always be a pointer at this state
for _, f := range d.fields {
f.sendTo(elem)
}
}
// PersistenceDataControl loads and re-stores
// the persistence data by scanning the original
// `TController.Value` instance of the user's controller.
func PersistenceDataControl() TControl {
return &persistenceDataControl{}
}

129
mvc/controller.go Normal file
View File

@ -0,0 +1,129 @@
package mvc
import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/memstore"
"github.com/kataras/iris/mvc/activator"
)
// 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"` or binded
// are being persistence and kept the same between the different 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 bind:
//
// type UserController struct {
// mvc.Controller
//
// DB *DB
// CreatedAt time.Time
// }
//
// // Get serves using the User controller when HTTP Method is "GET".
// func (c *UserController) Get() {
// c.Tmpl = "user/index.html"
// c.Data["title"] = "User Page"
// 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}", new(UserController), db, time.Now())
//
// Look `core/router/APIBuilder#Controller` method too.
type Controller struct {
// path and path params.
Path string
Params *context.RequestParams
// some info read and write,
// can be already set-ed by previous handlers as well.
Status int
Values *memstore.Store
// view read and write,
// can be already set-ed by previous handlers as well.
Layout string
Tmpl string
Data map[string]interface{}
// give access to the request context itself.
Ctx context.Context
}
// BeginRequest starts the main controller
// it initialize the Ctx and other fields.
//
// End-Developer can ovverride it but it still MUST be called.
func (c *Controller) BeginRequest(ctx context.Context) {
// path and path params
c.Path = ctx.Path()
c.Params = ctx.Params()
// response status code
c.Status = ctx.GetStatusCode()
// share values
c.Values = ctx.Values()
// view
c.Data = make(map[string]interface{}, 0)
// context itself
c.Ctx = ctx
}
// EndRequest is the final method which will be executed
// before response sent.
//
// It checks for the fields and calls the necessary context's
// methods to modify the response to the client.
//
// End-Developer can ovveride it but still should be called at the end.
func (c *Controller) EndRequest(ctx context.Context) {
if path := c.Path; path != "" && path != ctx.Path() {
// then redirect
ctx.Redirect(path)
return
}
if status := c.Status; status > 0 && status != ctx.GetStatusCode() {
ctx.StatusCode(status)
}
if view := c.Tmpl; view != "" {
if layout := c.Layout; layout != "" {
ctx.ViewLayout(layout)
}
if data := c.Data; data != nil {
for k, v := range data {
ctx.ViewData(k, v)
}
}
ctx.View(view)
}
}
var _ activator.BaseController = &Controller{}

284
mvc/controller_test.go Normal file
View File

@ -0,0 +1,284 @@
// black-box testing
package mvc_test
import (
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/mvc"
"github.com/kataras/iris/core/router"
"github.com/kataras/iris/httptest"
)
type testController struct {
mvc.Controller
}
var writeMethod = func(c mvc.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{ mvc.Controller }
testControllerAny struct{ mvc.Controller } // exactly the same as All
)
func (c *testControllerAll) All() {
writeMethod(c.Controller)
}
func (c *testControllerAny) Any() {
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)
}
}
func TestControllerMethodAndPathHandleMany(t *testing.T) {
app := iris.New()
app.Controller("/ /path1 /path2 /path3", new(testController))
e := httptest.New(t, app)
for _, method := range router.AllMethods {
e.Request(method, "/").Expect().Status(httptest.StatusOK).
Body().Equal(method)
e.Request(method, "/path1").Expect().Status(httptest.StatusOK).
Body().Equal(method)
e.Request(method, "/path2").Expect().Status(httptest.StatusOK).
Body().Equal(method)
}
}
type testControllerPersistence struct {
mvc.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 testControllerBeginAndEndRequestFunc struct {
mvc.Controller
Username string
}
// called before of every method (Get() or Post()).
//
// useful when more than one methods using the
// same request values or context's function calls.
func (t *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) {
t.Controller.BeginRequest(ctx)
t.Username = ctx.Params().Get("username")
// or t.Params.Get("username") because the
// t.Ctx == ctx and is being initialized at the t.Controller.BeginRequest.
}
// called after every method (Get() or Post()).
func (t *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) {
ctx.Writef("done") // append "done" to the response
t.Controller.EndRequest(ctx)
}
func (t *testControllerBeginAndEndRequestFunc) Get() {
t.Ctx.Writef(t.Username)
}
func (t *testControllerBeginAndEndRequestFunc) Post() {
t.Ctx.Writef(t.Username)
}
func TestControllerBeginAndEndRequestFunc(t *testing.T) {
app := iris.New()
app.Controller("/profile/{username}", new(testControllerBeginAndEndRequestFunc))
e := httptest.New(t, app)
usernames := []string{
"kataras",
"makis",
"efi",
"rg",
"bill",
"whoisyourdaddy",
}
doneResponse := "done"
for _, username := range usernames {
e.GET("/profile/" + username).Expect().Status(httptest.StatusOK).
Body().Equal(username + doneResponse)
e.POST("/profile/" + username).Expect().Status(httptest.StatusOK).
Body().Equal(username + doneResponse)
}
}
type Model struct {
Username string
}
type testControllerModel struct {
mvc.Controller
TestModel Model `iris:"model" name:"myModel"`
TestModel2 Model `iris:"model"`
}
func (t *testControllerModel) Get() {
username := t.Ctx.Params().Get("username")
t.TestModel = Model{Username: username}
t.TestModel2 = Model{Username: username + "2"}
}
func (t *testControllerModel) EndRequest(ctx context.Context) {
// t.Ctx == ctx
m, ok := t.Ctx.GetViewData()["myModel"]
if !ok {
t.Ctx.Writef("fail TestModel load and set")
return
}
model, ok := m.(Model)
if !ok {
t.Ctx.Writef("fail to override the TestModel name by the tag")
return
}
// test without custom name tag, should have the field's nae.
m, ok = t.Ctx.GetViewData()["TestModel2"]
if !ok {
t.Ctx.Writef("fail TestModel2 load and set")
return
}
model2, ok := m.(Model)
if !ok {
t.Ctx.Writef("fail to override the TestModel2 name by the tag")
return
}
// models are being rendered via the View at ViewData but
// we just test it here, so print it back.
t.Ctx.Writef(model.Username + model2.Username)
t.Controller.EndRequest(ctx)
}
func TestControllerModel(t *testing.T) {
app := iris.New()
app.Controller("/model/{username}", new(testControllerModel))
e := httptest.New(t, app)
usernames := []string{
"kataras",
"makis",
}
for _, username := range usernames {
e.GET("/model/" + username).Expect().Status(httptest.StatusOK).
Body().Equal(username + username + "2")
}
}
type testBindType struct {
title string
}
type testControllerBindStruct struct {
mvc.Controller
// should start with upper letter of course
TitlePointer *testBindType // should have the value of the "myTitlePtr" on test
TitleValue testBindType // should have the value of the "myTitleV" on test
Other string // just another type to check the field collection, should be empty
}
func (t *testControllerBindStruct) Get() {
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
}
type testControllerBindDeep struct {
testControllerBindStruct
}
func (t *testControllerBindDeep) Get() {
// t.testControllerBindStruct.Get()
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
}
func TestControllerBind(t *testing.T) {
app := iris.New()
t1, t2 := "my pointer title", "val title"
// test bind pointer to pointer of the correct type
myTitlePtr := &testBindType{title: t1}
// test bind value to value of the correct type
myTitleV := testBindType{title: t2}
app.Controller("/", new(testControllerBindStruct), myTitlePtr, myTitleV)
app.Controller("/deep", new(testControllerBindDeep), myTitlePtr, myTitleV)
e := httptest.New(t, app)
expected := t1 + t2
e.GET("/").Expect().Status(httptest.StatusOK).
Body().Equal(expected)
e.GET("/deep").Expect().Status(httptest.StatusOK).
Body().Equal(expected)
}

35
mvc/session_controller.go Normal file
View File

@ -0,0 +1,35 @@
package mvc
import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/sessions"
)
// SessionController is a simple `Controller` implementation
// which requires a binded session manager in order to give
// direct access to the current client's session via its `Session` field.
type SessionController struct {
Controller
Manager *sessions.Sessions
Session *sessions.Session
}
var managerMissing = "MVC SessionController: session manager field is nil, you have to bind it to a *sessions.Sessions"
// BeginRequest calls the Controller's BeginRequest
// and tries to initialize the current user's Session.
func (s *SessionController) BeginRequest(ctx context.Context) {
s.Controller.BeginRequest(ctx)
if s.Manager == nil {
ctx.Application().Logger().Errorf(managerMissing)
return
}
s.Session = s.Manager.Start(ctx)
}
/* TODO:
Maybe add struct tags on `binder` for required binded values
in order to log error if some of the bindings are missing or leave that to the end-developers?
*/

View File

@ -137,6 +137,11 @@ var errFindParse = errors.New("Unable to find the %s with key: %s. Found? %#v")
// GetInt same as Get but returns as int, if not found then returns -1 and an error. // GetInt same as Get but returns as int, if not found then returns -1 and an error.
func (s *Session) GetInt(key string) (int, error) { func (s *Session) GetInt(key string) (int, error) {
return s.GetIntDefault(key, -1)
}
// GetIntDefault same as Get but returns as int, if not found then returns the "defaultValue".
func (s *Session) GetIntDefault(key string, defaultValue int) (int, error) {
v := s.Get(key) v := s.Get(key)
if vint, ok := v.(int); ok { if vint, ok := v.(int); ok {
@ -147,7 +152,7 @@ func (s *Session) GetInt(key string) (int, error) {
return strconv.Atoi(vstring) return strconv.Atoi(vstring)
} }
return -1, errFindParse.Format("int", key, v) return defaultValue, errFindParse.Format("int", key, v)
} }
// GetInt64 same as Get but returns as int64, if not found then returns -1 and an error. // GetInt64 same as Get but returns as int64, if not found then returns -1 and an error.

View File

@ -7,7 +7,6 @@ import (
"net/url" "net/url"
"os" "os"
"os/exec" "os/exec"
"strings"
"sync" "sync"
"time" "time"
@ -15,11 +14,6 @@ import (
"github.com/kataras/iris/core/netutil" "github.com/kataras/iris/core/netutil"
) )
const (
versionURL = "http://iris-go.com/version"
updateCmd = "go get -u -f -v github.com/kataras/iris"
)
var checkVersionOnce = sync.Once{} var checkVersionOnce = sync.Once{}
// CheckVersion checks for any available updates. // CheckVersion checks for any available updates.
@ -36,8 +30,8 @@ type versionInfo struct {
} }
func checkVersion() { func checkVersion() {
client := netutil.Client(time.Duration(20 * time.Second)) client := netutil.Client(20 * time.Second)
r, err := client.PostForm(versionURL, url.Values{"current_version": {Version}}) r, err := client.PostForm("http://iris-go.com/version", url.Values{"current_version": {Version}})
if err != nil { if err != nil {
golog.Debugf("%v", err) golog.Debugf("%v", err)
@ -105,9 +99,8 @@ func checkVersion() {
} }
if shouldUpdate { if shouldUpdate {
goget := strings.Split(updateCmd, " ") repo := "github.com/kataras/iris"
// go get -u github.com/:owner/:repo cmd := exec.Command("go", "get", "-u", "-f", "-v", repo)
cmd := exec.Command(goget[0], goget[1:]...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout cmd.Stderr = os.Stdout
@ -116,6 +109,6 @@ func checkVersion() {
return return
} }
golog.Infof("Update process finished.\nManual restart is required to apply the changes...") golog.Infof("Update process finished.\nManual rebuild and restart is required to apply the changes...")
} }
} }

View File

@ -43,6 +43,12 @@ var (
// ExecuteWriter calls the correct view Engine's ExecuteWriter func // ExecuteWriter calls the correct view Engine's ExecuteWriter func
func (v *View) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error { func (v *View) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error {
if len(filename) > 2 {
if filename[0] == '/' { // omit first slash
filename = filename[1:]
}
}
e := v.Find(filename) e := v.Find(filename)
if e == nil { if e == nil {
return errNoViewEngineForExt.Format(filepath.Ext(filename)) return errNoViewEngineForExt.Format(filepath.Ext(filename))