mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
Add a useful(?) introduction README.md section to some examples
Former-commit-id: 14041307dc2f98810d2c20dee68d2c4a7aa63e13
This commit is contained in:
parent
0c6f7f7e0d
commit
5a8b17f0e8
283
_examples/configuration/README.md
Normal file
283
_examples/configuration/README.md
Normal file
|
@ -0,0 +1,283 @@
|
|||
# Configuration
|
||||
|
||||
All configuration's values have default values, things will work as you expected with `iris.New()`.
|
||||
|
||||
Configuration is useless before listen functions, so it should be passed on `Application#Run/2` (second argument(s)).
|
||||
|
||||
Iris has a type named `Configurator` which is a `func(*iris.Application)`, any function
|
||||
which completes this can be passed at `Application#Configure` and/or `Application#Run/2`.
|
||||
|
||||
`Application#ConfigurationReadOnly()` returns the configuration values.
|
||||
|
||||
`.Run` **by `Configuration` struct**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Get("/", func(ctx iris.Context) {
|
||||
ctx.HTML("<b>Hello!</b>")
|
||||
})
|
||||
// [...]
|
||||
|
||||
// Good when you want to modify the whole configuration.
|
||||
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.Configuration{
|
||||
DisableStartupLog: false,
|
||||
DisableInterruptHandler: false,
|
||||
DisablePathCorrection: false,
|
||||
EnablePathEscape: false,
|
||||
FireMethodNotAllowed: false,
|
||||
DisableBodyConsumptionOnUnmarshal: false,
|
||||
DisableAutoFireStatusCode: false,
|
||||
TimeFormat: "Mon, 02 Jan 2006 15:04:05 GMT",
|
||||
Charset: "UTF-8",
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
`.Run` **by options**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Get("/", func(ctx iris.Context) {
|
||||
ctx.HTML("<b>Hello!</b>")
|
||||
})
|
||||
// [...]
|
||||
|
||||
// Good when you want to change some of the configuration's field.
|
||||
// Prefix: "With", code editors will help you navigate through all
|
||||
// configuration options without even a glitch to the documentation.
|
||||
|
||||
app.Run(iris.Addr(":8080"), iris.WithoutStartupLog, iris.WithCharset("UTF-8"))
|
||||
|
||||
// or before run:
|
||||
// app.Configure(iris.WithoutStartupLog, iris.WithCharset("UTF-8"))
|
||||
// app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
```
|
||||
|
||||
`.Run` **by TOML config file**
|
||||
|
||||
```tml
|
||||
DisablePathCorrection = false
|
||||
EnablePathEscape = false
|
||||
FireMethodNotAllowed = true
|
||||
DisableBodyConsumptionOnUnmarshal = false
|
||||
TimeFormat = "Mon, 01 Jan 2006 15:04:05 GMT"
|
||||
Charset = "UTF-8"
|
||||
|
||||
[Other]
|
||||
MyServerName = "iris"
|
||||
|
||||
```
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Get("/", func(ctx iris.Context) {
|
||||
ctx.HTML("<b>Hello!</b>")
|
||||
})
|
||||
// [...]
|
||||
|
||||
// Good when you have two configurations, one for development and a different one for production use.
|
||||
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.TOML("./configs/iris.tml")))
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
`.Run` **by YAML config file**
|
||||
|
||||
```yml
|
||||
DisablePathCorrection: false
|
||||
EnablePathEscape: false
|
||||
FireMethodNotAllowed: true
|
||||
DisableBodyConsumptionOnUnmarshal: true
|
||||
TimeFormat: Mon, 01 Jan 2006 15:04:05 GMT
|
||||
Charset: UTF-8
|
||||
```
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Get("/", func(ctx iris.Context) {
|
||||
ctx.HTML("<b>Hello!</b>")
|
||||
})
|
||||
// [...]
|
||||
|
||||
app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.YAML("./configs/iris.yml")))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Built'n Configurators
|
||||
|
||||
```go
|
||||
// WithoutServerError will cause to ignore the matched "errors"
|
||||
// from the main application's `Run` function.
|
||||
//
|
||||
// Usage:
|
||||
// err := app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
|
||||
// will return `nil` if the server's error was `http/iris#ErrServerClosed`.
|
||||
//
|
||||
// See `Configuration#IgnoreServerErrors []string` too.
|
||||
//
|
||||
// Example: https://github.com/kataras/iris/tree/master/_examples/http-listening/listen-addr/omit-server-errors
|
||||
func WithoutServerError(errors ...error) Configurator
|
||||
|
||||
// WithoutStartupLog turns off the information send, once, to the terminal when the main server is open.
|
||||
var WithoutStartupLog
|
||||
|
||||
// WithoutInterruptHandler disables the automatic graceful server shutdown
|
||||
// when control/cmd+C pressed.
|
||||
var WithoutInterruptHandler
|
||||
|
||||
// WithoutPathCorrection disables the PathCorrection setting.
|
||||
//
|
||||
// See `Configuration`.
|
||||
var WithoutPathCorrection
|
||||
|
||||
// WithoutBodyConsumptionOnUnmarshal disables BodyConsumptionOnUnmarshal setting.
|
||||
//
|
||||
// See `Configuration`.
|
||||
var WithoutBodyConsumptionOnUnmarshal
|
||||
|
||||
// WithoutAutoFireStatusCode disables the AutoFireStatusCode setting.
|
||||
//
|
||||
// See `Configuration`.
|
||||
var WithoutAutoFireStatusCode
|
||||
|
||||
// WithPathEscape enanbles the PathEscape setting.
|
||||
//
|
||||
// See `Configuration`.
|
||||
var WithPathEscape
|
||||
|
||||
// WithOptimizations can force the application to optimize for the best performance where is possible.
|
||||
//
|
||||
// See `Configuration`.
|
||||
var WithOptimizations
|
||||
|
||||
// WithFireMethodNotAllowed enanbles the FireMethodNotAllowed setting.
|
||||
//
|
||||
// See `Configuration`.
|
||||
var WithFireMethodNotAllowed
|
||||
|
||||
// WithTimeFormat sets the TimeFormat setting.
|
||||
//
|
||||
// See `Configuration`.
|
||||
func WithTimeFormat(timeformat string) Configurator
|
||||
|
||||
// WithCharset sets the Charset setting.
|
||||
//
|
||||
// See `Configuration`.
|
||||
func WithCharset(charset string) Configurator
|
||||
|
||||
// WithRemoteAddrHeader enables or adds a new or existing request header name
|
||||
// that can be used to validate the client's real IP.
|
||||
//
|
||||
// Existing values are:
|
||||
// "X-Real-Ip": false,
|
||||
// "X-Forwarded-For": false,
|
||||
// "CF-Connecting-IP": false
|
||||
//
|
||||
// Look `context.RemoteAddr()` for more.
|
||||
func WithRemoteAddrHeader(headerName string) Configurator
|
||||
|
||||
// WithoutRemoteAddrHeader disables an existing request header name
|
||||
// that can be used to validate the client's real IP.
|
||||
//
|
||||
// Existing values are:
|
||||
// "X-Real-Ip": false,
|
||||
// "X-Forwarded-For": false,
|
||||
// "CF-Connecting-IP": false
|
||||
//
|
||||
// Look `context.RemoteAddr()` for more.
|
||||
func WithoutRemoteAddrHeader(headerName string) Configurator
|
||||
|
||||
// WithOtherValue adds a value based on a key to the Other setting.
|
||||
//
|
||||
// See `Configuration`.
|
||||
func WithOtherValue(key string, val interface{}) Configurator
|
||||
```
|
||||
|
||||
## Custom Configurator
|
||||
|
||||
With the `Configurator` developers can modularize their applications with ease.
|
||||
|
||||
Example Code:
|
||||
|
||||
```go
|
||||
// file counter/counter.go
|
||||
package counter
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/core/host"
|
||||
)
|
||||
|
||||
func Configurator(app *iris.Application) {
|
||||
counterValue := 0
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
|
||||
for range ticker.C {
|
||||
counterValue++
|
||||
}
|
||||
|
||||
app.ConfigureHost(func(h *host.Supervisor) { // <- HERE: IMPORTANT
|
||||
h.RegisterOnShutdown(func() {
|
||||
ticker.Stop()
|
||||
})
|
||||
})
|
||||
}()
|
||||
|
||||
app.Get("/counter", func(ctx iris.Context) {
|
||||
ctx.Writef("Counter value = %d", counterValue)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// file: main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"counter"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Configure(counter.Configurator)
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
```
|
251
_examples/http-listening/README.md
Normal file
251
_examples/http-listening/README.md
Normal file
|
@ -0,0 +1,251 @@
|
|||
# Hosts
|
||||
|
||||
## Listen and Serve
|
||||
|
||||
You can start the server(s) listening to any type of `net.Listener` or even `http.Server` instance.
|
||||
The method for initialization of the server should be passed at the end, via `Run` function.
|
||||
|
||||
The most common method that Go developers are use to serve their servers are
|
||||
by passing a network address with form of "hostname:ip". With Iris
|
||||
we use the `iris.Addr` which is an `iris.Runner` type
|
||||
|
||||
```go
|
||||
// Listening on tcp with network address 0.0.0.0:8080
|
||||
app.Run(iris.Addr(":8080"))
|
||||
```
|
||||
|
||||
Sometimes you have created a standard net/http server somewhere else in your app and want to use that to serve the Iris web app
|
||||
|
||||
```go
|
||||
// Same as before but using a custom http.Server which may being used somewhere else too
|
||||
app.Run(iris.Server(&http.Server{Addr:":8080"}))
|
||||
```
|
||||
|
||||
The most advanced usage is to create a custom or a standard `net.Listener` and pass that to `app.Run`
|
||||
|
||||
```go
|
||||
// Using a custom net.Listener
|
||||
l, err := net.Listen("tcp4", ":8080")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
app.Run(iris.Listener(l))
|
||||
```
|
||||
|
||||
A more complete example, using the unix-only socket files feature
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"net"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// UNIX socket
|
||||
if errOs := os.Remove(socketFile); errOs != nil && !os.IsNotExist(errOs) {
|
||||
app.Logger().Fatal(errOs)
|
||||
}
|
||||
|
||||
l, err := net.Listen("unix", socketFile)
|
||||
|
||||
if err != nil {
|
||||
app.Logger().Fatal(err)
|
||||
}
|
||||
|
||||
if err = os.Chmod(socketFile, mode); err != nil {
|
||||
app.Logger().Fatal(err)
|
||||
}
|
||||
|
||||
app.Run(iris.Listener(l))
|
||||
}
|
||||
```
|
||||
|
||||
UNIX and BSD hosts can take advandage of the reuse port feature
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
// Package tcplisten provides customizable TCP net.Listener with various
|
||||
// performance-related options:
|
||||
//
|
||||
// - SO_REUSEPORT. This option allows linear scaling server performance
|
||||
// on multi-CPU servers.
|
||||
// See https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ for details.
|
||||
//
|
||||
// - TCP_DEFER_ACCEPT. This option expects the server reads from the accepted
|
||||
// connection before writing to them.
|
||||
//
|
||||
// - TCP_FASTOPEN. See https://lwn.net/Articles/508865/ for details.
|
||||
"github.com/valyala/tcplisten"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
// go get github.com/valyala/tcplisten
|
||||
// go run main.go
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Get("/", func(ctx iris.Context) {
|
||||
ctx.HTML("<h1>Hello World!</h1>")
|
||||
})
|
||||
|
||||
listenerCfg := tcplisten.Config{
|
||||
ReusePort: true,
|
||||
DeferAccept: true,
|
||||
FastOpen: true,
|
||||
}
|
||||
|
||||
l, err := listenerCfg.NewListener("tcp", ":8080")
|
||||
if err != nil {
|
||||
app.Logger().Fatal(err)
|
||||
}
|
||||
|
||||
app.Run(iris.Listener(l))
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP/2 and Secure
|
||||
|
||||
If you have signed file keys you can use the `iris.TLS` to serve `https` based on those certification keys
|
||||
|
||||
```go
|
||||
// TLS using files
|
||||
app.Run(iris.TLS("127.0.0.1:443", "mycert.cert", "mykey.key"))
|
||||
```
|
||||
|
||||
The method you should use when your app is ready for **production** is the `iris.AutoTLS` which starts a secure server with automated certifications provided by https://letsencrypt.org for **free**
|
||||
|
||||
```go
|
||||
// Automatic TLS
|
||||
app.Run(iris.AutoTLS(":443", "example.com", "admin@example.com"))
|
||||
```
|
||||
|
||||
### Any `iris.Runner`
|
||||
|
||||
There may be times that you want something very special to listen on, which is not a type of `net.Listener`. You are able to do that by `iris.Raw`, but you're responsible of that method
|
||||
|
||||
```go
|
||||
// Using any func() error,
|
||||
// the responsibility of starting up a listener is up to you with this way,
|
||||
// for the sake of simplicity we will use the
|
||||
// ListenAndServe function of the `net/http` package.
|
||||
app.Run(iris.Raw(&http.Server{Addr:":8080"}).ListenAndServe)
|
||||
```
|
||||
|
||||
## Host configurators
|
||||
|
||||
All the above forms of listening are accepting a last, variadic argument of `func(*iris.Supervisor)`. This is used to add configurators for that specific host you passed via those functions.
|
||||
|
||||
For example let's say that we want to add a callback which is fired when
|
||||
the server is shutdown
|
||||
|
||||
```go
|
||||
app.Run(iris.Addr(":8080", func(h *iris.Supervisor) {
|
||||
h.RegisterOnShutdown(func() {
|
||||
println("server terminated")
|
||||
})
|
||||
}))
|
||||
```
|
||||
|
||||
You can even do that before `app.Run` method, but the difference is that
|
||||
these host configurators will be executed to all hosts that you may use to serve your web app (via `app.NewHost` we'll see that in a minute)
|
||||
|
||||
```go
|
||||
app := iris.New()
|
||||
app.ConfigureHost(func(h *iris.Supervisor) {
|
||||
h.RegisterOnShutdown(func() {
|
||||
println("server terminated")
|
||||
})
|
||||
})
|
||||
app.Run(iris.Addr(":8080"))
|
||||
```
|
||||
|
||||
Access to all hosts that serve your application can be provided by
|
||||
the `Application#Hosts` field, after the `Run` method.
|
||||
|
||||
But the most common scenario is that you may need access to the host before the `app.Run` method,
|
||||
there are two ways of gain access to the host supervisor, read below.
|
||||
|
||||
We have already saw how to configure all application's hosts by second argument of `app.Run` or `app.ConfigureHost`. There is one more way which suits better for simple scenarios and that is to use the `app.NewHost` to create a new host
|
||||
and use one of its `Serve` or `Listen` functions
|
||||
to start the application via the `iris#Raw` Runner.
|
||||
|
||||
Note that this way needs an extra import of the `net/http` package.
|
||||
|
||||
Example Code:
|
||||
|
||||
```go
|
||||
h := app.NewHost(&http.Server{Addr:":8080"})
|
||||
h.RegisterOnShutdown(func(){
|
||||
println("server terminated")
|
||||
})
|
||||
|
||||
app.Run(iris.Raw(h.ListenAndServe))
|
||||
```
|
||||
|
||||
## Multi hosts
|
||||
|
||||
You can serve your Iris web app using more than one server, the `iris.Router` is compatible with the `net/http/Handler` function therefore, as you can understand, it can be used to be adapted at any `net/http` server, however there is an easier way, by using the `app.NewHost` which is also copying all the host configurators and it closes all the hosts attached to the particular web app on `app.Shutdown`.
|
||||
|
||||
```go
|
||||
app := iris.New()
|
||||
app.Get("/", indexHandler)
|
||||
|
||||
// run in different goroutine in order to not block the main "goroutine".
|
||||
go app.Run(iris.Addr(":8080"))
|
||||
// start a second server which is listening on tcp 0.0.0.0:9090,
|
||||
// without "go" keyword because we want to block at the last server-run.
|
||||
app.NewHost(&http.Server{Addr:":9090"}).ListenAndServe()
|
||||
```
|
||||
|
||||
## Shutdown (Gracefully)
|
||||
|
||||
Let's continue by learning how to catch CONTROL+C/COMMAND+C or unix kill command and shutdown the server gracefully.
|
||||
|
||||
> Gracefully Shutdown on CONTROL+C/COMMAND+C or when kill command sent is ENABLED BY-DEFAULT.
|
||||
|
||||
In order to manually manage what to do when app is interrupted,
|
||||
we have to disable the default behavior with the option `WithoutInterruptHandler`
|
||||
and register a new interrupt handler (globally, across all possible hosts).
|
||||
|
||||
|
||||
Example code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
iris.RegisterOnInterrupt(func() {
|
||||
timeout := 5 * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
// close all hosts
|
||||
app.Shutdown(ctx)
|
||||
})
|
||||
|
||||
app.Get("/", func(ctx iris.Context) {
|
||||
ctx.HTML(" <h1>hi, I just exist in order to see if the server is closed</h1>")
|
||||
})
|
||||
|
||||
app.Run(iris.Addr(":8080"), iris.WithoutInterruptHandler)
|
||||
}
|
||||
```
|
970
_examples/mvc/README.md
Normal file
970
_examples/mvc/README.md
Normal file
|
@ -0,0 +1,970 @@
|
|||
# MVC
|
||||
|
||||
![](https://github.com/kataras/iris/raw/master/_examples/mvc/web_mvc_diagram.png)
|
||||
|
||||
Iris has **first-class support for the MVC pattern**, you'll not find
|
||||
these stuff anywhere else in the Go world.
|
||||
|
||||
Iris supports Request data, Models, Persistence Data and Binding
|
||||
with the fastest possible execution.
|
||||
|
||||
## Characteristics
|
||||
|
||||
All HTTP Methods are supported, for example if want to serve `GET`
|
||||
then the controller should have a function named `Get()`,
|
||||
you can define more than one method function to serve in the same Controller struct.
|
||||
|
||||
Persistence data inside your Controller struct (share data between requests)
|
||||
via `iris:"persistence"` tag right to the field or Bind using `app.Controller("/" , new(myController), theBindValue)`.
|
||||
|
||||
Models inside your Controller struct (set-ed at the Method function and rendered by the View)
|
||||
via `iris:"model"` tag right to the field, i.e ```User UserModel `iris:"model" name:"user"` ``` view will recognise it as `{{.user}}`.
|
||||
If `name` tag is missing then it takes the field's name, in this case the `"User"`.
|
||||
|
||||
Access to the request path and its parameters via the `Path and Params` fields.
|
||||
|
||||
Access to the template file that should be rendered via the `Tmpl` field.
|
||||
|
||||
Access to the template data that should be rendered inside
|
||||
the template file via `Data` field.
|
||||
|
||||
Access to the template layout via the `Layout` field.
|
||||
|
||||
Access to the low-level `iris.Context` via the `Ctx` field.
|
||||
|
||||
Get the relative request path by using the controller's name via `RelPath()`.
|
||||
|
||||
Get the relative template path directory by using the controller's name via `RelTmpl()`.
|
||||
|
||||
Flow as you used to, `Controllers` can be registered to any `Party`,
|
||||
including Subdomains, the Party's begin and done handlers work as expected.
|
||||
|
||||
Optional `BeginRequest(ctx)` function to perform any initialization before the method execution,
|
||||
useful to call middlewares or when many methods use the same collection of data.
|
||||
|
||||
Optional `EndRequest(ctx)` function to perform any finalization after any method executed.
|
||||
|
||||
Inheritance, recursively, see for example our `mvc.SessionController`, it has the `iris.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).
|
||||
|
||||
Read access to the current route via the `Route` field.
|
||||
|
||||
Register one or more relative paths and able to get path parameters, i.e
|
||||
|
||||
If `app.Controller("/user", new(user.Controller))`
|
||||
|
||||
- `func(*Controller) Get()` - `GET:/user` , as usual.
|
||||
- `func(*Controller) Post()` - `POST:/user`, as usual.
|
||||
- `func(*Controller) GetLogin()` - `GET:/user/login`
|
||||
- `func(*Controller) PostLogin()` - `POST:/user/login`
|
||||
- `func(*Controller) GetProfileFollowers()` - `GET:/user/profile/followers`
|
||||
- `func(*Controller) PostProfileFollowers()` - `POST:/user/profile/followers`
|
||||
- `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))`
|
||||
|
||||
- `func(*Controller) GetBy(username string)` - `GET:/profile/{param:string}`
|
||||
|
||||
If `app.Controller("/assets", new(file.Controller))`
|
||||
|
||||
- `func(*Controller) GetByWildard(path string)` - `GET:/assets/{param:path}`
|
||||
|
||||
Supported types for method functions receivers: int, int64, bool and string.
|
||||
|
||||
Response via output arguments, optionally, i.e
|
||||
|
||||
```go
|
||||
func(c *ExampleController) Get() string |
|
||||
(string, string) |
|
||||
(string, int) |
|
||||
int |
|
||||
(int, string) |
|
||||
(string, error) |
|
||||
error |
|
||||
(int, error) |
|
||||
(any, bool) |
|
||||
(customStruct, error) |
|
||||
customStruct |
|
||||
(customStruct, int) |
|
||||
(customStruct, string) |
|
||||
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)`.
|
||||
|
||||
## Using Iris MVC for code reuse
|
||||
|
||||
By creating components that are independent of one another, developers are able to reuse components quickly and easily in other applications. The same (or similar) view for one application can be refactored for another application with different data because the view is simply handling how the data is being displayed to the user.
|
||||
|
||||
If you're new to back-end web development read about the MVC architectural pattern first, a good start is that [wikipedia article](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
|
||||
|
||||
## Quick MVC Tutorial Part 1 (without output result)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Controller("/helloworld", new(HelloWorldController))
|
||||
|
||||
app.Run(iris.Addr("localhost:8080"))
|
||||
}
|
||||
|
||||
type HelloWorldController struct {
|
||||
mvc.C
|
||||
|
||||
// [ Your fields here ]
|
||||
// Request lifecycle data
|
||||
// Models
|
||||
// Database
|
||||
// Global properties
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld
|
||||
|
||||
func (c *HelloWorldController) Get() string {
|
||||
return "This is my default action..."
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/{name:string}
|
||||
|
||||
func (c *HelloWorldController) GetBy(name string) string {
|
||||
return "Hello " + name
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/welcome
|
||||
|
||||
func (c *HelloWorldController) GetWelcome() (string, int) {
|
||||
return "This is the GetWelcome action func...", iris.StatusOK
|
||||
}
|
||||
|
||||
//
|
||||
// GET: /helloworld/welcome/{name:string}/{numTimes:int}
|
||||
|
||||
func (c *HelloWorldController) GetWelcomeBy(name string, numTimes int) {
|
||||
// Access to the low-level Context,
|
||||
// output arguments are optional of course so we don't have to use them here.
|
||||
c.Ctx.Writef("Hello %s, NumTimes is: %d", name, numTimes)
|
||||
}
|
||||
|
||||
/*
|
||||
func (c *HelloWorldController) Post() {} handles HTTP POST method requests
|
||||
func (c *HelloWorldController) Put() {} handles HTTP PUT method requests
|
||||
func (c *HelloWorldController) Delete() {} handles HTTP DELETE method requests
|
||||
func (c *HelloWorldController) Connect() {} handles HTTP CONNECT method requests
|
||||
func (c *HelloWorldController) Head() {} handles HTTP HEAD method requests
|
||||
func (c *HelloWorldController) Patch() {} handles HTTP PATCH method requests
|
||||
func (c *HelloWorldController) Options() {} handles HTTP OPTIONS method requests
|
||||
func (c *HelloWorldController) Trace() {} handles HTTP TRACE method requests
|
||||
*/
|
||||
|
||||
/*
|
||||
func (c *HelloWorldController) All() {} handles All method requests
|
||||
// OR
|
||||
func (c *HelloWorldController) Any() {} handles All method requests
|
||||
*/
|
||||
```
|
||||
|
||||
> The [_examples/mvc](https://github.com/kataras/iris/tree/master/_examples/mvc) and [mvc/controller_test.go](https://github.com/kataras/iris/blob/master/mvc/controller_test.go) files explain each feature with simple paradigms, they show how you can take advandage of the Iris MVC Binder, Iris MVC Models and many more...
|
||||
|
||||
Every `exported` func prefixed with an HTTP Method(`Get`, `Post`, `Put`, `Delete`...) in a controller is callable as an HTTP endpoint. In the sample above, all funcs writes a string to the response. Note the comments preceding each method.
|
||||
|
||||
An HTTP endpoint is a targetable URL in the web application, such as `http://localhost:8080/helloworld`, and combines the protocol used: HTTP, the network location of the web server (including the TCP port): `localhost:8080` and the target URI `/helloworld`.
|
||||
|
||||
The first comment states this is an [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp) method that is invoked by appending "/helloworld" to the base URL. The third comment specifies an [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp) method that is invoked by appending "/helloworld/welcome" to the URL.
|
||||
|
||||
Controller knows how to handle the "name" on `GetBy` or the "name" and "numTimes" at `GetWelcomeBy`, because of the `By` keyword, and builds the dynamic route without boilerplate; the third comment specifies an [HTTP GET](https://www.w3schools.com/tags/ref_httpmethods.asp) dynamic method that is invoked by any URL that starts with "/helloworld/welcome" and followed by two more path parts, the first one can accept any value and the second can accept only numbers, i,e: "http://localhost:8080/helloworld/welcome/golang/32719", otherwise a [404 Not Found HTTP Error](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5) will be sent to the client instead.
|
||||
|
||||
----
|
||||
|
||||
### Quick MVC Tutorial #2
|
||||
|
||||
Iris has a very powerful and **blazing [fast](_benchmarks)** MVC support, you can return any value of any type from a method function
|
||||
and it will be sent to the client as expected.
|
||||
|
||||
* if `string` then it's the body.
|
||||
* if `string` is the second output argument then it's the content type.
|
||||
* if `int` then it's the status code.
|
||||
* if `error` and not nil then (any type) response will be omitted and error's text with a 400 bad request will be rendered instead.
|
||||
* if `(int, error)` and error is not nil then the response result will be the error's text with the status code as `int`.
|
||||
* if `bool` is false then it throws 404 not found http error by skipping everything else.
|
||||
* if `custom struct` or `interface{}` or `slice` or `map` then it will be rendered as json, unless a `string` content type is following.
|
||||
* if `mvc.Result` then it executes its `Dispatch` function, so good design patters can be used to split the model's logic where needed.
|
||||
|
||||
The example below is not intended to be used in production but it's a good showcase of some of the return types we saw before;
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/middleware/basicauth"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// Movie is our sample data structure.
|
||||
type Movie struct {
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
|
||||
// movies contains our imaginary data source.
|
||||
var movies = []Movie{
|
||||
{
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
{
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
{
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
{
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
var basicAuth = basicauth.New(basicauth.Config{
|
||||
Users: map[string]string{
|
||||
"admin": "password",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Use(basicAuth)
|
||||
|
||||
app.Controller("/movies", new(MoviesController))
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
// MoviesController is our /movies controller.
|
||||
type MoviesController struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
// Get returns list of the movies
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
func (c *MoviesController) Get() []Movie {
|
||||
return movies
|
||||
}
|
||||
|
||||
// GetBy returns a movie
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MoviesController) GetBy(id int) Movie {
|
||||
return movies[id]
|
||||
}
|
||||
|
||||
// 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 *MoviesController) PutBy(id int) Movie {
|
||||
// get the movie
|
||||
m := movies[id]
|
||||
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
c.Ctx.StatusCode(iris.StatusInternalServerError)
|
||||
return Movie{}
|
||||
}
|
||||
file.Close() // we don't need the file
|
||||
poster := info.Filename // imagine that as the url of the uploaded file...
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
// update the poster
|
||||
m.Poster = poster
|
||||
m.Genre = genre
|
||||
movies[id] = m
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// DeleteBy deletes a movie
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MoviesController) DeleteBy(id int) iris.Map {
|
||||
// delete the entry from the movies slice
|
||||
deleted := movies[id].Name
|
||||
movies = append(movies[:id], movies[id+1:]...)
|
||||
// and return the deleted movie's name
|
||||
return iris.Map{"deleted": deleted}
|
||||
}
|
||||
```
|
||||
|
||||
### Quick MVC Tutorial #3
|
||||
|
||||
Nothing stops you from using your favorite **folder structure**. Iris is a low level web framework, it has got MVC first-class support but it doesn't limit your folder structure, this is your choice.
|
||||
|
||||
Structuring depends on your own needs. We can't tell you how to design your own application for sure but you're free to take a closer look to one typical example below;
|
||||
|
||||
[![folder structure example](_examples/mvc/overview/folder_structure.png)](_examples/mvc/overview)
|
||||
|
||||
Shhh, let's spread the code itself.
|
||||
|
||||
#### Data Model Layer
|
||||
|
||||
```go
|
||||
// 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"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Data Source / Data Store Layer
|
||||
|
||||
```go
|
||||
// file: datasource/movies.go
|
||||
|
||||
package datasource
|
||||
|
||||
import "github.com/kataras/iris/_examples/mvc/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",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### Repositories
|
||||
|
||||
The layer which has direct access to the "datasource" and can manipulate data directly.
|
||||
|
||||
```go
|
||||
// file: repositories/movie_repository.go
|
||||
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/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)
|
||||
}
|
||||
```
|
||||
|
||||
#### Services
|
||||
|
||||
The layer which has access to call functions from the "repositories" and "models" (or even "datamodels" if simple application). It should contain the most of the domain logic.
|
||||
|
||||
```go
|
||||
// file: services/movie_service.go
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
"github.com/kataras/iris/_examples/mvc/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)
|
||||
}
|
||||
```
|
||||
|
||||
#### View Models
|
||||
|
||||
There should be the view models, the structure that the client will be able to see.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
type Movie struct {
|
||||
datamodels.Movie
|
||||
}
|
||||
|
||||
func (m Movie) IsValid() bool {
|
||||
/* do some checks and return true if it's valid... */
|
||||
return m.ID > 0
|
||||
}
|
||||
```
|
||||
|
||||
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
|
||||
so theoretically, something like the following is permitted if it's really necessary;
|
||||
|
||||
```go
|
||||
// Dispatch completes the `kataras/iris/mvc#Result` interface.
|
||||
// Sends a `Movie` as a controlled http response.
|
||||
// If its ID is zero or less then it returns a 404 not found error
|
||||
// else it returns its json representation,
|
||||
// (just like the controller's functions do for custom types by default).
|
||||
//
|
||||
// Don't overdo it, the application's logic should not be here.
|
||||
// It's just one more step of validation before the response,
|
||||
// simple checks can be added here.
|
||||
//
|
||||
// It's just a showcase,
|
||||
// imagine the potentials this feature gives when designing a bigger application.
|
||||
//
|
||||
// This is called where the return value from a controller's method functions
|
||||
// is type of `Movie`.
|
||||
// For example the `controllers/movie_controller.go#GetBy`.
|
||||
func (m Movie) Dispatch(ctx context.Context) {
|
||||
if !m.IsValid() {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.JSON(m, context.JSON{Indent: " "})
|
||||
}
|
||||
```
|
||||
|
||||
However, we will use the "datamodels" as the only one models package because
|
||||
Movie structure doesn't contain any sensitive data, clients are able to see all of its fields
|
||||
and we don't need any extra functionality or validation inside it.
|
||||
|
||||
#### Controllers
|
||||
|
||||
Handles web requests, bridge between the services and the client.
|
||||
|
||||
```go
|
||||
// file: web/controllers/movie_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datamodels"
|
||||
"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
|
||||
|
||||
// Our MovieService, it's an interface which
|
||||
// is binded from the main application.
|
||||
Service services.MovieService
|
||||
}
|
||||
|
||||
// Get returns list of the movies.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
//
|
||||
// The correct way if you have sensitive data:
|
||||
// func (c *MovieController) Get() (results []viewmodels.Movie) {
|
||||
// data := c.Service.GetAll()
|
||||
//
|
||||
// for _, movie := range data {
|
||||
// results = append(results, viewmodels.Movie{movie})
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// otherwise just return the datamodels.
|
||||
func (c *MovieController) Get() (results []datamodels.Movie) {
|
||||
return c.Service.GetAll()
|
||||
}
|
||||
|
||||
// GetBy returns a movie.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MovieController) GetBy(id int64) (movie datamodels.Movie, found bool) {
|
||||
return c.Service.GetByID(id) // it will throw 404 if not found.
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.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 := c.Ctx.FormValue("genre")
|
||||
|
||||
return c.Service.UpdatePosterAndGenreByID(id, poster, genre)
|
||||
}
|
||||
|
||||
// DeleteBy deletes a movie.
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MovieController) DeleteBy(id int64) interface{} {
|
||||
wasDel := c.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
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// file: web/controllers/hello_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// HelloController is our sample controller
|
||||
// it handles GET: /hello and GET: /hello/{name}
|
||||
type HelloController struct {
|
||||
mvc.C
|
||||
}
|
||||
|
||||
var helloView = mvc.View{
|
||||
Name: "hello/index.html",
|
||||
Data: map[string]interface{}{
|
||||
"Title": "Hello Page",
|
||||
"MyMessage": "Welcome to my awesome website",
|
||||
},
|
||||
}
|
||||
|
||||
// Get will return a predefined view with bind data.
|
||||
//
|
||||
// `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.
|
||||
func (c *HelloController) Get() mvc.Result {
|
||||
return helloView
|
||||
}
|
||||
|
||||
// you can define a standard error in order to be re-usable 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 mvc.Response to make it an mvc.Result compatible type.
|
||||
var badName = mvc.Response{Err: errBadName, Code: 400}
|
||||
|
||||
// GetBy returns a "Hello {name}" response.
|
||||
// Demos:
|
||||
// curl -i http://localhost:8080/hello/iris
|
||||
// curl -i http://localhost:8080/hello/anything
|
||||
func (c *HelloController) GetBy(name string) mvc.Result {
|
||||
if name != "iris" {
|
||||
return badName
|
||||
// or
|
||||
// GetBy(name string) (mvc.Result, error) {
|
||||
// return nil, errBadName
|
||||
// }
|
||||
}
|
||||
|
||||
// return mvc.Response{Text: "Hello " + name} OR:
|
||||
return mvc.View{
|
||||
Name: "hello/name.html",
|
||||
Data: name,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// 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",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: web/views/hello/index.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.Title}} - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>{{.MyMessage}}</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: web/views/hello/name.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{.}}' Portfolio - My App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Hello {{.}}</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
> Navigate to the [_examples/view](https://github.com/kataras/iris/tree/master/_examples/#view) for more examples
|
||||
like shared layouts, tmpl funcs, reverse routing and more!
|
||||
|
||||
#### Main
|
||||
|
||||
This file creates any necessary component and links them together.
|
||||
|
||||
```go
|
||||
// file: main.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/overview/datasource"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/repositories"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/services"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/web/controllers"
|
||||
"github.com/kataras/iris/_examples/mvc/overview/web/middleware"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// Load the template files.
|
||||
app.RegisterView(iris.HTML("./web/views", ".html"))
|
||||
|
||||
// Register our controllers.
|
||||
app.Controller("/hello", new(controllers.HelloController))
|
||||
|
||||
// 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(
|
||||
iris.Addr("localhost:8080"),
|
||||
iris.WithoutVersionChecker,
|
||||
iris.WithoutServerError(iris.ErrServerClosed),
|
||||
iris.WithOptimizations, // enables faster json serialization and more
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
More folder structure guidelines can be found at the [_examples/#structuring](https://github.com/kataras/iris/tree/master/_examples/#structuring) section.
|
1141
_examples/routing/README.md
Normal file
1141
_examples/routing/README.md
Normal file
File diff suppressed because it is too large
Load Diff
121
_examples/sessions/README.md
Normal file
121
_examples/sessions/README.md
Normal file
|
@ -0,0 +1,121 @@
|
|||
# Sessions
|
||||
|
||||
Iris provides a fast, fully featured and easy to use sessions manager.
|
||||
|
||||
Iris sessions manager lives on its own [kataras/iris/sessions](https://github.com/kataras/iris/tree/master/sessions) package.
|
||||
|
||||
Some trivial examples,
|
||||
|
||||
- [Overview](https://github.com/kataras/iris/blob/master/_examples/sessions/overview/main.go)
|
||||
- [Standalone](https://github.com/kataras/iris/blob/master/_examples/sessions/standalone/main.go)
|
||||
- [Secure Cookie](https://github.com/kataras/iris/blob/master/_examples/sessions/securecookie/main.go)
|
||||
- [Flash Messages](https://github.com/kataras/iris/blob/master/_examples/sessions/flash-messages/main.go)
|
||||
- [Databases](https://github.com/kataras/iris/tree/master/_examples/sessions/database)
|
||||
* [BadgerDB](https://github.com/kataras/iris/blob/master/_examples/sessions/database/badger/main.go) **fastest**
|
||||
* [File](https://github.com/kataras/iris/blob/master/_examples/sessions/database/file/main.go)
|
||||
* [BoltDB](https://github.com/kataras/iris/blob/master/_examples/sessions/database/boltdb/main.go)
|
||||
* [LevelDB](https://github.com/kataras/iris/blob/master/_examples/sessions/database/leveldb/main.go)
|
||||
* [Redis](https://github.com/kataras/iris/blob/master/_examples/sessions/database/redis/main.go)
|
||||
|
||||
## Overview
|
||||
|
||||
```go
|
||||
import "github.com/kataras/iris/sessions"
|
||||
|
||||
sess := sessions.Start(http.ResponseWriter, *http.Request)
|
||||
sess.
|
||||
ID() string
|
||||
Get(string) interface{}
|
||||
HasFlash() bool
|
||||
GetFlash(string) interface{}
|
||||
GetFlashString(string) string
|
||||
GetString(key string) string
|
||||
GetInt(key string) (int, error)
|
||||
GetInt64(key string) (int64, error)
|
||||
GetFloat32(key string) (float32, error)
|
||||
GetFloat64(key string) (float64, error)
|
||||
GetBoolean(key string) (bool, error)
|
||||
GetAll() map[string]interface{}
|
||||
GetFlashes() map[string]interface{}
|
||||
VisitAll(cb func(k string, v interface{}))
|
||||
Set(string, interface{})
|
||||
SetImmutable(key string, value interface{})
|
||||
SetFlash(string, interface{})
|
||||
Delete(string)
|
||||
Clear()
|
||||
ClearFlashes()
|
||||
```
|
||||
|
||||
This example will show how to store data from a session.
|
||||
|
||||
You don't need any third-party library except Iris, but if you want you can use anything, remember Iris is fully compatible with the standard library. You can find a more detailed examples by pressing [here](https://github.com/kataras/iris/tree/master/_examples/sessions).
|
||||
|
||||
In this example we will only allow authenticated users to view our secret message on the `/secret` age. To get access to it, the will first have to visit `/login` to get a valid session cookie, hich logs him in. Additionally he can visit `/logout` to revoke his access to our secret message.
|
||||
|
||||
```go
|
||||
// sessions.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
var (
|
||||
cookieNameForSessionID = "mycookiesessionnameid"
|
||||
sess = sessions.New(sessions.Config{Cookie: cookieNameForSessionID})
|
||||
)
|
||||
|
||||
func secret(ctx iris.Context) {
|
||||
// Check if user is authenticated
|
||||
if auth, _ := sess.Start(ctx).GetBoolean("authenticated"); !auth {
|
||||
ctx.StatusCode(iris.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Print secret message
|
||||
ctx.WriteString("The cake is a lie!")
|
||||
}
|
||||
|
||||
func login(ctx iris.Context) {
|
||||
session := sess.Start(ctx)
|
||||
|
||||
// Authentication goes here
|
||||
// ...
|
||||
|
||||
// Set user as authenticated
|
||||
session.Set("authenticated", true)
|
||||
}
|
||||
|
||||
func logout(ctx iris.Context) {
|
||||
session := sess.Start(ctx)
|
||||
|
||||
// Revoke users authentication
|
||||
session.Set("authenticated", false)
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Get("/secret", secret)
|
||||
app.Get("/login", login)
|
||||
app.Get("/logout", logout)
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```bash
|
||||
$ go run sessions.go
|
||||
|
||||
$ curl -s http://localhost:8080/secret
|
||||
Forbidden
|
||||
|
||||
$ curl -s -I http://localhost:8080/login
|
||||
Set-Cookie: mysessionid=MTQ4NzE5Mz...
|
||||
|
||||
$ curl -s --cookie "mysessionid=MTQ4NzE5Mz..." http://localhost:8080/secret
|
||||
The cake is a lie!
|
||||
```
|
132
_examples/websocket/README.md
Normal file
132
_examples/websocket/README.md
Normal file
|
@ -0,0 +1,132 @@
|
|||
# Websocket
|
||||
|
||||
[WebSocket](https://wikipedia.org/wiki/WebSocket) is a protocol that enables two-way persistent communication channels over TCP connections. It is used for applications such as chat, stock tickers, games, anywhere you want real-time functionality in a web application.
|
||||
|
||||
[View or download sample code](https://github.com/kataras/iris/tree/master/_examples/websocket).
|
||||
|
||||
## When to use it
|
||||
|
||||
Use WebSockets when you need to work directly with a socket connection. For example, you might need the best possible performance for a real-time game.
|
||||
|
||||
## How to use it
|
||||
|
||||
* import the `"github.com/kataras/iris/websocket"`
|
||||
* Configure the websocket package.
|
||||
* Accept WebSocket requests.
|
||||
* Send and receive messages.
|
||||
|
||||
### Import the websocket package
|
||||
|
||||
```go
|
||||
import "github.com/kataras/iris/websocket"
|
||||
```
|
||||
|
||||
### Configure the websocket package
|
||||
|
||||
```go
|
||||
import "github.com/kataras/iris/websocket"
|
||||
|
||||
func main() {
|
||||
ws := websocket.New(websocket.Config{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### Complete configuration
|
||||
|
||||
```go
|
||||
// Config the websocket server configuration
|
||||
// all of these are optional.
|
||||
type Config struct {
|
||||
// IDGenerator used to create (and later on, set)
|
||||
// an ID for each incoming websocket connections (clients).
|
||||
// The request is an argument which you can use to generate the ID (from headers for example).
|
||||
// If empty then the ID is generated by DefaultIDGenerator: randomString(64)
|
||||
IDGenerator func(ctx context.Context) string
|
||||
|
||||
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
||||
CheckOrigin func(r *http.Request) bool
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
// WriteTimeout time allowed to write a message to the connection.
|
||||
// 0 means no timeout.
|
||||
// Default value is 0
|
||||
WriteTimeout time.Duration
|
||||
// ReadTimeout time allowed to read a message from the connection.
|
||||
// 0 means no timeout.
|
||||
// Default value is 0
|
||||
ReadTimeout time.Duration
|
||||
// PongTimeout allowed to read the next pong message from the connection.
|
||||
// Default value is 60 * time.Second
|
||||
PongTimeout time.Duration
|
||||
// PingPeriod send ping messages to the connection with this period. Must be less than PongTimeout.
|
||||
// Default value is 60 *time.Second
|
||||
PingPeriod time.Duration
|
||||
// MaxMessageSize max message size allowed from connection.
|
||||
// Default value is 1024
|
||||
MaxMessageSize int64
|
||||
// BinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text
|
||||
// compatible if you wanna use the Connection's EmitMessage to send a custom binary data to the client, like a native server-client communication.
|
||||
// defaults to false
|
||||
BinaryMessages bool
|
||||
// ReadBufferSize is the buffer size for the underline reader
|
||||
// Default value is 4096
|
||||
ReadBufferSize int
|
||||
// WriteBufferSize is the buffer size for the underline writer
|
||||
// Default value is 4096
|
||||
WriteBufferSize int
|
||||
// EnableCompression specify if the server should attempt to negotiate per
|
||||
// message compression (RFC 7692). Setting this value to true does not
|
||||
// guarantee that compression will be supported. Currently only "no context
|
||||
// takeover" modes are supported.
|
||||
EnableCompression bool
|
||||
|
||||
// Subprotocols specifies the server's supported protocols in order of
|
||||
// preference. If this field is set, then the Upgrade method negotiates a
|
||||
// subprotocol by selecting the first match in this list with a protocol
|
||||
// requested by the client.
|
||||
Subprotocols []string
|
||||
}
|
||||
```
|
||||
|
||||
### Accept WebSocket requests & send & receive messages
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/websocket"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ws := websocket.New(websocket.Config{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
})
|
||||
|
||||
ws.OnConnection(handleConnection)
|
||||
|
||||
app := iris.New()
|
||||
// register the server on an endpoint.
|
||||
// see the inline javascript code in the websockets.html, this endpoint is used to connect to the server.
|
||||
app.Get("/echo", ws.Handler())
|
||||
|
||||
// serve the javascript built'n client-side library,
|
||||
// see weboskcets.html script tags, this path is used.
|
||||
app.Any("/iris-ws.js", func(ctx iris.Context) {
|
||||
ctx.Write(websocket.ClientSource)
|
||||
})
|
||||
}
|
||||
|
||||
func handleConnection(c websocket.Connection) {
|
||||
// Read events from browser
|
||||
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:
|
||||
// c.Emit("chat", msg)
|
||||
c.To(websocket.Broadcast).Emit("chat", msg)
|
||||
})
|
||||
}
|
||||
```
|
177
view/README.md
Normal file
177
view/README.md
Normal file
|
@ -0,0 +1,177 @@
|
|||
# View
|
||||
|
||||
Iris supports 5 template engines out-of-the-box, developers can still use any external golang template engine,
|
||||
as `context/context#ResponseWriter()` is an `io.Writer`.
|
||||
|
||||
All of these five template engines have common features with common API,
|
||||
like Layout, Template Funcs, Party-specific layout, partial rendering and more.
|
||||
|
||||
- The standard html, its template parser is the [golang.org/pkg/html/template/](https://golang.org/pkg/html/template/)
|
||||
- Django, its template parser is the [github.com/flosch/pongo2](https://github.com/flosch/pongo2)
|
||||
- Pug(Jade), its template parser is the [github.com/Joker/jade](https://github.com/Joker/jade)
|
||||
- Handlebars, its template parser is the [github.com/aymerick/raymond](https://github.com/aymerick/raymond)
|
||||
- Amber, its template parser is the [github.com/eknkc/amber](https://github.com/eknkc/amber)
|
||||
|
||||
## Overview
|
||||
|
||||
```go
|
||||
// file: main.go
|
||||
package main
|
||||
|
||||
import "github.com/kataras/iris"
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
// Load all templates from the "./views" folder
|
||||
// where extension is ".html" and parse them
|
||||
// using the standard `html/template` package.
|
||||
app.RegisterView(iris.HTML("./views", ".html"))
|
||||
|
||||
// Method: GET
|
||||
// Resource: http://localhost:8080
|
||||
app.Get("/", func(ctx iris.Context) {
|
||||
// Bind: {{.message}} with "Hello world!"
|
||||
ctx.ViewData("message", "Hello world!")
|
||||
// Render template file: ./views/hello.html
|
||||
ctx.View("hello.html")
|
||||
})
|
||||
|
||||
// Method: GET
|
||||
// Resource: http://localhost:8080/user/42
|
||||
app.Get("/user/{id:long}", func(ctx iris.Context) {
|
||||
userID, _ := ctx.Params().GetInt64("id")
|
||||
ctx.Writef("User ID: %d", userID)
|
||||
})
|
||||
|
||||
// Start the server using a network address.
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: ./views/hello.html -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Hello Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{.message}}</h1>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Template functions
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/kataras/iris"
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// - standard html | iris.HTML(...)
|
||||
// - django | iris.Django(...)
|
||||
// - pug(jade) | iris.Pug(...)
|
||||
// - handlebars | iris.Handlebars(...)
|
||||
// - amber | iris.Amber(...)
|
||||
tmpl := iris.HTML("./templates", ".html")
|
||||
|
||||
// built'n template funcs are:
|
||||
//
|
||||
// - {{ urlpath "mynamedroute" "pathParameter_ifneeded" }}
|
||||
// - {{ render "header.html" }}
|
||||
// - {{ render_r "header.html" }} // partial relative path to current page
|
||||
// - {{ yield }}
|
||||
// - {{ current }}
|
||||
|
||||
// register a custom template func.
|
||||
tmpl.AddFunc("greet", func(s string) string {
|
||||
return "Greetings " + s + "!"
|
||||
})
|
||||
|
||||
// register the view engine to the views, this will load the templates.
|
||||
app.RegisterView(tmpl)
|
||||
|
||||
app.Get("/", hi)
|
||||
|
||||
// http://localhost:8080
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
func hi(ctx iris.Context) {
|
||||
// render the template file "./templates/hi.html"
|
||||
ctx.View("hi.html")
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- file: ./templates/hi.html -->
|
||||
<b>{{greet "kataras"}}</b> <!-- will be rendered as: <b>Greetings kataras!</b> -->
|
||||
```
|
||||
|
||||
## Embedded
|
||||
|
||||
View engine supports bundled(https://github.com/jteeuwen/go-bindata) template files too.
|
||||
`go-bindata` gives you two functions, `Assset` and `AssetNames`,
|
||||
these can be setted to each of the template engines using the `.Binary` function.
|
||||
|
||||
Example code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/kataras/iris"
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
// $ go get -u github.com/jteeuwen/go-bindata/...
|
||||
// $ go-bindata ./templates/...
|
||||
// $ go build
|
||||
// $ ./embedding-templates-into-app
|
||||
// html files are not used, you can delete the folder and run the example
|
||||
app.RegisterView(iris.HTML("./templates", ".html").Binary(Asset, AssetNames))
|
||||
app.Get("/", hi)
|
||||
|
||||
// http://localhost:8080
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
|
||||
type page struct {
|
||||
Title, Name string
|
||||
}
|
||||
|
||||
func hi(ctx iris.Context) {
|
||||
// {{.Page.Title}} and {{Page.Name}}
|
||||
ctx.ViewData("Page", page{Title: "Hi Page", Name: "iris"})
|
||||
ctx.View("hi.html")
|
||||
}
|
||||
```
|
||||
|
||||
A real example can be found here: https://github.com/kataras/iris/tree/master/_examples/view/embedding-templates-into-app.
|
||||
|
||||
## Reload
|
||||
|
||||
Enable auto-reloading of templates on each request. Useful while developers are in dev mode
|
||||
as they no neeed to restart their app on every template edit.
|
||||
|
||||
Example code:
|
||||
|
||||
```go
|
||||
pugEngine := iris.Pug("./templates", ".jade")
|
||||
pugEngine.Reload(true) // <--- set to true to re-build the templates on each request.
|
||||
app.RegisterView(pugEngine)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
- [Overview](https://github.com/kataras/iris/blob/master/_examples/view/overview/main.go)
|
||||
- [Hi](https://github.com/kataras/iris/blob/master/_examples/view/template_html_0/main.go)
|
||||
- [A simple Layout](https://github.com/kataras/iris/blob/master/_examples/view/template_html_1/main.go)
|
||||
- [Layouts: `yield` and `render` tmpl funcs](https://github.com/kataras/iris/blob/master/_examples/view/template_html_2/main.go)
|
||||
- [The `urlpath` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_3/main.go)
|
||||
- [The `url` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_4/main.go)
|
||||
- [Inject Data Between Handlers](https://github.com/kataras/iris/blob/master/_examples/view/context-view-data/main.go)
|
||||
- [Embedding Templates Into App Executable File](https://github.com/kataras/iris/blob/master/_examples/view/embedding-templates-into-app/main.go)
|
||||
|
||||
You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `context#ResponseWriter`, take a look at the [iris/_examples/http_responsewriter/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/http_responsewriter/quicktemplate) example.
|
Loading…
Reference in New Issue
Block a user