Add two examples for folder structuring as requested at https://github.com/kataras/iris/issues/748
Former-commit-id: 27c97d005d9cbd2309587b11fc9e2bab85870502
|
@ -76,6 +76,7 @@ Help this project to continue deliver awesome and unique features with the highe
|
|||
* [Installation](#-installation)
|
||||
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#we-27-september-2017--v843)
|
||||
* [Learn](#-learn)
|
||||
* [Structuring](_examples/#structuring)
|
||||
* [HTTP Listening](_examples/#http-listening)
|
||||
* [Configuration](_examples/#configuration)
|
||||
* [Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context](_examples/#routing-grouping-dynamic-path-parameters-macros-and-custom-context)
|
||||
|
|
|
@ -14,7 +14,16 @@ It doesn't always contain the "best ways" but it does cover each important featu
|
|||
- [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)
|
||||
|
||||
### HTTP Listening
|
||||
### Structuring
|
||||
|
||||
Nothing stops you from using your favorite folder structure. Iris is a low level web framework, it has got MVC support but it doesn't limit your folder structure, this is your choice.
|
||||
|
||||
Structuring is always depends on your needs. We can't tell you how to design your own application for sure but you're free to take a closer look to the examples below; you may find something useful that you can borrow for your app
|
||||
|
||||
- [Example 1](mvc/login)
|
||||
- [Example 2](structuring/mvc)
|
||||
|
||||
### HTTP Listening
|
||||
|
||||
- [Common, with address](http-listening/listen-addr/main.go)
|
||||
* [omit server errors](http-listening/listen-addr/omit-server-errors/main.go)
|
||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 15 KiB |
144
_examples/structuring/mvc/app/app.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/middleware/logger"
|
||||
"github.com/kataras/iris/middleware/recover"
|
||||
"github.com/kataras/iris/sessions"
|
||||
|
||||
"github.com/kataras/iris/_examples/structuring/mvc/app/controllers/follower"
|
||||
"github.com/kataras/iris/_examples/structuring/mvc/app/controllers/following"
|
||||
"github.com/kataras/iris/_examples/structuring/mvc/app/controllers/index"
|
||||
"github.com/kataras/iris/_examples/structuring/mvc/app/controllers/like"
|
||||
)
|
||||
|
||||
// Application is our application wrapper and bootstrapper, keeps our settings.
|
||||
type Application struct {
|
||||
*iris.Application
|
||||
|
||||
Name string
|
||||
Owner string
|
||||
SpawnDate time.Time
|
||||
|
||||
Sessions *sessions.Sessions
|
||||
}
|
||||
|
||||
// NewApplication returns a new named Application.
|
||||
func NewApplication(name, owner string) *Application {
|
||||
return &Application{
|
||||
Name: name,
|
||||
Owner: owner,
|
||||
Application: iris.New(),
|
||||
SpawnDate: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// begin sends the app's identification info.
|
||||
func (app *Application) begin(ctx iris.Context) {
|
||||
// response headers
|
||||
ctx.Header("App-Name", app.Name)
|
||||
ctx.Header("App-Owner", app.Owner)
|
||||
ctx.Header("App-Since", time.Since(app.SpawnDate).String())
|
||||
|
||||
ctx.Header("Server", "Iris: https://iris-go.com")
|
||||
|
||||
// view data if ctx.View or c.Tmpl = "$page.html" will be called next.
|
||||
ctx.ViewData("AppName", app.Name)
|
||||
ctx.ViewData("AppOwner", app.Owner)
|
||||
ctx.Next()
|
||||
}
|
||||
|
||||
// SetupViews loads the templates.
|
||||
func (app *Application) SetupViews(viewsDir string) {
|
||||
app.RegisterView(iris.HTML(viewsDir, ".html").Layout("shared/layout.html"))
|
||||
}
|
||||
|
||||
// SetupSessions initializes the sessions, optionally.
|
||||
func (app *Application) SetupSessions(expires time.Duration, cookieHashKey, cookieBlockKey []byte) {
|
||||
app.Sessions = sessions.New(sessions.Config{
|
||||
Cookie: "SECRET_SESS_COOKIE_" + app.Name,
|
||||
Expires: expires,
|
||||
Encoding: securecookie.New(cookieHashKey, cookieBlockKey),
|
||||
})
|
||||
}
|
||||
|
||||
// SetupErrorHandlers prepares the http error handlers (>=400).
|
||||
// Remember that error handlers in Iris have their own middleware ecosystem
|
||||
// so the route's middlewares are not running when an http error happened.
|
||||
// So if we want a logger we have to re-create one, here we will customize that logger as well.
|
||||
func (app *Application) SetupErrorHandlers() {
|
||||
httpErrStatusLogger := logger.New(logger.Config{
|
||||
Status: true,
|
||||
IP: true,
|
||||
Method: true,
|
||||
Path: true,
|
||||
MessageContextKey: "message",
|
||||
LogFunc: func(now time.Time, latency time.Duration,
|
||||
status, ip, method, path string,
|
||||
message interface{}) {
|
||||
|
||||
line := fmt.Sprintf("%v %4v %s %s %s", status, latency, ip, method, path)
|
||||
|
||||
if message != nil {
|
||||
line += fmt.Sprintf(" %v", message)
|
||||
}
|
||||
app.Logger().Warn(line)
|
||||
},
|
||||
})
|
||||
|
||||
app.OnAnyErrorCode(app.begin, httpErrStatusLogger, func(ctx iris.Context) {
|
||||
err := iris.Map{
|
||||
"app": app.Name,
|
||||
"status": ctx.GetStatusCode(),
|
||||
"message": ctx.Values().GetString("message"),
|
||||
}
|
||||
|
||||
if jsonOutput, _ := ctx.URLParamBool("json"); jsonOutput {
|
||||
ctx.JSON(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.ViewData("Err", err)
|
||||
ctx.ViewData("Title", "Error")
|
||||
ctx.View("shared/error.html")
|
||||
})
|
||||
}
|
||||
|
||||
// SetupRouter registers the available routes from the "controllers" package.
|
||||
func (app *Application) SetupRouter() {
|
||||
app.Use(recover.New())
|
||||
app.Use(app.begin)
|
||||
app.Use(iris.Gzip)
|
||||
|
||||
app.Favicon("./public/favicon.ico")
|
||||
app.StaticWeb("/public", "./public")
|
||||
|
||||
app.Use(logger.New())
|
||||
|
||||
app.Controller("/", new(index.Controller))
|
||||
app.Controller("/follower", new(follower.Controller))
|
||||
app.Controller("/following", new(following.Controller))
|
||||
app.Controller("/like", new(like.Controller))
|
||||
}
|
||||
|
||||
// Instance is our global application bootstrap instance.
|
||||
var Instance = NewApplication("My Awesome App", "kataras2006@hotmail.com")
|
||||
|
||||
// Boot starts our default instance appolication.
|
||||
func Boot(runner iris.Runner, configurators ...iris.Configurator) {
|
||||
Instance.SetupViews("./app/views")
|
||||
Instance.SetupSessions(24*time.Hour,
|
||||
[]byte("the-big-and-secret-fash-key-here"),
|
||||
[]byte("lot-secret-of-characters-big-too"),
|
||||
)
|
||||
|
||||
Instance.SetupErrorHandlers()
|
||||
Instance.SetupRouter()
|
||||
|
||||
Instance.Run(runner, configurators...)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package follower
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
iris.Controller
|
||||
}
|
||||
|
||||
func (c *Controller) GetBy(id int64) {
|
||||
c.Ctx.Writef("from "+c.Route().Path()+" with ID: %d", id)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package following
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
iris.Controller
|
||||
}
|
||||
|
||||
func (c *Controller) GetBy(id int64) {
|
||||
c.Ctx.Writef("from "+c.Route().Path()+" with ID: %d", id)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
iris.Controller
|
||||
}
|
||||
|
||||
func (c *Controller) Get() {
|
||||
c.Data["Title"] = "Index"
|
||||
c.Tmpl = "index.html"
|
||||
}
|
13
_examples/structuring/mvc/app/controllers/like/controller.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package like
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
iris.Controller
|
||||
}
|
||||
|
||||
func (c *Controller) GetBy(id int64) {
|
||||
c.Ctx.Writef("from "+c.Route().Path()+" with ID: %d", id)
|
||||
}
|
1
_examples/structuring/mvc/app/views/index.html
Normal file
|
@ -0,0 +1 @@
|
|||
<h1>Welcome!!</h1>
|
5
_examples/structuring/mvc/app/views/shared/error.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
<h3>{{.Err.status}}</h3>
|
||||
<h4>{{.Err.message}}</h4>
|
23
_examples/structuring/mvc/app/views/shared/layout.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<title>{{.Title}} - {{.AppName}}</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<!-- Render the current template here -->
|
||||
{{ yield }}
|
||||
<hr />
|
||||
<footer>
|
||||
<p>© 2017 - {{.AppOwner}}</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
15
_examples/structuring/mvc/main.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
|
||||
"github.com/kataras/iris/_examples/structuring/mvc/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// http://localhost:8080
|
||||
// http://localhost:8080/follower/42
|
||||
// http://localhost:8080/following/42
|
||||
// http://localhost:8080/like/42
|
||||
app.Boot(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithoutVersionChecker)
|
||||
}
|
BIN
_examples/structuring/mvc/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 15 KiB |
|
@ -117,7 +117,7 @@ func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
|
|||
func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err error) {
|
||||
n, err = fmt.Fprintf(w, format, a...)
|
||||
if err == nil {
|
||||
w.ResponseWriter.Header().Set(contentTextHeaderValue, "text/plain")
|
||||
w.ResponseWriter.Header().Set(contentTypeHeaderKey, contentTextHeaderValue)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -128,7 +128,7 @@ func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err
|
|||
func (w *GzipResponseWriter) WriteString(s string) (n int, err error) {
|
||||
n, err = w.Write([]byte(s))
|
||||
if err == nil {
|
||||
w.ResponseWriter.Header().Set(contentTextHeaderValue, "text/plain")
|
||||
w.ResponseWriter.Header().Set(contentTypeHeaderKey, contentTextHeaderValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) {
|
|||
}
|
||||
|
||||
w.ResponseWriter.Header().Add(varyHeaderKey, "Accept-Encoding")
|
||||
w.ResponseWriter.Header().Set(contentEncodingHeaderKey, "gzip")
|
||||
w.ResponseWriter.Header().Add(contentEncodingHeaderKey, "gzip")
|
||||
|
||||
// if not `WriteNow` but "Content-Length" header
|
||||
// is exists, then delete it before `.Write`
|
||||
|
|
|
@ -11,12 +11,39 @@ const (
|
|||
DefaultCookieName = "irissessionid"
|
||||
)
|
||||
|
||||
// Encoding is the Cookie Encoder/Decoder interface, which can be passed as configuration field
|
||||
// alternatively to the `Encode` and `Decode` fields.
|
||||
type Encoding interface {
|
||||
// Encode the cookie value if not nil.
|
||||
// Should accept as first argument the cookie name (config.Name)
|
||||
// as second argument the server's generated session id.
|
||||
// Should return the new session id, if error the session id setted to empty which is invalid.
|
||||
//
|
||||
// Note: Errors are not printed, so you have to know what you're doing,
|
||||
// and remember: if you use AES it only supports key sizes of 16, 24 or 32 bytes.
|
||||
// You either need to provide exactly that amount or you derive the key from what you type in.
|
||||
//
|
||||
// Defaults to nil
|
||||
Encode(cookieName string, value interface{}) (string, error)
|
||||
// Decode the cookie value if not nil.
|
||||
// Should accept as first argument the cookie name (config.Name)
|
||||
// as second second accepts the client's cookie value (the encoded session id).
|
||||
// Should return an error if decode operation failed.
|
||||
//
|
||||
// Note: Errors are not printed, so you have to know what you're doing,
|
||||
// and remember: if you use AES it only supports key sizes of 16, 24 or 32 bytes.
|
||||
// You either need to provide exactly that amount or you derive the key from what you type in.
|
||||
//
|
||||
// Defaults to nil
|
||||
Decode(cookieName string, cookieValue string, v interface{}) error
|
||||
}
|
||||
|
||||
type (
|
||||
// Config is the configuration for sessions. Please review it well before using sessions.
|
||||
Config struct {
|
||||
// Cookie string, the session's client cookie name, for example: "mysessionid"
|
||||
//
|
||||
// Defaults to "irissessionid"
|
||||
// Defaults to "irissessionid".
|
||||
Cookie string
|
||||
|
||||
// CookieSecureTLS set to true if server is running over TLS
|
||||
|
@ -26,11 +53,11 @@ type (
|
|||
// Recommendation: You don't need this to be setted to true, just fill the Encode and Decode fields
|
||||
// with a third-party library like secure cookie, example is provided at the _examples folder.
|
||||
//
|
||||
// Defaults to false
|
||||
// Defaults to false.
|
||||
CookieSecureTLS bool
|
||||
|
||||
// Encode the cookie value if not nil.
|
||||
// Should accept as first argument the cookie name (config.Name)
|
||||
// Should accept as first argument the cookie name (config.Cookie)
|
||||
// as second argument the server's generated session id.
|
||||
// Should return the new session id, if error the session id setted to empty which is invalid.
|
||||
//
|
||||
|
@ -38,10 +65,10 @@ type (
|
|||
// and remember: if you use AES it only supports key sizes of 16, 24 or 32 bytes.
|
||||
// You either need to provide exactly that amount or you derive the key from what you type in.
|
||||
//
|
||||
// Defaults to nil
|
||||
// Defaults to nil.
|
||||
Encode func(cookieName string, value interface{}) (string, error)
|
||||
// Decode the cookie value if not nil.
|
||||
// Should accept as first argument the cookie name (config.Name)
|
||||
// Should accept as first argument the cookie name (config.Cookie)
|
||||
// as second second accepts the client's cookie value (the encoded session id).
|
||||
// Should return an error if decode operation failed.
|
||||
//
|
||||
|
@ -49,9 +76,13 @@ type (
|
|||
// and remember: if you use AES it only supports key sizes of 16, 24 or 32 bytes.
|
||||
// You either need to provide exactly that amount or you derive the key from what you type in.
|
||||
//
|
||||
// Defaults to nil
|
||||
// Defaults to nil.
|
||||
Decode func(cookieName string, cookieValue string, v interface{}) error
|
||||
|
||||
// Encoding same as Encode and Decode but receives a single instance which
|
||||
// completes the "CookieEncoder" interface, `Encode` and `Decode` functions.
|
||||
Encoding Encoding
|
||||
|
||||
// Expires the duration of which the cookie must expires (created_time.Add(Expires)).
|
||||
// If you want to delete the cookie when the browser closes, set it to -1.
|
||||
//
|
||||
|
@ -59,7 +90,7 @@ type (
|
|||
// -1 means when browser closes
|
||||
// > 0 is the time.Duration which the session cookies should expire.
|
||||
//
|
||||
// Defaults to infinitive/unlimited life duration(0)
|
||||
// Defaults to infinitive/unlimited life duration(0).
|
||||
Expires time.Duration
|
||||
|
||||
// SessionIDGenerator should returns a random session id.
|
||||
|
@ -69,7 +100,7 @@ type (
|
|||
|
||||
// DisableSubdomainPersistence set it to true in order dissallow your subdomains to have access to the session cookie
|
||||
//
|
||||
// Defaults to false
|
||||
// Defaults to false.
|
||||
DisableSubdomainPersistence bool
|
||||
}
|
||||
)
|
||||
|
@ -88,5 +119,10 @@ func (c Config) Validate() Config {
|
|||
}
|
||||
}
|
||||
|
||||
if c.Encoding != nil {
|
||||
c.Encode = c.Encoding.Encode
|
||||
c.Decode = c.Encoding.Decode
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
|