mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:26:26 +01:00
Happy New Year to everybody! Version 10 Published. Read HISTORY.md
Thank you all for your amazing feedback and requests! Former-commit-id: 1515aa37d73df109235813c94b3dc3cc9fd7fa48
This commit is contained in:
commit
4e9e1cba39
|
@ -3,11 +3,7 @@ os:
|
|||
- linux
|
||||
- osx
|
||||
go:
|
||||
# - go1.8 works of course but
|
||||
# we must encourage users to update to the latest go version,
|
||||
# so examples are running on go 1.9 mode.
|
||||
- go1.9
|
||||
# - tip
|
||||
go_import_path: github.com/kataras/iris
|
||||
install:
|
||||
- go get ./... # for iris-contrib/httpexpect, kataras/golog
|
||||
|
|
|
@ -47,6 +47,7 @@ Instructions can be found at: https://github.com/kataras/iris/issues/796
|
|||
|
||||
Write an article about Iris in https://medium.com , https://dev.to or if you're being a hackathon at https://hackernoon.com, some examples:
|
||||
|
||||
* [A Hasura starter project with a ready to deploy Golang hello-world web app with IRIS](bit.ly/2lmKaAZ)
|
||||
* [Top 6 web frameworks for Go as of 2017](https://blog.usejournal.com/top-6-web-frameworks-for-go-as-of-2017-23270e059c4b)
|
||||
* [Iris Go Framework + MongoDB](https://medium.com/go-language/iris-go-framework-mongodb-552e349eab9c)
|
||||
* [How to build a file upload form using DropzoneJS and Go](https://hackernoon.com/how-to-build-a-file-upload-form-using-dropzonejs-and-go-8fb9f258a991)
|
||||
|
|
19
FAQ.md
19
FAQ.md
|
@ -60,17 +60,30 @@ Iris may have reached version 8, but we're not stopping there. We have many feat
|
|||
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
|
||||
Go to our facebook page, like it and receive notifications about new job offers, we already have couple of them stay at the top of the page: https://www.facebook.com/iris.framework
|
||||
|
||||
<!--
|
||||
## Can Iris be used in production after Dubai purchase?
|
||||
|
||||
Yes, now more than ever.
|
||||
|
||||
https://github.com/kataras/iris/issues/711
|
||||
|
||||
-------
|
||||
|
||||
UPDATE which I could mention by the beginning of the Decemember of 2017:
|
||||
|
||||
Nothing keeps for ever, and we should move on to greater things.
|
||||
|
||||
As you probably know, I was hired to develop an inside Iris version for a Dubai-based startup company's specific requirements in the same time I was developing the open-source Iris repository with your help this time as well!
|
||||
|
||||
As our first deal was to end this agreement via last-time negotiatations by the end of the current year (2017), the
|
||||
agreement ended unofficially at 22 Novemember of 2017 (officially some weeks later, paper work), and after a week I came back to Greece as you may understood from the regularly commits and improvements to the public repository that I pushed.
|
||||
-->
|
||||
|
||||
## Do we have a community Chat?
|
||||
|
||||
Yes, https://kataras.rocket.chat/channel/iris.
|
||||
Yes, https://chat.iris-go.com
|
||||
|
||||
https://github.com/kataras/iris/issues/646
|
||||
|
||||
|
@ -78,4 +91,4 @@ https://github.com/kataras/iris/issues/646
|
|||
|
||||
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 via [PayPal](https://www.paypal.me/kataras)!
|
||||
Help this project to continue deliver awesome and unique features with the highest possible code quality as possible by donating any amount via [PayPal](https://www.paypal.me/kataras). Your name will be published [here](https://iris-go.com/donate) after your approval via e-mail.
|
6
Gopkg.lock
generated
6
Gopkg.lock
generated
|
@ -121,12 +121,6 @@
|
|||
packages = ["."]
|
||||
revision = "20e139a6d2469769ae88e0a3579ba5df71839ca7"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/skratchdot/open-golang"
|
||||
packages = ["."]
|
||||
revision = "75fb7ed4208cf72d323d7d02fd1a5964a7a9073c"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/klauspost/compress"
|
||||
packages = ["flate","gzip"]
|
||||
|
|
|
@ -46,10 +46,6 @@
|
|||
branch = "master"
|
||||
name = "github.com/kataras/survey"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/skratchdot/open-golang"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/klauspost/compress"
|
||||
version = "1.2.1"
|
||||
|
|
1869
HISTORY.md
1869
HISTORY.md
File diff suppressed because it is too large
Load Diff
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2017 The Iris Authors. All rights reserved.
|
||||
Copyright (c) 2017-2018 The Iris Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
|
1079
README_ZH.md
1079
README_ZH.md
File diff suppressed because it is too large
Load Diff
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
8.5.8:https://github.com/kataras/iris/blob/master/HISTORY.md#th-09-november-2017--v858
|
||||
10.0.0:https://github.com/kataras/iris/blob/master/HISTORY.md#mo-01-jenuary-2018--v1000
|
|
@ -2,10 +2,16 @@ package controllers
|
|||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
type AboutController struct{ mvc.Controller }
|
||||
type AboutController struct{}
|
||||
|
||||
func (c *AboutController) Get() {
|
||||
c.Data["Title"] = "About"
|
||||
c.Data["Message"] = "Your application description page."
|
||||
c.Tmpl = "about.html"
|
||||
var aboutView = mvc.View{
|
||||
Name: "about.html",
|
||||
Data: map[string]interface{}{
|
||||
"Title": "About",
|
||||
"Message": "Your application description page..",
|
||||
},
|
||||
}
|
||||
|
||||
func (c *AboutController) Get() mvc.View {
|
||||
return aboutView
|
||||
}
|
||||
|
|
|
@ -2,10 +2,16 @@ package controllers
|
|||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
type ContactController struct{ mvc.Controller }
|
||||
type ContactController struct{}
|
||||
|
||||
func (c *ContactController) Get() {
|
||||
c.Data["Title"] = "Contact"
|
||||
c.Data["Message"] = "Your contact page."
|
||||
c.Tmpl = "contact.html"
|
||||
var contactView = mvc.View{
|
||||
Name: "contact.html",
|
||||
Data: map[string]interface{}{
|
||||
"Title": "Contact",
|
||||
"Message": "Your contact page.",
|
||||
},
|
||||
}
|
||||
|
||||
func (c *ContactController) Get() mvc.View {
|
||||
return contactView
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package controllers
|
|||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
type HomeController struct{ mvc.C }
|
||||
type HomeController struct{}
|
||||
|
||||
func (c *HomeController) Get() mvc.Result {
|
||||
return mvc.View{Name: "index.html"}
|
||||
|
|
|
@ -2,9 +2,13 @@ package controllers
|
|||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
type IndexController struct{ mvc.Controller }
|
||||
type IndexController struct{}
|
||||
|
||||
func (c *IndexController) Get() {
|
||||
c.Data["Title"] = "Home Page"
|
||||
c.Tmpl = "index.html"
|
||||
var indexView = mvc.View{
|
||||
Name: "index.html",
|
||||
Data: map[string]interface{}{"Title": "Home Page"},
|
||||
}
|
||||
|
||||
func (c *IndexController) Get() mvc.View {
|
||||
return indexView
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package controllers
|
||||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
type IndexControllerStatic struct{ mvc.C }
|
||||
|
||||
var index = mvc.View{
|
||||
Name: "index.html",
|
||||
Data: map[string]interface{}{
|
||||
"Title": "Home Page",
|
||||
},
|
||||
}
|
||||
|
||||
func (c *IndexControllerStatic) Get() mvc.View {
|
||||
return index
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -17,21 +18,13 @@ const (
|
|||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Configure(configure)
|
||||
|
||||
// app.Controller("/", new(controllers.IndexController))
|
||||
// app.Controller("/about", new(controllers.AboutController))
|
||||
// app.Controller("/contact", new(controllers.ContactController))
|
||||
|
||||
app.Controller("/", new(controllers.HomeController))
|
||||
|
||||
app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker)
|
||||
}
|
||||
|
||||
func configure(app *iris.Application) {
|
||||
app.RegisterView(iris.HTML("./views", ".html").Layout("shared/layout.html"))
|
||||
app.StaticWeb("/public", publicDir)
|
||||
app.OnAnyErrorCode(onError)
|
||||
|
||||
mvc.New(app).Handle(new(controllers.HomeController))
|
||||
|
||||
app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker)
|
||||
}
|
||||
|
||||
type err struct {
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
package controllers
|
||||
|
||||
import "github.com/kataras/iris/mvc"
|
||||
|
||||
// ValuesController is the equivalent
|
||||
// `ValuesController` of the .net core 2.0 mvc application.
|
||||
type ValuesController struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
/* on windows tests(older) the Get was:
|
||||
func (vc *ValuesController) Get() {
|
||||
// id,_ := vc.Params.GetInt("id")
|
||||
// vc.Ctx.WriteString("value")
|
||||
}
|
||||
but as Iris is always going better, now supports return values as well*/
|
||||
type ValuesController struct{}
|
||||
|
||||
// Get handles "GET" requests to "api/values/{id}".
|
||||
func (vc *ValuesController) Get() string {
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
package main
|
||||
|
||||
/// TODO: remove this on the "master" branch, or even replace it
|
||||
// with the "iris-mvc" (the new implementatioin is even faster, close to handlers version,
|
||||
// with bindings or without).
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/_benchmarks/iris-mvc/controllers"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Controller("/api/values/{id}", new(controllers.ValuesController))
|
||||
mvc.New(app.Party("/api/values/{id}")).
|
||||
Handle(new(controllers.ValuesController))
|
||||
|
||||
// 24 August 2017: Iris has a built'n version updater but we don't need it
|
||||
// when benchmarking...
|
||||
app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker)
|
||||
}
|
||||
|
||||
// +2MB/s faster than the previous implementation, 0.4MB/s difference from the raw handlers.
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
import "github.com/kataras/iris"
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Get("/api/values/{id}", func(ctx context.Context) {
|
||||
app.Get("/api/values/{id}", func(ctx iris.Context) {
|
||||
ctx.WriteString("value")
|
||||
})
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ It doesn't always contain the "best ways" but it does cover each important featu
|
|||
- [Hello world!](hello-world/main.go)
|
||||
- [Glimpse](overview/main.go)
|
||||
- [Tutorial: Online Visitors](tutorial/online-visitors/main.go)
|
||||
- [Tutorial: Vue.js Todo MVC](tutorial/vuejs-todo-mvc)
|
||||
- [Tutorial: URL Shortener using BoltDB](https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7)
|
||||
- [Tutorial: How to turn your Android Device into a fully featured Web Server (**MUST**)](https://twitter.com/ThePracticalDev/status/892022594031017988)
|
||||
- [POC: Convert the medium-sized project "Parrot" from native to Iris](https://github.com/iris-contrib/parrot)
|
||||
|
@ -111,6 +112,11 @@ Navigate through examples for a better understanding.
|
|||
* [per-route](routing/writing-a-middleware/per-route/main.go)
|
||||
* [globally](routing/writing-a-middleware/globally/main.go)
|
||||
|
||||
### hero
|
||||
|
||||
- [Basic](hero/basic/main.go)
|
||||
- [Overview](hero/overview)
|
||||
|
||||
### MVC
|
||||
|
||||

|
||||
|
@ -125,43 +131,82 @@ with the fastest possible execution.
|
|||
|
||||
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.
|
||||
you can define more than one method function to serve in the same Controller.
|
||||
|
||||
Serve custom controller's struct's methods as handlers with custom paths(even with regex parametermized path) via the `BeforeActivation` custom event callback, per-controller. Example:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
mvc.Configure(app.Party("/root"), myMVC)
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
func myMVC(app *mvc.Application) {
|
||||
// app.Register(...)
|
||||
// app.Router.Use/UseGlobal/Done(...)
|
||||
app.Handle(new(MyController))
|
||||
}
|
||||
|
||||
type MyController struct {}
|
||||
|
||||
func (m *MyController) BeforeActivation(b mvc.BeforeActivation) {
|
||||
// b.Dependencies().Add/Remove
|
||||
// b.Router().Use/UseGlobal/Done // and any standard API call you already know
|
||||
|
||||
// 1-> Method
|
||||
// 2-> Path
|
||||
// 3-> The controller's function name to be parsed as handler
|
||||
// 4-> Any handlers that should run before the MyCustomHandler
|
||||
b.Handle("GET", "/something/{id:long}", "MyCustomHandler", anyMiddleware...)
|
||||
}
|
||||
|
||||
// GET: http://localhost:8080/root
|
||||
func (m *MyController) Get() string { return "Hey" }
|
||||
|
||||
// GET: http://localhost:8080/root/something/{id:long}
|
||||
func (m *MyController) MyCustomHandler(id int64) string { return "MyCustomHandler says Hey" }
|
||||
```
|
||||
|
||||
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)`.
|
||||
by defining services to the Dependencies or have a `Singleton` controller scope.
|
||||
|
||||
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"`.
|
||||
Share the dependencies between controllers or register them on a parent MVC Application, and ability
|
||||
to modify dependencies per-controller on the `BeforeActivation` optional event callback inside a Controller,
|
||||
i.e `func(c *MyController) BeforeActivation(b mvc.BeforeActivation) { b.Dependencies().Add/Remove(...) }`.
|
||||
|
||||
Access to the request path and its parameters via the `Path and Params` fields.
|
||||
Access to the `Context` as a controller's field(no manual binding is neede) i.e `Ctx iris.Context` or via a method's input argument, i.e `func(ctx iris.Context, otherArguments...)`.
|
||||
|
||||
Access to the template file that should be rendered via the `Tmpl` field.
|
||||
Models inside your Controller struct (set-ed at the Method function and rendered by the View).
|
||||
You can return models from a controller's method or set a field in the request lifecycle
|
||||
and return that field to another method, in the same request lifecycle.
|
||||
|
||||
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 `iris.Context/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.
|
||||
Flow as you used to, mvc application has its own `Router` which is a type of `iris/router.Party`, the standard iris api.
|
||||
`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).
|
||||
Inheritance, recursively, see for example our `mvc.SessionController`, it has the `Session *sessions.Session` and `Manager *sessions.Sessions` as embedded fields
|
||||
which are filled by its `BeginRequest`, [here](https://github.com/kataras/iris/blob/master/mvc/session_controller.go).
|
||||
This is just an example, you could use the `sessions.Session` which returned from the manager's `Start` as a dynamic dependency to the MVC Application, i.e
|
||||
`mvcApp.Register(sessions.New(sessions.Config{Cookie: "iris_session_id"}).Start)`.
|
||||
|
||||
Register one or more relative paths and able to get path parameters, i.e
|
||||
Access to the dynamic path parameters via the controller's methods' input arguments, no binding is needed.
|
||||
When you use the Iris' default syntax to parse handlers from a controller, you need to suffix the methods
|
||||
with the `By` word, uppercase is a new sub path. Example:
|
||||
|
||||
If `app.Controller("/user", new(user.Controller))`
|
||||
If `mvc.New(app.Party("/user")).Handle(new(user.Controller))`
|
||||
|
||||
- `func(*Controller) Get()` - `GET:/user` , as usual.
|
||||
- `func(*Controller) Post()` - `POST:/user`, as usual.
|
||||
- `func(*Controller) Get()` - `GET:/user`.
|
||||
- `func(*Controller) Post()` - `POST:/user`.
|
||||
- `func(*Controller) GetLogin()` - `GET:/user/login`
|
||||
- `func(*Controller) PostLogin()` - `POST:/user/login`
|
||||
- `func(*Controller) GetProfileFollowers()` - `GET:/user/profile/followers`
|
||||
|
@ -169,11 +214,11 @@ If `app.Controller("/user", new(user.Controller))`
|
|||
- `func(*Controller) GetBy(id int64)` - `GET:/user/{param:long}`
|
||||
- `func(*Controller) PostBy(id int64)` - `POST:/user/{param:long}`
|
||||
|
||||
If `app.Controller("/profile", new(profile.Controller))`
|
||||
If `mvc.New(app.Party("/profile")).Handle(new(profile.Controller))`
|
||||
|
||||
- `func(*Controller) GetBy(username string)` - `GET:/profile/{param:string}`
|
||||
|
||||
If `app.Controller("/assets", new(file.Controller))`
|
||||
If `mvc.New(app.Party("/assets")).Handle(new(file.Controller))`
|
||||
|
||||
- `func(*Controller) GetByWildard(path string)` - `GET:/assets/{param:path}`
|
||||
|
||||
|
@ -188,21 +233,19 @@ func(c *ExampleController) Get() string |
|
|||
int |
|
||||
(int, string) |
|
||||
(string, error) |
|
||||
bool |
|
||||
(any, bool) |
|
||||
(bool, any) |
|
||||
error |
|
||||
(int, error) |
|
||||
(any, bool) |
|
||||
(customStruct, error) |
|
||||
customStruct |
|
||||
(customStruct, int) |
|
||||
(customStruct, string) |
|
||||
mvc.Result or (mvc.Result, error) and so on...
|
||||
mvc.Result or (mvc.Result, error)
|
||||
```
|
||||
|
||||
where [mvc.Result](https://github.com/kataras/iris/blob/master/mvc/method_result.go) is an interface which contains only that function: `Dispatch(ctx iris.Context)`.
|
||||
where [mvc.Result](https://github.com/kataras/iris/blob/master/mvc/func_result.go) is an interface which contains only that function: `Dispatch(ctx iris.Context)`.
|
||||
|
||||
**Using Iris MVC for code reuse**
|
||||
## 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.
|
||||
|
||||
|
@ -212,19 +255,11 @@ Follow the examples below,
|
|||
|
||||
- [Hello world](mvc/hello-world/main.go) **UPDATED**
|
||||
- [Session Controller](mvc/session-controller/main.go) **UPDATED**
|
||||
- [Overview - Plus Repository and Service layers](mvc/overview) **NEW**
|
||||
- [Login showcase - Plus Repository and Service layers](mvc/login) **NEW**
|
||||
|
||||
<!--
|
||||
Why updated?
|
||||
Old method works, as promised no breaking changes.
|
||||
But mvc.C as controller marker and mvc.Result on method functions return value
|
||||
is more lightweight and faster than `mvc.Controller` because `mvc.Controller` initializes
|
||||
some fields like `Data, Path`... and Data is a map even if not used, at the opossite hand
|
||||
`mvc.C` just initializes the context `Ctx` field, the dev has all the `mvc.Controller`'s features
|
||||
by the `mvc.Result` built'n types like `mvc.Response` and `mvc.View` PLUS she/he can
|
||||
convert any custom type into a response dispatcher by implementing the `mvc.Result` interface.
|
||||
-->
|
||||
- [Overview - Plus Repository and Service layers](mvc/overview) **UPDATED**
|
||||
- [Login showcase - Plus Repository and Service layers](mvc/login) **UPDATED**
|
||||
- [Singleton](mvc/singleton) **NEW**
|
||||
- [Websocket Controller](mvc/websocket) **NEW**
|
||||
- [Vue.js Todo MVC](tutorial/vuejs-todo-mvc) **NEW**
|
||||
|
||||
### Subdomains
|
||||
|
||||
|
@ -259,7 +294,7 @@ convert any custom type into a response dispatcher by implementing the `mvc.Resu
|
|||
- [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go)
|
||||
|
||||
|
||||
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [hero](https://github.com/shiyanhui/hero/hero) files too, simply by using the `context#ResponseWriter`, take a look at the [http_responsewriter/quicktemplate](http_responsewriter/quicktemplate) and [http_responsewriter/hero] examples.
|
||||
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [hero templates](https://github.com/shiyanhui/hero/hero) files too, simply by using the `context#ResponseWriter`, take a look at the [http_responsewriter/quicktemplate](http_responsewriter/quicktemplate) and [http_responsewriter/herotemplate](http_responsewriter/herotemplate) examples.
|
||||
|
||||
### Authentication
|
||||
|
||||
|
@ -283,14 +318,15 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
|
|||
|
||||
- [Bind JSON](http_request/read-json/main.go)
|
||||
- [Bind Form](http_request/read-form/main.go)
|
||||
- [Upload/Read Files](http_request/upload-files/main.go)
|
||||
- [Upload/Read File](http_request/upload-file/main.go)
|
||||
- [Upload multiple files with an easy way](http_request/upload-files/main.go)
|
||||
|
||||
> The `context.Request()` returns the same *http.Request you already know, these examples show some places where the Context uses this object. Besides that you can use it as you did before iris.
|
||||
|
||||
### How to Write to `context.ResponseWriter() http.ResponseWriter`
|
||||
|
||||
- [Write `valyala/quicktemplate` templates](http_responsewriter/quicktemplate)
|
||||
- [Write `shiyanhui/hero` templates](http_responsewriter/hero)
|
||||
- [Write `shiyanhui/hero` templates](http_responsewriter/herotemplate)
|
||||
- [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go)
|
||||
- [Write Gzip](http_responsewriter/write-gzip/main.go)
|
||||
- [Stream Writer](http_responsewriter/stream-writer/main.go)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Logger().SetLevel("debug")
|
||||
// Optionally, add two built'n handlers
|
||||
// that can recover from any http-relative panics
|
||||
// and log the requests to the terminal.
|
||||
|
|
13
_examples/hero/basic/README.md
Normal file
13
_examples/hero/basic/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# hero: basic
|
||||
|
||||
## 1. Path Parameters - Built'n Dependencies
|
||||
|
||||

|
||||
|
||||
## 2. Services - Static Dependencies
|
||||
|
||||

|
||||
|
||||
## 3. Per-Request - Dynamic Dependencies
|
||||
|
||||

|
67
_examples/hero/basic/main.go
Normal file
67
_examples/hero/basic/main.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/hero"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app := iris.New()
|
||||
|
||||
// 1
|
||||
helloHandler := hero.Handler(hello)
|
||||
app.Get("/{to:string}", helloHandler)
|
||||
|
||||
// 2
|
||||
hero.Register(&myTestService{
|
||||
prefix: "Service: Hello",
|
||||
})
|
||||
|
||||
helloServiceHandler := hero.Handler(helloService)
|
||||
app.Get("/service/{to:string}", helloServiceHandler)
|
||||
|
||||
// 3
|
||||
hero.Register(func(ctx iris.Context) (form LoginForm) {
|
||||
// it binds the "form" with a
|
||||
// x-www-form-urlencoded form data and returns it.
|
||||
ctx.ReadForm(&form)
|
||||
return
|
||||
})
|
||||
|
||||
loginHandler := hero.Handler(login)
|
||||
app.Post("/login", loginHandler)
|
||||
|
||||
// http://localhost:8080/your_name
|
||||
// http://localhost:8080/service/your_name
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
func hello(to string) string {
|
||||
return "Hello " + to
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
SayHello(to string) string
|
||||
}
|
||||
|
||||
type myTestService struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (s *myTestService) SayHello(to string) string {
|
||||
return s.prefix + " " + to
|
||||
}
|
||||
|
||||
func helloService(to string, service Service) string {
|
||||
return service.SayHello(to)
|
||||
}
|
||||
|
||||
type LoginForm struct {
|
||||
Username string `form:"username"`
|
||||
Password string `form:"password"`
|
||||
}
|
||||
|
||||
func login(form LoginForm) string {
|
||||
return "Hello " + form.Username
|
||||
}
|
18
_examples/hero/overview/datamodels/movie.go
Normal file
18
_examples/hero/overview/datamodels/movie.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// file: datamodels/movie.go
|
||||
|
||||
package datamodels
|
||||
|
||||
// Movie is our sample data structure.
|
||||
// Keep note that the tags for public-use (for our web app)
|
||||
// should be kept in other file like "web/viewmodels/movie.go"
|
||||
// which could wrap by embedding the datamodels.Movie or
|
||||
// declare new fields instead butwe will use this datamodel
|
||||
// as the only one Movie model in our application,
|
||||
// for the shake of simplicty.
|
||||
type Movie struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
44
_examples/hero/overview/datasource/movies.go
Normal file
44
_examples/hero/overview/datasource/movies.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
// file: datasource/movies.go
|
||||
|
||||
package datasource
|
||||
|
||||
import "github.com/kataras/iris/_examples/hero/overview/datamodels"
|
||||
|
||||
// Movies is our imaginary data source.
|
||||
var Movies = map[int64]datamodels.Movie{
|
||||
1: {
|
||||
ID: 1,
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
2: {
|
||||
ID: 2,
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
3: {
|
||||
ID: 3,
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
4: {
|
||||
ID: 4,
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
5: {
|
||||
ID: 5,
|
||||
Name: "North by Northwest",
|
||||
Year: 1959,
|
||||
Genre: "Thriller",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/5.jpg",
|
||||
},
|
||||
}
|
60
_examples/hero/overview/main.go
Normal file
60
_examples/hero/overview/main.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
// file: main.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/hero/overview/datasource"
|
||||
"github.com/kataras/iris/_examples/hero/overview/repositories"
|
||||
"github.com/kataras/iris/_examples/hero/overview/services"
|
||||
"github.com/kataras/iris/_examples/hero/overview/web/middleware"
|
||||
"github.com/kataras/iris/_examples/hero/overview/web/routes"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/hero"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Logger().SetLevel("debug")
|
||||
|
||||
// Load the template files.
|
||||
app.RegisterView(iris.HTML("./web/views", ".html"))
|
||||
|
||||
// Create our movie repository with some (memory) data from the datasource.
|
||||
repo := repositories.NewMovieRepository(datasource.Movies)
|
||||
// Create our movie service, we will bind it to the movie app's dependencies.
|
||||
movieService := services.NewMovieService(repo)
|
||||
hero.Register(movieService)
|
||||
|
||||
// Serve our routes with hero handlers.
|
||||
app.PartyFunc("/hello", func(r iris.Party) {
|
||||
r.Get("/", hero.Handler(routes.Hello))
|
||||
r.Get("/{name}", hero.Handler(routes.HelloName))
|
||||
})
|
||||
|
||||
app.PartyFunc("/movies", func(r iris.Party) {
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /movies based requests.
|
||||
r.Use(middleware.BasicAuth)
|
||||
|
||||
r.Get("/", hero.Handler(routes.Movies))
|
||||
r.Get("/{id:long}", hero.Handler(routes.MovieByID))
|
||||
r.Put("/{id:long}", hero.Handler(routes.UpdateMovieByID))
|
||||
r.Delete("/{id:long}", hero.Handler(routes.DeleteMovieByID))
|
||||
})
|
||||
|
||||
// http://localhost:8080/hello
|
||||
// http://localhost:8080/hello/iris
|
||||
// http://localhost:8080/movies
|
||||
// http://localhost:8080/movies/1
|
||||
app.Run(
|
||||
// Start the web server at localhost:8080
|
||||
iris.Addr("localhost:8080"),
|
||||
// disables updates:
|
||||
iris.WithoutVersionChecker,
|
||||
// skip err server closed when CTRL/CMD+C pressed:
|
||||
iris.WithoutServerError(iris.ErrServerClosed),
|
||||
// enables faster json serialization and more:
|
||||
iris.WithOptimizations,
|
||||
)
|
||||
}
|
176
_examples/hero/overview/repositories/movie_repository.go
Normal file
176
_examples/hero/overview/repositories/movie_repository.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
// file: repositories/movie_repository.go
|
||||
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/_examples/hero/overview/datamodels"
|
||||
)
|
||||
|
||||
// Query represents the visitor and action queries.
|
||||
type Query func(datamodels.Movie) bool
|
||||
|
||||
// MovieRepository handles the basic operations of a movie entity/model.
|
||||
// It's an interface in order to be testable, i.e a memory movie repository or
|
||||
// a connected to an sql database.
|
||||
type MovieRepository interface {
|
||||
Exec(query Query, action Query, limit int, mode int) (ok bool)
|
||||
|
||||
Select(query Query) (movie datamodels.Movie, found bool)
|
||||
SelectMany(query Query, limit int) (results []datamodels.Movie)
|
||||
|
||||
InsertOrUpdate(movie datamodels.Movie) (updatedMovie datamodels.Movie, err error)
|
||||
Delete(query Query, limit int) (deleted bool)
|
||||
}
|
||||
|
||||
// NewMovieRepository returns a new movie memory-based repository,
|
||||
// the one and only repository type in our example.
|
||||
func NewMovieRepository(source map[int64]datamodels.Movie) MovieRepository {
|
||||
return &movieMemoryRepository{source: source}
|
||||
}
|
||||
|
||||
// movieMemoryRepository is a "MovieRepository"
|
||||
// which manages the movies using the memory data source (map).
|
||||
type movieMemoryRepository struct {
|
||||
source map[int64]datamodels.Movie
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
const (
|
||||
// ReadOnlyMode will RLock(read) the data .
|
||||
ReadOnlyMode = iota
|
||||
// ReadWriteMode will Lock(read/write) the data.
|
||||
ReadWriteMode
|
||||
)
|
||||
|
||||
func (r *movieMemoryRepository) Exec(query Query, action Query, actionLimit int, mode int) (ok bool) {
|
||||
loops := 0
|
||||
|
||||
if mode == ReadOnlyMode {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
} else {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
}
|
||||
|
||||
for _, movie := range r.source {
|
||||
ok = query(movie)
|
||||
if ok {
|
||||
if action(movie) {
|
||||
loops++
|
||||
if actionLimit >= loops {
|
||||
break // break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Select receives a query function
|
||||
// which is fired for every single movie model inside
|
||||
// our imaginary data source.
|
||||
// When that function returns true then it stops the iteration.
|
||||
//
|
||||
// It returns the query's return last known "found" value
|
||||
// and the last known movie model
|
||||
// to help callers to reduce the LOC.
|
||||
//
|
||||
// It's actually a simple but very clever prototype function
|
||||
// I'm using everywhere since I firstly think of it,
|
||||
// hope you'll find it very useful as well.
|
||||
func (r *movieMemoryRepository) Select(query Query) (movie datamodels.Movie, found bool) {
|
||||
found = r.Exec(query, func(m datamodels.Movie) bool {
|
||||
movie = m
|
||||
return true
|
||||
}, 1, ReadOnlyMode)
|
||||
|
||||
// set an empty datamodels.Movie if not found at all.
|
||||
if !found {
|
||||
movie = datamodels.Movie{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SelectMany same as Select but returns one or more datamodels.Movie as a slice.
|
||||
// If limit <=0 then it returns everything.
|
||||
func (r *movieMemoryRepository) SelectMany(query Query, limit int) (results []datamodels.Movie) {
|
||||
r.Exec(query, func(m datamodels.Movie) bool {
|
||||
results = append(results, m)
|
||||
return true
|
||||
}, limit, ReadOnlyMode)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// InsertOrUpdate adds or updates a movie to the (memory) storage.
|
||||
//
|
||||
// Returns the new movie and an error if any.
|
||||
func (r *movieMemoryRepository) InsertOrUpdate(movie datamodels.Movie) (datamodels.Movie, error) {
|
||||
id := movie.ID
|
||||
|
||||
if id == 0 { // Create new action
|
||||
var lastID int64
|
||||
// find the biggest ID in order to not have duplications
|
||||
// in productions apps you can use a third-party
|
||||
// library to generate a UUID as string.
|
||||
r.mu.RLock()
|
||||
for _, item := range r.source {
|
||||
if item.ID > lastID {
|
||||
lastID = item.ID
|
||||
}
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
|
||||
id = lastID + 1
|
||||
movie.ID = id
|
||||
|
||||
// map-specific thing
|
||||
r.mu.Lock()
|
||||
r.source[id] = movie
|
||||
r.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
// Update action based on the movie.ID,
|
||||
// here we will allow updating the poster and genre if not empty.
|
||||
// Alternatively we could do pure replace instead:
|
||||
// r.source[id] = movie
|
||||
// and comment the code below;
|
||||
current, exists := r.Select(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
|
||||
if !exists { // ID is not a real one, return an error.
|
||||
return datamodels.Movie{}, errors.New("failed to update a nonexistent movie")
|
||||
}
|
||||
|
||||
// or comment these and r.source[id] = m for pure replace
|
||||
if movie.Poster != "" {
|
||||
current.Poster = movie.Poster
|
||||
}
|
||||
|
||||
if movie.Genre != "" {
|
||||
current.Genre = movie.Genre
|
||||
}
|
||||
|
||||
// map-specific thing
|
||||
r.mu.Lock()
|
||||
r.source[id] = current
|
||||
r.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
func (r *movieMemoryRepository) Delete(query Query, limit int) bool {
|
||||
return r.Exec(query, func(m datamodels.Movie) bool {
|
||||
delete(r.source, m.ID)
|
||||
return true
|
||||
}, limit, ReadWriteMode)
|
||||
}
|
65
_examples/hero/overview/services/movie_service.go
Normal file
65
_examples/hero/overview/services/movie_service.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
// file: services/movie_service.go
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/hero/overview/datamodels"
|
||||
"github.com/kataras/iris/_examples/hero/overview/repositories"
|
||||
)
|
||||
|
||||
// MovieService handles some of the CRUID operations of the movie datamodel.
|
||||
// It depends on a movie repository for its actions.
|
||||
// It's here to decouple the data source from the higher level compoments.
|
||||
// As a result a different repository type can be used with the same logic without any aditional changes.
|
||||
// It's an interface and it's used as interface everywhere
|
||||
// because we may need to change or try an experimental different domain logic at the future.
|
||||
type MovieService interface {
|
||||
GetAll() []datamodels.Movie
|
||||
GetByID(id int64) (datamodels.Movie, bool)
|
||||
DeleteByID(id int64) bool
|
||||
UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error)
|
||||
}
|
||||
|
||||
// NewMovieService returns the default movie service.
|
||||
func NewMovieService(repo repositories.MovieRepository) MovieService {
|
||||
return &movieService{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
type movieService struct {
|
||||
repo repositories.MovieRepository
|
||||
}
|
||||
|
||||
// GetAll returns all movies.
|
||||
func (s *movieService) GetAll() []datamodels.Movie {
|
||||
return s.repo.SelectMany(func(_ datamodels.Movie) bool {
|
||||
return true
|
||||
}, -1)
|
||||
}
|
||||
|
||||
// GetByID returns a movie based on its id.
|
||||
func (s *movieService) GetByID(id int64) (datamodels.Movie, bool) {
|
||||
return s.repo.Select(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePosterAndGenreByID updates a movie's poster and genre.
|
||||
func (s *movieService) UpdatePosterAndGenreByID(id int64, poster string, genre string) (datamodels.Movie, error) {
|
||||
// update the movie and return it.
|
||||
return s.repo.InsertOrUpdate(datamodels.Movie{
|
||||
ID: id,
|
||||
Poster: poster,
|
||||
Genre: genre,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteByID deletes a movie by its id.
|
||||
//
|
||||
// Returns true if deleted otherwise false.
|
||||
func (s *movieService) DeleteByID(id int64) bool {
|
||||
return s.repo.Delete(func(m datamodels.Movie) bool {
|
||||
return m.ID == id
|
||||
}, 1)
|
||||
}
|
12
_examples/hero/overview/web/middleware/basicauth.go
Normal file
12
_examples/hero/overview/web/middleware/basicauth.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
// file: web/middleware/basicauth.go
|
||||
|
||||
package middleware
|
||||
|
||||
import "github.com/kataras/iris/middleware/basicauth"
|
||||
|
||||
// BasicAuth middleware sample.
|
||||
var BasicAuth = basicauth.New(basicauth.Config{
|
||||
Users: map[string]string{
|
||||
"admin": "password",
|
||||
},
|
||||
})
|
50
_examples/hero/overview/web/routes/hello.go
Normal file
50
_examples/hero/overview/web/routes/hello.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
// file: web/routes/hello.go
|
||||
|
||||
package routes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/hero"
|
||||
)
|
||||
|
||||
var helloView = hero.View{
|
||||
Name: "hello/index.html",
|
||||
Data: map[string]interface{}{
|
||||
"Title": "Hello Page",
|
||||
"MyMessage": "Welcome to my awesome website",
|
||||
},
|
||||
}
|
||||
|
||||
// Hello will return a predefined view with bind data.
|
||||
//
|
||||
// `hero.Result` is just an interface with a `Dispatch` function.
|
||||
// `hero.Response` and `hero.View` are the built'n result type dispatchers
|
||||
// you can even create custom response dispatchers by
|
||||
// implementing the `github.com/kataras/iris/hero#Result` interface.
|
||||
func Hello() hero.Result {
|
||||
return helloView
|
||||
}
|
||||
|
||||
// you can define a standard error in order to re-use anywhere in your app.
|
||||
var errBadName = errors.New("bad name")
|
||||
|
||||
// you can just return it as error or even better
|
||||
// wrap this error with an hero.Response to make it an hero.Result compatible type.
|
||||
var badName = hero.Response{Err: errBadName, Code: 400}
|
||||
|
||||
// HelloName returns a "Hello {name}" response.
|
||||
// Demos:
|
||||
// curl -i http://localhost:8080/hello/iris
|
||||
// curl -i http://localhost:8080/hello/anything
|
||||
func HelloName(name string) hero.Result {
|
||||
if name != "iris" {
|
||||
return badName
|
||||
}
|
||||
|
||||
// return hero.Response{Text: "Hello " + name} OR:
|
||||
return hero.View{
|
||||
Name: "hello/name.html",
|
||||
Data: name,
|
||||
}
|
||||
}
|
59
_examples/hero/overview/web/routes/movies.go
Normal file
59
_examples/hero/overview/web/routes/movies.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
// file: web/routes/movie.go
|
||||
|
||||
package routes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/_examples/hero/overview/datamodels"
|
||||
"github.com/kataras/iris/_examples/hero/overview/services"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
// Movies returns list of the movies.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
func Movies(service services.MovieService) (results []datamodels.Movie) {
|
||||
return service.GetAll()
|
||||
}
|
||||
|
||||
// MovieByID returns a movie.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func MovieByID(service services.MovieService, id int64) (movie datamodels.Movie, found bool) {
|
||||
return service.GetByID(id) // it will throw 404 if not found.
|
||||
}
|
||||
|
||||
// UpdateMovieByID updates a movie.
|
||||
// Demo:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func UpdateMovieByID(ctx iris.Context, service services.MovieService, id int64) (datamodels.Movie, error) {
|
||||
// get the request data for poster and genre
|
||||
file, info, err := ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
|
||||
}
|
||||
// we don't need the file so close it now.
|
||||
file.Close()
|
||||
|
||||
// imagine that is the url of the uploaded file...
|
||||
poster := info.Filename
|
||||
genre := ctx.FormValue("genre")
|
||||
|
||||
return service.UpdatePosterAndGenreByID(id, poster, genre)
|
||||
}
|
||||
|
||||
// DeleteMovieByID deletes a movie.
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func DeleteMovieByID(service services.MovieService, id int64) interface{} {
|
||||
wasDel := service.DeleteByID(id)
|
||||
if wasDel {
|
||||
// return the deleted movie's ID
|
||||
return iris.Map{"deleted": id}
|
||||
}
|
||||
// right here we can see that a method function can return any of those two types(map or int),
|
||||
// we don't have to specify the return type to a specific type.
|
||||
return iris.StatusBadRequest
|
||||
}
|
12
_examples/hero/overview/web/views/hello/index.html
Normal file
12
_examples/hero/overview/web/views/hello/index.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!-- file: web/views/hello/index.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.Title}} - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>{{.MyMessage}}</p>
|
||||
</body>
|
||||
|
||||
</html>
|
12
_examples/hero/overview/web/views/hello/name.html
Normal file
12
_examples/hero/overview/web/views/hello/name.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!-- file: web/views/hello/name.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.}}' Portfolio - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Hello {{.}}</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -66,7 +66,7 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
UNIX and BSD hosts can take advandage of the reuse port feature
|
||||
UNIX and BSD hosts can take advantage of the reuse port feature
|
||||
|
||||
```go
|
||||
package main
|
||||
|
|
73
_examples/http_request/upload-file/main.go
Normal file
73
_examples/http_request/upload-file/main.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.RegisterView(iris.HTML("./templates", ".html"))
|
||||
|
||||
// Serve the upload_form.html to the client.
|
||||
app.Get("/upload", func(ctx iris.Context) {
|
||||
// create a token (optionally).
|
||||
|
||||
now := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(now, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
// render the form with the token for any use you'd like.
|
||||
// ctx.ViewData("", token)
|
||||
// or add second argument to the `View` method.
|
||||
// Token will be passed as {{.}} in the template.
|
||||
ctx.View("upload_form.html", token)
|
||||
})
|
||||
|
||||
// Handle the post request from the upload_form.html to the server
|
||||
app.Post("/upload", func(ctx iris.Context) {
|
||||
// iris.LimitRequestBodySize(32 <<20) as middleware to a route
|
||||
// or use ctx.SetMaxRequestBodySize(32 << 20)
|
||||
// to limit the whole request body size,
|
||||
//
|
||||
// or let the configuration option at app.Run for global setting
|
||||
// for POST/PUT methods, including uploads of course.
|
||||
|
||||
// Get the file from the request.
|
||||
file, info, err := ctx.FormFile("uploadfile")
|
||||
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.HTML("Error while uploading: <b>" + err.Error() + "</b>")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
fname := info.Filename
|
||||
|
||||
// Create a file with the same name
|
||||
// assuming that you have a folder named 'uploads'
|
||||
out, err := os.OpenFile("./uploads/"+fname,
|
||||
os.O_WRONLY|os.O_CREATE, 0666)
|
||||
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.HTML("Error while uploading: <b>" + err.Error() + "</b>")
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
io.Copy(out, file)
|
||||
})
|
||||
|
||||
// start the server at http://localhost:8080 with post limit at 32 MB.
|
||||
app.Run(iris.Addr(":8080"), iris.WithPostMaxMemory(32<<20))
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Upload file</title>
|
||||
</head>
|
||||
<body>
|
||||
<form enctype="multipart/form-data"
|
||||
action="http://127.0.0.1:8080/upload" method="POST">
|
||||
<input type="file" name="uploadfile" /> <input type="hidden"
|
||||
name="token" value="{{.}}" /> <input type="submit" value="upload" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
0
_examples/http_request/upload-file/uploads/.gitkeep
Normal file
0
_examples/http_request/upload-file/uploads/.gitkeep
Normal file
|
@ -4,8 +4,9 @@ import (
|
|||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"mime/multipart"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
|
@ -26,43 +27,39 @@ func main() {
|
|||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
// render the form with the token for any use you'd like.
|
||||
ctx.ViewData("", token)
|
||||
ctx.View("upload_form.html")
|
||||
ctx.View("upload_form.html", token)
|
||||
})
|
||||
|
||||
// Handle the post request from the upload_form.html to the server
|
||||
app.Post("/upload", iris.LimitRequestBodySize(10<<20),
|
||||
func(ctx iris.Context) {
|
||||
// or use ctx.SetMaxRequestBodySize(10 << 20)
|
||||
// to limit the uploaded file(s) size.
|
||||
// Handle the post request from the upload_form.html to the server.
|
||||
app.Post("/upload", func(ctx iris.Context) {
|
||||
//
|
||||
// UploadFormFiles
|
||||
// uploads any number of incoming files (multiple property on the form input).
|
||||
//
|
||||
|
||||
// Get the file from the request
|
||||
file, info, err := ctx.FormFile("uploadfile")
|
||||
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.HTML("Error while uploading: <b>" + err.Error() + "</b>")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
fname := info.Filename
|
||||
|
||||
// Create a file with the same name
|
||||
// assuming that you have a folder named 'uploads'
|
||||
out, err := os.OpenFile("./uploads/"+fname,
|
||||
os.O_WRONLY|os.O_CREATE, 0666)
|
||||
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.HTML("Error while uploading: <b>" + err.Error() + "</b>")
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
io.Copy(out, file)
|
||||
// second argument is totally optionally,
|
||||
// it can be used to change a file's name based on the request,
|
||||
// at this example we will showcase how to use it
|
||||
// by prefixing the uploaded file with the current user's ip.
|
||||
ctx.UploadFormFiles("./uploads", beforeSave)
|
||||
})
|
||||
|
||||
// start the server at http://localhost:8080
|
||||
app.Run(iris.Addr(":8080"))
|
||||
// start the server at http://localhost:8080 with post limit at 32 MB.
|
||||
app.Run(iris.Addr(":8080"), iris.WithPostMaxMemory(32<<20))
|
||||
}
|
||||
|
||||
func beforeSave(ctx iris.Context, file *multipart.FileHeader) {
|
||||
ip := ctx.RemoteAddr()
|
||||
// make sure you format the ip in a way
|
||||
// that can be used for a file name (simple case):
|
||||
ip = strings.Replace(ip, ".", "_", -1)
|
||||
ip = strings.Replace(ip, ":", "_", -1)
|
||||
|
||||
// you can use the time.Now, to prefix or suffix the files
|
||||
// based on the current time as well, as an exercise.
|
||||
// i.e unixTime := time.Now().Unix()
|
||||
// prefix the Filename with the $IP-
|
||||
// no need for more actions, internal uploader will use this
|
||||
// name to save the file into the "./uploads" folder.
|
||||
file.Filename = ip + "-" + file.Filename
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<body>
|
||||
<form enctype="multipart/form-data"
|
||||
action="http://127.0.0.1:8080/upload" method="POST">
|
||||
<input type="file" name="uploadfile" /> <input type="hidden"
|
||||
<input type="file" name="uploadfile" multiple/> <input type="hidden"
|
||||
name="token" value="{{.}}" /> <input type="submit" value="upload" />
|
||||
</form>
|
||||
</body>
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"bytes"
|
||||
"log"
|
||||
|
||||
"github.com/kataras/iris/_examples/http_responsewriter/hero/template"
|
||||
"github.com/kataras/iris/_examples/http_responsewriter/herotemplate/template"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
@ -46,7 +46,7 @@ func main() {
|
|||
|
||||
// using an io.Writer for automatic buffer management (i.e. hero built-in buffer pool),
|
||||
// iris context implements the io.Writer by its ResponseWriter
|
||||
// which is an enhanced version of the standar http.ResponseWriter
|
||||
// which is an enhanced version of the standard http.ResponseWriter
|
||||
// but still 100% compatible.
|
||||
template.UserListToWriter(userList, ctx)
|
||||
})
|
|
@ -5,9 +5,11 @@
|
|||
//
|
||||
|
||||
//line base.qtpl:3
|
||||
|
||||
package templates
|
||||
|
||||
//line base.qtpl:3
|
||||
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
|
@ -15,12 +17,14 @@ import (
|
|||
)
|
||||
|
||||
//line base.qtpl:3
|
||||
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line base.qtpl:4
|
||||
|
||||
type Partial interface {
|
||||
//line base.qtpl:4
|
||||
Body() string
|
||||
|
@ -29,11 +33,13 @@ type Partial interface {
|
|||
//line base.qtpl:4
|
||||
WriteBody(qq422016 qtio422016.Writer)
|
||||
//line base.qtpl:4
|
||||
|
||||
}
|
||||
|
||||
// Template writes a template implementing the Partial interface.
|
||||
|
||||
//line base.qtpl:11
|
||||
|
||||
func StreamTemplate(qw422016 *qt422016.Writer, p Partial) {
|
||||
//line base.qtpl:11
|
||||
qw422016.N().S(`
|
||||
|
@ -61,9 +67,11 @@ func StreamTemplate(qw422016 *qt422016.Writer, p Partial) {
|
|||
</html>
|
||||
`)
|
||||
//line base.qtpl:30
|
||||
|
||||
}
|
||||
|
||||
//line base.qtpl:30
|
||||
|
||||
func WriteTemplate(qq422016 qtio422016.Writer, p Partial) {
|
||||
//line base.qtpl:30
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
|
@ -72,9 +80,11 @@ func WriteTemplate(qq422016 qtio422016.Writer, p Partial) {
|
|||
//line base.qtpl:30
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line base.qtpl:30
|
||||
|
||||
}
|
||||
|
||||
//line base.qtpl:30
|
||||
|
||||
func Template(p Partial) string {
|
||||
//line base.qtpl:30
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
|
@ -87,21 +97,27 @@ func Template(p Partial) string {
|
|||
//line base.qtpl:30
|
||||
return qs422016
|
||||
//line base.qtpl:30
|
||||
|
||||
}
|
||||
|
||||
// Base template implementation. Other pages may inherit from it if they need
|
||||
// overriding only certain Partial methods.
|
||||
|
||||
//line base.qtpl:35
|
||||
|
||||
type Base struct{}
|
||||
|
||||
//line base.qtpl:36
|
||||
|
||||
func (b *Base) StreamBody(qw422016 *qt422016.Writer) {
|
||||
//line base.qtpl:36
|
||||
qw422016.N().S(`This is the base body`) }
|
||||
|
||||
qw422016.N().S(`This is the base body`)
|
||||
}
|
||||
|
||||
//line base.qtpl:36
|
||||
//line base.qtpl:36
|
||||
|
||||
func (b *Base) WriteBody(qq422016 qtio422016.Writer) {
|
||||
//line base.qtpl:36
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
|
@ -110,9 +126,11 @@ func (b *Base) WriteBody(qq422016 qtio422016.Writer) {
|
|||
//line base.qtpl:36
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line base.qtpl:36
|
||||
|
||||
}
|
||||
|
||||
//line base.qtpl:36
|
||||
|
||||
func (b *Base) Body() string {
|
||||
//line base.qtpl:36
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
|
@ -125,4 +143,5 @@ func (b *Base) Body() string {
|
|||
//line base.qtpl:36
|
||||
return qs422016
|
||||
//line base.qtpl:36
|
||||
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
//
|
||||
|
||||
//line hello.qtpl:3
|
||||
|
||||
package templates
|
||||
|
||||
//line hello.qtpl:3
|
||||
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
|
@ -15,17 +17,20 @@ import (
|
|||
)
|
||||
|
||||
//line hello.qtpl:3
|
||||
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line hello.qtpl:4
|
||||
|
||||
type Hello struct {
|
||||
Vars map[string]interface{}
|
||||
}
|
||||
|
||||
//line hello.qtpl:9
|
||||
|
||||
func (h *Hello) StreamBody(qw422016 *qt422016.Writer) {
|
||||
//line hello.qtpl:9
|
||||
qw422016.N().S(`
|
||||
|
@ -43,9 +48,11 @@ func (h *Hello) StreamBody(qw422016 *qt422016.Writer) {
|
|||
</div>
|
||||
`)
|
||||
//line hello.qtpl:14
|
||||
|
||||
}
|
||||
|
||||
//line hello.qtpl:14
|
||||
|
||||
func (h *Hello) WriteBody(qq422016 qtio422016.Writer) {
|
||||
//line hello.qtpl:14
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
|
@ -54,9 +61,11 @@ func (h *Hello) WriteBody(qq422016 qtio422016.Writer) {
|
|||
//line hello.qtpl:14
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line hello.qtpl:14
|
||||
|
||||
}
|
||||
|
||||
//line hello.qtpl:14
|
||||
|
||||
func (h *Hello) Body() string {
|
||||
//line hello.qtpl:14
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
|
@ -69,4 +78,5 @@ func (h *Hello) Body() string {
|
|||
//line hello.qtpl:14
|
||||
return qs422016
|
||||
//line hello.qtpl:14
|
||||
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
//
|
||||
|
||||
//line index.qtpl:3
|
||||
|
||||
package templates
|
||||
|
||||
//line index.qtpl:3
|
||||
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
|
@ -15,15 +17,18 @@ import (
|
|||
)
|
||||
|
||||
//line index.qtpl:3
|
||||
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line index.qtpl:4
|
||||
|
||||
type Index struct{}
|
||||
|
||||
//line index.qtpl:7
|
||||
|
||||
func (i *Index) StreamBody(qw422016 *qt422016.Writer) {
|
||||
//line index.qtpl:7
|
||||
qw422016.N().S(`
|
||||
|
@ -33,9 +38,11 @@ func (i *Index) StreamBody(qw422016 *qt422016.Writer) {
|
|||
</div>
|
||||
`)
|
||||
//line index.qtpl:12
|
||||
|
||||
}
|
||||
|
||||
//line index.qtpl:12
|
||||
|
||||
func (i *Index) WriteBody(qq422016 qtio422016.Writer) {
|
||||
//line index.qtpl:12
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
|
@ -44,9 +51,11 @@ func (i *Index) WriteBody(qq422016 qtio422016.Writer) {
|
|||
//line index.qtpl:12
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line index.qtpl:12
|
||||
|
||||
}
|
||||
|
||||
//line index.qtpl:12
|
||||
|
||||
func (i *Index) Body() string {
|
||||
//line index.qtpl:12
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
|
@ -59,4 +68,5 @@ func (i *Index) Body() string {
|
|||
//line index.qtpl:12
|
||||
return qs422016
|
||||
//line index.qtpl:12
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
102
_examples/mvc/basic/main.go
Normal file
102
_examples/mvc/basic/main.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/sessions"
|
||||
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Logger().SetLevel("debug")
|
||||
mvc.Configure(app.Party("/basic"), basicMVC)
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
func basicMVC(app *mvc.Application) {
|
||||
// You can use normal middlewares at MVC apps of course.
|
||||
app.Router.Use(func(ctx iris.Context) {
|
||||
ctx.Application().Logger().Infof("Path: %s", ctx.Path())
|
||||
ctx.Next()
|
||||
})
|
||||
|
||||
// Register dependencies which will be binding to the controller(s),
|
||||
// can be either a function which accepts an iris.Context and returns a single value (dynamic binding)
|
||||
// or a static struct value (service).
|
||||
app.Register(
|
||||
sessions.New(sessions.Config{}).Start,
|
||||
&prefixedLogger{prefix: "DEV"},
|
||||
)
|
||||
|
||||
// GET: http://localhost:8080/basic
|
||||
// GET: http://localhost:8080/basic/custom
|
||||
app.Handle(new(basicController))
|
||||
|
||||
// All dependencies of the parent *mvc.Application
|
||||
// are cloned to this new child,
|
||||
// thefore it has access to the same session as well.
|
||||
// GET: http://localhost:8080/basic/sub
|
||||
app.Party("/sub").
|
||||
Handle(new(basicSubController))
|
||||
}
|
||||
|
||||
// If controller's fields (or even its functions) expecting an interface
|
||||
// but a struct value is binded then it will check
|
||||
// if that struct value implements
|
||||
// the interface and if true then it will add this to the
|
||||
// available bindings, as expected, before the server ran of course,
|
||||
// remember? Iris always uses the best possible way to reduce load
|
||||
// on serving web resources.
|
||||
|
||||
type LoggerService interface {
|
||||
Log(string)
|
||||
}
|
||||
|
||||
type prefixedLogger struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (s *prefixedLogger) Log(msg string) {
|
||||
fmt.Printf("%s: %s\n", s.prefix, msg)
|
||||
}
|
||||
|
||||
type basicController struct {
|
||||
Logger LoggerService
|
||||
|
||||
Session *sessions.Session
|
||||
}
|
||||
|
||||
func (c *basicController) BeforeActivation(b mvc.BeforeActivation) {
|
||||
b.Handle("GET", "/custom", "Custom")
|
||||
}
|
||||
|
||||
func (c *basicController) AfterActivation(a mvc.AfterActivation) {
|
||||
if a.Singleton() {
|
||||
panic("basicController should be stateless, a request-scoped, we have a 'Session' which depends on the context.")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *basicController) Get() string {
|
||||
count := c.Session.Increment("count", 1)
|
||||
|
||||
body := fmt.Sprintf("Hello from basicController\nTotal visits from you: %d", count)
|
||||
c.Logger.Log(body)
|
||||
return body
|
||||
}
|
||||
|
||||
func (c *basicController) Custom() string {
|
||||
return "custom"
|
||||
}
|
||||
|
||||
type basicSubController struct {
|
||||
Session *sessions.Session
|
||||
}
|
||||
|
||||
func (c *basicSubController) Get() string {
|
||||
count, _ := c.Session.GetIntDefault("count", 1)
|
||||
return fmt.Sprintf("Hello from basicSubController.\nRead-only visits count: %d", count)
|
||||
}
|
|
@ -3,13 +3,6 @@ package main
|
|||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
// auto-completion does not working well with type aliases
|
||||
// when embedded fields.
|
||||
// We should complete a report on golang repo for that at some point.
|
||||
//
|
||||
// Therefore import the "mvc" package manually
|
||||
// here at "hello-world" so users can see that
|
||||
// import path somewhere else than the "FAQ" section.
|
||||
|
||||
"github.com/kataras/iris/middleware/logger"
|
||||
"github.com/kataras/iris/middleware/recover"
|
||||
|
@ -35,7 +28,9 @@ import (
|
|||
// 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() {
|
||||
// Of course you can put all these to main func, it's just a separate function
|
||||
// for the main_test.go.
|
||||
func newApp() *iris.Application {
|
||||
app := iris.New()
|
||||
// Optionally, add two built'n handlers
|
||||
// that can recover from any http-relative panics
|
||||
|
@ -43,27 +38,23 @@ func main() {
|
|||
app.Use(recover.New())
|
||||
app.Use(logger.New())
|
||||
|
||||
app.Controller("/", new(ExampleController))
|
||||
// Serve a controller based on the root Router, "/".
|
||||
mvc.New(app).Handle(new(ExampleController))
|
||||
return app
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := newApp()
|
||||
|
||||
// http://localhost:8080
|
||||
// http://localhost:8080/ping
|
||||
// http://localhost:8080/hello
|
||||
// http://localhost:8080/custom_path
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
// ExampleController serves the "/", "/ping" and "/hello".
|
||||
type ExampleController struct {
|
||||
// if you build with go1.8 you have to use the mvc package always,
|
||||
// otherwise
|
||||
// you can, optionally
|
||||
// use the type alias `iris.C`,
|
||||
// same for
|
||||
// context.Context -> iris.Context,
|
||||
// mvc.Result -> iris.Result,
|
||||
// mvc.Response -> iris.Response,
|
||||
// mvc.View -> iris.View
|
||||
mvc.C
|
||||
}
|
||||
type ExampleController struct{}
|
||||
|
||||
// Get serves
|
||||
// Method: GET
|
||||
|
@ -89,6 +80,31 @@ func (c *ExampleController) GetHello() interface{} {
|
|||
return map[string]string{"message": "Hello Iris!"}
|
||||
}
|
||||
|
||||
// BeforeActivation called once, before the controller adapted to the main application
|
||||
// and of course before the server ran.
|
||||
// After version 9 you can also add custom routes for a specific controller's methods.
|
||||
// Here you can register custom method's handlers
|
||||
// use the standard router with `ca.Router` to do something that you can do without mvc as well,
|
||||
// and add dependencies that will be binded to a controller's fields or method function's input arguments.
|
||||
func (c *ExampleController) BeforeActivation(b mvc.BeforeActivation) {
|
||||
anyMiddlewareHere := func(ctx iris.Context) {
|
||||
ctx.Application().Logger().Warnf("Inside /custom_path")
|
||||
ctx.Next()
|
||||
}
|
||||
b.Handle("GET", "/custom_path", "CustomHandlerWithoutFollowingTheNamingGuide", anyMiddlewareHere)
|
||||
|
||||
// or even add a global middleware based on this controller's router,
|
||||
// which in this example is the root "/":
|
||||
// b.Router().Use(myMiddleware)
|
||||
}
|
||||
|
||||
// CustomHandlerWithoutFollowingTheNamingGuide serves
|
||||
// Method: GET
|
||||
// Resource: http://localhost:8080/custom_path
|
||||
func (c *ExampleController) CustomHandlerWithoutFollowingTheNamingGuide() string {
|
||||
return "hello from the custom handler without following the naming guide"
|
||||
}
|
||||
|
||||
// GetUserBy serves
|
||||
// Method: GET
|
||||
// Resource: http://localhost:8080/user/{username:string}
|
||||
|
@ -121,4 +137,18 @@ func (c *ExampleController) Trace() {}
|
|||
func (c *ExampleController) All() {}
|
||||
// OR
|
||||
func (c *ExampleController) Any() {}
|
||||
|
||||
|
||||
|
||||
func (c *ExampleController) BeforeActivation(b mvc.BeforeActivation) {
|
||||
// 1 -> the HTTP Method
|
||||
// 2 -> the route's path
|
||||
// 3 -> this controller's method name that should be handler for that route.
|
||||
b.Handle("GET", "/mypath/{param}", "DoIt", optionalMiddlewareHere...)
|
||||
}
|
||||
|
||||
// After activation, all dependencies are set-ed - so read only access on them
|
||||
// but still possible to add custom controller or simple standard handlers.
|
||||
func (c *ExampleController) AfterActivation(a mvc.AfterActivation) {}
|
||||
|
||||
*/
|
||||
|
|
23
_examples/mvc/hello-world/main_test.go
Normal file
23
_examples/mvc/hello-world/main_test.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/httptest"
|
||||
)
|
||||
|
||||
func TestMVCHelloWorld(t *testing.T) {
|
||||
e := httptest.New(t, newApp())
|
||||
|
||||
e.GET("/").Expect().Status(httptest.StatusOK).
|
||||
ContentType("text/html", "utf-8").Body().Equal("<h1>Welcome</h1>")
|
||||
|
||||
e.GET("/ping").Expect().Status(httptest.StatusOK).
|
||||
Body().Equal("pong")
|
||||
|
||||
e.GET("/hello").Expect().Status(httptest.StatusOK).
|
||||
JSON().Object().Value("message").Equal("Hello Iris!")
|
||||
|
||||
e.GET("/custom_path").Expect().Status(httptest.StatusOK).
|
||||
Body().Equal("hello from the custom handler without following the naming guide")
|
||||
}
|
|
@ -5,12 +5,14 @@ package main
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/_examples/mvc/login/datasource"
|
||||
"github.com/kataras/iris/_examples/mvc/login/repositories"
|
||||
"github.com/kataras/iris/_examples/mvc/login/services"
|
||||
"github.com/kataras/iris/_examples/mvc/login/web/controllers"
|
||||
"github.com/kataras/iris/_examples/mvc/login/web/middleware"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
|
@ -34,7 +36,9 @@ func main() {
|
|||
ctx.View("shared/error.html")
|
||||
})
|
||||
|
||||
// Create our repositories and services.
|
||||
// ---- Serve our controllers. ----
|
||||
|
||||
// Prepare our repositories and services.
|
||||
db, err := datasource.LoadUsers(datasource.Memory)
|
||||
if err != nil {
|
||||
app.Logger().Fatalf("error while loading the users: %v", err)
|
||||
|
@ -43,29 +47,38 @@ func main() {
|
|||
repo := repositories.NewUserRepository(db)
|
||||
userService := services.NewUserService(repo)
|
||||
|
||||
// Register our controllers.
|
||||
app.Controller("/users", new(controllers.UsersController),
|
||||
// "/users" based mvc application.
|
||||
users := mvc.New(app.Party("/users"))
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /users based requests.
|
||||
middleware.BasicAuth,
|
||||
users.Router.Use(middleware.BasicAuth)
|
||||
// Bind the "userService" to the UserController's Service (interface) field.
|
||||
userService,
|
||||
)
|
||||
users.Register(userService)
|
||||
users.Handle(new(controllers.UsersController))
|
||||
|
||||
// "/user" based mvc application.
|
||||
sessManager := sessions.New(sessions.Config{
|
||||
Cookie: "sessioncookiename",
|
||||
Expires: 24 * time.Hour,
|
||||
})
|
||||
app.Controller("/user", new(controllers.UserController), userService, sessManager)
|
||||
user := mvc.New(app.Party("/user"))
|
||||
user.Register(
|
||||
userService,
|
||||
sessManager.Start,
|
||||
)
|
||||
user.Handle(new(controllers.UserController))
|
||||
|
||||
// Start the web server at localhost:8080
|
||||
// http://localhost:8080/hello
|
||||
// http://localhost:8080/hello/iris
|
||||
// http://localhost:8080/noexist
|
||||
// and all controller's methods like
|
||||
// http://localhost:8080/users/1
|
||||
app.Run(
|
||||
// Starts the web server at localhost:8080
|
||||
iris.Addr("localhost:8080"),
|
||||
// Disables the updater.
|
||||
iris.WithoutVersionChecker,
|
||||
// Ignores err server closed log when CTRL/CMD+C pressed.
|
||||
iris.WithoutServerError(iris.ErrServerClosed),
|
||||
iris.WithOptimizations, // enables faster json serialization and more
|
||||
// Enables faster json serialization and more.
|
||||
iris.WithOptimizations,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/kataras/iris/_examples/mvc/login/datamodels"
|
||||
"github.com/kataras/iris/_examples/mvc/login/services"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
@ -20,39 +20,21 @@ import (
|
|||
// GET /user/me
|
||||
// All HTTP Methods /user/logout
|
||||
type UserController struct {
|
||||
// mvc.C is just a lightweight lightweight alternative
|
||||
// to the "mvc.Controller" controller type,
|
||||
// use it when you don't need mvc.Controller's fields
|
||||
// (you don't need those fields when you return values from the method functions).
|
||||
mvc.C
|
||||
// context is auto-binded by Iris on each request,
|
||||
// remember that on each incoming request iris creates a new UserController each time,
|
||||
// so all fields are request-scoped by-default, only dependency injection is able to set
|
||||
// custom fields like the Service which is the same for all requests (static binding)
|
||||
// and the Session which depends on the current context (dynamic binding).
|
||||
Ctx iris.Context
|
||||
|
||||
// Our UserService, it's an interface which
|
||||
// is binded from the main application.
|
||||
Service services.UserService
|
||||
|
||||
// Session-relative things.
|
||||
Manager *sessions.Sessions
|
||||
// Session, binded using dependency injection from the main.go.
|
||||
Session *sessions.Session
|
||||
}
|
||||
|
||||
// BeginRequest will set the current session to the controller.
|
||||
//
|
||||
// Remember: iris.Context and context.Context is exactly the same thing,
|
||||
// iris.Context is just a type alias for go 1.9 users.
|
||||
// We use context.Context here because we don't need all iris' root functions,
|
||||
// when we see the import paths, we make it visible to ourselves that this file is using only the context.
|
||||
func (c *UserController) BeginRequest(ctx context.Context) {
|
||||
c.C.BeginRequest(ctx)
|
||||
|
||||
if c.Manager == nil {
|
||||
ctx.Application().Logger().Errorf(`UserController: sessions manager is nil, you should bind it`)
|
||||
ctx.StopExecution() // dont run the main method handler and any "done" handlers.
|
||||
return
|
||||
}
|
||||
|
||||
c.Session = c.Manager.Start(ctx)
|
||||
}
|
||||
|
||||
const userIDKey = "UserID"
|
||||
|
||||
func (c *UserController) getCurrentUserID() int64 {
|
||||
|
@ -65,12 +47,12 @@ func (c *UserController) isLoggedIn() bool {
|
|||
}
|
||||
|
||||
func (c *UserController) logout() {
|
||||
c.Manager.DestroyByID(c.Session.ID())
|
||||
c.Session.Destroy()
|
||||
}
|
||||
|
||||
var registerStaticView = mvc.View{
|
||||
Name: "user/register.html",
|
||||
Data: context.Map{"Title": "User Registration"},
|
||||
Data: iris.Map{"Title": "User Registration"},
|
||||
}
|
||||
|
||||
// GetRegister handles GET: http://localhost:8080/user/register.
|
||||
|
@ -119,7 +101,7 @@ func (c *UserController) PostRegister() mvc.Result {
|
|||
|
||||
var loginStaticView = mvc.View{
|
||||
Name: "user/login.html",
|
||||
Data: context.Map{"Title": "User Login"},
|
||||
Data: iris.Map{"Title": "User Login"},
|
||||
}
|
||||
|
||||
// GetLogin handles GET: http://localhost:8080/user/login.
|
||||
|
@ -172,7 +154,7 @@ func (c *UserController) GetMe() mvc.Result {
|
|||
|
||||
return mvc.View{
|
||||
Name: "user/me.html",
|
||||
Data: context.Map{
|
||||
Data: iris.Map{
|
||||
"Title": "Profile of " + u.Username,
|
||||
"User": u,
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"github.com/kataras/iris/_examples/mvc/login/datamodels"
|
||||
"github.com/kataras/iris/_examples/mvc/login/services"
|
||||
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
// UsersController is our /users API controller.
|
||||
|
@ -14,30 +14,17 @@ import (
|
|||
// DELETE /users/{id:long} | delete by id
|
||||
// Requires basic authentication.
|
||||
type UsersController struct {
|
||||
mvc.C
|
||||
// Optionally: context is auto-binded by Iris on each request,
|
||||
// remember that on each incoming request iris creates a new UserController each time,
|
||||
// so all fields are request-scoped by-default, only dependency injection is able to set
|
||||
// custom fields like the Service which is the same for all requests (static binding).
|
||||
Ctx iris.Context
|
||||
|
||||
// Our UserService, it's an interface which
|
||||
// is binded from the main application.
|
||||
Service services.UserService
|
||||
}
|
||||
|
||||
// This could be possible but we should not call handlers inside the `BeginRequest`.
|
||||
// Because `BeginRequest` was introduced to set common, shared variables between all method handlers
|
||||
// before their execution.
|
||||
// We will add this middleware from our `app.Controller` call.
|
||||
//
|
||||
// var authMiddleware = basicauth.New(basicauth.Config{
|
||||
// Users: map[string]string{
|
||||
// "admin": "password",
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// func (c *UsersController) BeginRequest(ctx iris.Context) {
|
||||
// c.C.BeginRequest(ctx)
|
||||
//
|
||||
// if !ctx.Proceed(authMiddleware) {
|
||||
// ctx.StopExecution()
|
||||
// }
|
||||
// }
|
||||
|
||||
// Get returns list of the users.
|
||||
// Demo:
|
||||
// curl -i -u admin:password http://localhost:8080/users
|
||||
|
@ -97,5 +84,5 @@ func (c *UsersController) DeleteBy(id int64) interface{} {
|
|||
// right here we can see that a method function
|
||||
// can return any of those two types(map or int),
|
||||
// we don't have to specify the return type to a specific type.
|
||||
return 400 // same as `iris.StatusBadRequest`.
|
||||
return iris.StatusBadRequest // same as 400.
|
||||
}
|
||||
|
|
|
@ -10,38 +10,53 @@ import (
|
|||
"github.com/kataras/iris/_examples/mvc/overview/web/middleware"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Logger().SetLevel("debug")
|
||||
|
||||
// Load the template files.
|
||||
app.RegisterView(iris.HTML("./web/views", ".html"))
|
||||
|
||||
// Register our controllers.
|
||||
app.Controller("/hello", new(controllers.HelloController))
|
||||
// Serve our controllers.
|
||||
mvc.New(app.Party("/hello")).Handle(new(controllers.HelloController))
|
||||
// You can also split the code you write to configure an mvc.Application
|
||||
// using the `mvc.Configure` method, as shown below.
|
||||
mvc.Configure(app.Party("/movies"), movies)
|
||||
|
||||
// Create our movie repository with some (memory) data from the datasource.
|
||||
repo := repositories.NewMovieRepository(datasource.Movies)
|
||||
// Create our movie service, we will bind it to the movie controller.
|
||||
movieService := services.NewMovieService(repo)
|
||||
|
||||
app.Controller("/movies", new(controllers.MovieController),
|
||||
// Bind the "movieService" to the MovieController's Service (interface) field.
|
||||
movieService,
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /movies based requests.
|
||||
middleware.BasicAuth)
|
||||
|
||||
// Start the web server at localhost:8080
|
||||
// http://localhost:8080/hello
|
||||
// http://localhost:8080/hello/iris
|
||||
// http://localhost:8080/movies
|
||||
// http://localhost:8080/movies/1
|
||||
app.Run(
|
||||
// Start the web server at localhost:8080
|
||||
iris.Addr("localhost:8080"),
|
||||
// disables updates:
|
||||
iris.WithoutVersionChecker,
|
||||
// skip err server closed when CTRL/CMD+C pressed:
|
||||
iris.WithoutServerError(iris.ErrServerClosed),
|
||||
iris.WithOptimizations, // enables faster json serialization and more
|
||||
// enables faster json serialization and more:
|
||||
iris.WithOptimizations,
|
||||
)
|
||||
}
|
||||
|
||||
// note the mvc.Application, it's not iris.Application.
|
||||
func movies(app *mvc.Application) {
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /movies based requests.
|
||||
app.Router.Use(middleware.BasicAuth)
|
||||
|
||||
// Create our movie repository with some (memory) data from the datasource.
|
||||
repo := repositories.NewMovieRepository(datasource.Movies)
|
||||
// Create our movie service, we will bind it to the movie app's dependencies.
|
||||
movieService := services.NewMovieService(repo)
|
||||
app.Register(movieService)
|
||||
|
||||
// serve our movies controller.
|
||||
// Note that you can serve more than one controller
|
||||
// you can also create child mvc apps using the `movies.Party(relativePath)` or `movies.Clone(app.Party(...))`
|
||||
// if you want.
|
||||
app.Handle(new(controllers.MovieController))
|
||||
}
|
||||
|
|
|
@ -10,9 +10,7 @@ import (
|
|||
|
||||
// HelloController is our sample controller
|
||||
// it handles GET: /hello and GET: /hello/{name}
|
||||
type HelloController struct {
|
||||
mvc.C
|
||||
}
|
||||
type HelloController struct{}
|
||||
|
||||
var helloView = mvc.View{
|
||||
Name: "hello/index.html",
|
||||
|
@ -27,12 +25,12 @@ var helloView = mvc.View{
|
|||
// `mvc.Result` is just an interface with a `Dispatch` function.
|
||||
// `mvc.Response` and `mvc.View` are the built'n result type dispatchers
|
||||
// you can even create custom response dispatchers by
|
||||
// implementing the `github.com/kataras/iris/mvc#Result` interface.
|
||||
// implementing the `github.com/kataras/iris/hero#Result` interface.
|
||||
func (c *HelloController) Get() mvc.Result {
|
||||
return helloView
|
||||
}
|
||||
|
||||
// you can define a standard error in order to be re-usable anywhere in your app.
|
||||
// you can define a standard error in order to re-use anywhere in your app.
|
||||
var errBadName = errors.New("bad name")
|
||||
|
||||
// you can just return it as error or even better
|
||||
|
|
|
@ -9,17 +9,10 @@ import (
|
|||
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// MovieController is our /movies controller.
|
||||
type MovieController struct {
|
||||
// mvc.C is just a lightweight lightweight alternative
|
||||
// to the "mvc.Controller" controller type,
|
||||
// use it when you don't need mvc.Controller's fields
|
||||
// (you don't need those fields when you return values from the method functions).
|
||||
mvc.C
|
||||
|
||||
// Our MovieService, it's an interface which
|
||||
// is binded from the main application.
|
||||
Service services.MovieService
|
||||
|
@ -53,9 +46,9 @@ func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
|
|||
// PutBy updates a movie.
|
||||
// Demo:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MovieController) PutBy(id int64) (datamodels.Movie, error) {
|
||||
func (c *MovieController) PutBy(ctx iris.Context, id int64) (datamodels.Movie, error) {
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
file, info, err := ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
return datamodels.Movie{}, errors.New("failed due form file 'poster' missing")
|
||||
}
|
||||
|
@ -64,7 +57,7 @@ func (c *MovieController) PutBy(id int64) (datamodels.Movie, error) {
|
|||
|
||||
// imagine that is the url of the uploaded file...
|
||||
poster := info.Filename
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
genre := ctx.FormValue("genre")
|
||||
|
||||
return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
|
||||
}
|
||||
|
|
|
@ -7,72 +7,61 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
// VisitController handles the root route.
|
||||
type VisitController struct {
|
||||
iris.C
|
||||
|
||||
// the sessions manager, we need that to set `Session`.
|
||||
// It's binded from `app.Controller`.
|
||||
Manager *sessions.Sessions
|
||||
// the current request session,
|
||||
// its initialization happens at the `BeginRequest`.
|
||||
// its initialization happens by the dependency function that we've added to the `visitApp`.
|
||||
Session *sessions.Session
|
||||
|
||||
// A time.time which is binded from the `app.Controller`,
|
||||
// A time.time which is binded from the MVC,
|
||||
// order of binded fields doesn't matter.
|
||||
StartTime time.Time
|
||||
}
|
||||
|
||||
// BeginRequest is executed for each Get, Post, Put requests,
|
||||
// can be used to share context, common data
|
||||
// or to cancel the request via `ctx.StopExecution()`.
|
||||
func (c *VisitController) BeginRequest(ctx iris.Context) {
|
||||
// always call the embedded `BeginRequest` before everything else.
|
||||
c.C.BeginRequest(ctx)
|
||||
|
||||
if c.Manager == nil {
|
||||
ctx.Application().Logger().Errorf(`VisitController: sessions manager is nil, you should bind it`)
|
||||
// dont run the main method handler and any "done" handlers.
|
||||
ctx.StopExecution()
|
||||
return
|
||||
}
|
||||
|
||||
// set the `c.Session` we will use that in our Get method.
|
||||
c.Session = c.Manager.Start(ctx)
|
||||
}
|
||||
|
||||
// Get handles
|
||||
// Method: GET
|
||||
// Path: http://localhost:8080
|
||||
func (c *VisitController) Get() string {
|
||||
// get the visits, before calcuate this new one.
|
||||
visits, _ := c.Session.GetIntDefault("visits", 0)
|
||||
|
||||
// increment the visits and store to the session.
|
||||
visits++
|
||||
c.Session.Set("visits", visits)
|
||||
|
||||
// it increments a "visits" value of integer by one,
|
||||
// if the entry with key 'visits' doesn't exist it will create it for you.
|
||||
visits := c.Session.Increment("visits", 1)
|
||||
// write the current, updated visits.
|
||||
since := time.Now().Sub(c.StartTime).Seconds()
|
||||
return fmt.Sprintf("%d visit from my current session in %0.1f seconds of server's up-time",
|
||||
visits, since)
|
||||
}
|
||||
|
||||
var (
|
||||
manager = sessions.New(sessions.Config{Cookie: "mysession_cookie_name"})
|
||||
func newApp() *iris.Application {
|
||||
app := iris.New()
|
||||
sess := sessions.New(sessions.Config{Cookie: "mysession_cookie_name"})
|
||||
|
||||
visitApp := mvc.New(app.Party("/"))
|
||||
// bind the current *session.Session, which is required, to the `VisitController.Session`
|
||||
// and the time.Now() to the `VisitController.StartTime`.
|
||||
visitApp.Register(
|
||||
// if dependency is a function which accepts
|
||||
// a Context and returns a single value
|
||||
// then the result type of this function is resolved by the controller
|
||||
// and on each request it will call the function with its Context
|
||||
// and set the result(the *sessions.Session here) to the controller's field.
|
||||
//
|
||||
// If dependencies are registered without field or function's input arguments as
|
||||
// consumers then those dependencies are being ignored before the server ran,
|
||||
// so you can bind many dependecies and use them in different controllers.
|
||||
sess.Start,
|
||||
time.Now(),
|
||||
)
|
||||
visitApp.Handle(new(VisitController))
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// bind our session manager, which is required, to the `VisitController.Manager`
|
||||
// and the time.Now() to the `VisitController.StartTime`.
|
||||
app.Controller("/", new(VisitController),
|
||||
manager,
|
||||
time.Now())
|
||||
app := newApp()
|
||||
|
||||
// 1. open the browser (no in private mode)
|
||||
// 2. navigate to http://localhost:8080
|
||||
|
|
21
_examples/mvc/session-controller/main_test.go
Normal file
21
_examples/mvc/session-controller/main_test.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/httptest"
|
||||
)
|
||||
|
||||
func TestMVCSession(t *testing.T) {
|
||||
e := httptest.New(t, newApp(), httptest.URL("http://example.com"))
|
||||
|
||||
e1 := e.GET("/").Expect().Status(httptest.StatusOK)
|
||||
e1.Cookies().NotEmpty()
|
||||
e1.Body().Contains("1 visit")
|
||||
|
||||
e.GET("/").Expect().Status(httptest.StatusOK).
|
||||
Body().Contains("2 visit")
|
||||
|
||||
e.GET("/").Expect().Status(httptest.StatusOK).
|
||||
Body().Contains("3 visit")
|
||||
}
|
36
_examples/mvc/singleton/main.go
Normal file
36
_examples/mvc/singleton/main.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
mvc.New(app.Party("/")).Handle(&globalVisitorsController{visits: 0})
|
||||
|
||||
// http://localhost:8080
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
type globalVisitorsController struct {
|
||||
// When a singleton controller is used then concurent safe access is up to the developers, because
|
||||
// all clients share the same controller instance instead.
|
||||
// Note that any controller's methods
|
||||
// are per-client, but the struct's field can be shared across multiple clients if the structure
|
||||
// does not have any dynamic struct field dependencies that depend on the iris.Context
|
||||
// and ALL field's values are NOT zero, at this case we use uint64 which it's no zero (even if we didn't set it
|
||||
// manually ease-of-understand reasons) because it's a value of &{0}.
|
||||
// All the above declares a Singleton, note that you don't have to write a single line of code to do this, Iris is smart enough.
|
||||
//
|
||||
// see `Get`.
|
||||
visits uint64
|
||||
}
|
||||
|
||||
func (c *globalVisitorsController) Get() string {
|
||||
count := atomic.AddUint64(&c.visits, 1)
|
||||
return fmt.Sprintf("Total visitors: %d", count)
|
||||
}
|
82
_examples/mvc/websocket/main.go
Normal file
82
_examples/mvc/websocket/main.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris/websocket"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
// load templaes.
|
||||
app.RegisterView(iris.HTML("./views", ".html"))
|
||||
|
||||
// render the ./views/index.html.
|
||||
app.Get("/", func(ctx iris.Context) {
|
||||
ctx.View("index.html")
|
||||
})
|
||||
|
||||
mvc.Configure(app.Party("/websocket"), configureMVC)
|
||||
// Or mvc.New(app.Party(...)).Configure(configureMVC)
|
||||
|
||||
// http://localhost:8080
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
func configureMVC(m *mvc.Application) {
|
||||
ws := websocket.New(websocket.Config{})
|
||||
// http://localhost:8080/websocket/iris-ws.js
|
||||
m.Router.Any("/iris-ws.js", websocket.ClientHandler())
|
||||
|
||||
// This will bind the result of ws.Upgrade which is a websocket.Connection
|
||||
// to the controller(s) served by the `m.Handle`.
|
||||
m.Register(ws.Upgrade)
|
||||
m.Handle(new(websocketController))
|
||||
}
|
||||
|
||||
var visits uint64
|
||||
|
||||
func increment() uint64 {
|
||||
return atomic.AddUint64(&visits, 1)
|
||||
}
|
||||
|
||||
func decrement() uint64 {
|
||||
return atomic.AddUint64(&visits, ^uint64(0))
|
||||
}
|
||||
|
||||
type websocketController struct {
|
||||
// Note that you could use an anonymous field as well, it doesn't matter, binder will find it.
|
||||
//
|
||||
// This is the current websocket connection, each client has its own instance of the *websocketController.
|
||||
Conn websocket.Connection
|
||||
}
|
||||
|
||||
func (c *websocketController) onLeave(roomName string) {
|
||||
// visits--
|
||||
newCount := decrement()
|
||||
// This will call the "visit" event on all clients, except the current one,
|
||||
// (it can't because it's left but for any case use this type of design)
|
||||
c.Conn.To(websocket.Broadcast).Emit("visit", newCount)
|
||||
}
|
||||
|
||||
func (c *websocketController) update() {
|
||||
// visits++
|
||||
newCount := increment()
|
||||
|
||||
// This will call the "visit" event on all clients, including the current
|
||||
// with the 'newCount' variable.
|
||||
//
|
||||
// There are many ways that u can do it and faster, for example u can just send a new visitor
|
||||
// and client can increment itself, but here we are just "showcasing" the websocket controller.
|
||||
c.Conn.To(websocket.All).Emit("visit", newCount)
|
||||
}
|
||||
|
||||
func (c *websocketController) Get( /* websocket.Connection could be lived here as well, it doesn't matter */ ) {
|
||||
c.Conn.OnLeave(c.onLeave)
|
||||
c.Conn.On("visit", c.update)
|
||||
|
||||
// call it after all event callbacks registration.
|
||||
c.Conn.Wait()
|
||||
}
|
63
_examples/mvc/websocket/views/index.html
Normal file
63
_examples/mvc/websocket/views/index.html
Normal file
|
@ -0,0 +1,63 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>Online visitors MVC example</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, "San Francisco", "Helvetica Neue", "Noto", "Roboto", "Calibri Light", sans-serif;
|
||||
color: #212121;
|
||||
font-size: 1.0em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 750px;
|
||||
margin: auto;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#online_visitors {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<span id="online_visitors">1 online visitor</span>
|
||||
</div>
|
||||
|
||||
<script src="/websocket/iris-ws.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var socket = new Ws("ws://localhost:8080/websocket");
|
||||
|
||||
socket.OnConnect(function(){
|
||||
// update the rest of connected clients, including "myself" when "my" connection is 100% ready.
|
||||
socket.Emit("visit");
|
||||
});
|
||||
|
||||
|
||||
socket.On("visit", function (newCount) {
|
||||
console.log("visit websocket event with newCount of: ", newCount);
|
||||
|
||||
var text = "1 online visitor";
|
||||
if (newCount > 1) {
|
||||
text = newCount + " online visitors";
|
||||
}
|
||||
document.getElementById("online_visitors").innerHTML = text;
|
||||
});
|
||||
|
||||
socket.OnDisconnect(function () {
|
||||
document.getElementById("online_visitors").innerHTML = "you've been disconnected";
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -22,8 +22,8 @@ Some trivial examples,
|
|||
```go
|
||||
import "github.com/kataras/iris/sessions"
|
||||
|
||||
sess := sessions.Start(http.ResponseWriter, *http.Request)
|
||||
sess.
|
||||
manager := sessions.Start(iris.Context)
|
||||
manager.
|
||||
ID() string
|
||||
Get(string) interface{}
|
||||
HasFlash() bool
|
||||
|
|
|
@ -89,5 +89,5 @@ func main() {
|
|||
sess.ShiftExpiration(ctx)
|
||||
})
|
||||
|
||||
app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithoutVersionChecker)
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
|
@ -25,21 +24,20 @@ func main() {
|
|||
// or set a value, like 2 hours:
|
||||
Expires: time.Hour * 2,
|
||||
// if you want to invalid cookies on different subdomains
|
||||
// of the same host, then enable it
|
||||
DisableSubdomainPersistence: false,
|
||||
// want to be crazy safe? Take a look at the "securecookie" example folder.
|
||||
// of the same host, then enable it.
|
||||
// Defaults to false.
|
||||
DisableSubdomainPersistence: true,
|
||||
})
|
||||
|
||||
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.
|
||||
s := sess.Start(ctx)
|
||||
s.Set("name", "iris")
|
||||
|
||||
//test if setted here
|
||||
//test if setted here.
|
||||
ctx.Writef("All ok session setted to: %s", s.GetString("name"))
|
||||
|
||||
// Set will set the value as-it-is,
|
||||
|
@ -52,7 +50,8 @@ func main() {
|
|||
})
|
||||
|
||||
app.Get("/get", func(ctx iris.Context) {
|
||||
// get a specific value, as string, if no found returns just an empty string
|
||||
// get a specific value, as string,
|
||||
// if not found then it returns just an empty string.
|
||||
name := sess.Start(ctx).GetString("name")
|
||||
|
||||
ctx.Writef("The name on the /set was: %s", name)
|
||||
|
@ -64,17 +63,16 @@ func main() {
|
|||
})
|
||||
|
||||
app.Get("/clear", func(ctx iris.Context) {
|
||||
// removes all entries
|
||||
// removes all entries.
|
||||
sess.Start(ctx).Clear()
|
||||
})
|
||||
|
||||
app.Get("/update", func(ctx iris.Context) {
|
||||
// updates expire date
|
||||
// updates expire date.
|
||||
sess.ShiftExpiration(ctx)
|
||||
})
|
||||
|
||||
app.Get("/destroy", func(ctx iris.Context) {
|
||||
|
||||
//destroy, removes the entire session data and cookie
|
||||
sess.Destroy(ctx)
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/kataras/iris/_examples/structuring/login-mvc-single-responsibility-package/user"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
|
@ -19,13 +20,7 @@ func main() {
|
|||
|
||||
app.StaticWeb("/public", "./public")
|
||||
|
||||
manager := sessions.New(sessions.Config{
|
||||
Cookie: "sessioncookiename",
|
||||
Expires: 24 * time.Hour,
|
||||
})
|
||||
users := user.NewDataSource()
|
||||
|
||||
app.Controller("/user", new(user.Controller), manager, users)
|
||||
mvc.Configure(app, configureMVC)
|
||||
|
||||
// http://localhost:8080/user/register
|
||||
// http://localhost:8080/user/login
|
||||
|
@ -35,9 +30,22 @@ func main() {
|
|||
app.Run(iris.Addr(":8080"), configure)
|
||||
}
|
||||
|
||||
func configureMVC(app *mvc.Application) {
|
||||
manager := sessions.New(sessions.Config{
|
||||
Cookie: "sessioncookiename",
|
||||
Expires: 24 * time.Hour,
|
||||
})
|
||||
|
||||
userApp := app.Party("/user")
|
||||
userApp.Register(
|
||||
user.NewDataSource(),
|
||||
manager.Start,
|
||||
)
|
||||
userApp.Handle(new(user.Controller))
|
||||
}
|
||||
|
||||
func configure(app *iris.Application) {
|
||||
app.Configure(
|
||||
iris.WithoutServerError(iris.ErrServerClosed),
|
||||
iris.WithCharset("UTF-8"),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,51 +6,52 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
const sessionIDKey = "UserID"
|
||||
|
||||
// paths
|
||||
const (
|
||||
PathLogin = "/user/login"
|
||||
PathLogout = "/user/logout"
|
||||
)
|
||||
|
||||
// the session key for the user id comes from the Session.
|
||||
const (
|
||||
sessionIDKey = "UserID"
|
||||
var (
|
||||
PathLogin = mvc.Response{Path: "/user/login"}
|
||||
PathLogout = mvc.Response{Path: "/user/logout"}
|
||||
)
|
||||
|
||||
// AuthController is the user authentication controller, a custom shared controller.
|
||||
type AuthController struct {
|
||||
iris.SessionController
|
||||
// context is auto-binded if struct depends on this,
|
||||
// in this controller we don't we do everything with mvc-style,
|
||||
// and that's neither the 30% of its features.
|
||||
// Ctx iris.Context
|
||||
|
||||
Source *DataSource
|
||||
User Model `iris:"model"`
|
||||
Session *sessions.Session
|
||||
|
||||
// the whole controller is request-scoped because we already depend on Session, so
|
||||
// this will be new for each new incoming request, BeginRequest sets that based on the session.
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// BeginRequest saves login state to the context, the user id.
|
||||
func (c *AuthController) BeginRequest(ctx iris.Context) {
|
||||
c.SessionController.BeginRequest(ctx)
|
||||
c.UserID, _ = c.Session.GetInt64(sessionIDKey)
|
||||
}
|
||||
|
||||
if userID := c.Session.Get(sessionIDKey); userID != nil {
|
||||
ctx.Values().Set(sessionIDKey, userID)
|
||||
// EndRequest is here just to complete the BaseController
|
||||
// in order to be tell iris to call the `BeginRequest` before the main method.
|
||||
func (c *AuthController) EndRequest(ctx iris.Context) {}
|
||||
|
||||
func (c *AuthController) fireError(err error) mvc.View {
|
||||
return mvc.View{
|
||||
Code: iris.StatusBadRequest,
|
||||
Name: "shared/error.html",
|
||||
Data: iris.Map{"Title": "User Error", "Message": strings.ToUpper(err.Error())},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AuthController) fireError(err error) {
|
||||
if err != nil {
|
||||
c.Ctx.Application().Logger().Debug(err.Error())
|
||||
|
||||
c.Status = 400
|
||||
c.Data["Title"] = "User Error"
|
||||
c.Data["Message"] = strings.ToUpper(err.Error())
|
||||
c.Tmpl = "shared/error.html"
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AuthController) redirectTo(id int64) {
|
||||
if id > 0 {
|
||||
c.Path = "/user/" + strconv.Itoa(int(id))
|
||||
}
|
||||
func (c *AuthController) redirectTo(id int64) mvc.Response {
|
||||
return mvc.Response{Path: "/user/" + strconv.Itoa(int(id))}
|
||||
}
|
||||
|
||||
func (c *AuthController) createOrUpdate(firstname, username, password string) (user Model, err error) {
|
||||
|
@ -75,8 +76,8 @@ func (c *AuthController) createOrUpdate(firstname, username, password string) (u
|
|||
|
||||
func (c *AuthController) isLoggedIn() bool {
|
||||
// we don't search by session, we have the user id
|
||||
// already by the `SaveState` middleware.
|
||||
return c.Values.Get(sessionIDKey) != nil
|
||||
// already by the `BeginRequest` middleware.
|
||||
return c.UserID > 0
|
||||
}
|
||||
|
||||
func (c *AuthController) verify(username, password string) (user Model, err error) {
|
||||
|
@ -101,24 +102,9 @@ func (c *AuthController) verify(username, password string) (user Model, err erro
|
|||
// if logged in then destroy the session
|
||||
// and redirect to the login page
|
||||
// otherwise redirect to the registration page.
|
||||
func (c *AuthController) logout() {
|
||||
func (c *AuthController) logout() mvc.Response {
|
||||
if c.isLoggedIn() {
|
||||
// c.Manager is the Sessions manager created
|
||||
// by the embedded SessionController, automatically.
|
||||
c.Manager.DestroyByID(c.Session.ID())
|
||||
return
|
||||
c.Session.Destroy()
|
||||
}
|
||||
|
||||
c.Path = PathLogin
|
||||
}
|
||||
|
||||
// AllowUser will check if this client is a logged user,
|
||||
// if not then it will redirect that guest to the login page
|
||||
// otherwise it will allow the execution of the next handler.
|
||||
func AllowUser(ctx iris.Context) {
|
||||
if ctx.Values().Get(sessionIDKey) != nil {
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
ctx.Redirect(PathLogin)
|
||||
return PathLogin
|
||||
}
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
package user
|
||||
|
||||
const (
|
||||
pathMyProfile = "/user/me"
|
||||
pathRegister = "/user/register"
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
var (
|
||||
// About Code: iris.StatusSeeOther ->
|
||||
// When redirecting from POST to GET request you -should- use this HTTP status code,
|
||||
// however there're some (complicated) alternatives if you
|
||||
// search online or even the HTTP RFC.
|
||||
// "See Other" RFC 7231
|
||||
pathMyProfile = mvc.Response{Path: "/user/me", Code: iris.StatusSeeOther}
|
||||
pathRegister = mvc.Response{Path: "/user/register"}
|
||||
)
|
||||
|
||||
// Controller is responsible to handle the following requests:
|
||||
|
@ -17,71 +27,89 @@ type Controller struct {
|
|||
AuthController
|
||||
}
|
||||
|
||||
// GetRegister handles GET:/user/register.
|
||||
func (c *Controller) GetRegister() {
|
||||
if c.isLoggedIn() {
|
||||
c.logout()
|
||||
return
|
||||
type formValue func(string) string
|
||||
|
||||
// BeforeActivation called once before the server start
|
||||
// and before the controller's registration, here you can add
|
||||
// dependencies, to this controller and only, that the main caller may skip.
|
||||
func (c *Controller) BeforeActivation(b mvc.BeforeActivation) {
|
||||
// bind the context's `FormValue` as well in order to be
|
||||
// acceptable on the controller or its methods' input arguments (NEW feature as well).
|
||||
b.Dependencies().Add(func(ctx iris.Context) formValue { return ctx.FormValue })
|
||||
}
|
||||
|
||||
c.Data["Title"] = "User Registration"
|
||||
c.Tmpl = pathRegister + ".html"
|
||||
type page struct {
|
||||
Title string
|
||||
}
|
||||
|
||||
// GetRegister handles GET:/user/register.
|
||||
// mvc.Result can accept any struct which contains a `Dispatch(ctx iris.Context)` method.
|
||||
// Both mvc.Response and mvc.View are mvc.Result.
|
||||
func (c *Controller) GetRegister() mvc.Result {
|
||||
if c.isLoggedIn() {
|
||||
return c.logout()
|
||||
}
|
||||
|
||||
// You could just use it as a variable to win some time in serve-time,
|
||||
// this is an exersise for you :)
|
||||
return mvc.View{
|
||||
Name: pathRegister.Path + ".html",
|
||||
Data: page{"User Registration"},
|
||||
}
|
||||
}
|
||||
|
||||
// PostRegister handles POST:/user/register.
|
||||
func (c *Controller) PostRegister() {
|
||||
func (c *Controller) PostRegister(form formValue) mvc.Result {
|
||||
// we can either use the `c.Ctx.ReadForm` or read values one by one.
|
||||
var (
|
||||
firstname = c.Ctx.FormValue("firstname")
|
||||
username = c.Ctx.FormValue("username")
|
||||
password = c.Ctx.FormValue("password")
|
||||
firstname = form("firstname")
|
||||
username = form("username")
|
||||
password = form("password")
|
||||
)
|
||||
|
||||
user, err := c.createOrUpdate(firstname, username, password)
|
||||
if err != nil {
|
||||
c.fireError(err)
|
||||
return
|
||||
return c.fireError(err)
|
||||
}
|
||||
|
||||
// setting a session value was never easier.
|
||||
c.Session.Set(sessionIDKey, user.ID)
|
||||
// succeed, nothing more to do here, just redirect to the /user/me.
|
||||
return pathMyProfile
|
||||
}
|
||||
|
||||
// When redirecting from POST to GET request you -should- use this HTTP status code,
|
||||
// however there're some (complicated) alternatives if you
|
||||
// search online or even the HTTP RFC.
|
||||
c.Status = 303 // "See Other" RFC 7231
|
||||
|
||||
// Redirect to GET: /user/me
|
||||
// by changing the Path (and the status code because we're in POST request at this case).
|
||||
c.Path = pathMyProfile
|
||||
// with these static views,
|
||||
// you can use variables-- that are initialized before server start
|
||||
// so you can win some time on serving.
|
||||
// You can do it else where as well but I let them as pracise for you,
|
||||
// essentially you can understand by just looking below.
|
||||
var userLoginView = mvc.View{
|
||||
Name: PathLogin.Path + ".html",
|
||||
Data: page{"User Login"},
|
||||
}
|
||||
|
||||
// GetLogin handles GET:/user/login.
|
||||
func (c *Controller) GetLogin() {
|
||||
func (c *Controller) GetLogin() mvc.Result {
|
||||
if c.isLoggedIn() {
|
||||
c.logout()
|
||||
return
|
||||
return c.logout()
|
||||
}
|
||||
c.Data["Title"] = "User Login"
|
||||
c.Tmpl = PathLogin + ".html"
|
||||
return userLoginView
|
||||
}
|
||||
|
||||
// PostLogin handles POST:/user/login.
|
||||
func (c *Controller) PostLogin() {
|
||||
func (c *Controller) PostLogin(form formValue) mvc.Result {
|
||||
var (
|
||||
username = c.Ctx.FormValue("username")
|
||||
password = c.Ctx.FormValue("password")
|
||||
username = form("username")
|
||||
password = form("password")
|
||||
)
|
||||
|
||||
user, err := c.verify(username, password)
|
||||
if err != nil {
|
||||
c.fireError(err)
|
||||
return
|
||||
return c.fireError(err)
|
||||
}
|
||||
|
||||
c.Session.Set(sessionIDKey, user.ID)
|
||||
c.Path = pathMyProfile
|
||||
return pathMyProfile
|
||||
}
|
||||
|
||||
// AnyLogout handles any method on path /user/logout.
|
||||
|
@ -90,44 +118,72 @@ func (c *Controller) AnyLogout() {
|
|||
}
|
||||
|
||||
// GetMe handles GET:/user/me.
|
||||
func (c *Controller) GetMe() {
|
||||
func (c *Controller) GetMe() mvc.Result {
|
||||
id, err := c.Session.GetInt64(sessionIDKey)
|
||||
if err != nil || id <= 0 {
|
||||
// when not already logged in.
|
||||
c.Path = PathLogin
|
||||
return
|
||||
// when not already logged in, redirect to login.
|
||||
return PathLogin
|
||||
}
|
||||
|
||||
u, found := c.Source.GetByID(id)
|
||||
if !found {
|
||||
// if the session exists but for some reason the user doesn't exist in the "database"
|
||||
// then logout him and redirect to the register page.
|
||||
c.logout()
|
||||
return
|
||||
return c.logout()
|
||||
}
|
||||
|
||||
// set the model and render the view template.
|
||||
c.User = u
|
||||
c.Data["Title"] = "Profile of " + u.Username
|
||||
c.Tmpl = pathMyProfile + ".html"
|
||||
return mvc.View{
|
||||
Name: pathMyProfile.Path + ".html",
|
||||
Data: iris.Map{
|
||||
"Title": "Profile of " + u.Username,
|
||||
"User": u,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) renderNotFound(id int64) {
|
||||
c.Status = 404
|
||||
c.Data["Title"] = "User Not Found"
|
||||
c.Data["ID"] = id
|
||||
c.Tmpl = "user/notfound.html"
|
||||
func (c *Controller) renderNotFound(id int64) mvc.View {
|
||||
return mvc.View{
|
||||
Code: iris.StatusNotFound,
|
||||
Name: "user/notfound.html",
|
||||
Data: iris.Map{
|
||||
"Title": "User Not Found",
|
||||
"ID": id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch completes the `mvc.Result` interface
|
||||
// in order to be able to return a type of `Model`
|
||||
// as mvc.Result.
|
||||
// If this function didn't exist then
|
||||
// we should explicit set the output result to that Model or to an interface{}.
|
||||
func (u Model) Dispatch(ctx iris.Context) {
|
||||
ctx.JSON(u)
|
||||
}
|
||||
|
||||
// GetBy handles GET:/user/{id:long},
|
||||
// i.e http://localhost:8080/user/1
|
||||
func (c *Controller) GetBy(userID int64) {
|
||||
func (c *Controller) GetBy(userID int64) mvc.Result {
|
||||
// we have /user/{id}
|
||||
// fetch and render user json.
|
||||
if user, found := c.Source.GetByID(userID); !found {
|
||||
user, found := c.Source.GetByID(userID)
|
||||
if !found {
|
||||
// not user found with that ID.
|
||||
c.renderNotFound(userID)
|
||||
} else {
|
||||
c.Ctx.JSON(user)
|
||||
return c.renderNotFound(userID)
|
||||
}
|
||||
|
||||
// Q: how the hell Model can be return as mvc.Result?
|
||||
// A: I told you before on some comments and the docs,
|
||||
// any struct that has a `Dispatch(ctx iris.Context)`
|
||||
// can be returned as an mvc.Result(see ~20 lines above),
|
||||
// therefore we are able to combine many type of results in the same method.
|
||||
// For example, here, we return either an mvc.View to render a not found custom template
|
||||
// either a user which returns the Model as JSON via its Dispatch.
|
||||
//
|
||||
// We could also return just a struct value that is not an mvc.Result,
|
||||
// if the output result of the `GetBy` was that struct's type or an interface{}
|
||||
// and iris would render that with JSON as well, but here we can't do that without complete the `Dispatch`
|
||||
// function, because we may return an mvc.View which is an mvc.Result.
|
||||
return user
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -10,7 +11,7 @@ func main() {
|
|||
templates := iris.HTML("./views", ".html").Layout("shared/layout.html")
|
||||
app.RegisterView(templates)
|
||||
|
||||
app.Controller("/", new(Controller))
|
||||
mvc.New(app).Handle(new(Controller))
|
||||
|
||||
// http://localhost:9091
|
||||
app.Run(iris.Addr(":9091"))
|
||||
|
@ -21,22 +22,28 @@ type Layout struct {
|
|||
Title string
|
||||
}
|
||||
|
||||
// Controller is our example controller.
|
||||
// Controller is our example controller, request-scoped, each request has its own instance.
|
||||
type Controller struct {
|
||||
iris.Controller
|
||||
|
||||
Layout Layout `iris:"model"`
|
||||
Layout Layout
|
||||
}
|
||||
|
||||
// BeginRequest is the first method fires when client requests from this Controller's path.
|
||||
// BeginRequest is the first method fired when client requests from this Controller's root path.
|
||||
func (c *Controller) BeginRequest(ctx iris.Context) {
|
||||
c.Controller.BeginRequest(ctx)
|
||||
|
||||
c.Layout.Title = "Home Page"
|
||||
}
|
||||
|
||||
// EndRequest is the last method fired.
|
||||
// It's here just to complete the BaseController
|
||||
// in order to be tell iris to call the `BeginRequest` before the main method.
|
||||
func (c *Controller) EndRequest(ctx iris.Context) {}
|
||||
|
||||
// Get handles GET http://localhost:9091
|
||||
func (c *Controller) Get() {
|
||||
c.Tmpl = "index.html"
|
||||
c.Data["Message"] = "Welcome to my website!"
|
||||
func (c *Controller) Get() mvc.View {
|
||||
return mvc.View{
|
||||
Name: "index.html",
|
||||
Data: iris.Map{
|
||||
"Layout": c.Layout,
|
||||
"Message": "Welcome to my website!",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,18 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type postValue func(string) string
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Controller("/user", new(UserController))
|
||||
mvc.New(app.Party("/user")).Register(
|
||||
func(ctx iris.Context) postValue {
|
||||
return ctx.PostValue
|
||||
}).Handle(new(UserController))
|
||||
|
||||
// GET http://localhost:9092/user
|
||||
// GET http://localhost:9092/user/42
|
||||
|
@ -20,37 +25,44 @@ func main() {
|
|||
}
|
||||
|
||||
// UserController is our user example controller.
|
||||
type UserController struct {
|
||||
iris.Controller
|
||||
}
|
||||
type UserController struct{}
|
||||
|
||||
// Get handles GET /user
|
||||
func (c *UserController) Get() {
|
||||
c.Ctx.Writef("Select all users")
|
||||
func (c *UserController) Get() string {
|
||||
return "Select all users"
|
||||
}
|
||||
|
||||
// GetBy handles GET /user/42
|
||||
func (c *UserController) GetBy(id int) {
|
||||
c.Ctx.Writef("Select user by ID: %d", id)
|
||||
// User is our test User model, nothing tremendous here.
|
||||
type User struct{ ID int64 }
|
||||
|
||||
// GetBy handles GET /user/42, equal to .Get("/user/{id:long}")
|
||||
func (c *UserController) GetBy(id int64) User {
|
||||
// Select User by ID == $id.
|
||||
return User{id}
|
||||
}
|
||||
|
||||
// Post handles POST /user
|
||||
func (c *UserController) Post() {
|
||||
username := c.Ctx.PostValue("username")
|
||||
c.Ctx.Writef("Create by user with username: %s", username)
|
||||
func (c *UserController) Post(post postValue) string {
|
||||
username := post("username")
|
||||
return "Create by user with username: " + username
|
||||
}
|
||||
|
||||
// PutBy handles PUT /user/42
|
||||
func (c *UserController) PutBy(id int) {
|
||||
c.Ctx.Writef("Update user by ID: %d", id)
|
||||
func (c *UserController) PutBy(id int) string {
|
||||
// Update user by ID == $id
|
||||
return "User updated"
|
||||
}
|
||||
|
||||
// DeleteBy handles DELETE /user/42
|
||||
func (c *UserController) DeleteBy(id int) {
|
||||
c.Ctx.Writef("Delete user by ID: %d", id)
|
||||
func (c *UserController) DeleteBy(id int) bool {
|
||||
// Delete user by ID == %id
|
||||
//
|
||||
// when boolean then true = iris.StatusOK, false = iris.StatusNotFound
|
||||
return true
|
||||
}
|
||||
|
||||
// GetFollowersBy handles GET /user/followers/42
|
||||
func (c *UserController) GetFollowersBy(id int) {
|
||||
c.Ctx.Writef("Select all followers by user ID: %d", id)
|
||||
func (c *UserController) GetFollowersBy(id int) []User {
|
||||
// Select all followers by user ID == $id
|
||||
return []User{ /* ... */ }
|
||||
}
|
||||
|
|
567
_examples/tutorial/vuejs-todo-mvc/README.md
Normal file
567
_examples/tutorial/vuejs-todo-mvc/README.md
Normal file
|
@ -0,0 +1,567 @@
|
|||
# A Todo MVC Application using Iris and Vue.js
|
||||
|
||||
Vue.js is a front-end framework for building web applications using javascript. It has a blazing fast Virtual DOM renderer.
|
||||
|
||||
Iris is a back-end framework for building web applications using The Go Programming Language (disclaimer: author here). It's one of the fastest and featured web frameworks out there. We wanna use this to serve our "todo service".
|
||||
|
||||
## The Tools
|
||||
|
||||
Programming Languages are just tools for us, but we need a safe, fast and “cross-platform” programming language to power our service.
|
||||
|
||||
[Go](https://golang.org) is a [rapidly growing](https://www.tiobe.com/tiobe-index/) open source programming language designed for building simple, fast, and reliable software. Take a look [here](https://github.com/golang/go/wiki/GoUsers) which great companies use Go to power their services.
|
||||
|
||||
### Install the Go Programming Language
|
||||
|
||||
Extensive information about downloading & installing Go can be found [here](https://golang.org/dl/).
|
||||
|
||||
[](https://youtu.be/9x-pG3lvLi0)
|
||||
|
||||
> Maybe [Windows](https://www.youtube.com/watch?v=WT5mTznJBS0) or [Mac OS X](https://www.youtube.com/watch?v=5qI8z_lB5Lw) user?
|
||||
|
||||
> The article does not contain an introduction to the language itself, if you’re a newcomer I recommend you to bookmark this article, [learn](https://github.com/golang/go/wiki/Learn) the language’s fundamentals and come back later on.
|
||||
|
||||
## The Dependencies
|
||||
|
||||
Many articles have been written, in the past, that lead developers not to use a web framework because they are useless and "bad". I have to tell you that there is no such thing, it always depends on the (web) framework that you’re going to use. At production environment, we don’t have the time or the experience to code everything that we wanna use in the applications, and if we could are we sure that we can do better and safely than others? In short term: **Good frameworks are helpful tools for any developer, company or startup and "bad" frameworks are waste of time, crystal clear.**
|
||||
|
||||
You’ll need two dependencies:
|
||||
|
||||
1. Vue.js, for our client-side requirements. Download it from [here](https://vuejs.org/), latest v2.
|
||||
2. The Iris Web Framework, for our server-side requirements. Can be found [here](https://github.com/kataras/iris), latest v10.
|
||||
|
||||
> If you have Go already installed then just execute `go get -u github.com/kataras/iris` to install the Iris Web Framework.
|
||||
|
||||
## Start
|
||||
|
||||
If we are all in the same page, it’s time to learn how we can create a live todo application that will be easy to deploy and extend even more!
|
||||
|
||||
We're going to use a vue.js todo application which uses browser'
|
||||
s local storage and doesn't have any user-specified features like live sync between browser's tabs, you can find the original version inside the vue's [docs](https://vuejs.org/v2/examples/todomvc.html).
|
||||
|
||||
Assuming that you know how %GOPATH% works, create an empty folder, i.e "vuejs-todo-mvc" in the %GOPATH%/src directory, there you will create those files:
|
||||
|
||||
- web/public/js/app.js
|
||||
- web/public/index.html
|
||||
- todo/item.go
|
||||
- todo/service.go
|
||||
- web/controllers/todo_controller.go
|
||||
- web/main.go
|
||||
|
||||
_Read the comments in the source code, they may be very helpful_
|
||||
|
||||
### The client-side (vue.js)
|
||||
|
||||
```js
|
||||
/* file: vuejs-todo-mvc/web/public/js/app.js */
|
||||
// Full spec-compliant TodoMVC with Iris
|
||||
// and hash-based routing in ~200 effective lines of JavaScript.
|
||||
|
||||
var socket = new Ws("ws://localhost:8080/todos/sync");
|
||||
|
||||
socket.On("saved", function () {
|
||||
// console.log("receive: on saved");
|
||||
fetchTodos(function (items) {
|
||||
app.todos = items
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function fetchTodos(onComplete) {
|
||||
axios.get("/todos").then(response => {
|
||||
if (response.data === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
onComplete(response.data);
|
||||
});
|
||||
}
|
||||
|
||||
var todoStorage = {
|
||||
fetch: function () {
|
||||
var todos = [];
|
||||
fetchTodos(function (items) {
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
todos.push(items[i]);
|
||||
}
|
||||
});
|
||||
return todos;
|
||||
},
|
||||
save: function (todos) {
|
||||
axios.post("/todos", JSON.stringify(todos)).then(response => {
|
||||
if (!response.data.success) {
|
||||
window.alert("saving had a failure");
|
||||
return;
|
||||
}
|
||||
// console.log("send: save");
|
||||
socket.Emit("save")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// visibility filters
|
||||
var filters = {
|
||||
all: function (todos) {
|
||||
return todos
|
||||
},
|
||||
active: function (todos) {
|
||||
return todos.filter(function (todo) {
|
||||
return !todo.completed
|
||||
})
|
||||
},
|
||||
completed: function (todos) {
|
||||
return todos.filter(function (todo) {
|
||||
return todo.completed
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// app Vue instance
|
||||
var app = new Vue({
|
||||
// app initial state
|
||||
data: {
|
||||
todos: todoStorage.fetch(),
|
||||
newTodo: '',
|
||||
editedTodo: null,
|
||||
visibility: 'all'
|
||||
},
|
||||
|
||||
// we will not use the "watch" as it works with the fields like "hasChanges"
|
||||
// and callbacks to make it true but let's keep things very simple as it's just a small getting started.
|
||||
// // watch todos change for persistence
|
||||
// watch: {
|
||||
// todos: {
|
||||
// handler: function (todos) {
|
||||
// if (app.hasChanges) {
|
||||
// todoStorage.save(todos);
|
||||
// app.hasChanges = false;
|
||||
// }
|
||||
|
||||
// },
|
||||
// deep: true
|
||||
// }
|
||||
// },
|
||||
|
||||
// computed properties
|
||||
// http://vuejs.org/guide/computed.html
|
||||
computed: {
|
||||
filteredTodos: function () {
|
||||
return filters[this.visibility](this.todos)
|
||||
},
|
||||
remaining: function () {
|
||||
return filters.active(this.todos).length
|
||||
},
|
||||
allDone: {
|
||||
get: function () {
|
||||
return this.remaining === 0
|
||||
},
|
||||
set: function (value) {
|
||||
this.todos.forEach(function (todo) {
|
||||
todo.completed = value
|
||||
})
|
||||
this.notifyChange();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
pluralize: function (n) {
|
||||
return n === 1 ? 'item' : 'items'
|
||||
}
|
||||
},
|
||||
|
||||
// methods that implement data logic.
|
||||
// note there's no DOM manipulation here at all.
|
||||
methods: {
|
||||
notifyChange: function () {
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
addTodo: function () {
|
||||
var value = this.newTodo && this.newTodo.trim()
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
this.todos.push({
|
||||
id: this.todos.length + 1, // just for the client-side.
|
||||
title: value,
|
||||
completed: false
|
||||
})
|
||||
this.newTodo = ''
|
||||
this.notifyChange();
|
||||
},
|
||||
|
||||
completeTodo: function (todo) {
|
||||
if (todo.completed) {
|
||||
todo.completed = false;
|
||||
} else {
|
||||
todo.completed = true;
|
||||
}
|
||||
this.notifyChange();
|
||||
},
|
||||
removeTodo: function (todo) {
|
||||
this.todos.splice(this.todos.indexOf(todo), 1)
|
||||
this.notifyChange();
|
||||
},
|
||||
|
||||
editTodo: function (todo) {
|
||||
this.beforeEditCache = todo.title
|
||||
this.editedTodo = todo
|
||||
},
|
||||
|
||||
doneEdit: function (todo) {
|
||||
if (!this.editedTodo) {
|
||||
return
|
||||
}
|
||||
this.editedTodo = null
|
||||
todo.title = todo.title.trim();
|
||||
if (!todo.title) {
|
||||
this.removeTodo(todo);
|
||||
}
|
||||
this.notifyChange();
|
||||
},
|
||||
|
||||
cancelEdit: function (todo) {
|
||||
this.editedTodo = null
|
||||
todo.title = this.beforeEditCache
|
||||
},
|
||||
|
||||
removeCompleted: function () {
|
||||
this.todos = filters.active(this.todos);
|
||||
this.notifyChange();
|
||||
}
|
||||
},
|
||||
|
||||
// a custom directive to wait for the DOM to be updated
|
||||
// before focusing on the input field.
|
||||
// http://vuejs.org/guide/custom-directive.html
|
||||
directives: {
|
||||
'todo-focus': function (el, binding) {
|
||||
if (binding.value) {
|
||||
el.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// handle routing
|
||||
function onHashChange() {
|
||||
var visibility = window.location.hash.replace(/#\/?/, '')
|
||||
if (filters[visibility]) {
|
||||
app.visibility = visibility
|
||||
} else {
|
||||
window.location.hash = ''
|
||||
app.visibility = 'all'
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', onHashChange)
|
||||
onHashChange()
|
||||
|
||||
// mount
|
||||
app.$mount('.todoapp')
|
||||
```
|
||||
|
||||
Let's add our view, the static html.
|
||||
|
||||
```html
|
||||
<!-- file: vuejs-todo-mvc/web/public/index.html -->
|
||||
<!doctype html>
|
||||
<html data-framework="vue">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Iris + Vue.js • TodoMVC</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css@2.0.4/index.css">
|
||||
<!-- this needs to be loaded before guide's inline scripts -->
|
||||
<script src="https://vuejs.org/js/vue.js"></script>
|
||||
<!-- $http -->
|
||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||
<!-- -->
|
||||
<script src="https://unpkg.com/director@1.2.8/build/director.js"></script>
|
||||
<!-- websocket sync between multiple tabs -->
|
||||
<script src="/todos/iris-ws.js"></script>
|
||||
<!-- -->
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<h1>todos</h1>
|
||||
<input class="new-todo" autofocus autocomplete="off" placeholder="What needs to be done?" v-model="newTodo" @keyup.enter="addTodo">
|
||||
</header>
|
||||
<section class="main" v-show="todos.length" v-cloak>
|
||||
<input class="toggle-all" type="checkbox" v-model="allDone">
|
||||
<ul class="todo-list">
|
||||
<li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{ completed: todo.completed, editing: todo == editedTodo }">
|
||||
<div class="view">
|
||||
<!-- v-model="todo.completed" -->
|
||||
<input class="toggle" type="checkbox" @click="completeTodo(todo)">
|
||||
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
|
||||
<button class="destroy" @click="removeTodo(todo)"></button>
|
||||
</div>
|
||||
<input class="edit" type="text" v-model="todo.title" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)"
|
||||
@keyup.esc="cancelEdit(todo)">
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="footer" v-show="todos.length" v-cloak>
|
||||
<span class="todo-count">
|
||||
<strong>{{ remaining }}</strong> {{ remaining | pluralize }} left
|
||||
</span>
|
||||
<ul class="filters">
|
||||
<li>
|
||||
<a href="#/all" :class="{ selected: visibility == 'all' }">All</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/active" :class="{ selected: visibility == 'active' }">Active</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/completed" :class="{ selected: visibility == 'completed' }">Completed</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
|
||||
Clear completed
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
<footer class="info">
|
||||
<p>Double-click to edit a todo</p>
|
||||
</footer>
|
||||
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
### The server-side (iris)
|
||||
|
||||
Our view model.
|
||||
|
||||
```go
|
||||
// file: vuejs-todo-mvc/todo/item.go
|
||||
package todo
|
||||
|
||||
type Item struct {
|
||||
SessionID string `json:"-"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Completed bool `json:"completed"`
|
||||
}
|
||||
```
|
||||
|
||||
Our service.
|
||||
|
||||
```go
|
||||
// file: vuejs-todo-mvc/todo/service.go
|
||||
package todo
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Get(owner string) []Item
|
||||
Save(owner string, newItems []Item) error
|
||||
}
|
||||
|
||||
type MemoryService struct {
|
||||
// key = session id, value the list of todo items that this session id has.
|
||||
items map[string][]Item
|
||||
// protected by locker for concurrent access.
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMemoryService() *MemoryService {
|
||||
return &MemoryService{
|
||||
items: make(map[string][]Item, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MemoryService) Get(sessionOwner string) []Item {
|
||||
s.mu.RLock()
|
||||
items := s.items[sessionOwner]
|
||||
s.mu.RUnlock()
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func (s *MemoryService) Save(sessionOwner string, newItems []Item) error {
|
||||
var prevID int64
|
||||
for i := range newItems {
|
||||
if newItems[i].ID == 0 {
|
||||
newItems[i].ID = prevID
|
||||
prevID++
|
||||
}
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.items[sessionOwner] = newItems
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
We are going to use some of the MVC functionalities of the iris web framework here but you can do the same with the standard API as well.
|
||||
|
||||
```go
|
||||
// file: vuejs-todo-mvc/controllers/todo_controller.go
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"vuejs-todo-mvc/todo"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris/sessions"
|
||||
"github.com/kataras/iris/websocket"
|
||||
)
|
||||
|
||||
// TodoController is our TODO app's web controller.
|
||||
type TodoController struct {
|
||||
Service todo.Service
|
||||
|
||||
Session *sessions.Session
|
||||
}
|
||||
|
||||
// BeforeActivation called once before the server ran, and before
|
||||
// the routes and dependencies binded.
|
||||
// You can bind custom things to the controller, add new methods, add middleware,
|
||||
// add dependencies to the struct or the method(s) and more.
|
||||
func (c *TodoController) BeforeActivation(b mvc.BeforeActivation) {
|
||||
// this could be binded to a controller's function input argument
|
||||
// if any, or struct field if any:
|
||||
b.Dependencies().Add(func(ctx iris.Context) (items []todo.Item) {
|
||||
ctx.ReadJSON(&items)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Get handles the GET: /todos route.
|
||||
func (c *TodoController) Get() []todo.Item {
|
||||
return c.Service.Get(c.Session.ID())
|
||||
}
|
||||
|
||||
// PostItemResponse the response data that will be returned as json
|
||||
// after a post save action of all todo items.
|
||||
type PostItemResponse struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
var emptyResponse = PostItemResponse{Success: false}
|
||||
|
||||
// Post handles the POST: /todos route.
|
||||
func (c *TodoController) Post(newItems []todo.Item) PostItemResponse {
|
||||
if err := c.Service.Save(c.Session.ID(), newItems); err != nil {
|
||||
return emptyResponse
|
||||
}
|
||||
|
||||
return PostItemResponse{Success: true}
|
||||
}
|
||||
|
||||
func (c *TodoController) GetSync(conn websocket.Connection) {
|
||||
conn.Join(c.Session.ID())
|
||||
conn.On("save", func() { // "save" event from client.
|
||||
conn.To(c.Session.ID()).Emit("saved", nil) // fire a "saved" event to the rest of the clients w.
|
||||
})
|
||||
|
||||
conn.Wait()
|
||||
}
|
||||
```
|
||||
|
||||
And finally our main application's endpoint.
|
||||
|
||||
```go
|
||||
// file: web/main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"vuejs-todo-mvc/todo"
|
||||
"vuejs-todo-mvc/web/controllers"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/sessions"
|
||||
"github.com/kataras/iris/websocket"
|
||||
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// serve our app in public, public folder
|
||||
// contains the client-side vue.js application,
|
||||
// no need for any server-side template here,
|
||||
// actually if you're going to just use vue without any
|
||||
// back-end services, you can just stop afer this line and start the server.
|
||||
app.StaticWeb("/", "./public")
|
||||
|
||||
// configure the http sessions.
|
||||
sess := sessions.New(sessions.Config{
|
||||
Cookie: "iris_session",
|
||||
})
|
||||
|
||||
// configure the websocket server.
|
||||
ws := websocket.New(websocket.Config{})
|
||||
|
||||
// create a sub router and register the client-side library for the iris websockets,
|
||||
// you could skip it but iris websockets supports socket.io-like API.
|
||||
todosRouter := app.Party("/todos")
|
||||
// http://localhost:8080/todos/iris-ws.js
|
||||
// serve the javascript client library to communicate with
|
||||
// the iris high level websocket event system.
|
||||
todosRouter.Any("/iris-ws.js", websocket.ClientHandler())
|
||||
|
||||
// create our mvc application targeted to /todos relative sub path.
|
||||
todosApp := mvc.New(todosRouter)
|
||||
|
||||
// any dependencies bindings here...
|
||||
todosApp.Register(
|
||||
todo.NewMemoryService(),
|
||||
sess.Start,
|
||||
ws.Upgrade,
|
||||
)
|
||||
|
||||
// controllers registration here...
|
||||
todosApp.Handle(new(controllers.TodoController))
|
||||
|
||||
// start the web server at http://localhost:8080
|
||||
app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker)
|
||||
}
|
||||
```
|
||||
|
||||
Run the Iris web server you've just created by executing `go run main.go` from your current path (%GOPATH%/src/%your_folder%/web/).
|
||||
|
||||
```sh
|
||||
$ go run main.go
|
||||
Now listening on: http://localhost:8080
|
||||
Application Started. Press CTRL+C to shut down.
|
||||
_
|
||||
```
|
||||
|
||||
Open one or more browser tabs at: http://localhost:8080 and have fun!
|
||||
|
||||

|
||||
|
||||
### Download the Source Code
|
||||
|
||||
The whole project, all the files you saw in this article are located at: https://github.com/kataras/iris/tree/master/_examples/tutorial/vuejs-todo-mvc
|
||||
|
||||
## References
|
||||
|
||||
https://vuejs.org/v2/examples/todomvc.html (using browser's local storage)
|
||||
|
||||
https://github.com/kataras/iris/tree/master/_examples/mvc (mvc examples and features overview repository)
|
||||
|
||||
## Thank you, once again
|
||||
|
||||
Happy new year and thank you for your pattience, once again:) Don't hesitate to post any questions and provide feedback(I'm very active dev therefore you will be heard here!)
|
||||
|
||||
Don't forget to check out my medium profile and twitter as well, I'm posting some (useful) stuff there too:)
|
||||
|
||||
- https://medium.com/@kataras
|
||||
- https://twitter.com/MakisMaropoulos
|
BIN
_examples/tutorial/vuejs-todo-mvc/screen.png
Normal file
BIN
_examples/tutorial/vuejs-todo-mvc/screen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
8
_examples/tutorial/vuejs-todo-mvc/src/todo/item.go
Normal file
8
_examples/tutorial/vuejs-todo-mvc/src/todo/item.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package todo
|
||||
|
||||
type Item struct {
|
||||
SessionID string `json:"-"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Completed bool `json:"completed"`
|
||||
}
|
46
_examples/tutorial/vuejs-todo-mvc/src/todo/service.go
Normal file
46
_examples/tutorial/vuejs-todo-mvc/src/todo/service.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package todo
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Get(owner string) []Item
|
||||
Save(owner string, newItems []Item) error
|
||||
}
|
||||
|
||||
type MemoryService struct {
|
||||
// key = session id, value the list of todo items that this session id has.
|
||||
items map[string][]Item
|
||||
// protected by locker for concurrent access.
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMemoryService() *MemoryService {
|
||||
return &MemoryService{
|
||||
items: make(map[string][]Item, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MemoryService) Get(sessionOwner string) []Item {
|
||||
s.mu.RLock()
|
||||
items := s.items[sessionOwner]
|
||||
s.mu.RUnlock()
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func (s *MemoryService) Save(sessionOwner string, newItems []Item) error {
|
||||
var prevID int64
|
||||
for i := range newItems {
|
||||
if newItems[i].ID == 0 {
|
||||
newItems[i].ID = prevID
|
||||
prevID++
|
||||
}
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.items[sessionOwner] = newItems
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/todo"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
"github.com/kataras/iris/sessions"
|
||||
"github.com/kataras/iris/websocket"
|
||||
)
|
||||
|
||||
// TodoController is our TODO app's web controller.
|
||||
type TodoController struct {
|
||||
Service todo.Service
|
||||
|
||||
Session *sessions.Session
|
||||
}
|
||||
|
||||
// BeforeActivation called once before the server ran, and before
|
||||
// the routes and dependencies binded.
|
||||
// You can bind custom things to the controller, add new methods, add middleware,
|
||||
// add dependencies to the struct or the method(s) and more.
|
||||
func (c *TodoController) BeforeActivation(b mvc.BeforeActivation) {
|
||||
// this could be binded to a controller's function input argument
|
||||
// if any, or struct field if any:
|
||||
b.Dependencies().Add(func(ctx iris.Context) (items []todo.Item) {
|
||||
ctx.ReadJSON(&items)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Get handles the GET: /todos route.
|
||||
func (c *TodoController) Get() []todo.Item {
|
||||
return c.Service.Get(c.Session.ID())
|
||||
}
|
||||
|
||||
// PostItemResponse the response data that will be returned as json
|
||||
// after a post save action of all todo items.
|
||||
type PostItemResponse struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
var emptyResponse = PostItemResponse{Success: false}
|
||||
|
||||
// Post handles the POST: /todos route.
|
||||
func (c *TodoController) Post(newItems []todo.Item) PostItemResponse {
|
||||
if err := c.Service.Save(c.Session.ID(), newItems); err != nil {
|
||||
return emptyResponse
|
||||
}
|
||||
|
||||
return PostItemResponse{Success: true}
|
||||
}
|
||||
|
||||
func (c *TodoController) GetSync(conn websocket.Connection) {
|
||||
// join to the session in order to send "saved"
|
||||
// events only to a single user, that means
|
||||
// that if user has opened more than one browser window/tab
|
||||
// of the same session then the changes will be reflected to one another.
|
||||
conn.Join(c.Session.ID())
|
||||
conn.On("save", func() { // "save" event from client.
|
||||
conn.To(c.Session.ID()).Emit("saved", nil) // fire a "saved" event to the rest of the clients w.
|
||||
})
|
||||
|
||||
conn.Wait()
|
||||
}
|
55
_examples/tutorial/vuejs-todo-mvc/src/web/main.go
Normal file
55
_examples/tutorial/vuejs-todo-mvc/src/web/main.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/todo"
|
||||
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/web/controllers"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/sessions"
|
||||
"github.com/kataras/iris/websocket"
|
||||
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// serve our app in public, public folder
|
||||
// contains the client-side vue.js application,
|
||||
// no need for any server-side template here,
|
||||
// actually if you're going to just use vue without any
|
||||
// back-end services, you can just stop afer this line and start the server.
|
||||
app.StaticWeb("/", "./public")
|
||||
|
||||
// configure the http sessions.
|
||||
sess := sessions.New(sessions.Config{
|
||||
Cookie: "iris_session",
|
||||
})
|
||||
|
||||
// configure the websocket server.
|
||||
ws := websocket.New(websocket.Config{})
|
||||
|
||||
// create a sub router and register the client-side library for the iris websockets,
|
||||
// you could skip it but iris websockets supports socket.io-like API.
|
||||
todosRouter := app.Party("/todos")
|
||||
// http://localhost:8080/todos/iris-ws.js
|
||||
// serve the javascript client library to communicate with
|
||||
// the iris high level websocket event system.
|
||||
todosRouter.Any("/iris-ws.js", websocket.ClientHandler())
|
||||
|
||||
// create our mvc application targeted to /todos relative sub path.
|
||||
todosApp := mvc.New(todosRouter)
|
||||
|
||||
// any dependencies bindings here...
|
||||
todosApp.Register(
|
||||
todo.NewMemoryService(),
|
||||
sess.Start,
|
||||
ws.Upgrade,
|
||||
)
|
||||
|
||||
// controllers registration here...
|
||||
todosApp.Handle(new(controllers.TodoController))
|
||||
|
||||
// start the web server at http://localhost:8080
|
||||
app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker)
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
index.css is not here to reduce the disk space for the examples.
|
||||
https://unpkg.com/todomvc-app-css@2.0.4/index.css is used instead.
|
72
_examples/tutorial/vuejs-todo-mvc/src/web/public/index.html
Normal file
72
_examples/tutorial/vuejs-todo-mvc/src/web/public/index.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
<!doctype html>
|
||||
<html data-framework="vue">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Iris + Vue.js • TodoMVC</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css@2.0.4/index.css">
|
||||
<!-- this needs to be loaded before guide's inline scripts -->
|
||||
<script src="https://vuejs.org/js/vue.js"></script>
|
||||
<!-- $http -->
|
||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||
<!-- -->
|
||||
<script src="https://unpkg.com/director@1.2.8/build/director.js"></script>
|
||||
<!-- websocket sync between multiple tabs -->
|
||||
<script src="/todos/iris-ws.js"></script>
|
||||
<!-- -->
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<h1>todos</h1>
|
||||
<input class="new-todo" autofocus autocomplete="off" placeholder="What needs to be done?" v-model="newTodo" @keyup.enter="addTodo">
|
||||
</header>
|
||||
<section class="main" v-show="todos.length" v-cloak>
|
||||
<input class="toggle-all" type="checkbox" v-model="allDone">
|
||||
<ul class="todo-list">
|
||||
<li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{ completed: todo.completed, editing: todo == editedTodo }">
|
||||
<div class="view">
|
||||
<!-- v-model="todo.completed" -->
|
||||
<input class="toggle" type="checkbox" @click="completeTodo(todo)">
|
||||
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
|
||||
<button class="destroy" @click="removeTodo(todo)"></button>
|
||||
</div>
|
||||
<input class="edit" type="text" v-model="todo.title" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)"
|
||||
@keyup.esc="cancelEdit(todo)">
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="footer" v-show="todos.length" v-cloak>
|
||||
<span class="todo-count">
|
||||
<strong>{{ remaining }}</strong> {{ remaining | pluralize }} left
|
||||
</span>
|
||||
<ul class="filters">
|
||||
<li>
|
||||
<a href="#/all" :class="{ selected: visibility == 'all' }">All</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/active" :class="{ selected: visibility == 'active' }">Active</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/completed" :class="{ selected: visibility == 'completed' }">Completed</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
|
||||
Clear completed
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
<footer class="info">
|
||||
<p>Double-click to edit a todo</p>
|
||||
</footer>
|
||||
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
205
_examples/tutorial/vuejs-todo-mvc/src/web/public/js/app.js
Normal file
205
_examples/tutorial/vuejs-todo-mvc/src/web/public/js/app.js
Normal file
|
@ -0,0 +1,205 @@
|
|||
// Full spec-compliant TodoMVC with Iris
|
||||
// and hash-based routing in ~200 effective lines of JavaScript.
|
||||
|
||||
var socket = new Ws("ws://localhost:8080/todos/sync");
|
||||
|
||||
socket.On("saved", function () {
|
||||
// console.log("receive: on saved");
|
||||
fetchTodos(function (items) {
|
||||
app.todos = items
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function fetchTodos(onComplete) {
|
||||
axios.get("/todos").then(response => {
|
||||
if (response.data === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
onComplete(response.data);
|
||||
});
|
||||
}
|
||||
|
||||
var todoStorage = {
|
||||
fetch: function () {
|
||||
var todos = [];
|
||||
fetchTodos(function (items) {
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
todos.push(items[i]);
|
||||
}
|
||||
});
|
||||
return todos;
|
||||
},
|
||||
save: function (todos) {
|
||||
axios.post("/todos", JSON.stringify(todos)).then(response => {
|
||||
if (!response.data.success) {
|
||||
window.alert("saving had a failure");
|
||||
return;
|
||||
}
|
||||
// console.log("send: save");
|
||||
socket.Emit("save")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// visibility filters
|
||||
var filters = {
|
||||
all: function (todos) {
|
||||
return todos
|
||||
},
|
||||
active: function (todos) {
|
||||
return todos.filter(function (todo) {
|
||||
return !todo.completed
|
||||
})
|
||||
},
|
||||
completed: function (todos) {
|
||||
return todos.filter(function (todo) {
|
||||
return todo.completed
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// app Vue instance
|
||||
var app = new Vue({
|
||||
// app initial state
|
||||
data: {
|
||||
todos: todoStorage.fetch(),
|
||||
newTodo: '',
|
||||
editedTodo: null,
|
||||
visibility: 'all'
|
||||
},
|
||||
|
||||
// we will not use the "watch" as it works with the fields like "hasChanges"
|
||||
// and callbacks to make it true but let's keep things very simple as it's just a small getting started.
|
||||
// // watch todos change for persistence
|
||||
// watch: {
|
||||
// todos: {
|
||||
// handler: function (todos) {
|
||||
// if (app.hasChanges) {
|
||||
// todoStorage.save(todos);
|
||||
// app.hasChanges = false;
|
||||
// }
|
||||
|
||||
// },
|
||||
// deep: true
|
||||
// }
|
||||
// },
|
||||
|
||||
// computed properties
|
||||
// http://vuejs.org/guide/computed.html
|
||||
computed: {
|
||||
filteredTodos: function () {
|
||||
return filters[this.visibility](this.todos)
|
||||
},
|
||||
remaining: function () {
|
||||
return filters.active(this.todos).length
|
||||
},
|
||||
allDone: {
|
||||
get: function () {
|
||||
return this.remaining === 0
|
||||
},
|
||||
set: function (value) {
|
||||
this.todos.forEach(function (todo) {
|
||||
todo.completed = value
|
||||
})
|
||||
this.notifyChange();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
pluralize: function (n) {
|
||||
return n === 1 ? 'item' : 'items'
|
||||
}
|
||||
},
|
||||
|
||||
// methods that implement data logic.
|
||||
// note there's no DOM manipulation here at all.
|
||||
methods: {
|
||||
notifyChange: function () {
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
addTodo: function () {
|
||||
var value = this.newTodo && this.newTodo.trim()
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
this.todos.push({
|
||||
id: this.todos.length + 1, // just for the client-side.
|
||||
title: value,
|
||||
completed: false
|
||||
})
|
||||
this.newTodo = ''
|
||||
this.notifyChange();
|
||||
},
|
||||
|
||||
completeTodo: function (todo) {
|
||||
if (todo.completed) {
|
||||
todo.completed = false;
|
||||
} else {
|
||||
todo.completed = true;
|
||||
}
|
||||
this.notifyChange();
|
||||
},
|
||||
removeTodo: function (todo) {
|
||||
this.todos.splice(this.todos.indexOf(todo), 1)
|
||||
this.notifyChange();
|
||||
},
|
||||
|
||||
editTodo: function (todo) {
|
||||
this.beforeEditCache = todo.title
|
||||
this.editedTodo = todo
|
||||
},
|
||||
|
||||
doneEdit: function (todo) {
|
||||
if (!this.editedTodo) {
|
||||
return
|
||||
}
|
||||
this.editedTodo = null
|
||||
todo.title = todo.title.trim();
|
||||
if (!todo.title) {
|
||||
this.removeTodo(todo);
|
||||
}
|
||||
this.notifyChange();
|
||||
},
|
||||
|
||||
cancelEdit: function (todo) {
|
||||
this.editedTodo = null
|
||||
todo.title = this.beforeEditCache
|
||||
},
|
||||
|
||||
removeCompleted: function () {
|
||||
this.todos = filters.active(this.todos);
|
||||
this.notifyChange();
|
||||
}
|
||||
},
|
||||
|
||||
// a custom directive to wait for the DOM to be updated
|
||||
// before focusing on the input field.
|
||||
// http://vuejs.org/guide/custom-directive.html
|
||||
directives: {
|
||||
'todo-focus': function (el, binding) {
|
||||
if (binding.value) {
|
||||
el.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// handle routing
|
||||
function onHashChange() {
|
||||
var visibility = window.location.hash.replace(/#\/?/, '')
|
||||
if (filters[visibility]) {
|
||||
app.visibility = visibility
|
||||
} else {
|
||||
window.location.hash = ''
|
||||
app.visibility = 'all'
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', onHashChange)
|
||||
onHashChange()
|
||||
|
||||
// mount
|
||||
app.$mount('.todoapp')
|
|
@ -0,0 +1,2 @@
|
|||
vue.js is not here to reduce the disk space for the examples.
|
||||
Instead https://vuejs.org/js/vue.js is used instead.
|
|
@ -45,8 +45,11 @@ type Config struct {
|
|||
// 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 context.Context) string
|
||||
|
||||
// Error is the function that will be fired if any client couldn't upgrade the HTTP connection
|
||||
// to a websocket connection, a handshake error.
|
||||
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
||||
// CheckOrigin a function that is called right before the handshake,
|
||||
// if returns false then that client is not allowed to connect with the websocket server.
|
||||
CheckOrigin func(r *http.Request) bool
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
|
|
@ -47,8 +47,9 @@ func handleConnection(c websocket.Connection) {
|
|||
c.On("chat", func(msg string) {
|
||||
// Print the message to the console, c.Context() is the iris's http context.
|
||||
fmt.Printf("%s sent: %s\n", c.Context().RemoteAddr(), msg)
|
||||
// Write message back to the client message owner:
|
||||
// Write message back to the client message owner with:
|
||||
// c.Emit("chat", msg)
|
||||
// Write message to all except this client with:
|
||||
c.To(websocket.Broadcast).Emit("chat", msg)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
<!-- the message's input -->
|
||||
<input id="input" type="text" />
|
||||
|
||||
<!-- when clicked then an iris websocket event will be sent to the server, at this example we registered the 'chat' -->
|
||||
<button onclick="send()">Send</button>
|
||||
|
||||
<!-- the messages will be shown here -->
|
||||
<pre id="output"></pre>
|
||||
<!-- import the iris client-side library for browser-->
|
||||
<script src="/iris-ws.js"></script>
|
||||
|
||||
<script>
|
||||
var scheme = document.location.protocol == "https:" ? "wss" : "ws";
|
||||
var port = document.location.port ? (":" + document.location.port) : "";
|
||||
|
@ -23,11 +30,11 @@
|
|||
|
||||
// read events from the server
|
||||
socket.On("chat", function (msg) {
|
||||
addMessage(msg)
|
||||
addMessage(msg);
|
||||
});
|
||||
|
||||
function send() {
|
||||
addMessage("Me: " + input.value) // write ourselves
|
||||
addMessage("Me: " + input.value); // write ourselves
|
||||
socket.Emit("chat", input.value);// send chat event data to the websocket server
|
||||
input.value = ""; // clear the input
|
||||
}
|
||||
|
@ -35,5 +42,4 @@
|
|||
function addMessage(msg) {
|
||||
output.innerHTML += msg + "\n";
|
||||
}
|
||||
|
||||
</script>
|
|
@ -85,7 +85,7 @@ func SendMessage(serverID, to, method, message string) error {
|
|||
|
||||
// SendtBytes broadcast a message to server
|
||||
func SendtBytes(serverID, to, method string, message []byte) error {
|
||||
// look https://github.com/kataras/iris/tree/master/adaptors/websocket/message.go , client.go and client.js
|
||||
// look https://github.com/kataras/iris/blob/master/websocket/message.go , client.go and client.js
|
||||
// to understand the buffer line:
|
||||
buffer := []byte(fmt.Sprintf("iris-websocket-message:%v;0;%v;%v;", method, serverID, to))
|
||||
buffer = append(buffer, message...)
|
||||
|
|
|
@ -315,6 +315,18 @@ func WithCharset(charset string) Configurator {
|
|||
}
|
||||
}
|
||||
|
||||
// WithPostMaxMemory sets the maximum post data size
|
||||
// that a client can send to the server, this differs
|
||||
// from the overral request body size which can be modified
|
||||
// by the `context#SetMaxRequestBodySize` or `iris#LimitRequestBodySize`.
|
||||
//
|
||||
// Defaults to 32MB or 32 << 20 if you prefer.
|
||||
func WithPostMaxMemory(limit int64) Configurator {
|
||||
return func(app *Application) {
|
||||
app.config.PostMaxMemory = limit
|
||||
}
|
||||
}
|
||||
|
||||
// WithRemoteAddrHeader enables or adds a new or existing request header name
|
||||
// that can be used to validate the client's real IP.
|
||||
//
|
||||
|
@ -463,6 +475,13 @@ type Configuration struct {
|
|||
// Defaults to "UTF-8".
|
||||
Charset string `json:"charset,omitempty" yaml:"Charset" toml:"Charset"`
|
||||
|
||||
// PostMaxMemory sets the maximum post data size
|
||||
// that a client can send to the server, this differs
|
||||
// from the overral request body size which can be modified
|
||||
// by the `context#SetMaxRequestBodySize` or `iris#LimitRequestBodySize`.
|
||||
//
|
||||
// Defaults to 32MB or 32 << 20 if you prefer.
|
||||
PostMaxMemory int64 `json:"postMaxMemory" yaml:"PostMaxMemory" toml:"PostMaxMemory"`
|
||||
// +----------------------------------------------------+
|
||||
// | Context's keys for values used on various featuers |
|
||||
// +----------------------------------------------------+
|
||||
|
@ -504,8 +523,8 @@ type Configuration struct {
|
|||
RemoteAddrHeaders map[string]bool `json:"remoteAddrHeaders,omitempty" yaml:"RemoteAddrHeaders" toml:"RemoteAddrHeaders"`
|
||||
|
||||
// Other are the custom, dynamic options, can be empty.
|
||||
// This field used only by you to set any app's options you want
|
||||
// or by custom adaptors, it's a way to simple communicate between your adaptors (if any)
|
||||
// This field used only by you to set any app's options you want.
|
||||
//
|
||||
// Defaults to a non-nil empty map.
|
||||
Other map[string]interface{} `json:"other,omitempty" yaml:"Other" toml:"Other"`
|
||||
}
|
||||
|
@ -579,6 +598,16 @@ func (c Configuration) GetCharset() string {
|
|||
return c.Charset
|
||||
}
|
||||
|
||||
// GetPostMaxMemory returns the maximum configured post data size
|
||||
// that a client can send to the server, this differs
|
||||
// from the overral request body size which can be modified
|
||||
// by the `context#SetMaxRequestBodySize` or `iris#LimitRequestBodySize`.
|
||||
//
|
||||
// Defaults to 32MB or 32 << 20 if you prefer.
|
||||
func (c Configuration) GetPostMaxMemory() int64 {
|
||||
return c.PostMaxMemory
|
||||
}
|
||||
|
||||
// GetTranslateFunctionContextKey returns the configuration's TranslateFunctionContextKey value,
|
||||
// used for i18n.
|
||||
func (c Configuration) GetTranslateFunctionContextKey() string {
|
||||
|
@ -684,6 +713,10 @@ func WithConfiguration(c Configuration) Configurator {
|
|||
main.Charset = v
|
||||
}
|
||||
|
||||
if v := c.PostMaxMemory; v > 0 {
|
||||
main.PostMaxMemory = v
|
||||
}
|
||||
|
||||
if v := c.TranslateFunctionContextKey; v != "" {
|
||||
main.TranslateFunctionContextKey = v
|
||||
}
|
||||
|
@ -733,6 +766,13 @@ func DefaultConfiguration() Configuration {
|
|||
DisableAutoFireStatusCode: false,
|
||||
TimeFormat: "Mon, Jan 02 2006 15:04:05 GMT",
|
||||
Charset: "UTF-8",
|
||||
|
||||
// PostMaxMemory is for post body max memory.
|
||||
//
|
||||
// The request body the size limit
|
||||
// can be set by the middleware `LimitRequestBodySize`
|
||||
// or `context#SetMaxRequestBodySize`.
|
||||
PostMaxMemory: 32 << 20, // 32MB
|
||||
TranslateFunctionContextKey: "iris.translate",
|
||||
TranslateLanguageContextKey: "iris.language",
|
||||
ViewLayoutContextKey: "iris.viewLayout",
|
||||
|
|
|
@ -54,6 +54,14 @@ type ConfigurationReadOnly interface {
|
|||
// used for templates and the rest of the responses.
|
||||
GetCharset() string
|
||||
|
||||
// GetPostMaxMemory returns the maximum configured post data size
|
||||
// that a client can send to the server, this differs
|
||||
// from the overral request body size which can be modified
|
||||
// by the `context#SetMaxRequestBodySize` or `iris#LimitRequestBodySize`.
|
||||
//
|
||||
// Defaults to 32MB or 32 << 20 if you prefer.
|
||||
GetPostMaxMemory() int64
|
||||
|
||||
// GetTranslateLanguageContextKey returns the configuration's TranslateFunctionContextKey value,
|
||||
// used for i18n.
|
||||
GetTranslateFunctionContextKey() string
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
|
@ -26,6 +27,7 @@ import (
|
|||
"github.com/json-iterator/go"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/kataras/iris/core/memstore"
|
||||
|
@ -91,15 +93,27 @@ func (r *RequestParams) Visit(visitor func(key string, value string)) {
|
|||
})
|
||||
}
|
||||
|
||||
// GetEntry returns the internal Entry of the memstore, as value
|
||||
// if not found then it returns a zero Entry and false.
|
||||
var emptyEntry memstore.Entry
|
||||
|
||||
// GetEntryAt returns the internal Entry of the memstore based on its index,
|
||||
// the stored index by the router.
|
||||
// If not found then it returns a zero Entry and false.
|
||||
func (r RequestParams) GetEntryAt(index int) (memstore.Entry, bool) {
|
||||
if len(r.store) > index {
|
||||
return r.store[index], true
|
||||
}
|
||||
return emptyEntry, false
|
||||
}
|
||||
|
||||
// GetEntry returns the internal Entry of the memstore based on its "key".
|
||||
// If not found then it returns a zero Entry and false.
|
||||
func (r RequestParams) GetEntry(key string) (memstore.Entry, bool) {
|
||||
// we don't return the pointer here, we don't want to give the end-developer
|
||||
// the strength to change the entry that way.
|
||||
if e := r.store.GetEntry(key); e != nil {
|
||||
return *e, true
|
||||
}
|
||||
return memstore.Entry{}, false
|
||||
return emptyEntry, false
|
||||
}
|
||||
|
||||
// Get returns a path parameter's value based on its route's dynamic path key.
|
||||
|
@ -266,8 +280,7 @@ type Context interface {
|
|||
// Although `BeginRequest` should NOT be used to call other handlers,
|
||||
// the `BeginRequest` has been introduced to be able to set
|
||||
// common data to all method handlers before their execution.
|
||||
// Controllers can accept middleware(s) from the `app.Controller`
|
||||
// function.
|
||||
// Controllers can accept middleware(s) from the MVC's Application's Router as normally.
|
||||
//
|
||||
// That said let's see an example of `ctx.Proceed`:
|
||||
//
|
||||
|
@ -278,7 +291,6 @@ type Context interface {
|
|||
// })
|
||||
//
|
||||
// func (c *UsersController) BeginRequest(ctx iris.Context) {
|
||||
// c.C.BeginRequest(ctx) // call the parent's base controller BeginRequest first.
|
||||
// if !ctx.Proceed(authMiddleware) {
|
||||
// ctx.StopExecution()
|
||||
// }
|
||||
|
@ -463,52 +475,104 @@ type Context interface {
|
|||
// it returns an empty map if nothing found.
|
||||
URLParams() map[string]string
|
||||
|
||||
// FormValue returns a single form value by its name/key
|
||||
// FormValueDefault returns a single parsed form value by its "name",
|
||||
// including both the URL field's query parameters and the POST or PUT form data.
|
||||
//
|
||||
// Returns the "def" if not found.
|
||||
FormValueDefault(name string, def string) string
|
||||
// FormValue returns a single parsed form value by its "name",
|
||||
// including both the URL field's query parameters and the POST or PUT form data.
|
||||
FormValue(name string) string
|
||||
// FormValues returns all post data values with their keys
|
||||
// form data, get, post & put query arguments
|
||||
// FormValues returns the parsed form data, including both the URL
|
||||
// field's query parameters and the POST or PUT form data.
|
||||
//
|
||||
// The default form's memory maximum size is 32MB, it can be changed by the
|
||||
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
|
||||
//
|
||||
// NOTE: A check for nil is necessary.
|
||||
FormValues() map[string][]string
|
||||
|
||||
// PostValueDefault returns a form's only-post value by its name,
|
||||
// if not found then "def" is returned,
|
||||
// same as Request.PostFormValue.
|
||||
PostValueDefault(name string, def string) string
|
||||
// PostValue returns a form's only-post value by its name,
|
||||
// same as Request.PostFormValue.
|
||||
PostValue(name string) string
|
||||
// PostValueTrim returns a form's only-post value without trailing spaces by its name.
|
||||
PostValueTrim(name string) string
|
||||
// PostValueEscape returns a form's only-post escaped value by its name.
|
||||
PostValueEscape(name string) string
|
||||
// PostValueIntDefault returns a form's only-post value as int by its name.
|
||||
// If not found returns "def".
|
||||
PostValueIntDefault(name string, def int) (int, error)
|
||||
// PostValueInt returns a form's only-post value as int by its name.
|
||||
PostValueInt(name string) (int, error)
|
||||
// PostValueInt64Default returns a form's only-post value as int64 by its name.
|
||||
// If not found returns "def".
|
||||
PostValueInt64Default(name string, def int64) (int64, error)
|
||||
// PostValueInt64 returns a form's only-post value as int64 by its name.
|
||||
PostValueInt64(name string) (int64, error)
|
||||
// PostValueFloat64Default returns a form's only-post value as float64 by its name.
|
||||
// If not found returns "def".
|
||||
PostValueFloat64Default(name string, def float64) (float64, error)
|
||||
// PostValueFloat64 returns a form's only-post value as float64 by its name.
|
||||
PostValueFloat64(name string) (float64, error)
|
||||
// PostValue returns a form's only-post value as boolean by its name.
|
||||
PostValueBool(name string) (bool, error)
|
||||
// PostValues returns a form's only-post values.
|
||||
// PostValues calls ParseMultipartForm and ParseForm if necessary and ignores
|
||||
// any errors returned by these functions.
|
||||
PostValues(name string) []string
|
||||
|
||||
// FormFile returns the first file for the provided form key.
|
||||
// FormFile calls ctx.Request.ParseMultipartForm and ParseForm if necessary.
|
||||
// PostValueDefault returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name".
|
||||
//
|
||||
// same as Request.FormFile.
|
||||
// If not found then "def" is returned instead.
|
||||
PostValueDefault(name string, def string) string
|
||||
// PostValue returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name"
|
||||
PostValue(name string) string
|
||||
// PostValueTrim returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", without trailing spaces.
|
||||
PostValueTrim(name string) string
|
||||
// PostValueIntDefault returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as int.
|
||||
//
|
||||
// If not found returns the "def".
|
||||
PostValueIntDefault(name string, def int) (int, error)
|
||||
// PostValueInt returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as int.
|
||||
//
|
||||
// If not found returns 0.
|
||||
PostValueInt(name string) (int, error)
|
||||
// PostValueInt64Default returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as int64.
|
||||
//
|
||||
// If not found returns the "def".
|
||||
PostValueInt64Default(name string, def int64) (int64, error)
|
||||
// PostValueInt64 returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as float64.
|
||||
//
|
||||
// If not found returns 0.0.
|
||||
PostValueInt64(name string) (int64, error)
|
||||
// PostValueInt64Default returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as float64.
|
||||
//
|
||||
// If not found returns the "def".
|
||||
PostValueFloat64Default(name string, def float64) (float64, error)
|
||||
/// PostValueInt64Default returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as float64.
|
||||
//
|
||||
// If not found returns 0.0.
|
||||
PostValueFloat64(name string) (float64, error)
|
||||
// PostValueInt64Default returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as bool.
|
||||
//
|
||||
// If not found or value is false, then it returns false, otherwise true.
|
||||
PostValueBool(name string) (bool, error)
|
||||
// PostValues returns all the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name" as a string slice.
|
||||
//
|
||||
// The default form's memory maximum size is 32MB, it can be changed by the
|
||||
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
|
||||
PostValues(name string) []string
|
||||
// FormFile returns the first uploaded file that received from the client.
|
||||
//
|
||||
// The default form's memory maximum size is 32MB, it can be changed by the
|
||||
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
|
||||
FormFile(key string) (multipart.File, *multipart.FileHeader, error)
|
||||
// UploadFormFiles uploads any received file(s) from the client
|
||||
// to the system physical location "destDirectory".
|
||||
//
|
||||
// The second optional argument "before" gives caller the chance to
|
||||
// modify the *miltipart.FileHeader before saving to the disk,
|
||||
// it can be used to change a file's name based on the current request,
|
||||
// all FileHeader's options can be changed. You can ignore it if
|
||||
// you don't need to use this capability before saving a file to the disk.
|
||||
//
|
||||
// Note that it doesn't check if request body streamed.
|
||||
//
|
||||
// Returns the copied length as int64 and
|
||||
// a not nil error if at least one new file
|
||||
// can't be created due to the operating system's permissions or
|
||||
// http.ErrMissingFile if no file received.
|
||||
//
|
||||
// If you want to receive & accept files and manage them manually you can use the `context#FormFile`
|
||||
// instead and create a copy function that suits your needs, the below is for generic usage.
|
||||
//
|
||||
// The default form's memory maximum size is 32MB, it can be changed by the
|
||||
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
|
||||
//
|
||||
// See `FormFile` to a more controlled to receive a file.
|
||||
UploadFormFiles(destDirectory string, before ...func(Context, *multipart.FileHeader)) (n int64, err error)
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | Custom HTTP Errors |
|
||||
|
@ -631,7 +695,6 @@ type Context interface {
|
|||
//
|
||||
// Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/
|
||||
ViewLayout(layoutTmplFile string)
|
||||
|
||||
// ViewData saves one or more key-value pair in order to be passed if and when .View
|
||||
// is being called afterwards, in the same request.
|
||||
// Useful when need to set or/and change template data from previous hanadlers in the chain.
|
||||
|
@ -651,7 +714,6 @@ type Context interface {
|
|||
//
|
||||
// Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/
|
||||
ViewData(key string, value interface{})
|
||||
|
||||
// GetViewData returns the values registered by `context#ViewData`.
|
||||
// The return value is `map[string]interface{}`, this means that
|
||||
// if a custom struct registered to ViewData then this function
|
||||
|
@ -662,16 +724,20 @@ type Context interface {
|
|||
// Similarly to `viewData := ctx.Values().Get("iris.viewData")` or
|
||||
// `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`.
|
||||
GetViewData() map[string]interface{}
|
||||
|
||||
// View renders templates based on the adapted view engines.
|
||||
// First argument accepts the filename, relative to the view engine's Directory,
|
||||
// View renders a template based on the registered view engine(s).
|
||||
// First argument accepts the filename, relative to the view engine's Directory and Extension,
|
||||
// i.e: if directory is "./templates" and want to render the "./templates/users/index.html"
|
||||
// then you pass the "users/index.html" as the filename argument.
|
||||
//
|
||||
// Look: .ViewData and .ViewLayout too.
|
||||
// The second optional argument can receive a single "view model"
|
||||
// that will be binded to the view template if it's not nil,
|
||||
// otherwise it will check for previous view data stored by the `ViewData`
|
||||
// even if stored at any previous handler(middleware) for the same request.
|
||||
//
|
||||
// Examples: https://github.com/kataras/iris/tree/master/_examples/view/
|
||||
View(filename string) error
|
||||
// Look .ViewData` and .ViewLayout too.
|
||||
//
|
||||
// Examples: https://github.com/kataras/iris/tree/master/_examples/view
|
||||
View(filename string, optionalViewModel ...interface{}) error
|
||||
|
||||
// Binary writes out the raw bytes as binary data.
|
||||
Binary(data []byte) (int, error)
|
||||
|
@ -685,9 +751,10 @@ type Context interface {
|
|||
JSONP(v interface{}, options ...JSONP) (int, error)
|
||||
// XML marshals the given interface object and writes the XML response.
|
||||
XML(v interface{}, options ...XML) (int, error)
|
||||
// Markdown parses the markdown to html and renders to client.
|
||||
// Markdown parses the markdown to html and renders its result to the client.
|
||||
Markdown(markdownB []byte, options ...Markdown) (int, error)
|
||||
|
||||
// YAML parses the "v" using the yaml parser and renders its result to the client.
|
||||
YAML(v interface{}) (int, error)
|
||||
// +------------------------------------------------------------+
|
||||
// | Serve files |
|
||||
// +------------------------------------------------------------+
|
||||
|
@ -695,18 +762,23 @@ type Context interface {
|
|||
// ServeContent serves content, headers are autoset
|
||||
// receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string)
|
||||
//
|
||||
// You can define your own "Content-Type" header also, after this function call
|
||||
// Doesn't implements resuming (by range), use ctx.SendFile instead
|
||||
//
|
||||
// You can define your own "Content-Type" with `context#ContentType`, before this function call.
|
||||
//
|
||||
// This function doesn't support resuming (by range),
|
||||
// use ctx.SendFile or router's `StaticWeb` instead.
|
||||
ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error
|
||||
// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
|
||||
// ServeFile serves a file (to send a file, a zip for example to the client you should use the `SendFile` instead)
|
||||
// receives two parameters
|
||||
// filename/path (string)
|
||||
// gzipCompression (bool)
|
||||
//
|
||||
// You can define your own "Content-Type" header also, after this function call
|
||||
// This function doesn't implement resuming (by range), use ctx.SendFile instead
|
||||
// You can define your own "Content-Type" with `context#ContentType`, before this function call.
|
||||
//
|
||||
// Use it when you want to serve css/js/... files to the client, for bigger files and 'force-download' use the SendFile.
|
||||
// This function doesn't support resuming (by range),
|
||||
// use ctx.SendFile or router's `StaticWeb` instead.
|
||||
//
|
||||
// Use it when you want to serve dynamic files to the client.
|
||||
ServeFile(filename string, gzipCompression bool) error
|
||||
// SendFile sends file for force-download to the client
|
||||
//
|
||||
|
@ -804,8 +876,19 @@ type Context interface {
|
|||
// to be executed at serve-time. The full app's fields
|
||||
// and methods are not available here for the developer's safety.
|
||||
Application() Application
|
||||
|
||||
// String returns the string representation of this request.
|
||||
// Each context has a unique string representation.
|
||||
// It can be used for simple debugging scenarios, i.e print context as string.
|
||||
//
|
||||
// What it returns? A number which declares the length of the
|
||||
// total `String` calls per executable application, followed
|
||||
// by the remote IP (the client) and finally the method:url.
|
||||
String() string
|
||||
}
|
||||
|
||||
var _ Context = (*context)(nil)
|
||||
|
||||
// Next calls all the next handler from the handlers chain,
|
||||
// it should be used inside a middleware.
|
||||
func Next(ctx Context) {
|
||||
|
@ -855,7 +938,11 @@ type Map map[string]interface{}
|
|||
// +------------------------------------------------------------+
|
||||
|
||||
type context struct {
|
||||
// the http.ResponseWriter wrapped by custom writer
|
||||
// the unique id, it's zero until `String` function is called,
|
||||
// it's here to cache the random, unique context's id, although `String`
|
||||
// returns more than this.
|
||||
id uint64
|
||||
// the http.ResponseWriter wrapped by custom writer.
|
||||
writer ResponseWriter
|
||||
// the original http.Request
|
||||
request *http.Request
|
||||
|
@ -863,10 +950,10 @@ type context struct {
|
|||
currentRouteName string
|
||||
|
||||
// the local key-value storage
|
||||
params RequestParams // url named parameters
|
||||
values memstore.Store // generic storage, middleware communication
|
||||
params RequestParams // url named parameters.
|
||||
values memstore.Store // generic storage, middleware communication.
|
||||
|
||||
// the underline application app
|
||||
// the underline application app.
|
||||
app Application
|
||||
// the route's handlers
|
||||
handlers Handlers
|
||||
|
@ -1034,8 +1121,7 @@ func (ctx *context) HandlerIndex(n int) (currentIndex int) {
|
|||
// Although `BeginRequest` should NOT be used to call other handlers,
|
||||
// the `BeginRequest` has been introduced to be able to set
|
||||
// common data to all method handlers before their execution.
|
||||
// Controllers can accept middleware(s) from the `app.Controller`
|
||||
// function.
|
||||
// Controllers can accept middleware(s) from the MVC's Application's Router as normally.
|
||||
//
|
||||
// That said let's see an example of `ctx.Proceed`:
|
||||
//
|
||||
|
@ -1046,7 +1132,6 @@ func (ctx *context) HandlerIndex(n int) (currentIndex int) {
|
|||
// })
|
||||
//
|
||||
// func (c *UsersController) BeginRequest(ctx iris.Context) {
|
||||
// c.C.BeginRequest(ctx) // call the parent's base controller BeginRequest first.
|
||||
// if !ctx.Proceed(authMiddleware) {
|
||||
// ctx.StopExecution()
|
||||
// }
|
||||
|
@ -1512,62 +1597,107 @@ func (ctx *context) URLParams() map[string]string {
|
|||
return values
|
||||
}
|
||||
|
||||
func (ctx *context) askParseForm() error {
|
||||
if ctx.request.Form == nil {
|
||||
if err := ctx.request.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// No need anymore, net/http checks for the Form already.
|
||||
// func (ctx *context) askParseForm() error {
|
||||
// if ctx.request.Form == nil {
|
||||
// if err := ctx.request.ParseForm(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// FormValue returns a single form value by its name/key
|
||||
func (ctx *context) FormValue(name string) string {
|
||||
return ctx.request.FormValue(name)
|
||||
}
|
||||
|
||||
// FormValues returns all post data values with their keys
|
||||
// form data, get, post & put query arguments
|
||||
// FormValueDefault returns a single parsed form value by its "name",
|
||||
// including both the URL field's query parameters and the POST or PUT form data.
|
||||
//
|
||||
// NOTE: A check for nil is necessary.
|
||||
func (ctx *context) FormValues() map[string][]string {
|
||||
// we skip the check of multipart form, takes too much memory, if user wants it can do manually now.
|
||||
if err := ctx.askParseForm(); err != nil {
|
||||
return nil
|
||||
// Returns the "def" if not found.
|
||||
func (ctx *context) FormValueDefault(name string, def string) string {
|
||||
if form, has := ctx.form(); has {
|
||||
if v := form[name]; len(v) > 0 {
|
||||
return v[0]
|
||||
}
|
||||
return ctx.request.Form // nothing more to do, it's already contains both query and post & put args.
|
||||
|
||||
}
|
||||
|
||||
// PostValueDefault returns a form's only-post value by its name,
|
||||
// if not found then "def" is returned,
|
||||
// same as Request.PostFormValue.
|
||||
func (ctx *context) PostValueDefault(name string, def string) string {
|
||||
v := ctx.request.PostFormValue(name)
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
|
||||
// FormValue returns a single parsed form value by its "name",
|
||||
// including both the URL field's query parameters and the POST or PUT form data.
|
||||
func (ctx *context) FormValue(name string) string {
|
||||
return ctx.FormValueDefault(name, "")
|
||||
}
|
||||
|
||||
// PostValue returns a form's only-post value by its name,
|
||||
// same as Request.PostFormValue.
|
||||
// FormValues returns the parsed form data, including both the URL
|
||||
// field's query parameters and the POST or PUT form data.
|
||||
//
|
||||
// The default form's memory maximum size is 32MB, it can be changed by the
|
||||
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
|
||||
// NOTE: A check for nil is necessary.
|
||||
func (ctx *context) FormValues() map[string][]string {
|
||||
form, _ := ctx.form()
|
||||
return form
|
||||
}
|
||||
|
||||
// Form contains the parsed form data, including both the URL
|
||||
// field's query parameters and the POST or PUT form data.
|
||||
func (ctx *context) form() (form map[string][]string, found bool) {
|
||||
/*
|
||||
net/http/request.go#1219
|
||||
for k, v := range f.Value {
|
||||
r.Form[k] = append(r.Form[k], v...)
|
||||
// r.PostForm should also be populated. See Issue 9305.
|
||||
r.PostForm[k] = append(r.PostForm[k], v...)
|
||||
}
|
||||
*/
|
||||
|
||||
// ParseMultipartForm calls `request.ParseForm` automatically
|
||||
// therefore we don't need to call it here, although it doesn't hurt.
|
||||
// After one call to ParseMultipartForm or ParseForm,
|
||||
// subsequent calls have no effect, are idempotent.
|
||||
ctx.request.ParseMultipartForm(ctx.Application().ConfigurationReadOnly().GetPostMaxMemory())
|
||||
|
||||
if form := ctx.request.Form; len(form) > 0 {
|
||||
return form, true
|
||||
}
|
||||
|
||||
if form := ctx.request.PostForm; len(form) > 0 {
|
||||
return form, true
|
||||
}
|
||||
|
||||
if form := ctx.request.MultipartForm.Value; len(form) > 0 {
|
||||
return form, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// PostValueDefault returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name".
|
||||
//
|
||||
// If not found then "def" is returned instead.
|
||||
func (ctx *context) PostValueDefault(name string, def string) string {
|
||||
ctx.form()
|
||||
if v := ctx.request.PostForm[name]; len(v) > 0 {
|
||||
return v[0]
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// PostValue returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name"
|
||||
func (ctx *context) PostValue(name string) string {
|
||||
return ctx.PostValueDefault(name, "")
|
||||
}
|
||||
|
||||
// PostValueTrim returns a form's only-post value without trailing spaces by its name.
|
||||
// PostValueTrim returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", without trailing spaces.
|
||||
func (ctx *context) PostValueTrim(name string) string {
|
||||
return strings.TrimSpace(ctx.PostValue(name))
|
||||
}
|
||||
|
||||
// PostValueEscape returns a form's only-post escaped value by its name.
|
||||
func (ctx *context) PostValueEscape(name string) string {
|
||||
return DecodeQuery(ctx.PostValue(name))
|
||||
}
|
||||
|
||||
// PostValueIntDefault returns a form's only-post value as int by its name.
|
||||
// If not found returns "def".
|
||||
// PostValueIntDefault returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as int.
|
||||
//
|
||||
// If not found returns the "def".
|
||||
func (ctx *context) PostValueIntDefault(name string, def int) (int, error) {
|
||||
v := ctx.PostValue(name)
|
||||
if v == "" {
|
||||
|
@ -1576,14 +1706,18 @@ func (ctx *context) PostValueIntDefault(name string, def int) (int, error) {
|
|||
return strconv.Atoi(v)
|
||||
}
|
||||
|
||||
// PostValueInt returns a form's only-post value as int by its name.
|
||||
// PostValueInt returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as int.
|
||||
//
|
||||
// If not found returns 0.
|
||||
func (ctx *context) PostValueInt(name string) (int, error) {
|
||||
return ctx.PostValueIntDefault(name, 0)
|
||||
}
|
||||
|
||||
// PostValueInt64Default returns a form's only-post value as int64 by its name.
|
||||
// If not found returns "def".
|
||||
// PostValueInt64Default returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as int64.
|
||||
//
|
||||
// If not found returns the "def".
|
||||
func (ctx *context) PostValueInt64Default(name string, def int64) (int64, error) {
|
||||
v := ctx.PostValue(name)
|
||||
if v == "" {
|
||||
|
@ -1592,14 +1726,18 @@ func (ctx *context) PostValueInt64Default(name string, def int64) (int64, error)
|
|||
return strconv.ParseInt(v, 10, 64)
|
||||
}
|
||||
|
||||
// PostValueInt64 returns a form's only-post value as int64 by its name.
|
||||
// PostValueInt64 returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as float64.
|
||||
//
|
||||
// If not found returns 0.0.
|
||||
func (ctx *context) PostValueInt64(name string) (int64, error) {
|
||||
return ctx.PostValueInt64Default(name, 0.0)
|
||||
}
|
||||
|
||||
// PostValueFloat64Default returns a form's only-post value as float64 by its name.
|
||||
// If not found returns "def".
|
||||
// PostValueInt64Default returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as float64.
|
||||
//
|
||||
// If not found returns the "def".
|
||||
func (ctx *context) PostValueFloat64Default(name string, def float64) (float64, error) {
|
||||
v := ctx.PostValue(name)
|
||||
if v == "" {
|
||||
|
@ -1608,46 +1746,156 @@ func (ctx *context) PostValueFloat64Default(name string, def float64) (float64,
|
|||
return strconv.ParseFloat(v, 64)
|
||||
}
|
||||
|
||||
// PostValueFloat64 returns a form's only-post value as float64 by its name.
|
||||
// PostValueInt64Default returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as float64.
|
||||
//
|
||||
// If not found returns 0.0.
|
||||
func (ctx *context) PostValueFloat64(name string) (float64, error) {
|
||||
return ctx.PostValueFloat64Default(name, 0.0)
|
||||
}
|
||||
|
||||
// PostValue returns a form's only-post value as boolean by its name.
|
||||
// PostValueInt64Default returns the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name", as bool.
|
||||
//
|
||||
// If not found or value is false, then it returns false, otherwise true.
|
||||
func (ctx *context) PostValueBool(name string) (bool, error) {
|
||||
return strconv.ParseBool(ctx.PostValue(name))
|
||||
}
|
||||
|
||||
const (
|
||||
// DefaultMaxMemory is the default value
|
||||
// for post values' max memory, defaults to
|
||||
// 32MB.
|
||||
// Can be also changed by the middleware `LimitRequestBodySize`
|
||||
// or `context#SetMaxRequestBodySize`.
|
||||
DefaultMaxMemory = 32 << 20 // 32 MB
|
||||
)
|
||||
|
||||
// PostValues returns a form's only-post values.
|
||||
// PostValues calls ParseMultipartForm and ParseForm if necessary and ignores
|
||||
// any errors returned by these functions.
|
||||
func (ctx *context) PostValues(name string) []string {
|
||||
r := ctx.request
|
||||
if r.PostForm == nil {
|
||||
r.ParseMultipartForm(DefaultMaxMemory)
|
||||
}
|
||||
|
||||
return r.PostForm[name]
|
||||
}
|
||||
|
||||
// FormFile returns the first file for the provided form key.
|
||||
// FormFile calls ctx.request.ParseMultipartForm and ParseForm if necessary.
|
||||
// PostValues returns all the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters based on a "name" as a string slice.
|
||||
//
|
||||
// same as Request.FormFile.
|
||||
// The default form's memory maximum size is 32MB, it can be changed by the
|
||||
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
|
||||
func (ctx *context) PostValues(name string) []string {
|
||||
ctx.form()
|
||||
return ctx.request.PostForm[name]
|
||||
}
|
||||
|
||||
// FormFile returns the first uploaded file that received from the client.
|
||||
//
|
||||
//
|
||||
// The default form's memory maximum size is 32MB, it can be changed by the
|
||||
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
|
||||
func (ctx *context) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
|
||||
// we don't have access to see if the request is body stream
|
||||
// and then the ParseMultipartForm can be useless
|
||||
// here but do it in order to apply the post limit,
|
||||
// the internal request.FormFile will not do it if that's filled
|
||||
// and it's not a stream body.
|
||||
ctx.request.ParseMultipartForm(ctx.Application().ConfigurationReadOnly().GetPostMaxMemory())
|
||||
return ctx.request.FormFile(key)
|
||||
}
|
||||
|
||||
// UploadFormFiles uploads any received file(s) from the client
|
||||
// to the system physical location "destDirectory".
|
||||
//
|
||||
// The second optional argument "before" gives caller the chance to
|
||||
// modify the *miltipart.FileHeader before saving to the disk,
|
||||
// it can be used to change a file's name based on the current request,
|
||||
// all FileHeader's options can be changed. You can ignore it if
|
||||
// you don't need to use this capability before saving a file to the disk.
|
||||
//
|
||||
// Note that it doesn't check if request body streamed.
|
||||
//
|
||||
// Returns the copied length as int64 and
|
||||
// a not nil error if at least one new file
|
||||
// can't be created due to the operating system's permissions or
|
||||
// http.ErrMissingFile if no file received.
|
||||
//
|
||||
// If you want to receive & accept files and manage them manually you can use the `context#FormFile`
|
||||
// instead and create a copy function that suits your needs, the below is for generic usage.
|
||||
//
|
||||
// The default form's memory maximum size is 32MB, it can be changed by the
|
||||
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
|
||||
//
|
||||
// See `FormFile` to a more controlled to receive a file.
|
||||
func (ctx *context) UploadFormFiles(destDirectory string, before ...func(Context, *multipart.FileHeader)) (n int64, err error) {
|
||||
err = ctx.request.ParseMultipartForm(ctx.Application().ConfigurationReadOnly().GetPostMaxMemory())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if ctx.request.MultipartForm != nil {
|
||||
if fhs := ctx.request.MultipartForm.File; fhs != nil {
|
||||
for _, files := range fhs {
|
||||
for _, file := range files {
|
||||
|
||||
for _, b := range before {
|
||||
b(ctx, file)
|
||||
}
|
||||
|
||||
n0, err0 := uploadTo(file, destDirectory)
|
||||
if err0 != nil {
|
||||
return 0, err0
|
||||
}
|
||||
n += n0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, http.ErrMissingFile
|
||||
}
|
||||
|
||||
func uploadTo(fh *multipart.FileHeader, destDirectory string) (int64, error) {
|
||||
src, err := fh.Open()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
out, err := os.OpenFile(filepath.Join(destDirectory, fh.Filename),
|
||||
os.O_WRONLY|os.O_CREATE, os.FileMode(0666))
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
return io.Copy(out, src)
|
||||
}
|
||||
|
||||
/* Good idea of mine but it doesn't work of course...
|
||||
// Go can't use `io.ReadCloser` function return value the same
|
||||
// as with other function that returns a `multipart.File`, even if
|
||||
// multipart.File is an `io.ReadCloser`.
|
||||
// So comment all those and implement a function inside the context itself.
|
||||
//
|
||||
// Copiable is the interface which should be completed
|
||||
// by files or not that intend to be used inside the `context#CopyFile`.
|
||||
// This interface allows testing file uploads to your http test as well.
|
||||
//
|
||||
// See `CopyFile` for more.
|
||||
// type Copiable interface {
|
||||
// Open() (io.ReadCloser, error)
|
||||
// }
|
||||
//
|
||||
|
||||
|
||||
// CopyFile copies a `context#Copiable` "file", that can be acquired by the second argument of
|
||||
// a `context.FormFile` (*multipart.FileHeader) as well, to the "dest".
|
||||
//
|
||||
// Returns the copied length as int64 and
|
||||
// an error if file is not exist, or new file can't be created or closed at the end.
|
||||
func CopyFile(file Copiable, dest string) (int64, error) {
|
||||
src, err := fileOpen()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
out, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
return io.Copy(out, src)
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// Redirect sends a redirect response to the client
|
||||
// to a specific url or relative path.
|
||||
// accepts 2 parameters string and an optional int
|
||||
|
@ -2053,20 +2301,32 @@ func (ctx *context) GetViewData() map[string]interface{} {
|
|||
return nil
|
||||
}
|
||||
|
||||
// View renders templates based on the adapted view engines.
|
||||
// First argument accepts the filename, relative to the view engine's Directory,
|
||||
// View renders a template based on the registered view engine(s).
|
||||
// First argument accepts the filename, relative to the view engine's Directory and Extension,
|
||||
// i.e: if directory is "./templates" and want to render the "./templates/users/index.html"
|
||||
// then you pass the "users/index.html" as the filename argument.
|
||||
//
|
||||
// Look: .ViewData and .ViewLayout too.
|
||||
// The second optional argument can receive a single "view model"
|
||||
// that will be binded to the view template if it's not nil,
|
||||
// otherwise it will check for previous view data stored by the `ViewData`
|
||||
// even if stored at any previous handler(middleware) for the same request.
|
||||
//
|
||||
// Examples: https://github.com/kataras/iris/tree/master/_examples/view/
|
||||
func (ctx *context) View(filename string) error {
|
||||
// Look .ViewData and .ViewLayout too.
|
||||
//
|
||||
// Examples: https://github.com/kataras/iris/tree/master/_examples/view
|
||||
func (ctx *context) View(filename string, optionalViewModel ...interface{}) error {
|
||||
ctx.ContentType(ContentHTMLHeaderValue)
|
||||
cfg := ctx.Application().ConfigurationReadOnly()
|
||||
|
||||
layout := ctx.values.GetString(cfg.GetViewLayoutContextKey())
|
||||
bindingData := ctx.values.Get(cfg.GetViewDataContextKey())
|
||||
|
||||
var bindingData interface{}
|
||||
if len(optionalViewModel) > 0 {
|
||||
// a nil can override the existing data or model sent by `ViewData`.
|
||||
bindingData = optionalViewModel[0]
|
||||
} else {
|
||||
bindingData = ctx.values.Get(cfg.GetViewDataContextKey())
|
||||
}
|
||||
|
||||
err := ctx.Application().View(ctx.writer, filename, layout, bindingData)
|
||||
if err != nil {
|
||||
|
@ -2090,9 +2350,10 @@ const (
|
|||
ContentTextHeaderValue = "text/plain"
|
||||
// ContentXMLHeaderValue header value for XML data.
|
||||
ContentXMLHeaderValue = "text/xml"
|
||||
|
||||
// ContentMarkdownHeaderValue custom key/content type, the real is the text/html.
|
||||
ContentMarkdownHeaderValue = "text/markdown"
|
||||
// ContentYAMLHeaderValue header value for YAML data.
|
||||
ContentYAMLHeaderValue = "application/x-yaml"
|
||||
)
|
||||
|
||||
// Binary writes out the raw bytes as binary data.
|
||||
|
@ -2353,7 +2614,7 @@ func (ctx *context) XML(v interface{}, opts ...XML) (int, error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
// WriteMarkdown parses the markdown to html and renders these contents to the writer.
|
||||
// WriteMarkdown parses the markdown to html and writes these contents to the writer.
|
||||
func WriteMarkdown(writer io.Writer, markdownB []byte, options Markdown) (int, error) {
|
||||
buf := blackfriday.Run(markdownB)
|
||||
if options.Sanitize {
|
||||
|
@ -2366,7 +2627,7 @@ func WriteMarkdown(writer io.Writer, markdownB []byte, options Markdown) (int, e
|
|||
// from `WriteMarkdown` and `ctx.Markdown`.
|
||||
var DefaultMarkdownOptions = Markdown{}
|
||||
|
||||
// Markdown parses the markdown to html and renders to the client.
|
||||
// Markdown parses the markdown to html and renders its result to the client.
|
||||
func (ctx *context) Markdown(markdownB []byte, opts ...Markdown) (int, error) {
|
||||
options := DefaultMarkdownOptions
|
||||
|
||||
|
@ -2385,6 +2646,18 @@ func (ctx *context) Markdown(markdownB []byte, opts ...Markdown) (int, error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
// YAML marshals the "v" using the yaml marshaler and renders its result to the client.
|
||||
func (ctx *context) YAML(v interface{}) (int, error) {
|
||||
out, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
ctx.StatusCode(http.StatusInternalServerError)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ctx.ContentType(ContentYAMLHeaderValue)
|
||||
return ctx.Write(out)
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | Serve files |
|
||||
// +------------------------------------------------------------+
|
||||
|
@ -2482,7 +2755,7 @@ var (
|
|||
func (ctx *context) SetCookieKV(name, value string) {
|
||||
c := &http.Cookie{}
|
||||
c.Name = name
|
||||
c.Value = value
|
||||
c.Value = url.QueryEscape(value)
|
||||
c.HttpOnly = true
|
||||
c.Expires = time.Now().Add(SetCookieKVExpiration)
|
||||
c.MaxAge = int(SetCookieKVExpiration.Seconds())
|
||||
|
@ -2496,7 +2769,8 @@ func (ctx *context) GetCookie(name string) string {
|
|||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return cookie.Value
|
||||
value, _ := url.QueryUnescape(cookie.Value)
|
||||
return value
|
||||
}
|
||||
|
||||
// RemoveCookie deletes a cookie by it's name.
|
||||
|
@ -2717,3 +2991,28 @@ func (ctx *context) Exec(method string, path string) {
|
|||
func (ctx *context) Application() Application {
|
||||
return ctx.app
|
||||
}
|
||||
|
||||
var lastCapturedContextID uint64
|
||||
|
||||
// LastCapturedContextID returns the total number of `context#String` calls.
|
||||
func LastCapturedContextID() uint64 {
|
||||
return atomic.LoadUint64(&lastCapturedContextID)
|
||||
}
|
||||
|
||||
// String returns the string representation of this request.
|
||||
// Each context has a unique string representation.
|
||||
// It can be used for simple debugging scenarios, i.e print context as string.
|
||||
//
|
||||
// What it returns? A number which declares the length of the
|
||||
// total `String` calls per executable application, followed
|
||||
// by the remote IP (the client) and finally the method:url.
|
||||
func (ctx *context) String() string {
|
||||
if ctx.id == 0 {
|
||||
// set the id here.
|
||||
forward := atomic.AddUint64(&lastCapturedContextID, 1)
|
||||
ctx.id = forward
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[%d] %s ▶ %s:%s",
|
||||
ctx.id, ctx.RemoteAddr(), ctx.Method(), ctx.Request().RequestURI)
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ type GzipResponseWriter struct {
|
|||
disabled bool
|
||||
}
|
||||
|
||||
var _ ResponseWriter = &GzipResponseWriter{}
|
||||
var _ ResponseWriter = (*GzipResponseWriter)(nil)
|
||||
|
||||
// BeginGzipResponse accepts a ResponseWriter
|
||||
// and prepares the new gzip response writer.
|
||||
|
|
|
@ -39,7 +39,7 @@ type ResponseRecorder struct {
|
|||
headers http.Header
|
||||
}
|
||||
|
||||
var _ ResponseWriter = &ResponseRecorder{}
|
||||
var _ ResponseWriter = (*ResponseRecorder)(nil)
|
||||
|
||||
// Naive returns the simple, underline and original http.ResponseWriter
|
||||
// that backends this response writer.
|
||||
|
|
|
@ -115,7 +115,7 @@ type responseWriter struct {
|
|||
beforeFlush func()
|
||||
}
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
var _ ResponseWriter = (*responseWriter)(nil)
|
||||
|
||||
const (
|
||||
defaultStatusCode = http.StatusOK
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
const (
|
||||
// Version is the string representation of the current local Iris Web Framework version.
|
||||
Version = "8.5.8"
|
||||
Version = "10.0.0"
|
||||
)
|
||||
|
||||
// CheckForUpdates checks for any available updates
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/kataras/iris/core/router/macro"
|
||||
"github.com/kataras/iris/mvc/activator"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -190,11 +189,11 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
|
|||
// 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)
|
||||
// app.HandleMany("GET", "/user /user/{id:int} /user/me", genericUserHandler)
|
||||
// At the other side, with `Handle` we've had to write:
|
||||
// app.Handle(iris.MethodGet, "/user", userHandler)
|
||||
// app.Handle(iris.MethodGet, "/user/{id:int}", userHandler)
|
||||
// app.Handle(iris.MethodGet, "/user/me", userHandler)
|
||||
// app.Handle("GET", "/user", userHandler)
|
||||
// app.Handle("GET", "/user/{id:int}", userByIDHandler)
|
||||
// app.Handle("GET", "/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.
|
||||
|
@ -202,7 +201,6 @@ func (api *APIBuilder) HandleMany(methodOrMulti string, relativePathorMulti stri
|
|||
// at least slash
|
||||
// a space
|
||||
// at least one other slash for the next path
|
||||
// app.Controller("/user /user{id}", new(UserController))
|
||||
paths := splitPath(relativePathorMulti)
|
||||
methods := splitMethod(methodOrMulti)
|
||||
for _, p := range paths {
|
||||
|
@ -476,85 +474,6 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro
|
|||
return
|
||||
}
|
||||
|
||||
// Controller registers a `Controller` instance and returns the registered Routes.
|
||||
// The "controller" receiver should embed a field of `Controller` in order
|
||||
// to be compatible Iris `Controller`.
|
||||
//
|
||||
// It's just an alternative way of building an API for a specific
|
||||
// path, the controller can register all type of http methods.
|
||||
//
|
||||
// Keep note that 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 {
|
||||
// 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())
|
||||
// Note: Binded values of context.Handler type are being recognised as middlewares by the router.
|
||||
//
|
||||
// Read more at `/mvc#Controller`.
|
||||
func (api *APIBuilder) Controller(relativePath string, controller activator.BaseController,
|
||||
bindValues ...interface{}) (routes []*Route) {
|
||||
|
||||
registerFunc := func(ifRelPath string, method string, handlers ...context.Handler) {
|
||||
relPath := relativePath + ifRelPath
|
||||
r := api.HandleMany(method, relPath, handlers...)
|
||||
routes = append(routes, r...)
|
||||
}
|
||||
|
||||
// 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.
|
||||
if err := activator.Register(controller, bindValues, registerFunc); err != nil {
|
||||
api.reporter.Add("%v for path: '%s'", err, relativePath)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration
|
||||
// which can be changed.
|
||||
var StaticCacheDuration = 20 * time.Second
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"github.com/kataras/golog"
|
||||
"html"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/golog"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/kataras/iris/core/netutil"
|
||||
"github.com/kataras/iris/core/router/node"
|
||||
|
|
|
@ -51,23 +51,16 @@ const (
|
|||
ParamTypePath
|
||||
)
|
||||
|
||||
// ValidKind will return true if at least one param type is supported
|
||||
// for this std kind.
|
||||
func ValidKind(k reflect.Kind) bool {
|
||||
switch k {
|
||||
case reflect.String:
|
||||
fallthrough
|
||||
case reflect.Int:
|
||||
fallthrough
|
||||
case reflect.Int64:
|
||||
fallthrough
|
||||
case reflect.Bool:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
func (pt ParamType) String() string {
|
||||
for k, v := range paramTypes {
|
||||
if v == pt {
|
||||
return k
|
||||
}
|
||||
}
|
||||
|
||||
return "unexpected"
|
||||
}
|
||||
|
||||
// Not because for a single reason
|
||||
// a string may be a
|
||||
// ParamTypeString or a ParamTypeFile
|
||||
|
@ -96,6 +89,23 @@ func (pt ParamType) Kind() reflect.Kind {
|
|||
return reflect.Invalid // 0
|
||||
}
|
||||
|
||||
// ValidKind will return true if at least one param type is supported
|
||||
// for this std kind.
|
||||
func ValidKind(k reflect.Kind) bool {
|
||||
switch k {
|
||||
case reflect.String:
|
||||
fallthrough
|
||||
case reflect.Int:
|
||||
fallthrough
|
||||
case reflect.Int64:
|
||||
fallthrough
|
||||
case reflect.Bool:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Assignable returns true if the "k" standard type
|
||||
// is assignabled to this ParamType.
|
||||
func (pt ParamType) Assignable(k reflect.Kind) bool {
|
||||
|
@ -133,6 +143,30 @@ func LookupParamType(ident string) ParamType {
|
|||
return ParamTypeUnExpected
|
||||
}
|
||||
|
||||
// LookupParamTypeFromStd accepts the string representation of a standard go type.
|
||||
// It returns a ParamType, but it may differs for example
|
||||
// the alphabetical, file, path and string are all string go types, so
|
||||
// make sure that caller resolves these types before this call.
|
||||
//
|
||||
// string matches to string
|
||||
// int matches to int
|
||||
// int64 matches to long
|
||||
// bool matches to boolean
|
||||
func LookupParamTypeFromStd(goType string) ParamType {
|
||||
switch goType {
|
||||
case "string":
|
||||
return ParamTypeString
|
||||
case "int":
|
||||
return ParamTypeInt
|
||||
case "int64":
|
||||
return ParamTypeLong
|
||||
case "bool":
|
||||
return ParamTypeBoolean
|
||||
default:
|
||||
return ParamTypeUnExpected
|
||||
}
|
||||
}
|
||||
|
||||
// ParamStatement is a struct
|
||||
// which holds all the necessary information about a macro parameter.
|
||||
// It holds its type (string, int, alphabetical, file, path),
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user