1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-21 11:57:02 +00:00

Add notes for the new lead maintainer of the open-source iris project and align with @get-ion/ion by @hiveminded

Former-commit-id: da4f38eb9034daa49446df3ee529423b98f9b331
This commit is contained in:
kataras
2017-07-10 18:32:42 +03:00
parent 2d4c2779a7
commit 9f85b74fc9
344 changed files with 4842 additions and 5174 deletions

View File

@@ -0,0 +1,172 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
func main() {
app := iris.New()
// registers a custom handler for 404 not found http (error) status code,
// fires when route not found or manually by ctx.StatusCode(iris.StatusNotFound).
app.OnErrorCode(iris.StatusNotFound, notFoundHandler)
// GET -> HTTP Method
// / -> Path
// func(ctx context.Context) -> The route's handler.
//
// Third receiver should contains the route's handler(s), they are executed by order.
app.Handle("GET", "/", func(ctx context.Context) {
// navigate to the middle of $GOPATH/src/github.com/kataras/iris/context/context.go
// to overview all context's method (there a lot of them, read that and you will learn how iris works too)
ctx.HTML("Hello from " + ctx.Path()) // Hello from /
})
app.Get("/home", func(ctx context.Context) {
ctx.Writef(`Same as app.Handle("GET", "/", [...])`)
})
app.Get("/donate", donateHandler, donateFinishHandler)
// Pssst, don't forget dynamic-path example for more "magic"!
app.Get("/api/users/{userid:int min(1)}", func(ctx context.Context) {
userID, err := ctx.Params().GetInt("userid")
if err != nil {
ctx.Writef("error while trying to parse userid parameter," +
"this will never happen if :int is being used because if it's not integer it will fire Not Found automatically.")
ctx.StatusCode(iris.StatusBadRequest)
return
}
ctx.JSON(map[string]interface{}{
// you can pass any custom structured go value of course.
"user_id": userID,
})
})
// app.Post("/", func(ctx context.Context){}) -> for POST http method.
// app.Put("/", func(ctx context.Context){})-> for "PUT" http method.
// app.Delete("/", func(ctx context.Context){})-> for "DELETE" http method.
// app.Options("/", func(ctx context.Context){})-> for "OPTIONS" http method.
// app.Trace("/", func(ctx context.Context){})-> for "TRACE" http method.
// app.Head("/", func(ctx context.Context){})-> for "HEAD" http method.
// app.Connect("/", func(ctx context.Context){})-> for "CONNECT" http method.
// app.Patch("/", func(ctx context.Context){})-> for "PATCH" http method.
// app.Any("/", func(ctx context.Context){}) for all http methods.
// More than one route can contain the same path with a different http mapped method.
// You can catch any route creation errors with:
// route, err := app.Get(...)
// set a name to a route: route.Name = "myroute"
// You can also group routes by path prefix, sharing middleware(s) and done handlers.
adminRoutes := app.Party("/admin", adminMiddleware)
adminRoutes.Done(func(ctx context.Context) { // executes always last if ctx.Next()
ctx.Application().Logger().Infof("response sent to " + ctx.Path())
})
// adminRoutes.Layout("/views/layouts/admin.html") // set a view layout for these routes, see more at view examples.
// GET: http://localhost:8080/admin
adminRoutes.Get("/", func(ctx context.Context) {
// [...]
ctx.StatusCode(iris.StatusOK) // default is 200 == iris.StatusOK
ctx.HTML("<h1>Hello from admin/</h1>")
ctx.Next() // in order to execute the party's "Done" Handler(s)
})
// GET: http://localhost:8080/admin/login
adminRoutes.Get("/login", func(ctx context.Context) {
// [...]
})
// POST: http://localhost:8080/admin/login
adminRoutes.Post("/login", func(ctx context.Context) {
// [...]
})
// subdomains, easier than ever, should add localhost or 127.0.0.1 into your hosts file,
// etc/hosts on unix or C:/windows/system32/drivers/etc/hosts on windows.
v1 := app.Party("v1.")
{ // braces are optional, it's just type of style, to group the routes visually.
// http://v1.localhost:8080
v1.Get("/", func(ctx context.Context) {
ctx.HTML("Version 1 API. go to <a href='" + ctx.Path() + "/api" + "'>/api/users</a>")
})
usersAPI := v1.Party("/api/users")
{
// http://v1.localhost:8080/api/users
usersAPI.Get("/", func(ctx context.Context) {
ctx.Writef("All users")
})
// http://v1.localhost:8080/api/users/42
usersAPI.Get("/{userid:int}", func(ctx context.Context) {
ctx.Writef("user with id: %s", ctx.Params().Get("userid"))
})
}
}
// wildcard subdomains.
wildcardSubdomain := app.Party("*.")
{
wildcardSubdomain.Get("/", func(ctx context.Context) {
ctx.Writef("Subdomain can be anything, now you're here from: %s", ctx.Subdomain())
})
}
// http://localhost:8080
// http://localhost:8080/home
// http://localhost:8080/donate
// http://localhost:8080/api/users/42
// http://localhost:8080/admin
// http://localhost:8080/admin/login
//
// http://localhost:8080/api/users/0
// http://localhost:8080/api/users/blabla
// http://localhost:8080/wontfound
//
// if hosts edited:
// http://v1.localhost:8080
// http://v1.localhost:8080/api/users
// http://v1.localhost:8080/api/users/42
// http://anything.localhost:8080
app.Run(iris.Addr(":8080"))
}
func adminMiddleware(ctx context.Context) {
// [...]
ctx.Next() // to move to the next handler, or don't that if you have any auth logic.
}
func donateHandler(ctx context.Context) {
ctx.Writef("Just like an inline handler, but it can be " +
"used by other package, anywhere in your project.")
// let's pass a value to the next handler
// Values is the way handlers(or middleware) are communicating between each other.
ctx.Values().Set("donate_url", "https://github.com/kataras/iris#buy-me-a-cup-of-coffee")
ctx.Next() // in order to execute the next handler in the chain, look donate route.
}
func donateFinishHandler(ctx context.Context) {
// values can be any type of object so we could cast the value to a string
// but iris provides an easy to do that, if donate_url is not defined, then it returns an empty string instead.
donateURL := ctx.Values().GetString("donate_url")
ctx.Application().Logger().Infof("donate_url value was: " + donateURL)
ctx.Writef("\n\nDonate sent(?).")
}
func notFoundHandler(ctx context.Context) {
ctx.HTML("Custom route for 404 not found http code, here you can render a view, html, json <b>any valid response</b>.")
}
// Notes:
// A path parameter name should contain only alphabetical letters, symbols, containing '_' and numbers are NOT allowed.
// If route failed to be registered, the app will panic without any warnings
// if you didn't catch the second return value(error) on .Handle/.Get....
// See "file-server/single-page-application" to see how another feature, "WrapRouter", works.

