mirror of
https://github.com/kataras/iris.git
synced 2025-02-09 02:34:55 +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 = "/"
|
slash = "/"
|
||||||
// matchEverythingByte is just a byte of '*" rune/char
|
// matchEverythingByte is just a byte of '*" rune/char
|
||||||
matchEverythingByte = byte('*')
|
matchEverythingByte = byte('*')
|
||||||
|
|
||||||
isRoot entryCase = iota
|
|
||||||
hasParams
|
|
||||||
matchEverything
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
func min(a, b int) int {
|
||||||
// entryCase is the type which the type of muxEntryusing in order to determinate what type (parameterized, anything, static...) is the perticular node
|
if a <= b {
|
||||||
entryCase uint8
|
return a
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
)
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
func countParams(path string) uint8 {
|
||||||
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 {
|
|
||||||
var n uint
|
var n uint
|
||||||
for i := 0; i < len(path); i++ {
|
for i := 0; i < len(path); i++ {
|
||||||
if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
|
if path[i] != ':' && path[i] != '*' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
n++
|
n++
|
||||||
|
@ -110,304 +65,391 @@ func getParamsLen(path string) uint8 {
|
||||||
return uint8(n)
|
return uint8(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findLower returns the smaller number between a and b
|
type nodeType uint8
|
||||||
func findLower(a, b int) int {
|
|
||||||
if a <= b {
|
const (
|
||||||
return a
|
static nodeType = iota // default
|
||||||
}
|
root
|
||||||
return b
|
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
|
// increments priority of the given child and reorders if necessary
|
||||||
func (e *muxEntry) add(path string, middleware iris.Middleware) error {
|
func (n *node) incrementChildPrio(pos int) int {
|
||||||
fullPath := path
|
n.children[pos].priority++
|
||||||
e.precedence++
|
prio := n.children[pos].priority
|
||||||
numParams := getParamsLen(path)
|
|
||||||
|
|
||||||
if len(e.part) > 0 || len(e.nodes) > 0 {
|
// adjust position (move to front)
|
||||||
loop:
|
newPos := pos
|
||||||
for {
|
for newPos > 0 && n.children[newPos-1].priority < prio {
|
||||||
if numParams > e.paramsLen {
|
// swap node positions
|
||||||
e.paramsLen = numParams
|
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 {
|
||||||
|
// 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
|
i := 0
|
||||||
max := findLower(len(path), len(e.part))
|
max := min(len(path), len(n.path))
|
||||||
for i < max && path[i] == e.part[i] {
|
for i < max && path[i] == n.path[i] {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
if i < len(e.part) {
|
// Split edge
|
||||||
node := muxEntry{
|
if i < len(n.path) {
|
||||||
part: e.part[i:],
|
child := node{
|
||||||
hasWildNode: e.hasWildNode,
|
path: n.path[i:],
|
||||||
tokens: e.tokens,
|
wildChild: n.wildChild,
|
||||||
nodes: e.nodes,
|
nType: static,
|
||||||
middleware: e.middleware,
|
indices: n.indices,
|
||||||
precedence: e.precedence - 1,
|
children: n.children,
|
||||||
|
handle: n.handle,
|
||||||
|
priority: n.priority - 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range node.nodes {
|
// Update maxParams (max of all children)
|
||||||
if node.nodes[i].paramsLen > node.paramsLen {
|
for i := range child.children {
|
||||||
node.paramsLen = node.nodes[i].paramsLen
|
if child.children[i].maxParams > child.maxParams {
|
||||||
|
child.maxParams = child.children[i].maxParams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.nodes = []*muxEntry{&node}
|
n.children = []*node{&child}
|
||||||
e.tokens = string([]byte{e.part[i]})
|
// []byte for proper unicode char conversion, see #65
|
||||||
e.part = path[:i]
|
n.indices = string([]byte{n.path[i]})
|
||||||
e.middleware = nil
|
n.path = path[:i]
|
||||||
e.hasWildNode = false
|
n.handle = nil
|
||||||
|
n.wildChild = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make new node a child of this node
|
||||||
if i < len(path) {
|
if i < len(path) {
|
||||||
path = path[i:]
|
path = path[i:]
|
||||||
|
|
||||||
if e.hasWildNode {
|
if n.wildChild {
|
||||||
e = e.nodes[0]
|
n = n.children[0]
|
||||||
e.precedence++
|
n.priority++
|
||||||
|
|
||||||
if numParams > e.paramsLen {
|
// Update maxParams of the child node
|
||||||
e.paramsLen = numParams
|
if numParams > n.maxParams {
|
||||||
|
n.maxParams = numParams
|
||||||
}
|
}
|
||||||
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
|
// Check for longer wildcard, e.g. :name and :names
|
||||||
(len(e.part) >= len(path) || path[len(e.part)] == '/') {
|
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
|
||||||
continue loop
|
continue walk
|
||||||
} else {
|
} else {
|
||||||
// Wildcard conflict
|
// Wildcard conflict
|
||||||
part := strings.SplitN(path, "/", 2)[0]
|
pathSeg := strings.SplitN(path, "/", 2)[0]
|
||||||
prefix := fullPath[:strings.Index(fullPath, part)] + e.part
|
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||||
return errMuxEntryConflictsWildcard.Format(fullPath, e.part, prefix)
|
return errors.New("'" + pathSeg +
|
||||||
|
"' in new path '" + fullPath +
|
||||||
|
"' conflicts with existing wildcard '" + n.path +
|
||||||
|
"' in existing prefix '" + prefix +
|
||||||
|
"'")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c := path[0]
|
c := path[0]
|
||||||
|
|
||||||
if e.entryCase == hasParams && c == slashByte && len(e.nodes) == 1 {
|
// slash after param
|
||||||
e = e.nodes[0]
|
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||||
e.precedence++
|
n = n.children[0]
|
||||||
continue loop
|
n.priority++
|
||||||
|
continue walk
|
||||||
}
|
}
|
||||||
for i := range e.tokens {
|
|
||||||
if c == e.tokens[i] {
|
// Check if a child with the next path byte exists
|
||||||
i = e.precedenceTo(i)
|
for i := 0; i < len(n.indices); i++ {
|
||||||
e = e.nodes[i]
|
if c == n.indices[i] {
|
||||||
continue loop
|
i = n.incrementChildPrio(i)
|
||||||
|
n = n.children[i]
|
||||||
|
continue walk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c != parameterStartByte && c != matchEverythingByte {
|
// Otherwise insert it
|
||||||
|
if c != ':' && c != '*' {
|
||||||
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices += string([]byte{c})
|
||||||
|
child := &node{
|
||||||
|
maxParams: numParams,
|
||||||
|
}
|
||||||
|
n.children = append(n.children, child)
|
||||||
|
n.incrementChildPrio(len(n.indices) - 1)
|
||||||
|
n = child
|
||||||
|
}
|
||||||
|
return n.insertChild(numParams, path, fullPath, handle)
|
||||||
|
|
||||||
e.tokens += string([]byte{c})
|
} else if i == len(path) { // Make node a (in-path) leaf
|
||||||
node := &muxEntry{
|
if n.handle != nil {
|
||||||
paramsLen: numParams,
|
return errors.New("a handle is already registered for path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
e.nodes = append(e.nodes, node)
|
n.handle = handle
|
||||||
e.precedenceTo(len(e.tokens) - 1)
|
|
||||||
e = node
|
|
||||||
}
|
|
||||||
return e.addNode(numParams, path, fullPath, middleware)
|
|
||||||
|
|
||||||
} else if i == len(path) {
|
|
||||||
if e.middleware != nil {
|
|
||||||
return errMuxEntryMiddlewareAlreadyExists.Format(fullPath)
|
|
||||||
}
|
|
||||||
e.middleware = middleware
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
} else { // Empty tree
|
||||||
if err := e.addNode(numParams, path, fullPath, middleware); err != nil {
|
n.insertChild(numParams, path, fullPath, handle)
|
||||||
return err
|
n.nType = root
|
||||||
}
|
|
||||||
e.entryCase = isRoot
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addNode adds a muxEntry as children to other muxEntry
|
func (n *node) insertChild(numParams uint8, path, fullPath string, handle iris.Middleware) error {
|
||||||
func (e *muxEntry) addNode(numParams uint8, path string, fullPath string, middleware iris.Middleware) error {
|
var offset int // already handled bytes of the path
|
||||||
var offset int
|
|
||||||
|
|
||||||
|
// find prefix until first wildcard (beginning with ':'' or '*'')
|
||||||
for i, max := 0, len(path); numParams > 0; i++ {
|
for i, max := 0, len(path); numParams > 0; i++ {
|
||||||
c := path[i]
|
c := path[i]
|
||||||
if c != parameterStartByte && c != matchEverythingByte {
|
if c != ':' && c != '*' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find wildcard end (either '/' or path end)
|
||||||
end := i + 1
|
end := i + 1
|
||||||
for end < max && path[end] != slashByte {
|
for end < max && path[end] != '/' {
|
||||||
switch path[end] {
|
switch path[end] {
|
||||||
case parameterStartByte, matchEverythingByte:
|
// the wildcard name must not contain ':' and '*'
|
||||||
return errMuxEntryInvalidWildcard.Format(path[i:], fullPath)
|
case ':', '*':
|
||||||
|
return errors.New("only one wildcard per path segment is allowed, has: '" +
|
||||||
|
path[i:] + "' in path '" + fullPath + "'")
|
||||||
default:
|
default:
|
||||||
end++
|
end++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(e.nodes) > 0 {
|
// check if this Node existing children which would be
|
||||||
return errMuxEntryConflictsExistingWildcard.Format(path[i:end], fullPath)
|
// 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 {
|
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 {
|
if i > 0 {
|
||||||
e.part = path[offset:i]
|
n.path = path[offset:i]
|
||||||
offset = i
|
offset = i
|
||||||
}
|
}
|
||||||
|
|
||||||
child := &muxEntry{
|
child := &node{
|
||||||
entryCase: hasParams,
|
nType: param,
|
||||||
paramsLen: numParams,
|
maxParams: numParams,
|
||||||
}
|
}
|
||||||
e.nodes = []*muxEntry{child}
|
n.children = []*node{child}
|
||||||
e.hasWildNode = true
|
n.wildChild = true
|
||||||
e = child
|
n = child
|
||||||
e.precedence++
|
n.priority++
|
||||||
numParams--
|
numParams--
|
||||||
|
|
||||||
|
// if the path doesn't end with the wildcard, then there
|
||||||
|
// will be another non-wildcard subpath starting with '/'
|
||||||
if end < max {
|
if end < max {
|
||||||
e.part = path[offset:end]
|
n.path = path[offset:end]
|
||||||
offset = end
|
offset = end
|
||||||
|
|
||||||
child := &muxEntry{
|
child := &node{
|
||||||
paramsLen: numParams,
|
maxParams: numParams,
|
||||||
precedence: 1,
|
priority: 1,
|
||||||
}
|
}
|
||||||
e.nodes = []*muxEntry{child}
|
n.children = []*node{child}
|
||||||
e = child
|
n = child
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else { // catchAll
|
||||||
if end != max || numParams > 1 {
|
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] == '/' {
|
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||||
return errMuxEntryWildcardConflictsMiddleware.Format(fullPath)
|
return errors.New("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// currently fixed width 1 for '/'
|
||||||
i--
|
i--
|
||||||
if path[i] != slashByte {
|
if path[i] != '/' {
|
||||||
return errMuxEntryWildcardMissingSlash.Format(fullPath)
|
return errors.New("no / before catch-all in path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
e.part = path[offset:i]
|
n.path = path[offset:i]
|
||||||
|
|
||||||
child := &muxEntry{
|
// first node: catchAll node with empty path
|
||||||
hasWildNode: true,
|
child := &node{
|
||||||
entryCase: matchEverything,
|
wildChild: true,
|
||||||
paramsLen: 1,
|
nType: catchAll,
|
||||||
|
maxParams: 1,
|
||||||
}
|
}
|
||||||
e.nodes = []*muxEntry{child}
|
n.children = []*node{child}
|
||||||
e.tokens = string(path[i])
|
n.indices = string(path[i])
|
||||||
e = child
|
n = child
|
||||||
e.precedence++
|
n.priority++
|
||||||
|
|
||||||
child = &muxEntry{
|
// second node: node holding the variable
|
||||||
part: path[i:],
|
child = &node{
|
||||||
entryCase: matchEverything,
|
path: path[i:],
|
||||||
paramsLen: 1,
|
nType: catchAll,
|
||||||
middleware: middleware,
|
maxParams: 1,
|
||||||
precedence: 1,
|
handle: handle,
|
||||||
|
priority: 1,
|
||||||
}
|
}
|
||||||
e.nodes = []*muxEntry{child}
|
n.children = []*node{child}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.part = path[offset:]
|
// insert remaining path part and handle to the leaf
|
||||||
e.middleware = middleware
|
n.path = path[offset:]
|
||||||
|
n.handle = handle
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// get is used by the Router, it finds and returns the correct muxEntry for a path
|
// Returns the handle registered with the given path (key). The values of
|
||||||
func (e *muxEntry) get(path string, ctx *iris.Context) (mustRedirect bool) {
|
// wildcards are saved to a map.
|
||||||
loop:
|
// 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 {
|
for {
|
||||||
if len(path) > len(e.part) {
|
if len(path) > len(n.path) {
|
||||||
if path[:len(e.part)] == e.part {
|
if path[:len(n.path)] == n.path {
|
||||||
path = path[len(e.part):]
|
path = path[len(n.path):]
|
||||||
|
// If this node does not have a wildcard (param or catchAll)
|
||||||
if !e.hasWildNode {
|
// child, we can just look up the next child node and continue
|
||||||
|
// to walk down the tree
|
||||||
|
if !n.wildChild {
|
||||||
c := path[0]
|
c := path[0]
|
||||||
for i := range e.tokens {
|
for i := 0; i < len(n.indices); i++ {
|
||||||
if c == e.tokens[i] {
|
if c == n.indices[i] {
|
||||||
e = e.nodes[i]
|
n = n.children[i]
|
||||||
continue loop
|
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
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e = e.nodes[0]
|
// handle wildcard child
|
||||||
switch e.entryCase {
|
n = n.children[0]
|
||||||
case hasParams:
|
switch n.nType {
|
||||||
|
case param:
|
||||||
|
// find param end (either '/' or path end)
|
||||||
end := 0
|
end := 0
|
||||||
for end < len(path) && path[end] != '/' {
|
for end < len(path) && path[end] != '/' {
|
||||||
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 end < len(path) {
|
||||||
if len(e.nodes) > 0 {
|
if len(n.children) > 0 {
|
||||||
path = path[end:]
|
path = path[end:]
|
||||||
e = e.nodes[0]
|
n = n.children[0]
|
||||||
continue loop
|
continue walk
|
||||||
}
|
}
|
||||||
|
|
||||||
mustRedirect = (len(path) == end+1)
|
// ... but we can't
|
||||||
|
tsr = (len(path) == end+1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ctx.Middleware = e.middleware; ctx.Middleware != nil {
|
|
||||||
|
if ctx.Middleware = n.handle; ctx.Middleware != nil {
|
||||||
return
|
return
|
||||||
} else if len(e.nodes) == 1 {
|
} else if len(n.children) == 1 {
|
||||||
e = e.nodes[0]
|
// No handle found. Check if a handle for this path + a
|
||||||
mustRedirect = (e.part == slash && e.middleware != nil)
|
// trailing slash exists for TSR recommendation
|
||||||
|
n = n.children[0]
|
||||||
|
tsr = (n.path == "/" && n.handle != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
case matchEverything:
|
case catchAll:
|
||||||
|
// save param value
|
||||||
|
ctx.Set(n.path[2:], path)
|
||||||
|
|
||||||
ctx.Set(e.part[2:], path)
|
ctx.Middleware = n.handle
|
||||||
ctx.Middleware = e.middleware
|
|
||||||
return
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return
|
panic("invalid node type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if path == e.part {
|
} else if path == n.path {
|
||||||
if ctx.Middleware = e.middleware; ctx.Middleware != nil {
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == slash && e.hasWildNode && e.entryCase != isRoot {
|
if path == "/" && n.wildChild && n.nType != root {
|
||||||
mustRedirect = true
|
tsr = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range e.tokens {
|
// No handle found. Check if a handle for this path + a
|
||||||
if e.tokens[i] == slashByte {
|
// trailing slash exists for trailing slash recommendation
|
||||||
e = e.nodes[i]
|
for i := 0; i < len(n.indices); i++ {
|
||||||
mustRedirect = (len(e.part) == 1 && e.middleware != nil) ||
|
if n.indices[i] == '/' {
|
||||||
(e.entryCase == matchEverything && e.nodes[0].middleware != nil)
|
n = n.children[i]
|
||||||
|
tsr = (len(n.path) == 1 && n.handle != nil) ||
|
||||||
|
(n.nType == catchAll && n.children[0].handle != nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -415,34 +457,29 @@ loop:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mustRedirect = (path == slash) ||
|
// Nothing found. We can recommend to redirect to the same URL with an
|
||||||
(len(e.part) == len(path)+1 && e.part[len(path)] == slashByte &&
|
// extra trailing slash if a leaf exists for that path
|
||||||
path == e.part[:len(e.part)-1] && e.middleware != nil)
|
tsr = (path == "/") ||
|
||||||
|
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
||||||
|
path == n.path[:len(n.path)-1] && n.handle != nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// precedenceTo just adds the priority of this muxEntry by an index
|
// shift bytes in array by n bytes left
|
||||||
func (e *muxEntry) precedenceTo(index int) int {
|
func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
|
||||||
e.nodes[index].precedence++
|
switch n {
|
||||||
_precedence := e.nodes[index].precedence
|
case 0:
|
||||||
|
return rb
|
||||||
newindex := index
|
case 1:
|
||||||
for newindex > 0 && e.nodes[newindex-1].precedence < _precedence {
|
return [4]byte{rb[1], rb[2], rb[3], 0}
|
||||||
tmpN := e.nodes[newindex-1]
|
case 2:
|
||||||
e.nodes[newindex-1] = e.nodes[newindex]
|
return [4]byte{rb[2], rb[3]}
|
||||||
e.nodes[newindex] = tmpN
|
case 3:
|
||||||
|
return [4]byte{rb[3]}
|
||||||
newindex--
|
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 (
|
type (
|
||||||
|
@ -451,7 +488,7 @@ type (
|
||||||
// subdomain is empty for default-hostname routes,
|
// subdomain is empty for default-hostname routes,
|
||||||
// ex: mysubdomain.
|
// ex: mysubdomain.
|
||||||
subdomain string
|
subdomain string
|
||||||
entry *muxEntry
|
entry *node
|
||||||
}
|
}
|
||||||
|
|
||||||
serveMux struct {
|
serveMux struct {
|
||||||
|
@ -599,20 +636,20 @@ func New() iris.Policies {
|
||||||
tree := mux.getTree(method, subdomain)
|
tree := mux.getTree(method, subdomain)
|
||||||
if tree == nil {
|
if tree == nil {
|
||||||
//first time we register a route to this method with this domain
|
//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)
|
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
|
// 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)
|
// 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
|
// 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
|
// by-default it panics on these errors, but to make sure let's introduce the fatalErr to stop visiting
|
||||||
fatalErr = true
|
fatalErr = true
|
||||||
logger(iris.ProdMode, err.Error())
|
logger(iris.ProdMode, Name+" "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if mp := tree.entry.paramsLen; mp > mux.maxParameters {
|
if mp := tree.entry.maxParams; mp > mux.maxParameters {
|
||||||
mux.maxParameters = mp
|
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 {
|
if context.Middleware != nil {
|
||||||
// ok we found the correct route, serve it and exit entirely from here
|
// ok we found the correct route, serve it and exit entirely from here
|
||||||
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
|
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
|
||||||
|
|
|
@ -32,10 +32,6 @@ func TestRouterWrapperPolicySimple(t *testing.T) {
|
||||||
// w2 -> w1 -> httprouter -> handler
|
// w2 -> w1 -> httprouter -> handler
|
||||||
)
|
)
|
||||||
|
|
||||||
app.OnError(StatusNotFound, func(ctx *Context) {
|
|
||||||
ctx.Writef("not found")
|
|
||||||
})
|
|
||||||
|
|
||||||
app.Get("/", func(ctx *Context) {
|
app.Get("/", func(ctx *Context) {
|
||||||
ctx.Write([]byte("OK"))
|
ctx.Write([]byte("OK"))
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user