looli
looli copied to clipboard
a tiny web framework
looli
looli is a minimalist web framework for golang.
Feature
- Router
- full method support
- named parameter
- wildcard pattern
- trailing slash redirect
- case sensitive
- serving static files
- Context
- query and form
- header and cookie
- data binding
- string xml json rendering
- html rendering
- Middleware
- using middleware
- builtin middlewares
- custome middleware
Installation
go get github.com/cssivision/looli
Usage
Router
looli build on the top of router library, which support Named parameters Wildcard parameters Trailing slash redirect Case sensitive Prefix router, for detail.
Using GET, POST, PUT, PATCH, DELETE and OPTIONS
package main
import (
"github.com/cssivision/looli"
"log"
)
func main() {
router := looli.Default()
router.Get("/a", func(c *looli.Context) {})
router.Post("/a", func(c *looli.Context) {})
router.Put("/a", func(c *looli.Context) {})
router.Delete("/a", func(c *looli.Context) {})
router.Patch("/a", func(c *looli.Context) {})
router.Head("/a", func(c *looli.Context) {})
router.Options("/a", func(c *looli.Context) {})
log.Fatal(router.Run(":8080"))
}
Named parameter
Named parameters only match a single path segment:
Pattern: /user/:name
/user/gordon match
/user/you match
/user/gordon/profile no match
/user/ no match
Pattern: /:user/:name
/a/gordon match
/b/you match
/user/gordon/profile no match
/user/ no match
package main
import (
"net/http"
"github.com/cssivision/looli"
)
func main() {
router := looli.Default()
router.Get("/a/:name", func(c *looli.Context) {
c.Status(200)
c.String("hello " + c.Param("name") + "!\n")
})
http.ListenAndServe(":8080", router)
}
Wildcard pattern
Match everything, therefore they must always be at the end of the pattern:
Pattern: /src/*filepath
/src/ match
/src/somefile.go match
/src/subdir/somefile.go match
package main
import (
"net/http"
"github.com/cssivision/looli"
)
func main() {
router := looli.Default()
router.Get("/a/*filepath", func(c *looli.Context) {
c.Status(200)
c.String("hello " + c.Param("filepath") + "!\n")
})
http.ListenAndServe(":8080", router)
}
Trailing slash redirect
By default will redirect, which means if we register path /a/b, we can request with /a/b/, conversely also success. redirect will work only in the situation that the request can not found, if both define path /a/b and /a/b/, redirect will not work.
/a/b -> /a/b/
/a/b/ -> /a/b
package main
import (
"net/http"
"github.com/cssivision/looli"
)
func main() {
router := looli.Default()
// default is true, we can forbidden this behavior by set is to false
// request with /a/ will get 404
router.SetTrailingSlashRedirect(false)
router.Get("/a", func(c *looli.Context) {
c.Status(200)
c.String("hello world!\n")
})
http.ListenAndServe(":8080", router)
}
Case sensitive
By default is not case sensitive, which means if we register path /a/b, request with /A/B will get 404 not found. if we set true, request path with /A/B will success.
package main
import (
"net/http"
"github.com/cssivision/looli"
)
func main() {
router := looli.Default()
// default is false, we can forbidden this behavior by set is to true
// request with /A/ will success.
router.SetIgnoreCase(true)
router.Get("/a", func(c *looli.Context) {
c.Status(200)
c.String("hello world!\n")
})
http.ListenAndServe(":8080", router)
}
Prefix router
Group router using prefix
package main
import (
"net/http"
"github.com/cssivision/looli"
)
func main() {
router := looli.Default()
v1 := router.Prefix("/v1")
v1.Get("/a", func(c *looli.Context) {
c.Status(200)
c.String("hello world version1\n")
})
v2 := router.Prefix("/v2")
v2.Get("/a", func(c *looli.Context) {
c.Status(200)
c.String("hello world version2\n")
})
router.Get("/a", func(c *looli.Context) {
c.Status(200)
c.String("hello world!\n")
})
http.ListenAndServe(":8080", router)
}
Serving static files
package main
import (
"net/http"
"github.com/cssivision/looli"
)
func main() {
router := looli.Default()
// Serve file in the path
router.StaticFile("/somefile.go", "file/path")
// Serve files in staic directory
router.Static("/static", "./static")
http.ListenAndServe(":8080", router)
}
Context
Context supply some syntactic sugar.
Query and Form
package main
import (
"net/http"
"github.com/cssivision/looli"
)
func main() {
router := looli.Default()
router.Get("/query", func(c *looli.Context) {
id := c.Query("id")
name := c.DefaultQuery("name", "cssivision")
c.Status(200)
c.String("hello %s, %s\n", id, name)
})
router.Post("/form", func(c *looli.Context) {
name := c.DefaultPostForm("name", "somebody")
age := c.PostForm("age")
c.Status(200)
c.JSON(looli.JSON{
"name": name,
"age": age,
})
})
http.ListenAndServe(":8080", router)
}
query
curl 'localhost:8080/query?id=1&name=cssivision'
form
curl -d 'age=21&other=haha' 'localhost:8080/form?id=1&name=cssivision'
Header and Cookie
Use method to operate header and cookie
package main
import (
"fmt"
"github.com/cssivision/looli"
"log"
"net/http"
)
func main() {
router := looli.Default()
router.Get("/header", func(c *looli.Context) {
fmt.Println(c.Header("User-Agent"))
c.SetHeader("fake-header", "fake")
c.Status(200)
c.String("fake header has setted\n")
})
router.Get("/cookie", func(c *looli.Context) {
val, _ := c.Cookie("fake-cookie")
fmt.Println(val)
c.SetCookie(&http.Cookie{
Name: "fake-cookie",
Value: "fake",
})
c.Status(200)
c.String("fake cookie has setted\n")
})
log.Fatal(router.Run(":8080"))
}
Data binding
To bind a request into a type, use data binding, data can from query, post body. currently support binding of JSON, XML and standard form values (x-www-form-urlencoded and multipart/form-data). When using the Bind-method, the binder depending on the Content-Type header.
Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set json:"fieldname".
The Validation of the incoming data show below.
package main
import (
"fmt"
"github.com/cssivision/looli"
"net/http"
)
type Infomation struct {
Name string`json:"name"`
Age int`json:"age"`
}
func (i *Infomation) Validate() error {
// data validate,
return nil
}
func main() {
router := looli.Default()
// curl 'localhost:8080/query?name=cssivision&age=21'
router.Get("/query", func(c *looli.Context) {
query := new(Infomation)
if err := c.Bind(query); err != nil {
fmt.Println(err)
return
}
fmt.Println(query.Name)
fmt.Println(query.Age)
c.Status(200)
c.JSON(query)
})
// curl -d "name=cssivision&age=21" 'localhost:8080/form'
router.Post("/form", func(c *looli.Context) {
form := new(Infomation)
if err := c.Bind(form); err != nil {
fmt.Println(err)
return
}
fmt.Println(form.Name)
fmt.Println(form.Age)
c.Status(200)
c.JSON(form)
})
// curl -H "Content-Type: application/json" -X POST -d '{"name":"cssivision","age":21}' localhost:8080/json
router.Post("/json", func(c *looli.Context) {
json := new(Infomation)
if err := c.Bind(json); err != nil {
fmt.Println(err)
return
}
fmt.Println(json.Name)
fmt.Println(json.Age)
c.Status(200)
c.JSON(json)
})
http.ListenAndServe(":8080", router)
}
String JSON rendering
package main
import (
"github.com/cssivision/looli"
"net/http"
)
func main() {
router := looli.Default()
router.Get("/string", func(c *looli.Context) {
c.String("the response is %s\n", "string")
})
router.Get("/json1", func(c *looli.Context) {
c.JSON(looli.JSON{
"name": "cssivision",
"age": 21,
})
})
router.Get("/json2", func(c *looli.Context) {
var msg struct {
Name string`json:"name"`
Age int`json:"age"`
}
msg.Name = "cssivision"
msg.Age = 21
c.JSON(msg)
})
http.ListenAndServe(":8080", router)
}
HTML rendering
package main
import (
"github.com/cssivision/looli"
"net/http"
)
func main() {
router := looli.Default()
router.LoadHTMLGlob("templates/*")
router.Get("/html", func(c *looli.Context) {
c.HTML("index.tmpl", looli.JSON{
"title": "my site",
})
})
http.ListenAndServe(":8080", router)
}
templates/index.tmpl
<html>
<h1>
{{ .title }}
</h1>
</html>
Middleware
looli.Default() with middleware Logger() Recover() by default, without middleware use looli.New() instead.
Using middleware
package main
import (
"net/http"
"github.com/cssivision/looli"
"log"
)
func main() {
router := looli.New()
// global middleware
router.Use(looli.Logger())
router.Get("/a", func(c *looli.Context) {
c.Status(200)
c.String("hello world!\n")
})
// multi handler for specificed path
router.Get("/b", func(c *looli.Context) {
c.String("first handler\n")
}, func(c *looli.Context) {
c.String("second handler\n")
})
v1 := router.Prefix("/v1")
// recover middleware only work for /v1 prefix router
v1.Use(looli.Recover())
v1.Get("/a", func(c *looli.Context) {
panic("error!")
c.Status(200)
c.String("hello world!\n")
})
log.Fatal(http.ListenAndServe(":8080", router))
}
Builtin middlewares
- Logger middleware
- Recover middleware
- Session middleware
- Cors middleware
- Csrf middleware
Custome middleware
package main
import (
"log"
"net/http"
"github.com/cssivision/looli"
"time"
)
func Logger() looli.HandlerFunc {
return func(c *looli.Context) {
t := time.Now()
// before request
c.Next()
// after request
latency := time.Since(t)
log.Print(latency)
}
}
func main() {
router := looli.New()
// global middleware
router.Use(Logger())
router.Get("/a", func(c *looli.Context) {
c.Status(200)
c.String("hello world!\n")
})
http.ListenAndServe(":8080", router)
}
Licenses
All source code is licensed under the MIT License.
Todo
- elegant error handle