View File

@@ -0,0 +1,79 @@
package main
// In this package I'll show you how to override the existing Context's functions and methods.
// You can easly navigate to the custom-context example to see how you can add new functions
// to your own context (need a custom handler).
//
// This way is far easier to understand and it's faster when you want to override existing methods:
import (
"reflect"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
// Create your own custom Context, put any fields you wanna need.
type MyContext struct {
// Optional Part 1: embed (optional but required if you don't want to override all context's methods)
context.Context // it's the context/context.go#context struct but you don't need to know it.
}
var _ context.Context = &MyContext{} // optionally: validate on compile-time if MyContext implements context.Context.
// Optional Part 2:
// The only one important if you will override the Context with an embedded context.Context inside it.
func (ctx *MyContext) Next() {
context.Next(ctx)
}
// Override any context's method you want...
// [...]
func (ctx *MyContext) HTML(htmlContents string) (int, error) {
ctx.Application().Logger().Infof("Executing .HTML function from MyContext")
ctx.ContentType("text/html")
return ctx.WriteString(htmlContents)
}
func main() {
app := iris.New()
// Register a view engine on .html files inside the ./view/** directory.
app.RegisterView(iris.HTML("./view", ".html"))
// The only one Required:
// here is how you define how your own context will be created and acquired from the iris' generic context pool.
app.ContextPool.Attach(func() context.Context {
return &MyContext{
// Optional Part 3:
Context: context.NewContext(app),
}
})
// register your route, as you normally do
app.Handle("GET", "/", recordWhichContextJustForProofOfConcept, func(ctx context.Context) {
// use the context's overridden HTML method.
ctx.HTML("<h1> Hello from my custom context's HTML! </h1>")
})
// this will be executed by the MyContext.Context
// if MyContext is not directly define the View function by itself.
app.Handle("GET", "/hi/{firstname:alphabetical}", recordWhichContextJustForProofOfConcept, func(ctx context.Context) {
firstname := ctx.Values().GetString("firstname")
ctx.ViewData("firstname", firstname)
ctx.Gzip(true)
ctx.View("hi.html")
})
app.Run(iris.Addr(":8080"))
}
// should always print "($PATH) Handler is executing from 'MyContext'"
func recordWhichContextJustForProofOfConcept(ctx context.Context) {
ctx.Application().Logger().Infof("(%s) Handler is executing from: '%s'", ctx.Path(), reflect.TypeOf(ctx).Elem().Name())
ctx.Next()
}
// Look "new-implementation" to see how you can create an entirely new Context with new functions.

View File

@@ -0,0 +1 @@
<h1> Hi {{.firstname}} </h1>

View File

@@ -0,0 +1,104 @@
package main
import (
"sync"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/sessions"
)
// Owner is our application structure, it contains the methods or fields we need,
// think it as the owner of our *Context.
type Owner struct {
// define here the fields that are global
// and shared to all clients.
sessionsManager *sessions.Sessions
}
// this package-level variable "application" will be used inside context to communicate with our global Application.
var owner = &Owner{
sessionsManager: sessions.New(sessions.Config{Cookie: "mysessioncookie"}),
}
// Context is our custom context.
// Let's implement a context which will give us access
// to the client's Session with a trivial `ctx.Session()` call.
type Context struct {
context.Context
session *sessions.Session
}
// Session returns the current client's session.
func (ctx *Context) Session() *sessions.Session {
// this help us if we call `Session()` multiple times in the same handler
if ctx.session == nil {
// start a new session if not created before.
ctx.session = owner.sessionsManager.Start(ctx.Context)
}
return ctx.session
}
// Bold will send a bold text to the client.
func (ctx *Context) Bold(text string) {
ctx.HTML("<b>" + text + "</b>")
}
var contextPool = sync.Pool{New: func() interface{} {
return &Context{}
}}
func acquire(original context.Context) *Context {
ctx := contextPool.Get().(*Context)
ctx.Context = original // set the context to the original one in order to have access to iris's implementation.
ctx.session = nil // reset the session
return ctx
}
func release(ctx *Context) {
contextPool.Put(ctx)
}
// Handler will convert our handler of func(*Context) to an iris Handler,
// in order to be compatible with the HTTP API.
func Handler(h func(*Context)) context.Handler {
return func(original context.Context) {
ctx := acquire(original)
h(ctx)
release(ctx)
}
}
func newApp() *iris.Application {
app := iris.New()
// Work as you did before, the only difference
// is that the original context.Handler should be wrapped with our custom
// `Handler` function.
app.Get("/", Handler(func(ctx *Context) {
ctx.Bold("Hello from our *Context")
}))
app.Post("/set", Handler(func(ctx *Context) {
nameFieldValue := ctx.FormValue("name")
ctx.Session().Set("name", nameFieldValue)
ctx.Writef("set session = " + nameFieldValue)
}))
app.Get("/get", Handler(func(ctx *Context) {
name := ctx.Session().GetString("name")
ctx.Writef(name)
}))
return app
}
func main() {
app := newApp()
// GET: http://localhost:8080
// POST: http://localhost:8080/set
// GET: http://localhost:8080/get
app.Run(iris.Addr(":8080"))
}

View File

@@ -0,0 +1,26 @@
package main
import (
"testing"
"github.com/kataras/iris/httptest"
)
func TestCustomContextNewImpl(t *testing.T) {
app := newApp()
e := httptest.New(t, app, httptest.URL("http://localhost:8080"))
e.GET("/").Expect().
Status(httptest.StatusOK).
ContentType("text/html").
Body().Equal("<b>Hello from our *Context</b>")
expectedName := "iris"
e.POST("/set").WithFormField("name", expectedName).Expect().
Status(httptest.StatusOK).
Body().Equal("set session = " + expectedName)
e.GET("/get").Expect().
Status(httptest.StatusOK).
Body().Equal(expectedName)
}

View File

@@ -0,0 +1,92 @@
package main
import (
"net/http"
"strings"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
// In this example you'll just see one use case of .WrapRouter.
// You can use the .WrapRouter to add custom logic when or when not the router should
// be executed in order to execute the registered routes' handlers.
//
// To see how you can serve files on root "/" without a custom wrapper
// just navigate to the "file-server/single-page-application" example.
//
// This is just for the proof of concept, you can skip this tutorial if it's too much for you.
func newApp() *iris.Application {
app := iris.New()
app.OnErrorCode(iris.StatusNotFound, func(ctx context.Context) {
ctx.HTML("<b>Resource Not found</b>")
})
app.Get("/", func(ctx context.Context) {
ctx.ServeFile("./public/index.html", false)
})
app.Get("/profile/{username}", func(ctx context.Context) {
ctx.Writef("Hello %s", ctx.Params().Get("username"))
})
// serve files from the root "/", if we used .StaticWeb it could override
// all the routes because of the underline need of wildcard.
// Here we will see how you can by-pass this behavior
// by creating a new file server handler and
// setting up a wrapper for the router(like a "low-level" middleware)
// in order to manually check if we want to process with the router as normally
// or execute the file server handler instead.
// use of the .StaticHandler
// which is the same as StaticWeb but it doesn't
// registers the route, it just returns the handler.
fileServer := app.StaticHandler("./public", false, false)
// wrap the router with a native net/http handler.
// if url does not contain any "." (i.e: .css, .js...)
// (depends on the app , you may need to add more file-server exceptions),
// then the handler will execute the router that is responsible for the
// registered routes (look "/" and "/profile/{username}")
// if not then it will serve the files based on the root "/" path.
app.WrapRouter(func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
path := r.URL.Path
// Note that if path has suffix of "index.html" it will auto-permant redirect to the "/",
// so our first handler will be executed instead.
if !strings.Contains(path, ".") { // if it's not a resource then continue to the router as normally.
router(w, r)
return
}
// acquire and release a context in order to use it to execute
// our file server
// remember: we use net/http.Handler because here we are in the "low-level", before the router itself.
ctx := app.ContextPool.Acquire(w, r)
fileServer(ctx)
app.ContextPool.Release(ctx)
})
return app
}
func main() {
app := newApp()
// http://localhost:8080
// http://localhost:8080/index.html
// http://localhost:8080/app.js
// http://localhost:8080/css/main.css
// http://localhost:8080/profile/anyusername
app.Run(iris.Addr(":8080"))
// Note: In this example we just saw one use case,
// you may want to .WrapRouter or .Downgrade in order to bypass the iris' default router, i.e:
// you can use that method to setup custom proxies too.
//
// If you just want to serve static files on other path than root
// you can just use the StaticWeb, i.e:
// .StaticWeb("/static", "./public")
// ________________________________requestPath, systemPath
}

View File

@@ -0,0 +1,59 @@
package main
import (
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/kataras/iris/httptest"
)
type resource string
func (r resource) String() string {
return string(r)
}
func (r resource) strip(strip string) string {
s := r.String()
return strings.TrimPrefix(s, strip)
}
func (r resource) loadFromBase(dir string) string {
filename := r.String()
if filename == "/" {
filename = "/index.html"
}
fullpath := filepath.Join(dir, filename)
b, err := ioutil.ReadFile(fullpath)
if err != nil {
panic(fullpath + " failed with error: " + err.Error())
}
return string(b)
}
var urls = []resource{
"/",
"/index.html",
"/app.js",
"/css/main.css",
}
func TestCustomWrapper(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
for _, u := range urls {
url := u.String()
contents := u.loadFromBase("./public")
e.GET(url).Expect().
Status(httptest.StatusOK).
Body().Equal(contents)
}
}

View File

@@ -0,0 +1 @@
window.alert("app.js loaded from \"/");

View File

@@ -0,0 +1,3 @@
body {
background-color: black;
}

View File

@@ -0,0 +1,14 @@
<html>
<head>
<title>{{ .Page.Title }}</title>
</head>
<body>
<h1> Hello from index.html </h1>
<script src="/app.js"> </script>
</body>
</html>

View File

@@ -0,0 +1,183 @@
package main
import (
"strconv"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
func main() {
app := iris.New()
// At the previous example "routing/basic",
// we've seen static routes, group of routes, subdomains, wildcard subdomains, a small example of parameterized path
// with a single known paramete and custom http errors, now it's time to see wildcard parameters and macros.
// iris, like net/http std package registers route's handlers
// by a Handler, the iris' type of handler is just a func(ctx context.Context)
// where context comes from github.com/kataras/iris/context.
// Until go 1.9 you will have to import that package too, after go 1.9 this will be not be necessary.
//
// iris has the easiest and the most powerful routing process you have ever meet.
//
// At the same time,
// iris has its own interpeter(yes like a programming language)
// for route's path syntax and their dynamic path parameters parsing and evaluation,
// I am calling them "macros" for shortcut.
// How? It calculates its needs and if not any special regexp needed then it just
// registers the route with the low-level underline path syntax,
// otherwise it pre-compiles the regexp and adds the necessary middleware(s).
//
// Standard macro types for parameters:
// +------------------------+
// | {param:string} |
// +------------------------+
// string type
// anything
//
// +------------------------+
// | {param:int} |
// +------------------------+
// int type
// only numbers (0-9)
//
// +------------------------+
// | {param:alphabetical} |
// +------------------------+
// alphabetical/letter type
// letters only (upper or lowercase)
//
// +------------------------+
// | {param:file} |
// +------------------------+
// file type
// letters (upper or lowercase)
// numbers (0-9)
// underscore (_)
// dash (-)
// point (.)
// no spaces ! or other character
//
// +------------------------+
// | {param:path} |
// +------------------------+
// path type
// anything, should be the last part, more than one path segment,
// i.e: /path1/path2/path3 , ctx.Params().GetString("param") == "/path1/path2/path3"
//
// if type is missing then parameter's type is defaulted to string, so
// {param} == {param:string}.
//
// If a function not found on that type then the "string"'s types functions are being used.
// i.e:
// {param:int min(3)}
//
//
// Besides the fact that iris provides the basic types and some default "macro funcs"
// you are able to register your own too!.
//
// Register a named path parameter function:
// app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool {
// [...]
// return true/false -> true means valid.
// })
//
// at the func(argument ...) you can have any standard type, it will be validated before the server starts
// so don't care about performance here, the only thing it runs at serve time is the returning func(paramValue string) bool.
//
// {param:string equal(iris)} , "iris" will be the argument here:
// app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool {
// return func(paramValue string){ return argument == paramValue }
// })
// you can use the "string" type which is valid for a single path parameter that can be anything.
app.Get("/username/{name}", func(ctx context.Context) {
ctx.Writef("Hello %s", ctx.Params().Get("name"))
}) // type is missing = {name:string}
// Let's register our first macro attached to int macro type.
// "min" = the function
// "minValue" = the argument of the function
// func(string) bool = the macro's path parameter evaluator, this executes in serve time when
// a user requests a path which contains the :int macro type with the min(...) macro parameter function.
app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool {
// do anything before serve here [...]
// at this case we don't need to do anything
return func(paramValue string) bool {
n, err := strconv.Atoi(paramValue)
if err != nil {
return false
}
return n >= minValue
}
})
// http://localhost:8080/profile/id>=1
// this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1
// macro parameter functions are optional of course.
app.Get("/profile/{id:int min(1)}", func(ctx context.Context) {
// second parameter is the error but it will always nil because we use macros,
// the validaton already happened.
id, _ := ctx.Params().GetInt("id")
ctx.Writef("Hello id: %d", id)
})
// to change the error code per route's macro evaluator:
app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx context.Context) {
id, _ := ctx.Params().GetInt("id")
friendid, _ := ctx.Params().GetInt("friendid")
ctx.Writef("Hello id: %d looking for friend id: ", id, friendid)
}) // this will throw e 504 error code instead of 404 if all route's macros not passed.
// http://localhost:8080/game/a-zA-Z/level/0-9
// remember, alphabetical is lowercase or uppercase letters only.
app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx context.Context) {
ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level"))
})
app.Get("/lowercase/static", func(ctx context.Context) {
ctx.Writef("static and dynamic paths are not conflicted anymore!")
})
// let's use a trivial custom regexp that validates a single path parameter
// which its value is only lowercase letters.
// http://localhost:8080/lowercase/anylowercase
app.Get("/lowercase/{name:string regexp(^[a-z]+)}", func(ctx context.Context) {
ctx.Writef("name should be only lowercase, otherwise this handler will never executed: %s", ctx.Params().Get("name"))
})
// http://localhost:8080/single_file/app.js
app.Get("/single_file/{myfile:file}", func(ctx context.Context) {
ctx.Writef("file type validates if the parameter value has a form of a file name, got: %s", ctx.Params().Get("myfile"))
})
// http://localhost:8080/myfiles/any/directory/here/
// this is the only macro type that accepts any number of path segments.
app.Get("/myfiles/{directory:path}", func(ctx context.Context) {
ctx.Writef("path type accepts any number of path segments, path after /myfiles/ is: %s", ctx.Params().Get("directory"))
}) // for wildcard path (any number of path segments) without validation you can use:
// /myfiles/*
// "{param}"'s performance is exactly the same of ":param"'s.
// alternatives -> ":param" for single path parameter and "*" for wildcard path parameter.
// Note these:
// if "/mypath/*" then the parameter name is "*".
// if "/mypath/{myparam:path}" then the parameter has two names, one is the "*" and the other is the user-defined "myparam".
// WARNING:
// A path parameter name should contain only alphabetical letters, symbols, containing '_' and numbers are NOT allowed.
// If route failed to be registered, the app will panic without any warnings
// if you didn't catch the second return value(error) on .Handle/.Get....
// Last, do not confuse ctx.Values() with ctx.Params().
// Path parameter's values goes to ctx.Params() and context's local storage
// that can be used to communicate between handlers and middleware(s) goes to
// ctx.Values(), path parameters and the rest of any custom values are separated for your own good.
if err := app.Run(iris.Addr(":8080")); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,29 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
func main() {
app := iris.New()
app.OnErrorCode(iris.StatusInternalServerError, func(ctx context.Context) {
ctx.HTML("Message: <b>" + ctx.Values().GetString("message") + "</b>")
})
app.Get("/", func(ctx context.Context) {
ctx.HTML(`Click <a href="/my500">here</a> to fire the 500 status code`)
})
app.Get("/my500", func(ctx context.Context) {
ctx.Values().Set("message", "this is the error message")
ctx.StatusCode(500)
})
app.Get("/u/{firstname:alphabetical}", func(ctx context.Context) {
ctx.Writef("Hello %s", ctx.Params().Get("firstname"))
})
app.Run(iris.Addr(":8080"))
}

