mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Add some _examples in the main repository too.
Former-commit-id: 98895c34115ec2076b431332f0ffe9645adf7590
This commit is contained in:
parent
d76d9b1ec6
commit
f487cd0029
33
_examples/README.md
Normal file
33
_examples/README.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
## Examples
|
||||
|
||||
This folder provides easy to understand code snippets on how to get started with web development with the Go programming language using the [Iris](https://github.com/kataras/iris) web framework.
|
||||
|
||||
|
||||
It doesn't contains "best ways" neither explains all its features. It's just a simple, practical cookbook for young Go developers!
|
||||
|
||||
Developers should read the official [documentation](https://godoc.org/gopkg.in/kataras/iris.v6) in depth.
|
||||
|
||||
|
||||
<a href ="https://github.com/kataras/iris"> <img src="http://iris-go.com/assets/book/cover_4.jpg" width="300" /> </a>
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Hello World](examples/hello-world/main.go)
|
||||
* [Routes (using httprouter)](examples/routes-using-httprouter/main.go)
|
||||
* [Routes (using gorillamux)](examples/routes-using-gorillamux/main.go)
|
||||
* [Templates](examples/templates/main.go)
|
||||
* [Forms](examples/forms/main.go)
|
||||
* [JSON](examples/json/main.go)
|
||||
* [Upload Files](examples/upload-files/main.go)
|
||||
* [Static Files](examples/static-files/main.go)
|
||||
* [Favicon](examples/favicon/main.go)
|
||||
* [Password Hashing](examples/password-hashing/main.go)
|
||||
* [Sessions](examples/sessions/main.go)
|
||||
* [Websockets](examples/websockets/main.go)
|
||||
* [Markdown and Cache](examples/cache-markdown/main.go)
|
||||
* [Online Visitors](examples/online-visitors/main.go)
|
||||
* [URL Shortener](examples/url-shortener/main.go)
|
||||
|
||||
|
||||
> Take look at the [community examples](https://github.com/iris-contrib/examples) too!
|
82
_examples/examples/cache-markdown/main.go
Normal file
82
_examples/examples/cache-markdown/main.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
)
|
||||
|
||||
var testMarkdownContents = `## Hello Markdown
|
||||
|
||||
This is a sample of Markdown contents
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
All features of Sundown are supported, including:
|
||||
|
||||
* **Compatibility**. The Markdown v1.0.3 test suite passes with
|
||||
the --tidy option. Without --tidy, the differences are
|
||||
mostly in whitespace and entity escaping, where blackfriday is
|
||||
more consistent and cleaner.
|
||||
|
||||
* **Common extensions**, including table support, fenced code
|
||||
blocks, autolinks, strikethroughs, non-strict emphasis, etc.
|
||||
|
||||
* **Safety**. Blackfriday is paranoid when parsing, making it safe
|
||||
to feed untrusted user input without fear of bad things
|
||||
happening. The test suite stress tests this and there are no
|
||||
known inputs that make it crash. If you find one, please let me
|
||||
know and send me the input that does it.
|
||||
|
||||
NOTE: "safety" in this context means *runtime safety only*. In order to
|
||||
protect yourself against JavaScript injection in untrusted content, see
|
||||
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
|
||||
|
||||
* **Fast processing**. It is fast enough to render on-demand in
|
||||
most web applications without having to cache the output.
|
||||
|
||||
* **Thread safety**. You can run multiple parsers in different
|
||||
goroutines without ill effect. There is no dependence on global
|
||||
shared state.
|
||||
|
||||
* **Minimal dependencies**. Blackfriday only depends on standard
|
||||
library packages in Go. The source code is pretty
|
||||
self-contained, so it is easy to add to any project, including
|
||||
Google App Engine projects.
|
||||
|
||||
* **Standards compliant**. Output successfully validates using the
|
||||
W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
|
||||
|
||||
[this is a link](https://github.com/kataras/iris) `
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
// output startup banner and error logs on os.Stdout
|
||||
app.Adapt(iris.DevLogger())
|
||||
// set the router, you can choose gorillamux too
|
||||
app.Adapt(httprouter.New())
|
||||
|
||||
app.Get("/hi", app.Cache(func(c *iris.Context) {
|
||||
c.WriteString("Hi this is a big content, do not try cache on small content it will not make any significant difference!")
|
||||
}, time.Duration(10)*time.Second))
|
||||
|
||||
bodyHandler := func(ctx *iris.Context) {
|
||||
ctx.Markdown(iris.StatusOK, testMarkdownContents)
|
||||
}
|
||||
|
||||
expiration := time.Duration(5 * time.Second)
|
||||
|
||||
app.Get("/", app.Cache(bodyHandler, expiration))
|
||||
|
||||
// if expiration is <=time.Second then the cache tries to set the expiration from the "cache-control" maxage header's value(in seconds)
|
||||
// // if this header doesn't founds then the default is 5 minutes
|
||||
app.Get("/cache_control", app.Cache(func(ctx *iris.Context) {
|
||||
ctx.HTML(iris.StatusOK, "<h1>Hello!</h1>")
|
||||
}, -1))
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
23
_examples/examples/favicon/main.go
Normal file
23
_examples/examples/favicon/main.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(httprouter.New())
|
||||
// This will serve the ./static/favicons/iris_favicon_32_32.ico to: localhost:8080/favicon.ico
|
||||
app.Favicon("./static/favicons/iris_favicon_32_32.ico")
|
||||
|
||||
// app.Favicon("./static/favicons/iris_favicon_32_32.ico", "/favicon_32_32.ico")
|
||||
// This will serve the ./static/favicons/iris_favicon_32_32.ico to: localhost:8080/favicon_32_32.ico
|
||||
|
||||
app.Get("/", func(ctx *iris.Context) {
|
||||
ctx.HTML(iris.StatusOK, `You should see the favicon now at the side of your browser,
|
||||
if not, please refresh or clear the browser's cache.`)
|
||||
})
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
47
_examples/examples/forms/main.go
Normal file
47
_examples/examples/forms/main.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/view"
|
||||
)
|
||||
|
||||
// ContactDetails the information from user
|
||||
type ContactDetails struct {
|
||||
Email string `form:"email"`
|
||||
Subject string `form:"subject"`
|
||||
Message string `form:"message"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(httprouter.New())
|
||||
|
||||
// Parse all files inside `./mytemplates` directory ending with `.html`
|
||||
app.Adapt(view.HTML("./mytemplates", ".html"))
|
||||
|
||||
app.Get("/", func(ctx *iris.Context) {
|
||||
ctx.Render("forms.html", nil)
|
||||
})
|
||||
|
||||
// Equivalent with app.HandleFunc("POST", ...)
|
||||
app.Post("/", func(ctx *iris.Context) {
|
||||
|
||||
// details := ContactDetails{
|
||||
// Email: ctx.FormValue("email"),
|
||||
// Subject: ctx.FormValue("subject"),
|
||||
// Message: ctx.FormValue("message"),
|
||||
// }
|
||||
|
||||
// or simply:
|
||||
var details ContactDetails
|
||||
ctx.ReadForm(&details)
|
||||
|
||||
// do something with details
|
||||
_ = details
|
||||
|
||||
ctx.Render("forms.html", struct{ Success bool }{true})
|
||||
})
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
14
_examples/examples/forms/mytemplates/forms.html
Normal file
14
_examples/examples/forms/mytemplates/forms.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{{if .Success}}
|
||||
<h1>Thanks for your message!</h1>
|
||||
{{else}}
|
||||
<h1>Contact</h1>
|
||||
<form method="POST">
|
||||
<label>Email:</label><br />
|
||||
<input type="text" name="email"><br />
|
||||
<label>Subject:</label><br />
|
||||
<input type="text" name="subject"><br />
|
||||
<label>Message:</label><br />
|
||||
<textarea name="message"></textarea><br />
|
||||
<input type="submit">
|
||||
</form>
|
||||
{{end}}
|
20
_examples/examples/hello-world/main.go
Normal file
20
_examples/examples/hello-world/main.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
// Adapt the "httprouter", faster,
|
||||
// but it has limits on named path parameters' validation,
|
||||
// you can adapt "gorillamux" if you need regexp path validation!
|
||||
app.Adapt(httprouter.New())
|
||||
|
||||
app.HandleFunc("GET", "/", func(ctx *iris.Context) {
|
||||
ctx.Writef("hello world\n")
|
||||
})
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
37
_examples/examples/json/main.go
Normal file
37
_examples/examples/json/main.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
)
|
||||
|
||||
// User bind struct
|
||||
type User struct {
|
||||
Firstname string `json:"firstname"`
|
||||
Lastname string `json:"lastname"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(httprouter.New())
|
||||
|
||||
app.Post("/decode", func(ctx *iris.Context) {
|
||||
var user User
|
||||
ctx.ReadJSON(&user)
|
||||
|
||||
ctx.Writef("%s %s is %d years old!", user.Firstname, user.Lastname, user.Age)
|
||||
})
|
||||
|
||||
app.Get("/encode", func(ctx *iris.Context) {
|
||||
peter := User{
|
||||
Firstname: "John",
|
||||
Lastname: "Doe",
|
||||
Age: 25,
|
||||
}
|
||||
|
||||
ctx.JSON(iris.StatusOK, peter)
|
||||
})
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
170
_examples/examples/online-visitors/main.go
Normal file
170
_examples/examples/online-visitors/main.go
Normal file
|
@ -0,0 +1,170 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/view"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
app *iris.Framework
|
||||
ws websocket.Server
|
||||
)
|
||||
|
||||
func init() {
|
||||
// init the server instance
|
||||
app = iris.New()
|
||||
// adapt a logger in dev mode
|
||||
app.Adapt(iris.DevLogger())
|
||||
// adapt router
|
||||
app.Adapt(httprouter.New())
|
||||
// adapt templaes
|
||||
app.Adapt(view.HTML("./templates", ".html").Reload(true))
|
||||
// adapt websocket
|
||||
ws = websocket.New(websocket.Config{Endpoint: "/my_endpoint"})
|
||||
ws.OnConnection(HandleWebsocketConnection)
|
||||
app.Adapt(ws)
|
||||
}
|
||||
|
||||
type page struct {
|
||||
PageID string
|
||||
}
|
||||
|
||||
func main() {
|
||||
app.StaticWeb("/js", "./static/assets/js")
|
||||
|
||||
h := func(ctx *iris.Context) {
|
||||
ctx.Render("index.html", page{PageID: "index page"})
|
||||
}
|
||||
|
||||
h2 := func(ctx *iris.Context) {
|
||||
ctx.Render("other.html", page{PageID: "other page"})
|
||||
}
|
||||
|
||||
// Open some browser tabs/or windows
|
||||
// and navigate to
|
||||
// http://localhost:8080/ and http://localhost:8080/other
|
||||
// Each page has its own online-visitors counter.
|
||||
app.Get("/", h)
|
||||
app.Get("/other", h2)
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
type pageView struct {
|
||||
source string
|
||||
count uint64
|
||||
}
|
||||
|
||||
func (v *pageView) increment() {
|
||||
atomic.AddUint64(&v.count, 1)
|
||||
}
|
||||
|
||||
func (v *pageView) decrement() {
|
||||
oldCount := v.count
|
||||
if oldCount > 0 {
|
||||
atomic.StoreUint64(&v.count, oldCount-1)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *pageView) getCount() uint64 {
|
||||
val := atomic.LoadUint64(&v.count)
|
||||
return val
|
||||
}
|
||||
|
||||
type (
|
||||
pageViews []pageView
|
||||
)
|
||||
|
||||
func (v *pageViews) Add(source string) {
|
||||
args := *v
|
||||
n := len(args)
|
||||
for i := 0; i < n; i++ {
|
||||
kv := &args[i]
|
||||
if kv.source == source {
|
||||
kv.increment()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c := cap(args)
|
||||
if c > n {
|
||||
args = args[:n+1]
|
||||
kv := &args[n]
|
||||
kv.source = source
|
||||
kv.count = 1
|
||||
*v = args
|
||||
return
|
||||
}
|
||||
|
||||
kv := pageView{}
|
||||
kv.source = source
|
||||
kv.count = 1
|
||||
*v = append(args, kv)
|
||||
}
|
||||
|
||||
func (v *pageViews) Get(source string) *pageView {
|
||||
args := *v
|
||||
n := len(args)
|
||||
for i := 0; i < n; i++ {
|
||||
kv := &args[i]
|
||||
if kv.source == source {
|
||||
return kv
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *pageViews) Reset() {
|
||||
*v = (*v)[:0]
|
||||
}
|
||||
|
||||
var v pageViews
|
||||
|
||||
// HandleWebsocketConnection handles the online viewers per example(gist source)
|
||||
func HandleWebsocketConnection(c websocket.Connection) {
|
||||
|
||||
c.On("watch", func(pageSource string) {
|
||||
v.Add(pageSource)
|
||||
// join the socket to a room linked with the page source
|
||||
c.Join(pageSource)
|
||||
|
||||
viewsCount := v.Get(pageSource).getCount()
|
||||
if viewsCount == 0 {
|
||||
viewsCount++ // count should be always > 0 here
|
||||
}
|
||||
c.To(pageSource).Emit("watch", viewsCount)
|
||||
})
|
||||
|
||||
c.OnLeave(func(roomName string) {
|
||||
if roomName != c.ID() { // if the roomName it's not the connection iself
|
||||
// the roomName here is the source, this is the only room(except the connection's ID room) which we join the users to.
|
||||
pageV := v.Get(roomName)
|
||||
if pageV == nil {
|
||||
return // for any case that this room is not a pageView source
|
||||
}
|
||||
// decrement -1 the specific counter for this page source.
|
||||
pageV.decrement()
|
||||
// 1. open 30 tabs.
|
||||
// 2. close the browser.
|
||||
// 3. re-open the browser
|
||||
// 4. should be v.getCount() = 1
|
||||
// in order to achieve the previous flow we should decrement exactly when the user disconnects
|
||||
// but emit the result a little after, on a goroutine
|
||||
// getting all connections within this room and emit the online views one by one.
|
||||
// note:
|
||||
// we can also add a time.Sleep(2-3 seconds) inside the goroutine at the future if we don't need 'real-time' updates.
|
||||
go func(currentConnID string) {
|
||||
for _, conn := range ws.GetConnectionsByRoom(roomName) {
|
||||
if conn.ID() != currentConnID {
|
||||
conn.Emit("watch", pageV.getCount())
|
||||
}
|
||||
|
||||
}
|
||||
}(c.ID())
|
||||
}
|
||||
|
||||
})
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
(function() {
|
||||
var socket = new Ws("ws://localhost:8080/my_endpoint");
|
||||
|
||||
socket.OnConnect(function () {
|
||||
socket.Emit("watch", PAGE_SOURCE);
|
||||
});
|
||||
|
||||
|
||||
socket.On("watch", function (onlineViews) {
|
||||
var text = "1 online view";
|
||||
if (onlineViews > 1) {
|
||||
text = onlineViews + " online views";
|
||||
}
|
||||
document.getElementById("online_views").innerHTML = text;
|
||||
});
|
||||
|
||||
socket.OnDisconnect(function () {
|
||||
document.getElementById("online_views").innerHTML = "you've been disconnected";
|
||||
});
|
||||
|
||||
})();
|
43
_examples/examples/online-visitors/templates/index.html
Normal file
43
_examples/examples/online-visitors/templates/index.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>Online visitors example</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, "San Francisco", "Helvetica Neue", "Noto", "Roboto", "Calibri Light", sans-serif;
|
||||
color: #212121;
|
||||
font-size: 1.0em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 750px;
|
||||
margin: auto;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#online_views {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<span id="online_views">1 online view</span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
/* take the page source from our passed struct on .Render */
|
||||
var PAGE_SOURCE = {{ .PageID }}
|
||||
</script>
|
||||
|
||||
<script src="/iris-ws.js"></script>
|
||||
|
||||
<script src="/js/visitors.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
29
_examples/examples/online-visitors/templates/other.html
Normal file
29
_examples/examples/online-visitors/templates/other.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>Different page, different results</title>
|
||||
<style>
|
||||
#online_views {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<span id="online_views">1 online view</span>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
/* take the page source from our passed struct on .Render */
|
||||
var PAGE_SOURCE = {{ .PageID }}
|
||||
</script>
|
||||
|
||||
<script src="/iris-ws.js"></script>
|
||||
|
||||
<script src="/js/visitors.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
28
_examples/examples/password-hashing/main.go
Normal file
28
_examples/examples/password-hashing/main.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func CheckPasswordHash(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
password := "secret"
|
||||
hash, _ := HashPassword(password) // ignore error for the sake of simplicity
|
||||
|
||||
fmt.Println("Password:", password)
|
||||
fmt.Println("Hash: ", hash)
|
||||
|
||||
match := CheckPasswordHash(password, hash)
|
||||
fmt.Println("Match: ", match)
|
||||
}
|
28
_examples/examples/routes-using-gorillamux/main.go
Normal file
28
_examples/examples/routes-using-gorillamux/main.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
// Adapt the "httprouter", you can use "gorillamux" too.
|
||||
app.Adapt(gorillamux.New())
|
||||
|
||||
userAges := map[string]int{
|
||||
"Alice": 25,
|
||||
"Bob": 30,
|
||||
"Claire": 29,
|
||||
}
|
||||
|
||||
// Equivalent with app.HandleFunc("GET", ...)
|
||||
app.Get("/users/{name}", func(ctx *iris.Context) {
|
||||
name := ctx.Param("name")
|
||||
age := userAges[name]
|
||||
|
||||
ctx.Writef("%s is %d years old!", name, age)
|
||||
})
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
27
_examples/examples/routes-using-httprouter/main.go
Normal file
27
_examples/examples/routes-using-httprouter/main.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(httprouter.New())
|
||||
|
||||
userAges := map[string]int{
|
||||
"Alice": 25,
|
||||
"Bob": 30,
|
||||
"Claire": 29,
|
||||
}
|
||||
|
||||
// Equivalent with app.HandleFunc("GET", ...)
|
||||
app.Get("/users/:name", func(ctx *iris.Context) {
|
||||
name := ctx.Param("name")
|
||||
age := userAges[name]
|
||||
|
||||
ctx.Writef("%s is %d years old!", name, age)
|
||||
})
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
54
_examples/examples/sessions/main.go
Normal file
54
_examples/examples/sessions/main.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/sessions"
|
||||
)
|
||||
|
||||
var (
|
||||
key = "my_sessionid"
|
||||
)
|
||||
|
||||
func secret(ctx *iris.Context) {
|
||||
|
||||
// Check if user is authenticated
|
||||
if auth, _ := ctx.Session().GetBoolean("authenticated"); !auth {
|
||||
ctx.EmitError(iris.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Print secret message
|
||||
ctx.WriteString("The cake is a lie!")
|
||||
}
|
||||
|
||||
func login(ctx *iris.Context) {
|
||||
session := ctx.Session()
|
||||
|
||||
// Authentication goes here
|
||||
// ...
|
||||
|
||||
// Set user as authenticated
|
||||
session.Set("authenticated", true)
|
||||
}
|
||||
|
||||
func logout(ctx *iris.Context) {
|
||||
session := ctx.Session()
|
||||
|
||||
// Revoke users authentication
|
||||
session.Set("authenticated", false)
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(httprouter.New())
|
||||
|
||||
sess := sessions.New(sessions.Config{Cookie: key})
|
||||
app.Adapt(sess)
|
||||
|
||||
app.Get("/secret", secret)
|
||||
app.Get("/login", login)
|
||||
app.Get("/logout", logout)
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
3
_examples/examples/static-files/assets/css/styles.css
Normal file
3
_examples/examples/static-files/assets/css/styles.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
background-color: black;
|
||||
}
|
16
_examples/examples/static-files/main.go
Normal file
16
_examples/examples/static-files/main.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(httprouter.New())
|
||||
// first parameter is the request path
|
||||
// second is the operating system directory
|
||||
app.StaticWeb("/static", "./assets")
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
39
_examples/examples/templates/main.go
Normal file
39
_examples/examples/templates/main.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/view"
|
||||
)
|
||||
|
||||
// Todo bind struct
|
||||
type Todo struct {
|
||||
Task string
|
||||
Done bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Configuration is optional
|
||||
app := iris.New(iris.Configuration{Gzip: false, Charset: "UTF-8"})
|
||||
|
||||
// Adapt a logger which will print all errors to os.Stdout
|
||||
app.Adapt(iris.DevLogger())
|
||||
|
||||
// Adapt the httprouter (we will use that on all examples)
|
||||
app.Adapt(httprouter.New())
|
||||
|
||||
// Parse all files inside `./mytemplates` directory ending with `.html`
|
||||
app.Adapt(view.HTML("./mytemplates", ".html"))
|
||||
|
||||
todos := []Todo{
|
||||
{"Learn Go", true},
|
||||
{"Read GopherBOOk", true},
|
||||
{"Create a web app in Go", false},
|
||||
}
|
||||
|
||||
app.Get("/", func(ctx *iris.Context) {
|
||||
ctx.Render("todos.html", struct{ Todos []Todo }{todos})
|
||||
})
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
10
_examples/examples/templates/mytemplates/todos.html
Normal file
10
_examples/examples/templates/mytemplates/todos.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<h1>Todos</h1>
|
||||
<ul>
|
||||
{{range .Todos}}
|
||||
{{if .Done}}
|
||||
<li><s>{{.Task}}</s></li>
|
||||
{{else}}
|
||||
<li>{{.Task}}</li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
69
_examples/examples/upload-files/main.go
Normal file
69
_examples/examples/upload-files/main.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/view"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(iris.DevLogger())
|
||||
app.Adapt(httprouter.New())
|
||||
app.Adapt(view.HTML("./templates", ".html"))
|
||||
|
||||
// Serve the form.html to the user
|
||||
app.Get("/upload", func(ctx *iris.Context) {
|
||||
//create a token (optionally)
|
||||
|
||||
now := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(now, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
//render the form with the token for any use you like
|
||||
ctx.Render("upload_form.html", token)
|
||||
})
|
||||
|
||||
// Handle the post request from the upload_form.html to the server
|
||||
app.Post("/upload", iris.LimitRequestBodySize(10<<20),
|
||||
func(ctx *iris.Context) {
|
||||
// or use ctx.SetMaxRequestBodySize(10 << 20)
|
||||
//to limit the uploaded file(s) size.
|
||||
|
||||
// Get the file from the request
|
||||
file, info, err := ctx.FormFile("uploadfile")
|
||||
|
||||
if err != nil {
|
||||
ctx.HTML(iris.StatusInternalServerError,
|
||||
"Error while uploading: <b>"+err.Error()+"</b>")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
fname := info.Filename
|
||||
|
||||
// Create a file with the same name
|
||||
// assuming that you have a folder named 'uploads'
|
||||
out, err := os.OpenFile("./uploads/"+fname,
|
||||
os.O_WRONLY|os.O_CREATE, 0666)
|
||||
|
||||
if err != nil {
|
||||
ctx.HTML(iris.StatusInternalServerError,
|
||||
"Error while uploading: <b>"+err.Error()+"</b>")
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
io.Copy(out, file)
|
||||
})
|
||||
|
||||
// start the server at 127.0.0.1:8080
|
||||
app.Listen(":8080")
|
||||
}
|
12
_examples/examples/upload-files/templates/upload_form.html
Normal file
12
_examples/examples/upload-files/templates/upload_form.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Upload file</title>
|
||||
</head>
|
||||
<body>
|
||||
<form enctype="multipart/form-data"
|
||||
action="http://127.0.0.1:8080/upload" method="post">
|
||||
<input type="file" name="uploadfile" /> <input type="hidden"
|
||||
name="token" value="{{.}}" /> <input type="submit" value="upload" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
132
_examples/examples/url-shortener/main.go
Normal file
132
_examples/examples/url-shortener/main.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/view"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(
|
||||
iris.DevLogger(),
|
||||
httprouter.New(),
|
||||
view.HTML("./templates", ".html").Reload(true),
|
||||
)
|
||||
|
||||
// Serve static files (css)
|
||||
app.StaticWeb("/static", "./static_files")
|
||||
|
||||
var mu sync.Mutex
|
||||
var urls = map[string]string{
|
||||
"iris": "http://support.iris-go.com",
|
||||
}
|
||||
|
||||
app.Get("/", func(ctx *iris.Context) {
|
||||
ctx.Render("index.html", iris.Map{"url_count": len(urls)})
|
||||
})
|
||||
|
||||
// find and execute a short url by its key
|
||||
// used on http://localhost:8080/url/dsaoj41u321dsa
|
||||
execShortURL := func(ctx *iris.Context, key string) {
|
||||
if key == "" {
|
||||
ctx.EmitError(iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
value, found := urls[key]
|
||||
if !found {
|
||||
ctx.SetStatusCode(iris.StatusNotFound)
|
||||
ctx.Writef("Short URL for key: '%s' not found", key)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(value, iris.StatusTemporaryRedirect)
|
||||
}
|
||||
app.Get("/url/:shortkey", func(ctx *iris.Context) {
|
||||
execShortURL(ctx, ctx.Param("shortkey"))
|
||||
})
|
||||
|
||||
// for wildcard subdomain (yeah.. cool) http://dsaoj41u321dsa.localhost:8080
|
||||
// Note:
|
||||
// if you want subdomains (chrome doesn't works on localhost, so you have to define other hostname on app.Listen)
|
||||
// app.Party("*.", func(ctx *iris.Context) {
|
||||
// execShortURL(ctx, ctx.Subdomain())
|
||||
// })
|
||||
|
||||
app.Post("/url/shorten", func(ctx *iris.Context) {
|
||||
data := make(map[string]interface{}, 0)
|
||||
data["url_count"] = len(urls)
|
||||
value := ctx.FormValue("url")
|
||||
if value == "" {
|
||||
data["form_result"] = "You need to a enter a URL."
|
||||
} else {
|
||||
urlValue, err := url.ParseRequestURI(value)
|
||||
if err != nil {
|
||||
// ctx.JSON(iris.StatusInternalServerError,
|
||||
// iris.Map{"status": iris.StatusInternalServerError,
|
||||
// "error": err.Error(),
|
||||
// "reason": "Invalid URL",
|
||||
// })
|
||||
data["form_result"] = "Invalid URL."
|
||||
} else {
|
||||
key := randomString(12)
|
||||
// Make sure that the key is unique
|
||||
for {
|
||||
if _, exists := urls[key]; !exists {
|
||||
break
|
||||
}
|
||||
key = randomString(8)
|
||||
}
|
||||
mu.Lock()
|
||||
urls[key] = urlValue.String()
|
||||
mu.Unlock()
|
||||
ctx.SetStatusCode(iris.StatusOK)
|
||||
shortenURL := "http://" + app.Config.VHost + "/url/" + key
|
||||
data["form_result"] = template.HTML("<pre>Here is your short URL: <a href='" + shortenURL + "'>" + shortenURL + " </a></pre>")
|
||||
}
|
||||
|
||||
}
|
||||
ctx.Render("index.html", data)
|
||||
})
|
||||
|
||||
app.Listen("localhost:8080")
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | |
|
||||
// | Random String |
|
||||
// | |
|
||||
// +------------------------------------------------------------+
|
||||
|
||||
const (
|
||||
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
)
|
||||
|
||||
func randomString(n int) string {
|
||||
src := rand.NewSource(time.Now().UnixNano())
|
||||
b := make([]byte, n)
|
||||
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = src.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||
b[i] = letterBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
3
_examples/examples/url-shortener/static_files/style.css
Normal file
3
_examples/examples/url-shortener/static_files/style.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
body{
|
||||
background-color:silver;
|
||||
}
|
20
_examples/examples/url-shortener/templates/index.html
Normal file
20
_examples/examples/url-shortener/templates/index.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Golang URL Shortener</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Golang URL Shortener</h2>
|
||||
<h3>{{ .form_result}}</h3>
|
||||
<form action="/url/shorten" method="POST">
|
||||
<input type="text" name="url" style="width: 35em;" />
|
||||
<input type="submit" value="Shorten!" />
|
||||
</form>
|
||||
|
||||
<p>{{ .url_count }} URLs shortened</p>
|
||||
</body>
|
||||
|
||||
</html>
|
48
_examples/examples/websockets/main.go
Normal file
48
_examples/examples/websockets/main.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/websocket"
|
||||
)
|
||||
|
||||
func handleConnection(c websocket.Connection) {
|
||||
|
||||
// Read events from browser
|
||||
c.On("chat", func(msg string) {
|
||||
|
||||
// Print the message to the console
|
||||
fmt.Printf("%s sent: %s\n", c.Context().RemoteAddr(), msg)
|
||||
|
||||
// Write message back to browser
|
||||
c.Emit("chat", msg)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Adapt(iris.DevLogger())
|
||||
app.Adapt(httprouter.New())
|
||||
|
||||
// create our echo websocket server
|
||||
ws := websocket.New(websocket.Config{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
Endpoint: "/echo",
|
||||
})
|
||||
|
||||
ws.OnConnection(handleConnection)
|
||||
|
||||
// Adapt the websocket server.
|
||||
// you can adapt more than one of course.
|
||||
app.Adapt(ws)
|
||||
|
||||
app.Get("/", func(ctx *iris.Context) {
|
||||
ctx.ServeFile("websockets.html", false) // second parameter: enable gzip?
|
||||
})
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
29
_examples/examples/websockets/websockets.html
Normal file
29
_examples/examples/websockets/websockets.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<input id="input" type="text" />
|
||||
<button onclick="send()">Send</button>
|
||||
<pre id="output"></pre>
|
||||
<script src="/iris-ws.js"></script>
|
||||
<script>
|
||||
var input = document.getElementById("input");
|
||||
var output = document.getElementById("output");
|
||||
|
||||
// Ws comes from the auto-served '/iris-ws.js'
|
||||
var socket = new Ws("ws://localhost:8080/echo");
|
||||
socket.OnConnect(function () {
|
||||
output.innerHTML += "Status: Connected\n";
|
||||
});
|
||||
|
||||
socket.OnDisconnect(function () {
|
||||
output.innerHTML += "Status: Disconnected\n";
|
||||
});
|
||||
|
||||
// read events from the server
|
||||
socket.On("chat", function (msg) {
|
||||
output.innerHTML += "Server: " + msg + "\n";
|
||||
});
|
||||
|
||||
function send() {
|
||||
// send chat event data to the server
|
||||
socket.Emit("chat", input.value);
|
||||
input.value = "";
|
||||
}
|
||||
</script>
|
|
@ -42,64 +42,19 @@ const (
|
|||
slash = "/"
|
||||
// matchEverythingByte is just a byte of '*" rune/char
|
||||
matchEverythingByte = byte('*')
|
||||
|
||||
isRoot entryCase = iota
|
||||
hasParams
|
||||
matchEverything
|
||||
)
|
||||
|
||||
type (
|
||||
// entryCase is the type which the type of muxEntryusing in order to determinate what type (parameterized, anything, static...) is the perticular node
|
||||
entryCase uint8
|
||||
|
||||
// muxEntry is the node of a tree of the routes,
|
||||
// in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
|
||||
// this method is used by the BSD's kernel also
|
||||
muxEntry struct {
|
||||
part string
|
||||
entryCase entryCase
|
||||
hasWildNode bool
|
||||
tokens string
|
||||
nodes []*muxEntry
|
||||
middleware iris.Middleware
|
||||
precedence uint64
|
||||
paramsLen uint8
|
||||
func min(a, b int) int {
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
var (
|
||||
errMuxEntryConflictsWildcard = errors.New(`
|
||||
httprouter: '%s' in new path '%s'
|
||||
conflicts with existing wildcarded route with path: '%s'
|
||||
in existing prefix of'%s' `)
|
||||
|
||||
errMuxEntryMiddlewareAlreadyExists = errors.New(`
|
||||
httprouter: Middleware were already registered for the path: '%s'`)
|
||||
|
||||
errMuxEntryInvalidWildcard = errors.New(`
|
||||
httprouter: More than one wildcard found in the path part: '%s' in route's path: '%s'`)
|
||||
|
||||
errMuxEntryConflictsExistingWildcard = errors.New(`
|
||||
httprouter: Wildcard for route path: '%s' conflicts with existing children in route path: '%s'`)
|
||||
|
||||
errMuxEntryWildcardUnnamed = errors.New(`
|
||||
httprouter: Unnamed wildcard found in path: '%s'`)
|
||||
|
||||
errMuxEntryWildcardInvalidPlace = errors.New(`
|
||||
httprouter: Wildcard is only allowed at the end of the path, in the route path: '%s'`)
|
||||
|
||||
errMuxEntryWildcardConflictsMiddleware = errors.New(`
|
||||
httprouter: Wildcard conflicts with existing middleware for the route path: '%s'`)
|
||||
|
||||
errMuxEntryWildcardMissingSlash = errors.New(`
|
||||
httprouter: No slash(/) were found before wildcard in the route path: '%s'`)
|
||||
)
|
||||
|
||||
// getParamsLen returns the parameters length from a given path
|
||||
func getParamsLen(path string) uint8 {
|
||||
func countParams(path string) uint8 {
|
||||
var n uint
|
||||
for i := 0; i < len(path); i++ {
|
||||
if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
|
||||
if path[i] != ':' && path[i] != '*' {
|
||||
continue
|
||||
}
|
||||
n++
|
||||
|
@ -110,304 +65,391 @@ func getParamsLen(path string) uint8 {
|
|||
return uint8(n)
|
||||
}
|
||||
|
||||
// findLower returns the smaller number between a and b
|
||||
func findLower(a, b int) int {
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
type nodeType uint8
|
||||
|
||||
const (
|
||||
static nodeType = iota // default
|
||||
root
|
||||
param
|
||||
catchAll
|
||||
)
|
||||
|
||||
type node struct {
|
||||
path string
|
||||
wildChild bool
|
||||
nType nodeType
|
||||
maxParams uint8
|
||||
indices string
|
||||
children []*node
|
||||
handle iris.Middleware
|
||||
priority uint32
|
||||
}
|
||||
|
||||
// add adds a muxEntry to the existing muxEntry or to the tree if no muxEntry has the prefix of
|
||||
func (e *muxEntry) add(path string, middleware iris.Middleware) error {
|
||||
fullPath := path
|
||||
e.precedence++
|
||||
numParams := getParamsLen(path)
|
||||
// increments priority of the given child and reorders if necessary
|
||||
func (n *node) incrementChildPrio(pos int) int {
|
||||
n.children[pos].priority++
|
||||
prio := n.children[pos].priority
|
||||
|
||||
if len(e.part) > 0 || len(e.nodes) > 0 {
|
||||
loop:
|
||||
// adjust position (move to front)
|
||||
newPos := pos
|
||||
for newPos > 0 && n.children[newPos-1].priority < prio {
|
||||
// swap node positions
|
||||
n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
|
||||
|
||||
newPos--
|
||||
}
|
||||
|
||||
// build new index char string
|
||||
if newPos != pos {
|
||||
n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
|
||||
n.indices[pos:pos+1] + // the index char we move
|
||||
n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
|
||||
}
|
||||
|
||||
return newPos
|
||||
}
|
||||
|
||||
// addRoute adds a node with the given handle to the path.
|
||||
// Not concurrency-safe!
|
||||
func (n *node) addRoute(path string, handle iris.Middleware) error {
|
||||
fullPath := path
|
||||
n.priority++
|
||||
numParams := countParams(path)
|
||||
|
||||
// non-empty tree
|
||||
if len(n.path) > 0 || len(n.children) > 0 {
|
||||
walk:
|
||||
for {
|
||||
if numParams > e.paramsLen {
|
||||
e.paramsLen = numParams
|
||||
// Update maxParams of the current node
|
||||
if numParams > n.maxParams {
|
||||
n.maxParams = numParams
|
||||
}
|
||||
|
||||
// Find the longest common prefix.
|
||||
// This also implies that the common prefix contains no ':' or '*'
|
||||
// since the existing key can't contain those chars.
|
||||
i := 0
|
||||
max := findLower(len(path), len(e.part))
|
||||
for i < max && path[i] == e.part[i] {
|
||||
max := min(len(path), len(n.path))
|
||||
for i < max && path[i] == n.path[i] {
|
||||
i++
|
||||
}
|
||||
|
||||
if i < len(e.part) {
|
||||
node := muxEntry{
|
||||
part: e.part[i:],
|
||||
hasWildNode: e.hasWildNode,
|
||||
tokens: e.tokens,
|
||||
nodes: e.nodes,
|
||||
middleware: e.middleware,
|
||||
precedence: e.precedence - 1,
|
||||
// Split edge
|
||||
if i < len(n.path) {
|
||||
child := node{
|
||||
path: n.path[i:],
|
||||
wildChild: n.wildChild,
|
||||
nType: static,
|
||||
indices: n.indices,
|
||||
children: n.children,
|
||||
handle: n.handle,
|
||||
priority: n.priority - 1,
|
||||
}
|
||||
|
||||
for i := range node.nodes {
|
||||
if node.nodes[i].paramsLen > node.paramsLen {
|
||||
node.paramsLen = node.nodes[i].paramsLen
|
||||
// Update maxParams (max of all children)
|
||||
for i := range child.children {
|
||||
if child.children[i].maxParams > child.maxParams {
|
||||
child.maxParams = child.children[i].maxParams
|
||||
}
|
||||
}
|
||||
|
||||
e.nodes = []*muxEntry{&node}
|
||||
e.tokens = string([]byte{e.part[i]})
|
||||
e.part = path[:i]
|
||||
e.middleware = nil
|
||||
e.hasWildNode = false
|
||||
n.children = []*node{&child}
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices = string([]byte{n.path[i]})
|
||||
n.path = path[:i]
|
||||
n.handle = nil
|
||||
n.wildChild = false
|
||||
}
|
||||
|
||||
// Make new node a child of this node
|
||||
if i < len(path) {
|
||||
path = path[i:]
|
||||
|
||||
if e.hasWildNode {
|
||||
e = e.nodes[0]
|
||||
e.precedence++
|
||||
if n.wildChild {
|
||||
n = n.children[0]
|
||||
n.priority++
|
||||
|
||||
if numParams > e.paramsLen {
|
||||
e.paramsLen = numParams
|
||||
// Update maxParams of the child node
|
||||
if numParams > n.maxParams {
|
||||
n.maxParams = numParams
|
||||
}
|
||||
numParams--
|
||||
|
||||
if len(path) >= len(e.part) && e.part == path[:len(e.part)] &&
|
||||
// Check if the wildcard matches
|
||||
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
|
||||
// Check for longer wildcard, e.g. :name and :names
|
||||
(len(e.part) >= len(path) || path[len(e.part)] == '/') {
|
||||
continue loop
|
||||
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
|
||||
continue walk
|
||||
} else {
|
||||
// Wildcard conflict
|
||||
part := strings.SplitN(path, "/", 2)[0]
|
||||
prefix := fullPath[:strings.Index(fullPath, part)] + e.part
|
||||
return errMuxEntryConflictsWildcard.Format(fullPath, e.part, prefix)
|
||||
|
||||
pathSeg := strings.SplitN(path, "/", 2)[0]
|
||||
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||
return errors.New("'" + pathSeg +
|
||||
"' in new path '" + fullPath +
|
||||
"' conflicts with existing wildcard '" + n.path +
|
||||
"' in existing prefix '" + prefix +
|
||||
"'")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c := path[0]
|
||||
|
||||
if e.entryCase == hasParams && c == slashByte && len(e.nodes) == 1 {
|
||||
e = e.nodes[0]
|
||||
e.precedence++
|
||||
continue loop
|
||||
// slash after param
|
||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||
n = n.children[0]
|
||||
n.priority++
|
||||
continue walk
|
||||
}
|
||||
for i := range e.tokens {
|
||||
if c == e.tokens[i] {
|
||||
i = e.precedenceTo(i)
|
||||
e = e.nodes[i]
|
||||
continue loop
|
||||
|
||||
// Check if a child with the next path byte exists
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if c == n.indices[i] {
|
||||
i = n.incrementChildPrio(i)
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
if c != parameterStartByte && c != matchEverythingByte {
|
||||
|
||||
e.tokens += string([]byte{c})
|
||||
node := &muxEntry{
|
||||
paramsLen: numParams,
|
||||
// Otherwise insert it
|
||||
if c != ':' && c != '*' {
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices += string([]byte{c})
|
||||
child := &node{
|
||||
maxParams: numParams,
|
||||
}
|
||||
e.nodes = append(e.nodes, node)
|
||||
e.precedenceTo(len(e.tokens) - 1)
|
||||
e = node
|
||||
n.children = append(n.children, child)
|
||||
n.incrementChildPrio(len(n.indices) - 1)
|
||||
n = child
|
||||
}
|
||||
return e.addNode(numParams, path, fullPath, middleware)
|
||||
return n.insertChild(numParams, path, fullPath, handle)
|
||||
|
||||
} else if i == len(path) {
|
||||
if e.middleware != nil {
|
||||
return errMuxEntryMiddlewareAlreadyExists.Format(fullPath)
|
||||
} else if i == len(path) { // Make node a (in-path) leaf
|
||||
if n.handle != nil {
|
||||
return errors.New("a handle is already registered for path '" + fullPath + "'")
|
||||
}
|
||||
e.middleware = middleware
|
||||
n.handle = handle
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if err := e.addNode(numParams, path, fullPath, middleware); err != nil {
|
||||
return err
|
||||
}
|
||||
e.entryCase = isRoot
|
||||
} else { // Empty tree
|
||||
n.insertChild(numParams, path, fullPath, handle)
|
||||
n.nType = root
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addNode adds a muxEntry as children to other muxEntry
|
||||
func (e *muxEntry) addNode(numParams uint8, path string, fullPath string, middleware iris.Middleware) error {
|
||||
var offset int
|
||||
func (n *node) insertChild(numParams uint8, path, fullPath string, handle iris.Middleware) error {
|
||||
var offset int // already handled bytes of the path
|
||||
|
||||
// find prefix until first wildcard (beginning with ':'' or '*'')
|
||||
for i, max := 0, len(path); numParams > 0; i++ {
|
||||
c := path[i]
|
||||
if c != parameterStartByte && c != matchEverythingByte {
|
||||
if c != ':' && c != '*' {
|
||||
continue
|
||||
}
|
||||
|
||||
// find wildcard end (either '/' or path end)
|
||||
end := i + 1
|
||||
for end < max && path[end] != slashByte {
|
||||
for end < max && path[end] != '/' {
|
||||
switch path[end] {
|
||||
case parameterStartByte, matchEverythingByte:
|
||||
return errMuxEntryInvalidWildcard.Format(path[i:], fullPath)
|
||||
// the wildcard name must not contain ':' and '*'
|
||||
case ':', '*':
|
||||
return errors.New("only one wildcard per path segment is allowed, has: '" +
|
||||
path[i:] + "' in path '" + fullPath + "'")
|
||||
default:
|
||||
end++
|
||||
}
|
||||
}
|
||||
|
||||
if len(e.nodes) > 0 {
|
||||
return errMuxEntryConflictsExistingWildcard.Format(path[i:end], fullPath)
|
||||
// check if this Node existing children which would be
|
||||
// unreachable if we insert the wildcard here
|
||||
if len(n.children) > 0 {
|
||||
return errors.New("wildcard route '" + path[i:end] +
|
||||
"' conflicts with existing children in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// check if the wildcard has a name
|
||||
if end-i < 2 {
|
||||
return errMuxEntryWildcardUnnamed.Format(fullPath)
|
||||
return errors.New("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if c == parameterStartByte {
|
||||
|
||||
if c == ':' { // param
|
||||
// split path at the beginning of the wildcard
|
||||
if i > 0 {
|
||||
e.part = path[offset:i]
|
||||
n.path = path[offset:i]
|
||||
offset = i
|
||||
}
|
||||
|
||||
child := &muxEntry{
|
||||
entryCase: hasParams,
|
||||
paramsLen: numParams,
|
||||
child := &node{
|
||||
nType: param,
|
||||
maxParams: numParams,
|
||||
}
|
||||
e.nodes = []*muxEntry{child}
|
||||
e.hasWildNode = true
|
||||
e = child
|
||||
e.precedence++
|
||||
n.children = []*node{child}
|
||||
n.wildChild = true
|
||||
n = child
|
||||
n.priority++
|
||||
numParams--
|
||||
|
||||
// if the path doesn't end with the wildcard, then there
|
||||
// will be another non-wildcard subpath starting with '/'
|
||||
if end < max {
|
||||
e.part = path[offset:end]
|
||||
n.path = path[offset:end]
|
||||
offset = end
|
||||
|
||||
child := &muxEntry{
|
||||
paramsLen: numParams,
|
||||
precedence: 1,
|
||||
child := &node{
|
||||
maxParams: numParams,
|
||||
priority: 1,
|
||||
}
|
||||
e.nodes = []*muxEntry{child}
|
||||
e = child
|
||||
n.children = []*node{child}
|
||||
n = child
|
||||
}
|
||||
|
||||
} else {
|
||||
} else { // catchAll
|
||||
if end != max || numParams > 1 {
|
||||
return errMuxEntryWildcardInvalidPlace.Format(fullPath)
|
||||
return errors.New("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
if len(e.part) > 0 && e.part[len(e.part)-1] == '/' {
|
||||
return errMuxEntryWildcardConflictsMiddleware.Format(fullPath)
|
||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||
return errors.New("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
// currently fixed width 1 for '/'
|
||||
i--
|
||||
if path[i] != slashByte {
|
||||
return errMuxEntryWildcardMissingSlash.Format(fullPath)
|
||||
if path[i] != '/' {
|
||||
return errors.New("no / before catch-all in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
e.part = path[offset:i]
|
||||
n.path = path[offset:i]
|
||||
|
||||
child := &muxEntry{
|
||||
hasWildNode: true,
|
||||
entryCase: matchEverything,
|
||||
paramsLen: 1,
|
||||
// first node: catchAll node with empty path
|
||||
child := &node{
|
||||
wildChild: true,
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
}
|
||||
e.nodes = []*muxEntry{child}
|
||||
e.tokens = string(path[i])
|
||||
e = child
|
||||
e.precedence++
|
||||
n.children = []*node{child}
|
||||
n.indices = string(path[i])
|
||||
n = child
|
||||
n.priority++
|
||||
|
||||
child = &muxEntry{
|
||||
part: path[i:],
|
||||
entryCase: matchEverything,
|
||||
paramsLen: 1,
|
||||
middleware: middleware,
|
||||
precedence: 1,
|
||||
// second node: node holding the variable
|
||||
child = &node{
|
||||
path: path[i:],
|
||||
nType: catchAll,
|
||||
maxParams: 1,
|
||||
handle: handle,
|
||||
priority: 1,
|
||||
}
|
||||
e.nodes = []*muxEntry{child}
|
||||
n.children = []*node{child}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
e.part = path[offset:]
|
||||
e.middleware = middleware
|
||||
|
||||
// insert remaining path part and handle to the leaf
|
||||
n.path = path[offset:]
|
||||
n.handle = handle
|
||||
return nil
|
||||
}
|
||||
|
||||
// get is used by the Router, it finds and returns the correct muxEntry for a path
|
||||
func (e *muxEntry) get(path string, ctx *iris.Context) (mustRedirect bool) {
|
||||
loop:
|
||||
// Returns the handle registered with the given path (key). The values of
|
||||
// wildcards are saved to a map.
|
||||
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
||||
// made if a handle exists with an extra (without the) trailing slash for the
|
||||
// given path.
|
||||
func (n *node) getValue(path string, ctx *iris.Context) (tsr bool) {
|
||||
walk: // outer loop for walking the tree
|
||||
for {
|
||||
if len(path) > len(e.part) {
|
||||
if path[:len(e.part)] == e.part {
|
||||
path = path[len(e.part):]
|
||||
|
||||
if !e.hasWildNode {
|
||||
if len(path) > len(n.path) {
|
||||
if path[:len(n.path)] == n.path {
|
||||
path = path[len(n.path):]
|
||||
// If this node does not have a wildcard (param or catchAll)
|
||||
// child, we can just look up the next child node and continue
|
||||
// to walk down the tree
|
||||
if !n.wildChild {
|
||||
c := path[0]
|
||||
for i := range e.tokens {
|
||||
if c == e.tokens[i] {
|
||||
e = e.nodes[i]
|
||||
continue loop
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if c == n.indices[i] {
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
mustRedirect = (path == slash && e.middleware != nil)
|
||||
// Nothing found.
|
||||
// We can recommend to redirect to the same URL without a
|
||||
// trailing slash if a leaf exists for that path.
|
||||
tsr = (path == "/" && n.handle != nil)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
e = e.nodes[0]
|
||||
switch e.entryCase {
|
||||
case hasParams:
|
||||
|
||||
// handle wildcard child
|
||||
n = n.children[0]
|
||||
switch n.nType {
|
||||
case param:
|
||||
// find param end (either '/' or path end)
|
||||
end := 0
|
||||
for end < len(path) && path[end] != '/' {
|
||||
end++
|
||||
}
|
||||
|
||||
ctx.Set(e.part[1:], path[:end])
|
||||
// save param value
|
||||
ctx.Set(n.path[1:], path[:end])
|
||||
|
||||
// we need to go deeper!
|
||||
if end < len(path) {
|
||||
if len(e.nodes) > 0 {
|
||||
if len(n.children) > 0 {
|
||||
path = path[end:]
|
||||
e = e.nodes[0]
|
||||
continue loop
|
||||
n = n.children[0]
|
||||
continue walk
|
||||
}
|
||||
|
||||
mustRedirect = (len(path) == end+1)
|
||||
// ... but we can't
|
||||
tsr = (len(path) == end+1)
|
||||
return
|
||||
}
|
||||
if ctx.Middleware = e.middleware; ctx.Middleware != nil {
|
||||
|
||||
if ctx.Middleware = n.handle; ctx.Middleware != nil {
|
||||
return
|
||||
} else if len(e.nodes) == 1 {
|
||||
e = e.nodes[0]
|
||||
mustRedirect = (e.part == slash && e.middleware != nil)
|
||||
} else if len(n.children) == 1 {
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists for TSR recommendation
|
||||
n = n.children[0]
|
||||
tsr = (n.path == "/" && n.handle != nil)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case matchEverything:
|
||||
case catchAll:
|
||||
// save param value
|
||||
ctx.Set(n.path[2:], path)
|
||||
|
||||
ctx.Set(e.part[2:], path)
|
||||
ctx.Middleware = e.middleware
|
||||
ctx.Middleware = n.handle
|
||||
return
|
||||
|
||||
default:
|
||||
return
|
||||
panic("invalid node type")
|
||||
}
|
||||
}
|
||||
} else if path == e.part {
|
||||
if ctx.Middleware = e.middleware; ctx.Middleware != nil {
|
||||
} else if path == n.path {
|
||||
// We should have reached the node containing the handle.
|
||||
// Check if this node has a handle registered.
|
||||
if ctx.Middleware = n.handle; ctx.Middleware != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if path == slash && e.hasWildNode && e.entryCase != isRoot {
|
||||
mustRedirect = true
|
||||
if path == "/" && n.wildChild && n.nType != root {
|
||||
tsr = true
|
||||
return
|
||||
}
|
||||
|
||||
for i := range e.tokens {
|
||||
if e.tokens[i] == slashByte {
|
||||
e = e.nodes[i]
|
||||
mustRedirect = (len(e.part) == 1 && e.middleware != nil) ||
|
||||
(e.entryCase == matchEverything && e.nodes[0].middleware != nil)
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists for trailing slash recommendation
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if n.indices[i] == '/' {
|
||||
n = n.children[i]
|
||||
tsr = (len(n.path) == 1 && n.handle != nil) ||
|
||||
(n.nType == catchAll && n.children[0].handle != nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -415,34 +457,29 @@ loop:
|
|||
return
|
||||
}
|
||||
|
||||
mustRedirect = (path == slash) ||
|
||||
(len(e.part) == len(path)+1 && e.part[len(path)] == slashByte &&
|
||||
path == e.part[:len(e.part)-1] && e.middleware != nil)
|
||||
// Nothing found. We can recommend to redirect to the same URL with an
|
||||
// extra trailing slash if a leaf exists for that path
|
||||
tsr = (path == "/") ||
|
||||
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
||||
path == n.path[:len(n.path)-1] && n.handle != nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// precedenceTo just adds the priority of this muxEntry by an index
|
||||
func (e *muxEntry) precedenceTo(index int) int {
|
||||
e.nodes[index].precedence++
|
||||
_precedence := e.nodes[index].precedence
|
||||
|
||||
newindex := index
|
||||
for newindex > 0 && e.nodes[newindex-1].precedence < _precedence {
|
||||
tmpN := e.nodes[newindex-1]
|
||||
e.nodes[newindex-1] = e.nodes[newindex]
|
||||
e.nodes[newindex] = tmpN
|
||||
|
||||
newindex--
|
||||
// shift bytes in array by n bytes left
|
||||
func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
|
||||
switch n {
|
||||
case 0:
|
||||
return rb
|
||||
case 1:
|
||||
return [4]byte{rb[1], rb[2], rb[3], 0}
|
||||
case 2:
|
||||
return [4]byte{rb[2], rb[3]}
|
||||
case 3:
|
||||
return [4]byte{rb[3]}
|
||||
default:
|
||||
return [4]byte{}
|
||||
}
|
||||
|
||||
if newindex != index {
|
||||
e.tokens = e.tokens[:newindex] +
|
||||
e.tokens[index:index+1] +
|
||||
e.tokens[newindex:index] + e.tokens[index+1:]
|
||||
}
|
||||
|
||||
return newindex
|
||||
}
|
||||
|
||||
type (
|
||||
|
@ -451,7 +488,7 @@ type (
|
|||
// subdomain is empty for default-hostname routes,
|
||||
// ex: mysubdomain.
|
||||
subdomain string
|
||||
entry *muxEntry
|
||||
entry *node
|
||||
}
|
||||
|
||||
serveMux struct {
|
||||
|
@ -599,20 +636,20 @@ func New() iris.Policies {
|
|||
tree := mux.getTree(method, subdomain)
|
||||
if tree == nil {
|
||||
//first time we register a route to this method with this domain
|
||||
tree = &muxTree{method: method, subdomain: subdomain, entry: &muxEntry{}}
|
||||
tree = &muxTree{method: method, subdomain: subdomain, entry: &node{}}
|
||||
mux.garden = append(mux.garden, tree)
|
||||
}
|
||||
// I decide that it's better to explicit give subdomain and a path to it than registeredPath(mysubdomain./something) now its: subdomain: mysubdomain., path: /something
|
||||
// we have different tree for each of subdomains, now you can use everything you can use with the normal paths ( before you couldn't set /any/*path)
|
||||
if err := tree.entry.add(path, middleware); err != nil {
|
||||
if err := tree.entry.addRoute(path, middleware); err != nil {
|
||||
// while ProdMode means that the iris should not continue running
|
||||
// by-default it panics on these errors, but to make sure let's introduce the fatalErr to stop visiting
|
||||
fatalErr = true
|
||||
logger(iris.ProdMode, err.Error())
|
||||
logger(iris.ProdMode, Name+" "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if mp := tree.entry.paramsLen; mp > mux.maxParameters {
|
||||
if mp := tree.entry.maxParams; mp > mux.maxParameters {
|
||||
mux.maxParameters = mp
|
||||
}
|
||||
|
||||
|
@ -677,7 +714,7 @@ func (mux *serveMux) buildHandler(pool iris.ContextPool) http.Handler {
|
|||
}
|
||||
}
|
||||
|
||||
mustRedirect := tree.entry.get(routePath, context) // pass the parameters here for 0 allocation
|
||||
mustRedirect := tree.entry.getValue(routePath, context) // pass the parameters here for 0 allocation
|
||||
if context.Middleware != nil {
|
||||
// ok we found the correct route, serve it and exit entirely from here
|
||||
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
|
||||
|
|
|
@ -32,10 +32,6 @@ func TestRouterWrapperPolicySimple(t *testing.T) {
|
|||
// w2 -> w1 -> httprouter -> handler
|
||||
)
|
||||
|
||||
app.OnError(StatusNotFound, func(ctx *Context) {
|
||||
ctx.Writef("not found")
|
||||
})
|
||||
|
||||
app.Get("/", func(ctx *Context) {
|
||||
ctx.Write([]byte("OK"))
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue
Block a user