iris/HISTORY.md

1383 lines
48 KiB
Markdown
Raw Normal View History

# Changelog
2016-05-30 16:08:09 +02:00
**How to upgrade**: Open your command-line and execute this command: `go get -u gopkg.in/kataras/iris.v6`.
## Looking for free support?
http://support.iris-go.com
## 6.1.4 -> 6.2.0 (√Νεxτ)
_Update: 18 March 2017_
- **Sessions**: Enchance the community feature request about custom encode and decode methods for the cookie value(sessionid) as requested [here](http://support.iris-go.com/d/29-mark-cookie-for-session-as-secure).
_Update: 12 March 2017_
- Enhance Custom http errors with gzip and static files handler, as requested/reported [here](http://support.iris-go.com/d/17-fallback-handler-for-non-matched-routes/9).
- Enhance per-party custom http errors (now it works on any wildcard path too).
- Add a third parameter on `app.OnError(...)` for custom http errors with regexp validation, see [status_test.go](https://github.com/kataras/iris/blob/v6/status_test.go) for an example.
- Add a `context.ParamIntWildcard(...)` to skip the first slash, useful for wildcarded paths' parameters.
> Prepare for nice things, tomorrow is Iris' first birthday!
_Update: 28 Feb 2017_
> Note: I want you to know that I spent more than 200 hours (16 days of ~10-15 hours per-day, do the math) for this release, two days to write these changes, please read the sections before think that you have an issue and post a new question, thanks!
Users already notified for some breaking-changes, this section will help you
to adapt the new changes to your application, it contains an overview of the new features too.
- Shutdown with `app.Shutdown(context.Context) error`, no need for any third-parties, with `EventPolicy.Interrupted` and Go's 1.8 Gracefully Shutdown feature you're ready to go!
- HTTP/2 Go 1.8 `context.Push(target string, opts *http.PushOptions) error` is supported, example can be found [here](https://github.com/kataras/iris/blob/v6/adaptors/websocket/_examples/websocket_secure/main.go)
- Router (two lines to add, new features)
- Template engines (two lines to add, same features as before, except their easier configuration)
- Basic middleware, that have been written by me, are transfared to the main repository[/middleware](https://github.com/kataras/iris/tree/v6/middleware) with a lot of improvements to the `recover middleware` (see the next)
- `func(http.ResponseWriter, r *http.Request, next http.HandlerFunc)` signature is fully compatible using `iris.ToHandler` helper wrapper func, without any need of custom boilerplate code. So all net/http middleware out there are supported, no need to re-invert the world here, search to the internet and you'll find a suitable to your case.
- Load Configuration from an external file, yaml and toml:
- [yaml-based](http://www.yaml.org/) configuration file using the `iris.YAML` function: `app := iris.New(iris.YAML("myconfiguration.yaml"))`
- [toml-based](https://github.com/toml-lang/toml) configuration file using the `iris.TOML` function: `app := iris.New(iris.TOML("myconfiguration.toml"))`
- Add `.Regex` middleware which does path validation using the `regexp` package, i.e `.Regex("param", "[0-9]+$")`. Useful for routers that don't support regex route path validation out-of-the-box.
- Websocket additions: `c.Context() *iris.Context`, `ws.GetConnectionsByRoom("room name") []websocket.Connection`, `c.OnLeave(func(roomName string){})`,
```go
// SetValue sets a key-value pair on the connection's mem store.
c.SetValue(key string, value interface{})
// GetValue gets a value by its key from the connection's mem store.
c.GetValue(key string) interface{}
// GetValueArrString gets a value as []string by its key from the connection's mem store.
c.GetValueArrString(key string) []string
// GetValueString gets a value as string by its key from the connection's mem store.
c.GetValueString(key string) string
// GetValueInt gets a value as integer by its key from the connection's mem store.
c.GetValueInt(key string) int
```
[examples here](https://github.com/kataras/iris/blob/v6/adaptors/websocket/_examples).
Fixes:
- Websocket improvements and fix errors when using custom golang client
- Sessions performance improvements
- Fix cors by using `rs/cors` and add a new adaptor to be able to wrap the entire router
- Fix and improve oauth/oauth2 plugin(now adaptor)
- Improve and fix recover middleware
- Fix typescript compiler and hot-reloader plugin(now adaptor)
- Fix and improve the cloud-editor `alm/alm-tools` plugin(now adaptor)
- Fix gorillamux serve static files (custom routers are supported with a workaround, not a complete solution as they are now)
- Fix `iris run main.go` app reload while user saved the file from gogland
- Fix [StaticEmbedded doesn't works on root "/"](https://github.com/kataras/iris/issues/633)
Changes:
- `context.TemplateString` replaced with `app.Render(w io.Writer, name string, bind interface{}, options ...map[string]interface{}) error)` which gives you more functionality.
```go
import "bytes"
// ....
app := iris.New()
// ....
buff := &bytes.Buffer{}
app.Render(buff, "my_template.html", nil)
// buff.String() is the template parser's result, use that string to send a rich-text e-mail based on a template.
```
```go
// you can take the app(*Framework instance) via *Context.Framework() too:
app.Get("/send_mail", func(ctx *iris.Context){
buff := &bytes.Buffer{}
ctx.Framework().Render(buff, "my_template.html", nil)
// ...
})
```
- `.Close() error` replaced with gracefully `.Shutdown(context.Context) error`
- Remove all the package-level functions and variables for a default `*iris.Framework, iris.Default`
- Remove `.API`, use `iris.Handle/.HandleFunc/.Get/.Post/.Put/.Delete/.Trace/.Options/.Use/.UseFunc/.UseGlobal/.Party/` instead
- Remove `.Logger`, `.Config.IsDevelopment`, `.Config.LoggerOut`, `.Config.LoggerPrefix` you can adapt a logger which will log to each log message mode by `app.Adapt(iris.DevLogger())` or adapt a new one, it's just a `func(mode iris.LogMode, message string)`.
- Remove `.Config.DisableTemplateEngines`, are disabled by-default, you have to `.Adapt` a view engine by yourself
- Remove `context.RenderTemplateSource` you should make a new template file and use the `iris.Render` to specify an `io.Writer` like `bytes.Buffer`
- Remove `plugins`, replaced with more pluggable echosystem that I designed from zero on this release, named `Policy` [Adaptors](https://github.com/kataras/iris/tree/v6/adaptors) (all plugins have been converted, fixed and improvement, except the iriscontrol).
- `context.Log(string,...interface{})` -> `context.Log(iris.LogMode, string)`
- Remove `.Config.DisableBanner`, now it's controlled by `app.Adapt(iris.LoggerPolicy(func(mode iris.LogMode, msg string)))`
- Remove `.Config.Websocket` , replaced with the `kataras/iris/adaptors/websocket.Config` adaptor.
- https://github.com/iris-contrib/plugin -> https://github.com/iris-contrib/adaptors
- `import "github.com/iris-contrib/middleware/basicauth"` -> `import "gopkg.in/kataras/iris.v6/middleware/basicauth"`
- `import "github.com/iris-contrib/middleware/i18n"` -> `import "gopkg.in/kataras/iris.v6/middleware/i18n"`
- `import "github.com/iris-contrib/middleware/logger"` -> `import "gopkg.in/kataras/iris.v6/middleware/logger"`
- `import "github.com/iris-contrib/middleware/recovery"` -> `import "gopkg.in/kataras/iris.v6/middleware/recover"`
- `import "github.com/iris-contrib/plugin/typescript"` -> `import "gopkg.in/kataras/iris.v6/adaptors/typescript"`
- `import "github.com/iris-contrib/plugin/editor"` -> `import "gopkg.in/kataras/iris.v6/adaptors/typescript/editor"`
- `import "github.com/iris-contrib/plugin/cors"` -> `import "gopkg.in/kataras/iris.v6/adaptors/cors"`
- `import "github.com/iris-contrib/plugin/gorillamux"` -> `import "gopkg.in/kataras/iris.v6/adaptors/gorillamux"`
- `import github.com/iris-contrib/plugin/oauth"` -> `import "github.com/iris-contrib/adaptors/oauth"`
- `import "github.com/kataras/go-template/html"` -> `import "gopkg.in/kataras/iris.v6/adaptors/view"`
- `import "github.com/kataras/go-template/django"` -> `import "gopkg.in/kataras/iris.v6/adaptors/view"`
- `import "github.com/kataras/go-template/pug"` -> `import "gopkg.in/kataras/iris.v6/adaptors/view"`
- `import "github.com/kataras/go-template/handlebars"` -> `import "gopkg.in/kataras/iris.v6/adaptors/view"`
- `import "github.com/kataras/go-template/amber"` -> `import "gopkg.in/kataras/iris.v6/adaptors/view"`
**Read more below** for the lines you have to change. Package-level removal is critical, you will have build-time errors. Router(less) is MUST, otherwise your app will fatal with a detailed error message.
> If I missed something please [chat](https://kataras.rocket.chat/channel/iris).
### Router(less)
**Iris server does not contain a default router anymore**, yes your eyes are ok.
This decision came up because of your requests of using other routers than the iris' defaulted.
At the past I gave you many workarounds, but they are just workarounds, not a complete solution.
**Don't worry:**
- you have to add only two lines, one is the `import path` and another is the `.Adapt`, after the `iris.New()`, so it can be tolerated.
- you are able to use all iris' features as you used before, **the API for routing has not been changed**.
Two routers available to use, today:
- [httprouter](https://github.com/kataras/iris/tree/v6/adaptors/httprouter), the old defaulted. A router that can be adapted, it's a custom version of https://github.comjulienschmidt/httprouter which is edited to support iris' subdomains, reverse routing, custom http errors and a lot features, it should be a bit faster than the original too because of iris' Context. It uses `/mypath/:firstParameter/path/:secondParameter` and `/mypath/*wildcardParamName` .
Example:
```go
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter" // <---- NEW
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New()) // <---- NEW
app.OnError(iris.StatusNotFound, func(ctx *iris.Context){
ctx.HTML(iris.StatusNotFound, "<h1> custom http error page </h1>")
})
app.Get("/healthcheck", h)
gamesMiddleware := func(ctx *iris.Context) {
println(ctx.Method() + ": " + ctx.Path())
ctx.Next()
}
games:= app.Party("/games", gamesMiddleware)
{ // braces are optional of course, it's just a style of code
games.Get("/:gameID/clans", h)
games.Get("/:gameID/clans/clan/:publicID", h)
games.Get("/:gameID/clans/search", h)
games.Put("/:gameID/players/:publicID", h)
games.Put("/:gameID/clans/clan/:publicID", h)
games.Post("/:gameID/clans", h)
games.Post("/:gameID/players", h)
games.Post("/:gameID/clans/:publicID/leave", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/application", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/application/:action", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/invitation", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/invitation/:action", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/delete", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/promote", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/demote", h)
}
app.Get("/anything/*anythingparameter", func(ctx *iris.Context){
s := ctx.Param("anythingparameter")
ctx.Writef("The path after /anything is: %s",s)
})
app.Listen(":80")
/*
gameID = 1
publicID = 2
clanPublicID = 22
action = 3
GET
http://localhost/healthcheck
http://localhost/games/1/clans
http://localhost/games/1/clans/clan/2
http://localhost/games/1/clans/search
PUT
http://localhost/games/1/players/2
http://localhost/games/1/clans/clan/2
POST
http://localhost/games/1/clans
http://localhost/games/1/players
http://localhost/games/1/clans/2/leave
http://localhost/games/1/clans/22/memberships/application -> 494
http://localhost/games/1/clans/22/memberships/application/3- > 404
http://localhost/games/1/clans/22/memberships/invitation
http://localhost/games/1/clans/22/memberships/invitation/3
http://localhost/games/1/clans/2/memberships/delete
http://localhost/games/1/clans/22/memberships/promote
http://localhost/games/1/clans/22/memberships/demote
*/
}
func h(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, "<h1>Path<h1/>"+ctx.Path())
}
```
- [gorillamux](https://github.com/kataras/iris/tree/v6/adaptors/gorillamux), a router that can be adapted, it's the https://github.com/gorilla/mux which supports subdomains, custom http errors, reverse routing, pattern matching via regex and the rest of the iris' features.
Example:
```go
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux" // <---- NEW
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New()) // <---- NEW
app.OnError(iris.StatusNotFound, func(ctx *iris.Context){
ctx.HTML(iris.StatusNotFound, "<h1> custom http error page </h1>")
})
app.Get("/healthcheck", h)
gamesMiddleware := func(ctx *iris.Context) {
println(ctx.Method() + ": " + ctx.Path())
ctx.Next()
}
games:= app.Party("/games", gamesMiddleware)
{ // braces are optional of course, it's just a style of code
games.Get("/{gameID:[0-9]+}/clans", h)
games.Get("/{gameID:[0-9]+}/clans/clan/{publicID:[0-9]+}", h)
games.Get("/{gameID:[0-9]+}/clans/search", h)
games.Put("/{gameID:[0-9]+}/players/{publicID:[0-9]+}", h)
games.Put("/{gameID:[0-9]+}/clans/clan/{publicID:[0-9]+}", h)
games.Post("/{gameID:[0-9]+}/clans", h)
games.Post("/{gameID:[0-9]+}/players", h)
games.Post("/{gameID:[0-9]+}/clans/{publicID:[0-9]+}/leave", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/application", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/application/:action", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/invitation", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/invitation/:action", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/delete", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/promote", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/demote", h)
}
app.Get("/anything/{anythingparameter:.*}", func(ctx *iris.Context){
s := ctx.Param("anythingparameter")
ctx.Writef("The path after /anything is: %s",s)
})
app.Listen(":80")
/*
gameID = 1
publicID = 2
clanPublicID = 22
action = 3
GET
http://localhost/healthcheck
http://localhost/games/1/clans
http://localhost/games/1/clans/clan/2
http://localhost/games/1/clans/search
PUT
http://localhost/games/1/players/2
http://localhost/games/1/clans/clan/2
POST
http://localhost/games/1/clans
http://localhost/games/1/players
http://localhost/games/1/clans/2/leave
http://localhost/games/1/clans/22/memberships/application -> 494
http://localhost/games/1/clans/22/memberships/application/3- > 404
http://localhost/games/1/clans/22/memberships/invitation
http://localhost/games/1/clans/22/memberships/invitation/3
http://localhost/games/1/clans/2/memberships/delete
http://localhost/games/1/clans/22/memberships/promote
http://localhost/games/1/clans/22/memberships/demote
*/
}
func h(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, "<h1>Path<h1/>"+ctx.Path())
}
```
**No changes whatever router you use**, only the `path` is changed(otherwise it doesn't make sense to support more than one router).
At the `gorillamux`'s path example we get pattern matching using regexp, at the other hand `httprouter` doesn't provides path validations
but it provides parameter and wildcard parameters too, it's also a lot faster than gorillamux.
Original Gorilla Mux made my life easier when I had to adapt the reverse routing and subdomains features, it has got these features by its own too, so it was easy.
Original Httprouter doesn't supports subdomains, multiple paths on different methods, reverse routing, custom http errors, I had to implement
all of them by myself and after adapt them using the policies, it was a bit painful but this is my job. Result: It runs blazy-fast!
As we said, all iris' features works as before even if you are able to adapt any custom router. Template funcs that were relative-closed to reverse router, like `{{ url }} and {{ urlpath }}`, works as before too, no change for your app's side need.
> I would love to see more routers (as more as they can provide different `path declaration` features) from the community, create an adaptor for an iris' router and I will share your repository to the rest of the users!
Adaptors are located [there](https://github.com/kataras/iris/tree/v6/adaptors).
### View engine (5 template engine adaptors)
At the past, If no template engine was used then iris selected the [html standard](https://github.com/kataras/go-template/tree/master/html).
**Now, iris doesn't defaults any template engine** (also the `.Config.DisableTemplateEngines` has been removed, it has no use anymore).
So, again you have to do two changes, the `import path` and the `.Adapt`.
**Template files are no need to change, the template engines does the same exactly things as before**
All of these **five template engines** have common features with common API, like Layout, Template Funcs, Party-specific layout, partial rendering and more.
- **the standard html**, based on [go-template/html](https://github.com/kataras/go-template/tree/master/html), its template parser is the [html/template](https://golang.org/pkg/html/template/).
- **django**, based on [go-template/django](https://github.com/kataras/go-template/tree/master/django), its template parser is the [pongo2](https://github.com/flosch/pongo2)
- **pug**, based on [go-template/pug](https://github.com/kataras/go-template/tree/master/pug), its template parser is the [jade](https://github.com/Joker/jade)
- **handlebars**, based on [go-template/handlebars](https://github.com/kataras/go-template/tree/master/handlebars), its template parser is the [raymond](https://github.com/aymerick/raymond)
- **amber**, based on [go-template/amber](https://github.com/kataras/go-template/tree/master/amber), its template parser is the [amber](https://github.com/eknkc/amber).
Each of the template engines has different options, view adaptors are located [here](https://github.com/kataras/iris/tree/v6/adaptors/view).
Example:
```go
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux" // <--- NEW (previous section)
"gopkg.in/kataras/iris.v6/adaptors/view" // <--- NEW it contains all the template engines
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New()) // <--- NEW (previous section)
// - standard html | view.HTML(...)
// - django | view.Django(...)
// - pug(jade) | view.Pug(...)
// - handlebars | view.Handlebars(...)
// - amber | view.Amber(...)
app.Adapt(view.HTML("./templates", ".html").Reload(true)) // <---- NEW (set .Reload to true when you're in dev mode.)
// default template funcs:
//
// - {{ url "mynamedroute" "pathParameter_ifneeded"} }
// - {{ urlpath "mynamedroute" "pathParameter_ifneeded" }}
// - {{ render "header.html" }}
// - {{ render_r "header.html" }} // partial relative path to current page
// - {{ yield }}
// - {{ current }}
//
// to adapt custom funcs, use:
app.Adapt(iris.TemplateFuncsPolicy{"myfunc": func(s string) string {
return "hi "+s
}}) // usage inside template: {{ hi "kataras"}}
app.Get("/hi", func(ctx *iris.Context) {
ctx.MustRender(
"hi.html", // the file name of the template relative to the './templates'
iris.Map{"Name": "Iris"}, // the .Name inside the ./templates/hi.html
iris.Map{"gzip": false}, // enable gzip for big files
)
})
// http://127.0.0.1:8080/hi
app.Listen(":8080")
}
```
`.UseTemplate` have been removed and replaced with the `.Adapt` which is using `iris.RenderPolicy` and `iris.TemplateFuncsPolicy`
to adapt the behavior of the custom template engines.
**BEFORE**
```go
import "github.com/kataras/go-template/django"
// ...
app := iris.New()
app.UseTemplate(django.New()).Directory("./templates", ".html")/*.Binary(...)*/)
```
**AFTER**
```go
import ""gopkg.in/kataras/iris.v6/adaptors/view"
// ...
app := iris.New()
app.Adapt(view.Django("./templates",".htmll")/*.Binary(...)*/)
```
The rest remains the same. Don't forget the real changes were `only import path and .Adapt(imported)`, at general when you see an 'adaptor' these two declarations should happen to your code.
### Package-level functions and variables for `iris.Default` have been removed.
The form of variable use for an Iris *Framework remains as it was:
```go
app := iris.New()
app.$FUNCTION/$VARIABLE
```
> When I refer to `iris.$FUNCTION/$VARIABLE` it means `iris.Handle/.HandleFunc/.Get/.Post/.Put/.Delete/.Trace/.Options/.Use/.UseFunc/.UseGlobal/.Party/.Set/.Config`
and the rest of the package-level functions referred to the `iris.Default` variable.
**BEFORE**
```go
iris.Config.FireMethodNotAllowed = true
iris.Set(OptionDisableBodyConsumptionOnUnmarshal(true))
```
```go
iris.Get("/", func(ctx *iris.Context){
})
iris.ListenLETSENCRYPT(":8080")
```
**AFTER**
```go
app := iris.New()
app.Config.FireMethodNotAllowed = true
// or iris.Default.Config.FireMethodNotAllowed = true and so on
app.Set(OptionDisableBodyConsumptionOnUnmarshal(true))
// same as
// app := iris.New(iris.Configuration{FireMethodNotAllowed:true, DisableBodyConsumptionOnUnmarshal:true})
```
```go
app := iris.New()
app.Get("/", func(ctx *iris.Context){
})
app.ListenLETSENCRYPT(":8080")
```
For those who had splitted the application in different packages they could do just that `iris.$FUNCTION/$VARIABLE` without the need
of import a singleton package which would initialize a new `App := iris.New()`.
`Iris.Default` remains, so you can refer to that if you don't want to initialize a new `App := iris.New()` by your own.
**BEFORE**
```go
package controllers
import "github.com/kataras/iris"
func init(){
iris.Get("/", func(ctx *iris.Context){
})
}
```
```go
package main
import (
"github.com/kataras/iris"
_ "github.com/mypackage/controllers"
)
func main(){
iris.Listen(":8080")
}
```
**AFTER**
```go
package controllers
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
)
func init(){
iris.Default.Adapt(httprouter.New())
iris.Default.Get("/", func(ctx *iris.Context){
})
}
```
```go
package main
import (
"gopkg.in/kataras/iris.v6"
_ "github.com/mypackage/controllers"
)
func main(){
iris.Default.Listen(":8080")
}
```
You got the point, let's continue to the next conversion.
### Remove the slow .API | iris.API(...) / app := iris.New(); app.API(...)
The deprecated `.API` has been removed entirely, it should be removed after v5(look on the v5 history tag).
At first I created that func in order to give newcovers a chance to be able to quick start a new `controller-like`
with one function, but that function was using generics at runtime and it was very slow compared to the
`iris.Handle/.HandleFunc/.Get/.Post/.Put/.Delete/.Trace/.Options/.Use/.UseFunc/.UseGlobal/.Party`.
Also some users they used only `.API`, they didn't bother to 'learn' about the standard rest api functions
and their power(including per-route middleware, cors, recover and so on). So we had many unrelational questions about the `.API` func.
**BEFORE**
```go
package main
import (
"github.com/kataras/iris"
)
type UserAPI struct {
*iris.Context
}
// GET /users
func (u UserAPI) Get() {
u.Writef("Get from /users")
// u.JSON(iris.StatusOK,myDb.AllUsers())
}
// GET /users/:param1 which its value passed to the id argument
func (u UserAPI) GetBy(id string) { // id equals to u.Param("param1")
u.Writef("Get from /users/%s", id)
// u.JSON(iris.StatusOK, myDb.GetUserById(id))
}
// POST /users
func (u UserAPI) Post() {
name := u.FormValue("name")
// myDb.InsertUser(...)
println(string(name))
println("Post from /users")
}
// PUT /users/:param1
func (u UserAPI) PutBy(id string) {
name := u.FormValue("name") // you can still use the whole Context's features!
// myDb.UpdateUser(...)
println(string(name))
println("Put from /users/" + id)
}
// DELETE /users/:param1
func (u UserAPI) DeleteBy(id string) {
// myDb.DeleteUser(id)
println("Delete from /" + id)
}
func main() {
iris.API("/users", UserAPI{})
iris.Listen(":8080")
}
```
**AFTER**
```go
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
)
func GetAllUsersHandler(ctx *iris.Context) {
ctx.Writef("Get from /users")
// ctx.JSON(iris.StatusOK,myDb.AllUsers())
}
func GetUserByIdHandler(ctx *iris.Context) {
ctx.Writef("Get from /users/%s",
ctx.Param("id")) // or id, err := ctx.ParamInt("id")
// ctx.JSON(iris.StatusOK, myDb.GetUserById(id))
}
func InsertUserHandler(ctx *iris.Context){
name := ctx.FormValue("name")
// myDb.InsertUser(...)
println(string(name))
println("Post from /users")
}
func UpdateUserHandler(ctx *iris.Context) {
name := ctx.FormValue("name")
// myDb.UpdateUser(...)
println(string(name))
println("Put from /users/" + ctx.Param("id"))
}
func DeleteUserById(id string) {
// myDb.DeleteUser(id)
println("Delete from /" + ctx.param("id"))
}
func main() {
app := iris.New()
app.Adapt(gorillamux.New())
// create a new router targeted for "/users" path prefix
// you can learn more about Parties on the examples and book too
// they can share middleware, template layout and more.
userRoutes := app.Party("users")
// GET http://localhost:8080/users/ and /users
userRoutes.Get("/", GetAllUsersHandler)
// GET http://localhost:8080/users/:id
userRoutes.Get("/:id", GetUserByIdHandler)
// POST http://localhost:8080/users
userRoutes.Post("/", InsertUserHandler)
// PUT http://localhost:8080/users/:id
userRoutes.Put("/:id", UpdateUserHandler)
// DELETE http://localhost:8080/users/:id
userRoutes.Delete("/:id", DeleteUserById)
app.Listen(":8080")
}
```
### Old Plugins and the new `.Adapt` Policies
A lot of changes to old -so-called Plugins and many features have been adopted to this new ecosystem.
First of all plugins renamed to `policies with adaptors which, adaptors, adapts the policies to the framework`
(it is not just a simple rename of the word, it's a new concept).
Policies are declared inside Framework, they are implemented outside of the Framework and they are adapted to Framework by a user call.
Policy adaptors are just like a plugins but they have to implement a specific action/behavior to a specific policy type(or more than one at the time).
The old plugins are fired 'when something happens do that' (ex: PreBuild,PostBuild,PreListen and so on) this behavior is the new `EventPolicy`
which has **4 main flow events** with their callbacks been wrapped, so you can use more than EventPolicy (most of the policies works this way).
```go
type (
// EventListener is the signature for type of func(*Framework),
// which is used to register events inside an EventPolicy.
//
// Keep note that, inside the policy this is a wrapper
// in order to register more than one listener without the need of slice.
EventListener func(*Framework)
// EventPolicy contains the available Framework's flow event callbacks.
// Available events:
// - Boot
// - Build
// - Interrupted
// - Recovery
EventPolicy struct {
// Boot with a listener type of EventListener.
// Fires when '.Boot' is called (by .Serve functions or manually),
// before the Build of the components and the Listen,
// after VHost and VSCheme configuration has been setted.
Boot EventListener
// Before Listen, after Boot
Build EventListener
// Interrupted with a listener type of EventListener.
// Fires after the terminal is interrupted manually by Ctrl/Cmd + C
// which should be used to release external resources.
// Iris will close and os.Exit at the end of custom interrupted events.
// If you want to prevent the default behavior just block on the custom Interrupted event.
Interrupted EventListener
// Recovery with a listener type of func(*Framework,error).
// Fires when an unexpected error(panic) is happening at runtime,
// while the server's net.Listener accepting requests
// or when a '.Must' call contains a filled error.
// Used to release external resources and '.Close' the server.
// Only one type of this callback is allowed.
//
// If not empty then the Framework will skip its internal
// server's '.Close' and panic to its '.Logger' and execute that callback instaed.
// Differences from Interrupted:
// 1. Fires on unexpected errors
// 2. Only one listener is allowed.
Recovery func(*Framework, error)
}
)
```
**A quick overview on how they can be adapted** to an iris *Framework (iris.New()'s result).
Let's adapt `EventPolicy`:
```go
app := iris.New()
evts := iris.EventPolicy{
// we ommit the *Framework's variable name because we have already the 'app'
// if we were on different file with no access to the 'app' then the varialbe name will be useful.
Boot: func(*Framework){
app.Log("Here you can change any field and configuration for iris before being used
also you can adapt more policies that should be used to the next step which is the Build and Listen,
only the app.Config.VHost and app.Config.VScheme have been setted here, but you can change them too\n")
},
Build: func(*Framework){
app.Log("Here all configuration and all app' fields and features have been builded, here you are ready to call
anything (you shouldn't change fields and configuration here)\n")
},
}
// Adapt the EventPolicy 'evts' to the Framework
app.Adapt(evts)
// let's register one more
app.Adapt(iris.EventPolicy{
Boot: func(*Framework){
app.Log("the second log message from .Boot!\n")
}})
// you can also adapt multiple and different(or same) types of policies in the same call
// using: app.Adapt(iris.EventPolicy{...}, iris.LoggerPolicy(...), iris.RouterWrapperPolicy(...))
// starts the server, executes the Boot -> Build...
app.Adapt(httprouter.New()) // read below for this line
app.Listen(":8080")
```
This pattern allows us to be very pluggable and add features that the *Framework itself doesn't knows,
it knows only the main policies which implement but their features are our(as users) business.
We have 8 policies, so far, and some of them have 'subpolicies' (the RouterReversionPolicy for example).
- LoggerPolicy
- EventPolicy
- Boot
- Build
- Interrupted
- Recover
- RouterReversionPolicy
- StaticPath
- WildcardPath
- Param
- URLPath
- RouterBuilderPolicy
- RouterWrapperPolicy
- RenderPolicy
- TemplateFuncsPolicy
- SessionsPolicy
**Details** of these can be found at [policy.go](https://github.com/kataras/iris/blob/v6/policy.go).
The **Community**'s adaptors are [here](https://github.com/iris-contrib/adaptors).
**Iris' Built'n Adaptors** for these policies can be found at [/adaptors folder](https://github.com/kataras/iris/tree/v6/adaptors).
The folder contains:
- cors, a cors (router) wrapper based on `rs/cors`.
It's a `RouterWrapperPolicy`
- gorillamux, a router that can be adapted, it's the `gorilla/mux` which supports subdomains, custom http errors, reverse routing, pattern matching.
It's a compination of`EventPolicy`, `RouterReversionPolicy with StaticPath, WildcardPath, URLPath, RouteContextLinker` and the `RouterBuilderPolicy`.
- httprouter, a router that can be adapted, it's a custom version of `julienschmidt/httprouter` which is edited to support iris' subdomains, reverse routing, custom http errors and a lot features, it should be a bit faster than the original too.
It's a compination of`EventPolicy`, `RouterReversionPolicy with StaticPath, WildcardPath, URLPath, RouteContextLinker` and the `RouterBuilderPolicy`.
- typescript and cloud editor, contains the typescript compiler with hot reload feature and a typescript cloud editor ([alm-tools](https://github.com/alm-tools/alm)), it's an `EventPolicy`
- view, contains 5 template engines based on the `kataras/go-template`.
All of these have common features with common API, like Layout, Template Funcs, Party-specific layout, partial rendering and more.
It's a `RenderPolicy` with a compinaton of `EventPolicy` and use of `TemplateFuncsPolicy`.
- the standard html
- pug(jade)
- django(pongo2)
- handlebars
- amber.
#### Note
Go v1.8 introduced a new plugin system with `.so` files, users should not be confused with old iris' plugins and new adaptors.
It is not ready for all operating systems(yet) when it will be ready, Iris will take leverage of this Golang's feature.
### http.Handler and third-party middleware
We were compatible before this version but if a third-party middleware had the form of:
`func(http.ResponseWriter, *http.Request, http.HandlerFunc)`you were responsible of make a wrapper
which would return an `iris.Handler/HandlerFunc`.
Now you're able to pass an `func(http.ResponseWriter, *http.Request, http.HandlerFunc)` third-party net/http middleware(Chain-of-responsibility pattern) using the `iris.ToHandler` wrapper func without any other custom boilerplate.
Example:
```go
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
"github.com/rs/cors"
)
// myCors returns a new cors middleware
// with the provided options.
myCors := func(opts cors.Options) iris.HandlerFunc {
handlerWithNext := cors.New(opts).ServeHTTP
return iris.ToHandler(handlerWithNext)
}
func main(){
app := iris.New()
app.Adapt(httprouter.New())
app.Post("/user", myCors(cors.Options{}), func(ctx *iris.Context){
// ....
})
app.Listen(":8080")
}
```
- Irrelative info but this is the best place to put it: `iris/app.AcquireCtx/.ReleaseCtx` replaced to: `app.Context.Acquire/.Release/.Run`.
### iris cmd
- FIX: [iris run main.go](https://github.com/kataras/iris/tree/v6/iris#run) not reloading when file changes maden by some of the IDEs,
because they do override the operating system's fs signals. The majority of
editors worked before but I couldn't let some developers without support.
### Sessions
Sessions manager is also an Adaptor now, `iris.SessionsPolicy`.
So far we used the `kataras/go-sessions`, you could always use other session manager ofcourse but you would lose the `context.Session()`
and its returning value, the `iris.Session` now.
`SessionsPolicy` gives the developers the opportunity to adapt any,
compatible with a particular simple interface(Start and Destroy methods), third-party sessions managers.
- The API for sessions inside context is the same, no matter what session manager you wanna to adapt.
- The API for sessions inside context didn't changed, it's the same as you knew it.
- Iris, of course, has built'n `SessionsPolicy` adaptor(the kataras/go-sessions: edited to remove fasthttp dependencies).
- Sessions manager works even faster now and a bug fixed for some browsers.
- Functions like, adding a database or store(i.e: `UseDatabase`) depends on the session manager of your choice,
Iris doesn't requires these things
to adapt a package as a session manager. So `iris.UseDatabase` has been removed and depends on the `mySessions.UseDatabase` you 'll see below.
- `iris.DestroySessionByID and iris.DestroyAllSessions` have been also removed, depends on the session manager of your choice, `mySessions.DestroyByID and mySessions.DestroyAll` should do the job now.
> Don't worry about forgetting to adapt any feature that you use inside Iris, Iris will print you a how-to-fix message at iris.DevMode log level.
**[Examples folder](https://github.com/kataras/iris/tree/v6/adaptors/sessions/_examples)**
```go
package main
import (
"time"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/sessions"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger()) // enable all (error) logs
app.Adapt(httprouter.New()) // select the httprouter as the servemux
mySessions := sessions.New(sessions.Config{
// Cookie string, the session's client cookie name, for example: "mysessionid"
//
// Defaults to "irissessionid"
Cookie: "mysessionid",
// base64 urlencoding,
// if you have strange name cookie name enable this
DecodeCookie: false,
// it's time.Duration, from the time cookie is created, how long it can be alive?
// 0 means no expire.
// -1 means expire when browser closes
// or set a value, like 2 hours:
Expires: time.Hour * 2,
// the length of the sessionid's cookie's value
CookieLength: 32,
// if you want to invalid cookies on different subdomains
// of the same host, then enable it
DisableSubdomainPersistence: false,
})
// OPTIONALLY:
// import "gopkg.in/kataras/iris.v6/adaptors/sessions/sessiondb/redis"
// or import "github.com/kataras/go-sessions/sessiondb/$any_available_community_database"
// mySessions.UseDatabase(redis.New(...))
app.Adapt(mySessions) // Adapt the session manager we just created.
app.Get("/", func(ctx *iris.Context) {
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
})
app.Get("/set", func(ctx *iris.Context) {
//set session values
ctx.Session().Set("name", "iris")
//test if setted here
ctx.Writef("All ok session setted to: %s", ctx.Session().GetString("name"))
})
app.Get("/get", func(ctx *iris.Context) {
// get a specific key, as string, if no found returns just an empty string
name := ctx.Session().GetString("name")
ctx.Writef("The name on the /set was: %s", name)
})
app.Get("/delete", func(ctx *iris.Context) {
// delete a specific key
ctx.Session().Delete("name")
})
app.Get("/clear", func(ctx *iris.Context) {
// removes all entries
ctx.Session().Clear()
})
app.Get("/destroy", func(ctx *iris.Context) {
//destroy, removes the entire session and cookie
ctx.SessionDestroy()
msg := "You have to refresh the page to completely remove the session (browsers works this way, it's not iris-specific.)"
ctx.Writef(msg)
ctx.Log(iris.DevMode, msg)
}) // Note about destroy:
//
// You can destroy a session outside of a handler too, using the:
// mySessions.DestroyByID
// mySessions.DestroyAll
app.Listen(":8080")
}
```
### Websockets
There are many internal improvements to the websocket server, it
operates slighty faster to.
Websocket is an Adaptor too and you can edit more configuration fields than before.
No Write and Read timeout by default, you have to set the fields if you want to enable timeout.
Below you'll see the before and the after, keep note that the static and templates didn't changed, so I am not putting the whole
html and javascript sources here, you can run the full examples from [here](https://github.com/kataras/iris/tree/v6/adaptors/websocket/_examples).
**BEFORE:***
```go
package main
import (
"fmt" // optional
"github.com/kataras/iris"
)
type clientPage struct {
Title string
Host string
}
func main() {
iris.StaticWeb("/js", "./static/js")
iris.Get("/", func(ctx *iris.Context) {
ctx.Render("client.html", clientPage{"Client Page", ctx.Host()})
})
// the path which the websocket client should listen/registered to ->
iris.Config.Websocket.Endpoint = "/my_endpoint"
// by-default all origins are accepted, you can change this behavior by setting:
// iris.Config.Websocket.CheckOrigin
var myChatRoom = "room1"
iris.Websocket.OnConnection(func(c iris.WebsocketConnection) {
// Request returns the (upgraded) *http.Request of this connection
// avoid using it, you normally don't need it,
// websocket has everything you need to authenticate the user BUT if it's necessary
// then you use it to receive user information, for example: from headers.
// httpRequest := c.Request()
// fmt.Printf("Headers for the connection with ID: %s\n\n", c.ID())
// for k, v := range httpRequest.Header {
// fmt.Printf("%s = '%s'\n", k, strings.Join(v, ", "))
// }
// join to a room (optional)
c.Join(myChatRoom)
c.On("chat", func(message string) {
if message == "leave" {
c.Leave(myChatRoom)
c.To(myChatRoom).Emit("chat", "Client with ID: "+c.ID()+" left from the room and cannot send or receive message to/from this room.")
c.Emit("chat", "You have left from the room: "+myChatRoom+" you cannot send or receive any messages from others inside that room.")
return
}
// to all except this connection ->
// c.To(iris.Broadcast).Emit("chat", "Message from: "+c.ID()+"-> "+message)
// to all connected clients: c.To(iris.All)
// to the client itself ->
//c.Emit("chat", "Message from myself: "+message)
//send the message to the whole room,
//all connections are inside this room will receive this message
c.To(myChatRoom).Emit("chat", "From: "+c.ID()+": "+message)
})
// or create a new leave event
// c.On("leave", func() {
// c.Leave(myChatRoom)
// })
c.OnDisconnect(func() {
fmt.Printf("Connection with ID: %s has been disconnected!\n", c.ID())
})
})
iris.Listen(":8080")
}
```
**AFTER**
```go
package main
import (
"fmt" // optional
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
"gopkg.in/kataras/iris.v6/adaptors/websocket"
)
type clientPage struct {
Title string
Host string
}
func main() {
app := iris.New()
app.Adapt(iris.DevLogger()) // enable all (error) logs
app.Adapt(httprouter.New()) // select the httprouter as the servemux
app.Adapt(view.HTML("./templates", ".html")) // select the html engine to serve templates
ws := websocket.New(websocket.Config{
// the path which the websocket client should listen/registered to,
Endpoint: "/my_endpoint",
// the client-side javascript static file path
// which will be served by Iris.
// default is /iris-ws.js
// if you change that you have to change the bottom of templates/client.html
// script tag:
ClientSourcePath: "/iris-ws.js",
//
// Set the timeouts, 0 means no timeout
// websocket has more configuration, go to ../../config.go for more:
// WriteTimeout: 0,
// ReadTimeout: 0,
// by-default all origins are accepted, you can change this behavior by setting:
// CheckOrigin: (r *http.Request ) bool {},
//
//
// IDGenerator used to create (and later on, set)
// an ID for each incoming websocket connections (clients).
// The request is an argument which you can use to generate the ID (from headers for example).
// If empty then the ID is generated by DefaultIDGenerator: randomString(64):
// IDGenerator func(ctx *iris.Context) string {},
})
app.Adapt(ws) // adapt the websocket server, you can adapt more than one with different Endpoint
app.StaticWeb("/js", "./static/js") // serve our custom javascript code
app.Get("/", func(ctx *iris.Context) {
ctx.Render("client.html", clientPage{"Client Page", ctx.Host()})
})
var myChatRoom = "room1"
ws.OnConnection(func(c websocket.Connection) {
// Context returns the (upgraded) *iris.Context of this connection
// avoid using it, you normally don't need it,
// websocket has everything you need to authenticate the user BUT if it's necessary
// then you use it to receive user information, for example: from headers.
// ctx := c.Context()
// join to a room (optional)
c.Join(myChatRoom)
c.On("chat", func(message string) {
if message == "leave" {
c.Leave(myChatRoom)
c.To(myChatRoom).Emit("chat", "Client with ID: "+c.ID()+" left from the room and cannot send or receive message to/from this room.")
c.Emit("chat", "You have left from the room: "+myChatRoom+" you cannot send or receive any messages from others inside that room.")
return
}
// to all except this connection ->
// c.To(websocket.Broadcast).Emit("chat", "Message from: "+c.ID()+"-> "+message)
// to all connected clients: c.To(websocket.All)
// to the client itself ->
//c.Emit("chat", "Message from myself: "+message)
//send the message to the whole room,
//all connections are inside this room will receive this message
c.To(myChatRoom).Emit("chat", "From: "+c.ID()+": "+message)
})
// or create a new leave event
// c.On("leave", func() {
// c.Leave(myChatRoom)
// })
c.OnDisconnect(func() {
fmt.Printf("Connection with ID: %s has been disconnected!\n", c.ID())
})
})
app.Listen(":8080")
}
```
If the iris' websocket feature does not cover your app's needs, you can simply use any other
library for websockets that you used to use, like the Golang's compatible to `socket.io`, simple example:
```go
package main
import (
"log"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"github.com/googollee/go-socket.io"
)
func main() {
app := iris.New()
app.Adapt(httprouter.New())
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
server.On("connection", func(so socketio.Socket) {
log.Println("on connection")
so.Join("chat")
so.On("chat message", func(msg string) {
log.Println("emit:", so.Emit("chat message", msg))
so.BroadcastTo("chat", "chat message", msg)
})
so.On("disconnection", func() {
log.Println("on disconnect")
})
})
server.On("error", func(so socketio.Socket, err error) {
log.Println("error:", err)
})
app.Any("/socket.io", iris.ToHandler(server))
app.Listen(":5000")
}
```
### Typescript compiler and cloud-based editor
The Typescript compiler adaptor(old 'plugin') has been fixed (it had an issue on new typescript versions).
Example can be bound [here](https://github.com/kataras/iris/tree/v6/adaptors/typescript/_example).
The Cloud-based editor adaptor(old 'plugin') also fixed and improved to show debug messages to your iris' LoggerPolicy.
Example can be bound [here](https://github.com/kataras/iris/tree/v6/adaptors/typescript/editor/_example).
Their import paths also changed as the rest of the old plugins from: https://github.com/iris-contrib/plugin to https://github.com/kataras/adaptors and https://github.com/iris-contrib/adaptors
I had them on iris-contrib because I thought that community would help but it didn't, no problem, they are at the same codebase now
which making things easier to debug for me.
### Oauth/OAuth2
Fix the oauth/oauth2 adaptor (old 'plugin') .
Example can be found [here](https://github.com/iris-contrib/adaptors/tree/master/oauth/_example).
### CORS Middleware and the new Wrapper
Lets speak about history of cors middleware, almost all the issues users reported to the iris-contrib/middleware repository
were relative to the CORS middleware, some users done it work some others don't... it was strange. Keep note that this was one of the two middleware that I didn't
wrote by myself, it was a PR by a member who wrote that middleware and after didn't answer on users' issues.
Forget about it I removed it entirely and replaced with the `rs/cors`: we now use the https://github.com/rs/cors in two forms:
First, you can use the original middlare that you can install by `go get -u github.com/rs/cors`
(You had already see its example on the net/http handlers and iris.ToHandler section)
Can be registered globally or per-route but the `MethodsAllowed option doesn't works`.
Example:
```go
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
"github.com/rs/cors"
)
func main(){
app := iris.New()
app.Adapt(httprouter.New()) // see below for that
corsMiddleware := iris.ToHandler(cors.Default().ServeHTTP)
app.Post("/user", corsMiddleware, func(ctx *iris.Context){
// ....
})
app.Listen(":8080")
}
```
Secondly, probably the one which you will choose to use, is the `cors` Router Wrapper Adaptor.
It's already installed when you install iris because it's located at `kataras/iris/adaptors/cors`.
This will wrap the entirely router so the whole of your app will be passing by the rules you setted up on its `cors.Options`.
Again, it's functionality comes from the well-tested `rs/cors`, all known Options are working as expected.
Example:
```go
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/cors"
)
func main(){
app := iris.New()
app.Adapt(httprouter.New()) // see below for that
app.Adapt(cors.New(cors.Options{})) // or cors.Default()
app.Post("/user", func(ctx *iris.Context){
// ....
})
app.Listen(":8080")
}
```
### FAQ
You know that you can always share your opinion and ask anything iris-relative with the rest of us, [here](https://kataras.rocket.chat/channel/iris).