Add two examples for folder structuring as requested at https://github.com/kataras/iris/issues/748

Former-commit-id: 27c97d005d9cbd2309587b11fc9e2bab85870502
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-10-01 04:31:13 +03:00
parent 7989a2fd72
commit cf1e580cde
19 changed files with 299 additions and 12 deletions

View File

@ -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)

View File

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View 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...)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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"
}

View 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)
}

View File

@ -0,0 +1 @@
<h1>Welcome!!</h1>

View 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>

View 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>&copy; 2017 - {{.AppOwner}}</p>
</footer>
</div>
</body>
</html>

View 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)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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`

View File

@ -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
}