145
_examples/routing/main.go Normal file
View File

@@ -0,0 +1,145 @@
package main
import (
"io/ioutil"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
/*
Read:
"overview"
"basic"
"dynamic-path"
and "reverse" examples if you want to release iris' real power.
*/
const maxBodySize = 1 << 20
const notFoundHTML = "<h1> custom http error page </h1>"
func registerErrors(app *iris.Application) {
// set a custom 404 handler
app.OnErrorCode(iris.StatusNotFound, func(ctx context.Context) {
ctx.HTML(notFoundHTML)
})
}
func registerGamesRoutes(app *iris.Application) {
gamesMiddleware := func(ctx context.Context) {
ctx.Next()
}
// party is just a group of routes with the same prefix
// and middleware, i.e: "/games" and gamesMiddleware.
games := app.Party("/games", gamesMiddleware)
{ // braces are optional of course, it's just a style of code
// "GET" method
games.Get("/{gameID:int}/clans", h)
games.Get("/{gameID:int}/clans/clan/{clanPublicID:int}", h)
games.Get("/{gameID:int}/clans/search", h)
// "PUT" method
games.Put("/{gameID:int}/players/{clanPublicID:int}", h)
games.Put("/{gameID:int}/clans/clan/{clanPublicID:int}", h)
// remember: "clanPublicID" should not be changed to other routes with the same prefix.
// "POST" method
games.Post("/{gameID:int}/clans", h)
games.Post("/{gameID:int}/players", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/leave", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/application", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/application/{action}", h) // {action} == {action:string}
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/invitation", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/invitation/{action}", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/delete", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/promote", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/demote", h)
}
}
func registerSubdomains(app *iris.Application) {
mysubdomain := app.Party("mysubdomain.")
// http://mysubdomain.myhost.com
mysubdomain.Get("/", h)
}
func newApp() *iris.Application {
app := iris.New()
registerErrors(app)
registerGamesRoutes(app)
registerSubdomains(app)
app.Handle("GET", "/healthcheck", h)
// "POST" method
// this handler reads raw body from the client/request
// and sends back the same body
// remember, we have limit to that body in order
// to protect ourselves from "over heating".
app.Post("/", context.LimitRequestBodySize(maxBodySize), func(ctx context.Context) {
// get request body
b, err := ioutil.ReadAll(ctx.Request().Body)
// if is larger then send a bad request status
if err != nil {
ctx.StatusCode(iris.StatusBadRequest)
ctx.Writef(err.Error())
return
}
// send back the post body
ctx.Write(b)
})
return app
}
func h(ctx context.Context) {
method := ctx.Method() // the http method requested a server's resource.
subdomain := ctx.Subdomain() // the subdomain, if any.
// the request path (without scheme and host).
path := ctx.Path()
// how to get all parameters, if we don't know
// the names:
paramsLen := ctx.Params().Len()
ctx.Params().Visit(func(name string, value string) {
ctx.Writef("%s = %s\n", name, value)
})
ctx.Writef("Info\n\n")
ctx.Writef("Method: %s\nSubdomain: %s\nPath: %s\nParameters length: %d", method, subdomain, path, paramsLen)
}
func main() {
app := newApp()
/*
// GET
http://localhost:8080/healthcheck
http://localhost:8080/games/42/clans
http://localhost:8080/games/42/clans/clan/93
http://localhost:8080/games/42/clans/search
http://mysubdomain.localhost:8080/
// PUT
http://localhost:8080/games/42/players/93
http://localhost:8080/games/42/clans/clan/93
// POST
http://localhost:8080/
http://localhost:8080/games/42/clans
http://localhost:8080/games/42/players
http://localhost:8080/games/42/clans/93/leave
http://localhost:8080/games/42/clans/93/memberships/application
http://localhost:8080/games/42/clans/93/memberships/application/anystring
http://localhost:8080/games/42/clans/93/memberships/invitation
http://localhost:8080/games/42/clans/93/memberships/invitation/anystring
http://localhost:8080/games/42/clans/93/memberships/delete
http://localhost:8080/games/42/clans/93/memberships/promote
http://localhost:8080/games/42/clans/93/memberships/demote
// FIRE NOT FOUND
http://localhost:8080/coudlntfound
*/
app.Run(iris.Addr(":8080"))
}

View File

@@ -0,0 +1,125 @@
package main
import (
"strconv"
"strings"
"testing"
"github.com/kataras/iris/httptest"
)
func calculatePathAndResponse(method, subdomain, path string, paramKeyValue ...string) (string, string) {
paramsLen := 0
if l := len(paramKeyValue); l >= 2 {
paramsLen = len(paramKeyValue) / 2
}
paramsInfo := ""
if paramsLen > 0 {
for i := 0; i < len(paramKeyValue); i++ {
paramKey := paramKeyValue[i]
i++
if i >= len(paramKeyValue) {
panic("paramKeyValue should be align with path parameters {} and must be placed in order")
}
paramValue := paramKeyValue[i]
paramsInfo += paramKey + " = " + paramValue + "\n"
beginParam := strings.IndexByte(path, '{')
endParam := strings.IndexByte(path, '}')
if beginParam == -1 || endParam == -1 {
panic("something wrong with parameters, please define them in order")
}
path = path[:beginParam] + paramValue + path[endParam+1:]
}
}
return path, paramsInfo + `Info
Method: ` + method + `
Subdomain: ` + subdomain + `
Path: ` + path + `
Parameters length: ` + strconv.Itoa(paramsLen)
}
type troute struct {
method, subdomain, path string
status int
expectedBody string
contentType string
}
func newTroute(method, subdomain, path string, status int, paramKeyValue ...string) troute {
finalPath, expectedBody := calculatePathAndResponse(method, subdomain, path, paramKeyValue...)
contentType := "text/plain; charset=UTF-8"
if status == httptest.StatusNotFound {
expectedBody = notFoundHTML
contentType = "text/html; charset=UTF-8"
}
return troute{
contentType: contentType,
method: method,
subdomain: subdomain,
path: finalPath,
status: status,
expectedBody: expectedBody,
}
}
func TestRouting(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
var tests = []troute{
// GET
newTroute("GET", "", "/healthcheck", httptest.StatusOK),
newTroute("GET", "", "/games/{gameID}/clans", httptest.StatusOK, "gameID", "42"),
newTroute("GET", "", "/games/{gameID}/clans/clan/{clanPublicID}", httptest.StatusOK, "gameID", "42", "clanPublicID", "93"),
newTroute("GET", "", "/games/{gameID}/clans/search", httptest.StatusOK, "gameID", "42"),
newTroute("GET", "mysubdomain", "/", httptest.StatusOK),
// PUT
newTroute("PUT", "", "/games/{gameID}/players/{clanPublicID}", httptest.StatusOK, "gameID", "42", "clanPublicID", "93"),
newTroute("PUT", "", "/games/{gameID}/clans/clan/{clanPublicID}", httptest.StatusOK, "gameID", "42", "clanPublicID", "93"),
// POST
newTroute("POST", "", "/games/{gameID}/clans", httptest.StatusOK, "gameID", "42"),
newTroute("POST", "", "/games/{gameID}/players", httptest.StatusOK, "gameID", "42"),
newTroute("POST", "", "/games/{gameID}/clans/{clanPublicID}/leave", httptest.StatusOK, "gameID", "42", "clanPublicID", "93"),
newTroute("POST", "", "/games/{gameID}/clans/{clanPublicID}/memberships/application", httptest.StatusOK, "gameID", "42", "clanPublicID", "93"),
newTroute("POST", "", "/games/{gameID}/clans/{clanPublicID}/memberships/application/{action}", httptest.StatusOK, "gameID", "42", "clanPublicID", "93", "action", "somethinghere"),
newTroute("POST", "", "/games/{gameID}/clans/{clanPublicID}/memberships/invitation", httptest.StatusOK, "gameID", "42", "clanPublicID", "93"),
newTroute("POST", "", "/games/{gameID}/clans/{clanPublicID}/memberships/invitation/{action}", httptest.StatusOK, "gameID", "42", "clanPublicID", "93", "action", "somethinghere"),
newTroute("POST", "", "/games/{gameID}/clans/{clanPublicID}/memberships/delete", httptest.StatusOK, "gameID", "42", "clanPublicID", "93"),
newTroute("POST", "", "/games/{gameID}/clans/{clanPublicID}/memberships/promote", httptest.StatusOK, "gameID", "42", "clanPublicID", "93"),
newTroute("POST", "", "/games/{gameID}/clans/{clanPublicID}/memberships/demote", httptest.StatusOK, "gameID", "42", "clanPublicID", "93"),
// POST: / will be tested alone
// custom not found
newTroute("GET", "", "/notfound", httptest.StatusNotFound),
newTroute("POST", "", "/notfound2", httptest.StatusNotFound),
newTroute("PUT", "", "/notfound3", httptest.StatusNotFound),
newTroute("GET", "mysubdomain", "/notfound42", httptest.StatusNotFound),
}
for _, tt := range tests {
et := e.Request(tt.method, tt.path)
if tt.subdomain != "" {
et.WithURL("http://" + tt.subdomain + ".localhost:8080")
}
et.Expect().Status(tt.status).Body().Equal(tt.expectedBody)
}
// test POST "/" limit data and post data return
// test with small body
e.POST("/").WithBytes([]byte("ok")).Expect().Status(httptest.StatusOK).Body().Equal("ok")
// test with equal to max body size limit
bsent := make([]byte, maxBodySize, maxBodySize)
e.POST("/").WithBytes(bsent).Expect().Status(httptest.StatusOK).Body().Length().Equal(len(bsent))
// test with larger body sent and wait for the custom response
largerBSent := make([]byte, maxBodySize+1, maxBodySize+1)
e.POST("/").WithBytes(largerBSent).Expect().Status(httptest.StatusBadRequest).Body().Equal("http: request body too large")
}

View File

@@ -0,0 +1,136 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
func main() {
app := iris.New()
// GET: http://localhost:8080
app.Get("/", info)
// GET: http://localhost:8080/profile/anyusername
app.Get("/profile/{username:string}", info)
// GET: http://localhost:8080/profile/anyusername/backups/any/number/of/paths/here
app.Get("/profile/{username:string}/backups/{filepath:path}", info)
// Favicon
// GET: http://localhost:8080/favicon.ico
app.Favicon("./public/images/favicon.ico")
// Static assets
// GET: http://localhost:8080/assets/css/bootstrap.min.css
// maps to ./public/assets/css/bootstrap.min.css file at system location.
// GET: http://localhost:8080/assets/js/react.min.js
// maps to ./public/assets/js/react.min.js file at system location.
app.StaticWeb("/assets", "./public/assets")
/* OR
// GET: http://localhost:8080/js/react.min.js
// maps to ./public/assets/js/react.min.js file at system location.
app.StaticWeb("/js", "./public/assets/js")
// GET: http://localhost:8080/css/bootstrap.min.css
// maps to ./public/assets/css/bootstrap.min.css file at system location.
app.StaticWeb("/css", "./public/assets/css")
*/
// Grouping
usersRoutes := app.Party("/users")
// GET: http://localhost:8080/users/help
usersRoutes.Get("/help", func(ctx context.Context) {
ctx.Writef("GET / -- fetch all users\n")
ctx.Writef("GET /$ID -- fetch a user by id\n")
ctx.Writef("POST / -- create new user\n")
ctx.Writef("PUT /$ID -- update an existing user\n")
ctx.Writef("DELETE /$ID -- delete an existing user\n")
})
// GET: http://localhost:8080/users
usersRoutes.Get("/", func(ctx context.Context) {
ctx.Writef("get all users")
})
// GET: http://localhost:8080/users/42
// **/users/42 and /users/help works after iris version 7.0.5**
usersRoutes.Get("/{id:int}", func(ctx context.Context) {
id, _ := ctx.Params().GetInt("id")
ctx.Writef("get user by id: %d", id)
})
// POST: http://localhost:8080/users
usersRoutes.Post("/", func(ctx context.Context) {
username, password := ctx.PostValue("username"), ctx.PostValue("password")
ctx.Writef("create user for username= %s and password= %s", username, password)
})
// PUT: http://localhost:8080/users
usersRoutes.Put("/{id:int}", func(ctx context.Context) {
id, _ := ctx.Params().GetInt("id") // or .Get to get its string represatantion.
username := ctx.PostValue("username")
ctx.Writef("update user for id= %d and new username= %s", id, username)
})
// DELETE: http://localhost:8080/users/42
usersRoutes.Delete("/{id:int}", func(ctx context.Context) {
id, _ := ctx.Params().GetInt("id")
ctx.Writef("delete user by id: %d", id)
})
// Subdomains, depends on the host, you have to edit the hosts or nginx/caddy's configuration if you use them.
//
// See more subdomains examples at _examples/subdomains folder.
adminRoutes := app.Party("admin.")
// GET: http://admin.localhost:8080
adminRoutes.Get("/", info)
// GET: http://admin.localhost:8080/settings
adminRoutes.Get("/settings", info)
// Wildcard/dynamic subdomain
dynamicSubdomainRoutes := app.Party("*.")
// GET: http://any_thing_here.localhost:8080
dynamicSubdomainRoutes.Get("/", info)
// GET: http://localhost:8080/
// GET: http://localhost:8080/profile/anyusername
// GET: http://localhost:8080/profile/anyusername/backups/any/number/of/paths/here
// GET: http://localhost:8080/users/help
// GET: http://localhost:8080/users
// GET: http://localhost:8080/users/42
// POST: http://localhost:8080/users
// PUT: http://localhost:8080/users
// DELETE: http://localhost:8080/users/42
// GET: http://admin.localhost:8080
// GET: http://admin.localhost:8080/settings
// GET: http://any_thing_here.localhost:8080
if err := app.Run(iris.Addr(":8080")); err != nil {
panic(err)
}
}
func info(ctx context.Context) {
method := ctx.Method() // the http method requested a server's resource.
subdomain := ctx.Subdomain() // the subdomain, if any.
// the request path (without scheme and host).
path := ctx.Path()
// how to get all parameters, if we don't know
// the names:
paramsLen := ctx.Params().Len()
ctx.Params().Visit(func(name string, value string) {
ctx.Writef("%s = %s\n", name, value)
})
ctx.Writef("\nInfo\n\n")
ctx.Writef("Method: %s\nSubdomain: %s\nPath: %s\nParameters length: %d", method, subdomain, path, paramsLen)
}

View File

@@ -0,0 +1,35 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
)
func main() {
app := iris.New()
// need for manually reverse routing when needed outside of view engine.
// you normally don't need it because of the {{ urlpath "routename" "path" "values" "here"}}
rv := router.NewRoutePathReverser(app)
myroute := app.Get("/anything/{anythingparameter:path}", func(ctx context.Context) {
paramValue := ctx.Params().Get("anythingparameter")
ctx.Writef("The path after /anything is: %s", paramValue)
})
// useful for links, although iris' view engine has the {{ urlpath "routename" "path values"}} already.
app.Get("/reverse_myroute", func(ctx context.Context) {
myrouteRequestPath := rv.Path(myroute.Name, "any/path")
ctx.HTML("Should be <b>/anything/any/path</b>: " + myrouteRequestPath)
})
// execute a route, similar to redirect but without redirect :)
app.Get("/execute_myroute", func(ctx context.Context) {
ctx.Exec("GET", "/anything/any/path") // like it was called by the client.
})
// http://localhost:8080/reverse_myroute
// http://localhost:8080/execute_myroute
// http://localhost:8080/anything/any/path/here
app.Run(iris.Addr(":8080"))
}

View File

@@ -0,0 +1,39 @@
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/context"
)
func main() {
app := iris.New()
none := app.None("/invisible/{username}", func(ctx context.Context) {
ctx.Writef("Hello %s with method: %s", ctx.Values().GetString("username"), ctx.Method())
if from := ctx.Values().GetString("from"); from != "" {
ctx.Writef("\nI see that you're coming from %s", from)
}
})
app.Get("/change", func(ctx context.Context) {
if none.IsOnline() {
none.Method = iris.MethodNone
} else {
none.Method = iris.MethodGet
}
// refresh re-builds the router at serve-time in order to be notified for its new routes.
app.RefreshRouter()
})
app.Get("/execute", func(ctx context.Context) {
// same as navigating to "http://localhost:8080/invisible/iris" when /change has being invoked and route state changed
// from "offline" to "online"
ctx.Values().Set("from", "/execute") // values and session can be shared when calling Exec from a "foreign" context.
ctx.Exec("GET", "/invisible/iris")
})
app.Run(iris.Addr(":8080"))
}