Compare commits
11 Commits
1aa9b78bdc
...
my-framewo
Author | SHA1 | Date | |
---|---|---|---|
c5ab1debdb | |||
c0fea38790 | |||
b98513ad36 | |||
0786a97a77 | |||
cb1ea9f701 | |||
8f4b378fdd | |||
099d1aeb0f | |||
3b16d6b16a | |||
fc4103cff3 | |||
e770731643 | |||
3bf14b9c04 |
@ -2,12 +2,7 @@ package framework
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -24,6 +19,8 @@ type Context struct {
|
|||||||
// current handler index
|
// current handler index
|
||||||
index int
|
index int
|
||||||
|
|
||||||
|
params map[string]string
|
||||||
|
|
||||||
hasTimeout bool
|
hasTimeout bool
|
||||||
writerMux *sync.Mutex
|
writerMux *sync.Mutex
|
||||||
}
|
}
|
||||||
@ -120,172 +117,8 @@ func (ctx *Context) SetHandlers(handlers []ControllerHandler) {
|
|||||||
ctx.handlers = handlers
|
ctx.handlers = handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
// }}}
|
func (ctx *Context) SetParams(params map[string]string) {
|
||||||
// {{{ Implements request functions
|
ctx.params = params
|
||||||
|
|
||||||
// {{{ Request URI
|
|
||||||
|
|
||||||
// QueryInt gets an int value from the query request
|
|
||||||
func (ctx *Context) QueryInt(key string, defval int) (int, error) {
|
|
||||||
params, err := ctx.QueryAll()
|
|
||||||
if err != nil {
|
|
||||||
return defval, err
|
|
||||||
}
|
|
||||||
if vals, ok := params[key]; ok {
|
|
||||||
len := len(vals)
|
|
||||||
if len > 0 {
|
|
||||||
intval, err := strconv.Atoi(vals[len-1]) // return the last elem
|
|
||||||
if err != nil {
|
|
||||||
return defval, err
|
|
||||||
}
|
|
||||||
return intval, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defval, errors.New("key not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryString gets a string value from the query request
|
|
||||||
func (ctx *Context) QueryString(key string, defval string) (string, error) {
|
|
||||||
params, err := ctx.QueryAll()
|
|
||||||
if err != nil {
|
|
||||||
return defval, err
|
|
||||||
}
|
|
||||||
if vals, ok := params[key]; ok {
|
|
||||||
len := len(vals)
|
|
||||||
if len > 0 {
|
|
||||||
return vals[len-1], nil // return the last elem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defval, errors.New("key not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryArray gets an array of string values from the query request
|
|
||||||
func (ctx *Context) QueryArray(key string, defval []string) ([]string, error) {
|
|
||||||
params, err := ctx.QueryAll()
|
|
||||||
if err != nil {
|
|
||||||
return defval, err
|
|
||||||
}
|
|
||||||
if vals, ok := params[key]; ok {
|
|
||||||
return vals, nil // return the last elem
|
|
||||||
}
|
|
||||||
return defval, errors.New("key not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryAll returns all queries in a request URL
|
|
||||||
func (ctx *Context) QueryAll() (url.Values, error) {
|
|
||||||
if ctx.request != nil {
|
|
||||||
return map[string][]string(ctx.request.URL.Query()), nil
|
|
||||||
}
|
|
||||||
return url.Values{}, errors.New("missing request in the context")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
// {{{ Post form
|
|
||||||
|
|
||||||
// FormInt gets an int value from the submitted form
|
|
||||||
func (ctx *Context) FormInt(key string, defval int) (int, error) {
|
|
||||||
vals, err := ctx.FormAll()
|
|
||||||
if err != nil {
|
|
||||||
return defval, err
|
|
||||||
}
|
|
||||||
|
|
||||||
valStrs, ok := vals[key]
|
|
||||||
if !ok {
|
|
||||||
return defval, errors.New("key not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
valInt, err := strconv.Atoi(valStrs[0]) // Get the first one as result
|
|
||||||
if err != nil {
|
|
||||||
return defval, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return valInt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormString gets a string value from the submitted form
|
|
||||||
func (ctx *Context) FormString(key string, defval string) (string, error) {
|
|
||||||
vals, err := ctx.FormAll()
|
|
||||||
if err != nil {
|
|
||||||
return defval, err
|
|
||||||
}
|
|
||||||
|
|
||||||
valStrs, ok := vals[key]
|
|
||||||
if !ok {
|
|
||||||
return defval, errors.New("key not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return valStrs[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormArray gets an array of string values from the submitted form
|
|
||||||
func (ctx *Context) FormArray(key string, defval []string) ([]string, error) {
|
|
||||||
vals, err := ctx.FormAll()
|
|
||||||
if err != nil {
|
|
||||||
return defval, err
|
|
||||||
}
|
|
||||||
|
|
||||||
valStrs, ok := vals[key]
|
|
||||||
if !ok {
|
|
||||||
return defval, errors.New("key not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return valStrs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormAll gets everything from the submitted form
|
|
||||||
func (ctx *Context) FormAll() (url.Values, error) {
|
|
||||||
if ctx.request != nil {
|
|
||||||
err := ctx.request.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
return url.Values{}, err
|
|
||||||
}
|
|
||||||
return ctx.request.PostForm, err
|
|
||||||
}
|
|
||||||
return url.Values{}, errors.New("missing request in the context")
|
|
||||||
}
|
|
||||||
|
|
||||||
// }}}
|
|
||||||
// {{{ application/json
|
|
||||||
|
|
||||||
// ReadJSON binds the request JSON body to an object.
|
|
||||||
//
|
|
||||||
// A pointer of obj should be passed.
|
|
||||||
func (ctx *Context) ReadJSON(obj any) error {
|
|
||||||
if ctx.request == nil {
|
|
||||||
return errors.New("missing request in the context")
|
|
||||||
}
|
|
||||||
dec := json.NewDecoder(ctx.request.Body)
|
|
||||||
err := dec.Decode(obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = dec.Decode(&struct{}{})
|
|
||||||
if err != io.EOF {
|
|
||||||
return errors.New("body must have only a single JSON value")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteJSON send back an object in JSON format with the status code
|
|
||||||
func (ctx *Context) WriteJSON(status int, obj any) error {
|
|
||||||
// There is a timeout, some error message data must have already been
|
|
||||||
// written to the output. Stop writing anything into the responseWriter.
|
|
||||||
if ctx.HasTimeout() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx.responseWriter.Header().Set("Content-type", "application/json")
|
|
||||||
ctx.responseWriter.WriteHeader(status)
|
|
||||||
_, err = ctx.responseWriter.Write(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// }}}
|
|
||||||
// }}}
|
|
||||||
|
@ -8,7 +8,8 @@ import (
|
|||||||
|
|
||||||
// Core is the core struct of the framework
|
// Core is the core struct of the framework
|
||||||
type Core struct {
|
type Core struct {
|
||||||
router map[string]*Trie
|
router map[string]*Trie
|
||||||
|
middlewares []ControllerHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCore initialize the Core.
|
// NewCore initialize the Core.
|
||||||
@ -28,40 +29,44 @@ func NewCore() *Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get is a simple get router
|
// Get is a simple get router
|
||||||
func (c *Core) Get(url string, handler ControllerHandler) {
|
func (c *Core) Get(url string, handlers ...ControllerHandler) {
|
||||||
upperUrl := strings.ToUpper(url)
|
allHandlers := append(c.middlewares, handlers...)
|
||||||
if err := c.router["GET"].AddRouter(upperUrl, handler); err != nil {
|
if err := c.router["GET"].AddRouter(url, allHandlers); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post is a simple post router
|
// Post is a simple post router
|
||||||
func (c *Core) Post(url string, handler ControllerHandler) {
|
func (c *Core) Post(url string, handlers ...ControllerHandler) {
|
||||||
upperUrl := strings.ToUpper(url)
|
allHandlers := append(c.middlewares, handlers...)
|
||||||
if err := c.router["POST"].AddRouter(upperUrl, handler); err != nil {
|
if err := c.router["POST"].AddRouter(url, allHandlers); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put is a simple put router
|
// Put is a simple put router
|
||||||
func (c *Core) Put(url string, handler ControllerHandler) {
|
func (c *Core) Put(url string, handlers ...ControllerHandler) {
|
||||||
upperUrl := strings.ToUpper(url)
|
allHandlers := append(c.middlewares, handlers...)
|
||||||
if err := c.router["PUT"].AddRouter(upperUrl, handler); err != nil {
|
if err := c.router["PUT"].AddRouter(url, allHandlers); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is a simple delete router
|
// Delete is a simple delete router
|
||||||
func (c *Core) Delete(url string, handler ControllerHandler) {
|
func (c *Core) Delete(url string, handlers ...ControllerHandler) {
|
||||||
upperUrl := strings.ToUpper(url)
|
allHandlers := append(c.middlewares, handlers...)
|
||||||
if err := c.router["DELETE"].AddRouter(upperUrl, handler); err != nil {
|
if err := c.router["DELETE"].AddRouter(url, allHandlers); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use registers middlewares
|
||||||
|
func (c *Core) Use(middlewares ...ControllerHandler) {
|
||||||
|
c.middlewares = append(c.middlewares, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
// FindRouteByRequest finds route using the request
|
// FindRouteByRequest finds route using the request
|
||||||
func (c *Core) FindRouteByRequest(r *http.Request) []ControllerHandler {
|
func (c *Core) FindRouteByRequest(r *http.Request) *node {
|
||||||
upperUri := strings.ToUpper(r.URL.Path)
|
|
||||||
upperMethod := strings.ToUpper(r.Method)
|
upperMethod := strings.ToUpper(r.Method)
|
||||||
|
|
||||||
mapper, ok := c.router[upperMethod]
|
mapper, ok := c.router[upperMethod]
|
||||||
@ -70,13 +75,13 @@ func (c *Core) FindRouteByRequest(r *http.Request) []ControllerHandler {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
controllers := mapper.FindRoute(upperUri)
|
node := mapper.FindRoute(r.URL.Path)
|
||||||
if controllers == nil {
|
if node == nil {
|
||||||
log.Printf("URI %q is not recognized\n", r.URL.Path)
|
log.Printf("URI %q is not recognized\n", r.URL.Path)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return controllers
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) Group(prefix string) IGroup {
|
func (c *Core) Group(prefix string) IGroup {
|
||||||
@ -92,13 +97,16 @@ func (c *Core) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
ctx := NewContext(w, r)
|
ctx := NewContext(w, r)
|
||||||
|
|
||||||
handlers := c.FindRouteByRequest(r)
|
node := c.FindRouteByRequest(r)
|
||||||
if handlers == nil {
|
if node == nil {
|
||||||
ctx.WriteJSON(http.StatusNotFound, "Request not found")
|
ctx.WriteJSON(http.StatusNotFound, "Request not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetHandlers(handlers)
|
params := node.ParseParamsFromEndNode(r.URL.Path)
|
||||||
|
|
||||||
|
ctx.SetParams(params)
|
||||||
|
ctx.SetHandlers(node.handlers)
|
||||||
|
|
||||||
if err := ctx.Next(); err != nil {
|
if err := ctx.Next(); err != nil {
|
||||||
ctx.WriteJSON(http.StatusInternalServerError, "Internal error")
|
ctx.WriteJSON(http.StatusInternalServerError, "Internal error")
|
||||||
|
@ -2,16 +2,18 @@ package framework
|
|||||||
|
|
||||||
// IGroup prefix routes
|
// IGroup prefix routes
|
||||||
type IGroup interface {
|
type IGroup interface {
|
||||||
Get(string, ControllerHandler)
|
Get(string, ...ControllerHandler)
|
||||||
Post(string, ControllerHandler)
|
Post(string, ...ControllerHandler)
|
||||||
Put(string, ControllerHandler)
|
Put(string, ...ControllerHandler)
|
||||||
Delete(string, ControllerHandler)
|
Delete(string, ...ControllerHandler)
|
||||||
|
Use(...ControllerHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group is the implementation of IGroup interface
|
// Group is the implementation of IGroup interface
|
||||||
type Group struct {
|
type Group struct {
|
||||||
core *Core
|
core *Core
|
||||||
prefix string
|
prefix string
|
||||||
|
middlewares []ControllerHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGroup create a new prefix group
|
// NewGroup create a new prefix group
|
||||||
@ -23,21 +25,30 @@ func NewGroup(core *Core, prefix string) *Group {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get is a simple get router of the group
|
// Get is a simple get router of the group
|
||||||
func (g *Group) Get(url string, handler ControllerHandler) {
|
func (g *Group) Get(url string, handlers ...ControllerHandler) {
|
||||||
g.core.Get(g.prefix+url, handler)
|
allHandlers := append(g.middlewares, handlers...)
|
||||||
|
g.core.Get(g.prefix+url, allHandlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post is a simple post router of the group
|
// Post is a simple post router of the group
|
||||||
func (g *Group) Post(url string, handler ControllerHandler) {
|
func (g *Group) Post(url string, handlers ...ControllerHandler) {
|
||||||
g.core.Post(g.prefix+url, handler)
|
allHandlers := append(g.middlewares, handlers...)
|
||||||
|
g.core.Post(g.prefix+url, allHandlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put is a simple put router of the group
|
// Put is a simple put router of the group
|
||||||
func (g *Group) Put(url string, handler ControllerHandler) {
|
func (g *Group) Put(url string, handlers ...ControllerHandler) {
|
||||||
g.core.Put(g.prefix+url, handler)
|
allHandlers := append(g.middlewares, handlers...)
|
||||||
|
g.core.Put(g.prefix+url, allHandlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is a simple delete router of the group
|
// Delete is a simple delete router of the group
|
||||||
func (g *Group) Delete(url string, handler ControllerHandler) {
|
func (g *Group) Delete(url string, handlers ...ControllerHandler) {
|
||||||
g.core.Delete(g.prefix+url, handler)
|
allHandlers := append(g.middlewares, handlers...)
|
||||||
|
g.core.Delete(g.prefix+url, allHandlers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use registers middlewares
|
||||||
|
func (g *Group) Use(middlewares ...ControllerHandler) {
|
||||||
|
g.middlewares = append(g.middlewares, middlewares...)
|
||||||
}
|
}
|
||||||
|
21
framework/middleware/recovery.go
Normal file
21
framework/middleware/recovery.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recovery is a middleware that recovers from the panic
|
||||||
|
func Recovery() framework.ControllerHandler {
|
||||||
|
return func(c *framework.Context) error {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
c.WriteJSON(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
34
framework/middleware/test.go
Normal file
34
framework/middleware/test.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test1() framework.ControllerHandler {
|
||||||
|
return func(c *framework.Context) error {
|
||||||
|
log.Println("middleware test1 pre")
|
||||||
|
c.Next()
|
||||||
|
log.Println("middleware test1 post")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test2() framework.ControllerHandler {
|
||||||
|
return func(c *framework.Context) error {
|
||||||
|
log.Println("middleware test2 pre")
|
||||||
|
c.Next()
|
||||||
|
log.Println("middleware test2 post")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test3() framework.ControllerHandler {
|
||||||
|
return func(c *framework.Context) error {
|
||||||
|
log.Println("middleware test3 pre")
|
||||||
|
c.Next()
|
||||||
|
log.Println("middleware test3 post")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,7 @@ func Timeout(d time.Duration) framework.ControllerHandler {
|
|||||||
log.Println("finish")
|
log.Println("finish")
|
||||||
case <-durationCtx.Done():
|
case <-durationCtx.Done():
|
||||||
c.SetHasTimeout()
|
c.SetHasTimeout()
|
||||||
|
c.GetResponseWriter().WriteHeader(http.StatusRequestTimeout)
|
||||||
c.GetResponseWriter().Write([]byte("time out"))
|
c.GetResponseWriter().Write([]byte("time out"))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
@ -13,13 +15,89 @@ func TestTimeout(t *testing.T) {
|
|||||||
t.Run("Test timeout handler", func(t *testing.T) {
|
t.Run("Test timeout handler", func(t *testing.T) {
|
||||||
timeoutHandler := Timeout(1 * time.Millisecond)
|
timeoutHandler := Timeout(1 * time.Millisecond)
|
||||||
|
|
||||||
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
longHandler := func(c *framework.Context) error {
|
||||||
response := httptest.NewRecorder()
|
time.Sleep(2 * time.Millisecond)
|
||||||
c := framework.NewContext(response, request)
|
return nil
|
||||||
|
|
||||||
err := timeoutHandler(c)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res := prepareMiddlewareTest(t, timeoutHandler, longHandler)
|
||||||
|
|
||||||
|
assertCode(t, res.StatusCode, http.StatusRequestTimeout)
|
||||||
|
assertBody(t, res.Body, "time out")
|
||||||
|
})
|
||||||
|
t.Run("Test no timeout", func(t *testing.T) {
|
||||||
|
timeoutHandler := Timeout(2 * time.Millisecond)
|
||||||
|
|
||||||
|
quickHandler := func(c *framework.Context) error {
|
||||||
|
// time.Sleep(1 * time.Millisecond)
|
||||||
|
c.WriteJSON(http.StatusOK, "ok")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := prepareMiddlewareTest(t, timeoutHandler, quickHandler)
|
||||||
|
|
||||||
|
assertCode(t, res.StatusCode, http.StatusOK)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRecover(t *testing.T) {
|
||||||
|
t.Run("Test panic", func(t *testing.T) {
|
||||||
|
recoverer := Recovery()
|
||||||
|
|
||||||
|
panicHandler := func(c *framework.Context) error {
|
||||||
|
panic("panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
res := prepareMiddlewareTest(t, recoverer, panicHandler)
|
||||||
|
|
||||||
|
assertCode(t, res.StatusCode, http.StatusInternalServerError)
|
||||||
|
})
|
||||||
|
t.Run("Test no panic", func(t *testing.T) {
|
||||||
|
recoverer := Recovery()
|
||||||
|
|
||||||
|
normalHandler := func(c *framework.Context) error {
|
||||||
|
c.WriteJSON(http.StatusOK, "ok")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := prepareMiddlewareTest(t, recoverer, normalHandler)
|
||||||
|
|
||||||
|
assertCode(t, res.StatusCode, http.StatusOK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareMiddlewareTest(
|
||||||
|
t testing.TB,
|
||||||
|
mid framework.ControllerHandler,
|
||||||
|
in framework.ControllerHandler,
|
||||||
|
) *http.Response {
|
||||||
|
t.Helper()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
c := framework.NewContext(response, request)
|
||||||
|
|
||||||
|
c.SetHandlers([]framework.ControllerHandler{in})
|
||||||
|
|
||||||
|
err := mid(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := response.Result()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCode(t testing.TB, got int, want int) {
|
||||||
|
t.Helper()
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("status code got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertBody(t testing.TB, got io.Reader, want string) {
|
||||||
|
t.Helper()
|
||||||
|
buf, _ := io.ReadAll(got)
|
||||||
|
if cmp := bytes.Compare(buf, []byte(want)); cmp != 0 {
|
||||||
|
t.Errorf("got %q, want %q", string(buf), want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
411
framework/request.go
Normal file
411
framework/request.go
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
package framework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IRequest interface {
|
||||||
|
// url query
|
||||||
|
// e.g. foo.com?a=1&b=bar&c[]=bar
|
||||||
|
QueryAll(key string) url.Values
|
||||||
|
QueryInt(key string, defval int) (int, bool)
|
||||||
|
QueryInt64(key string, defval int64) (int64, bool)
|
||||||
|
QueryFloat32(key string, defval float32) (float32, bool)
|
||||||
|
QueryFloat64(key string, defval float64) (float64, bool)
|
||||||
|
QueryBool(key string, defval bool) (bool, bool)
|
||||||
|
QueryString(key string, defval string) (string, bool)
|
||||||
|
QueryStringSlice(key string, defval []string) ([]string, bool)
|
||||||
|
|
||||||
|
// url params
|
||||||
|
// e.g. /book/:id
|
||||||
|
Param(key string) any
|
||||||
|
ParamInt(key string, defval int) (int, bool)
|
||||||
|
ParamInt64(key string, defval int64) (int64, bool)
|
||||||
|
ParamFloat32(key string, defval float32) (float32, bool)
|
||||||
|
ParamFloat64(key string, defval float64) (float64, bool)
|
||||||
|
ParamBool(key string, defval bool) (bool, bool)
|
||||||
|
ParamString(key string, defval string) (string, bool)
|
||||||
|
|
||||||
|
// form
|
||||||
|
FormAll(key string) url.Values
|
||||||
|
FormInt(key string, defval int) (int, bool)
|
||||||
|
FormInt64(key string, defval int64) (int64, bool)
|
||||||
|
FormFloat32(key string, defval float32) (float32, bool)
|
||||||
|
FormFloat64(key string, defval float64) (float64, bool)
|
||||||
|
FormBool(key string, defval bool) (bool, bool)
|
||||||
|
FormString(key string, defval string) (string, bool)
|
||||||
|
FormStringSlice(key string, defval []string) ([]string, bool)
|
||||||
|
FormFile(key string) (*multipart.FileHeader, error)
|
||||||
|
|
||||||
|
// JSON body
|
||||||
|
BindJSON(obj any) error
|
||||||
|
|
||||||
|
// XML body
|
||||||
|
BindXML(obj any) error
|
||||||
|
|
||||||
|
// RAW body
|
||||||
|
GetRawData() ([]byte, error)
|
||||||
|
|
||||||
|
// Basic informations
|
||||||
|
Uri() string
|
||||||
|
Method() string
|
||||||
|
Host() string
|
||||||
|
ClientIP() string
|
||||||
|
|
||||||
|
// Header
|
||||||
|
Headers() map[string][]string
|
||||||
|
Header(key string) (string, bool)
|
||||||
|
|
||||||
|
// Cookie
|
||||||
|
Cookies() map[string]string
|
||||||
|
Cookie(key string) (string, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{ url query
|
||||||
|
|
||||||
|
// QueryAll returns all queries in a request URL
|
||||||
|
func (ctx *Context) QueryAll() url.Values {
|
||||||
|
if ctx.request != nil {
|
||||||
|
return map[string][]string(ctx.request.URL.Query())
|
||||||
|
}
|
||||||
|
return url.Values{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryInt gets an int value from the query request
|
||||||
|
func (ctx *Context) QueryInt(key string, defval int) (int, bool) {
|
||||||
|
params := ctx.QueryAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToInt(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) QueryInt64(key string, defval int64) (int64, bool) {
|
||||||
|
params := ctx.QueryAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToInt64(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) QueryBool(key string, defval bool) (bool, bool) {
|
||||||
|
params := ctx.QueryAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToBool(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) QueryFloat32(key string, defval float32) (float32, bool) {
|
||||||
|
params := ctx.QueryAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToFloat32(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) QueryFloat64(key string, defval float64) (float64, bool) {
|
||||||
|
params := ctx.QueryAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToFloat64(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryString gets a string value from the query request
|
||||||
|
func (ctx *Context) QueryString(key string, defval string) (string, bool) {
|
||||||
|
params := ctx.QueryAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToString(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryArray gets an array of string values from the query request
|
||||||
|
func (ctx *Context) QueryStringSlice(key string, defval []string) ([]string, bool) {
|
||||||
|
params := ctx.QueryAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
return cast.ToStringSlice(vals[0]), true
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
// {{{ url params
|
||||||
|
|
||||||
|
func (ctx *Context) Param(key string) any {
|
||||||
|
if ctx.params != nil {
|
||||||
|
if val, ok := ctx.params[key]; ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) ParamInt(key string, def int) (int, bool) {
|
||||||
|
if val := ctx.Param(key); val != nil {
|
||||||
|
return cast.ToInt(val), true
|
||||||
|
}
|
||||||
|
return def, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) ParamInt64(key string, def int64) (int64, bool) {
|
||||||
|
if val := ctx.Param(key); val != nil {
|
||||||
|
return cast.ToInt64(val), true
|
||||||
|
}
|
||||||
|
return def, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) ParamFloat64(key string, def float64) (float64, bool) {
|
||||||
|
if val := ctx.Param(key); val != nil {
|
||||||
|
return cast.ToFloat64(val), true
|
||||||
|
}
|
||||||
|
return def, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) ParamFloat32(key string, def float32) (float32, bool) {
|
||||||
|
if val := ctx.Param(key); val != nil {
|
||||||
|
return cast.ToFloat32(val), true
|
||||||
|
}
|
||||||
|
return def, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) ParamBool(key string, def bool) (bool, bool) {
|
||||||
|
if val := ctx.Param(key); val != nil {
|
||||||
|
return cast.ToBool(val), true
|
||||||
|
}
|
||||||
|
return def, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) ParamString(key string, def string) (string, bool) {
|
||||||
|
if val := ctx.Param(key); val != nil {
|
||||||
|
return cast.ToString(val), true
|
||||||
|
}
|
||||||
|
return def, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
// {{{ Post form
|
||||||
|
|
||||||
|
// FormAll gets everything from the submitted form
|
||||||
|
func (ctx *Context) FormAll() url.Values {
|
||||||
|
if ctx.request != nil {
|
||||||
|
_ = ctx.request.ParseForm()
|
||||||
|
return ctx.request.PostForm
|
||||||
|
}
|
||||||
|
return url.Values{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormInt gets an int value from the submitted form
|
||||||
|
func (ctx *Context) FormInt(key string, defval int) (int, bool) {
|
||||||
|
params := ctx.FormAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToInt(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) FormInt64(key string, defval int64) (int64, bool) {
|
||||||
|
params := ctx.FormAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToInt64(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) FormBool(key string, defval bool) (bool, bool) {
|
||||||
|
params := ctx.FormAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToBool(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) FormFloat32(key string, defval float32) (float32, bool) {
|
||||||
|
params := ctx.FormAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToFloat32(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) FormFloat64(key string, defval float64) (float64, bool) {
|
||||||
|
params := ctx.FormAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToFloat64(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) FormString(key string, defval string) (string, bool) {
|
||||||
|
params := ctx.FormAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
if len(vals) > 0 {
|
||||||
|
return cast.ToString(vals[0]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) FormStringSlice(key string, defval []string) ([]string, bool) {
|
||||||
|
params := ctx.FormAll()
|
||||||
|
if vals, ok := params[key]; ok {
|
||||||
|
return cast.ToStringSlice(vals[0]), true
|
||||||
|
}
|
||||||
|
return defval, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
// {{{ type binder
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoRequest = errors.New("missing request in the context")
|
||||||
|
ErrNotSingleObj = errors.New("body must have only a single value")
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSON body
|
||||||
|
func (ctx *Context) BindJSON(obj any) error {
|
||||||
|
if ctx.request == nil {
|
||||||
|
return ErrNoRequest
|
||||||
|
}
|
||||||
|
dec := json.NewDecoder(ctx.request.Body)
|
||||||
|
err := dec.Decode(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dec.Decode(&struct{}{})
|
||||||
|
if err != io.EOF {
|
||||||
|
return ErrNotSingleObj
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XML body
|
||||||
|
func (ctx *Context) BindXML(obj any) error {
|
||||||
|
if ctx.request == nil {
|
||||||
|
return ErrNoRequest
|
||||||
|
}
|
||||||
|
dec := xml.NewDecoder(ctx.request.Body)
|
||||||
|
err := dec.Decode(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dec.Decode(&struct{}{})
|
||||||
|
if err != io.EOF {
|
||||||
|
return ErrNotSingleObj
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAW body
|
||||||
|
func (ctx *Context) GetRawData() ([]byte, error) {
|
||||||
|
if ctx.request == nil {
|
||||||
|
return []byte{}, ErrNoRequest
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(ctx.request.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
/* Restore the body (io.ReadCloser) to it's original state */
|
||||||
|
ctx.request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
// {{{ Basic informations
|
||||||
|
|
||||||
|
func (ctx *Context) Uri() string {
|
||||||
|
return ctx.request.RequestURI
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Method() string {
|
||||||
|
return ctx.request.Method
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Host() string {
|
||||||
|
return ctx.request.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) ClientIP() string {
|
||||||
|
r := ctx.request
|
||||||
|
ipAddress := r.Header.Get("X-Real-Ip")
|
||||||
|
if ipAddress == "" {
|
||||||
|
ipAddress = r.Header.Get("X-Forwarded-For")
|
||||||
|
}
|
||||||
|
if ipAddress == "" {
|
||||||
|
ipAddress = r.RemoteAddr
|
||||||
|
}
|
||||||
|
return ipAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
// {{{ Headers
|
||||||
|
|
||||||
|
// Header
|
||||||
|
func (ctx *Context) Headers() map[string][]string {
|
||||||
|
return ctx.request.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Header(key string) (string, bool) {
|
||||||
|
vals := ctx.request.Header.Values(key)
|
||||||
|
if vals == nil || len(vals) <= 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return vals[0], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
// {{{ Cookies
|
||||||
|
|
||||||
|
// Cookies gets cookie key-value pairs
|
||||||
|
func (ctx *Context) Cookies() map[string]string {
|
||||||
|
cookies := ctx.request.Cookies()
|
||||||
|
ret := map[string]string{}
|
||||||
|
for _, c := range cookies {
|
||||||
|
ret[c.Name] = c.Value
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Cookie(key string) (string, bool) {
|
||||||
|
cookies := ctx.Cookies()
|
||||||
|
if val, ok := cookies[key]; ok {
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// }}}
|
123
framework/response.go
Normal file
123
framework/response.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package framework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IResponse interface {
|
||||||
|
WriteJSON(status int, obj any) IResponse
|
||||||
|
WriteXML(status int, obj any) IResponse
|
||||||
|
WriteHTML(status int, filepath string, obj any) IResponse
|
||||||
|
WriteText(format string, values ...any) IResponse
|
||||||
|
|
||||||
|
Redirect(path string) IResponse
|
||||||
|
SetHeader(key string, val string) IResponse
|
||||||
|
SetCookie(
|
||||||
|
key string,
|
||||||
|
val string,
|
||||||
|
maxAge int,
|
||||||
|
path, domain string,
|
||||||
|
secure, httpOnly bool,
|
||||||
|
) IResponse
|
||||||
|
SetStatus(code int) IResponse
|
||||||
|
|
||||||
|
// set 200
|
||||||
|
SetOkStatus() IResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) WriteJSON(status int, obj any) IResponse {
|
||||||
|
// There is a timeout, some error message data must have already been
|
||||||
|
// written to the output. Stop writing anything into the responseWriter.
|
||||||
|
if ctx.HasTimeout() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.SetStatus(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
ctx.responseWriter.Header().Set("Content-type", "application/json")
|
||||||
|
ctx.responseWriter.WriteHeader(status)
|
||||||
|
_, _ = ctx.responseWriter.Write(data)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) WriteXML(status int, obj any) IResponse {
|
||||||
|
// There is a timeout, some error message data must have already been
|
||||||
|
// written to the output. Stop writing anything into the responseWriter.
|
||||||
|
if ctx.HasTimeout() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data, err := xml.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.SetStatus(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
ctx.responseWriter.Header().Set("Content-type", "application/json")
|
||||||
|
ctx.responseWriter.WriteHeader(status)
|
||||||
|
_, _ = ctx.responseWriter.Write(data)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) WriteHTML(status int, filepath string, obj any) IResponse {
|
||||||
|
ctx.SetHeader("Content-Type", "application/html")
|
||||||
|
t, _ := template.New("output").ParseFiles(filepath)
|
||||||
|
t.Execute(ctx.responseWriter, obj)
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) WriteText(format string, values ...any) IResponse {
|
||||||
|
out := fmt.Sprintf(format, values...)
|
||||||
|
ctx.SetHeader("Content-Type", "application/text")
|
||||||
|
ctx.responseWriter.Write([]byte(out))
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Redirect(path string) IResponse {
|
||||||
|
http.Redirect(ctx.responseWriter, ctx.request, path, http.StatusTemporaryRedirect)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) SetHeader(key string, val string) IResponse {
|
||||||
|
ctx.responseWriter.Header().Add(key, val)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) SetCookie(
|
||||||
|
key string,
|
||||||
|
val string,
|
||||||
|
maxAge int,
|
||||||
|
path, domain string,
|
||||||
|
secure, httpOnly bool,
|
||||||
|
) IResponse {
|
||||||
|
if path == "" {
|
||||||
|
path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
http.SetCookie(ctx.responseWriter, &http.Cookie{
|
||||||
|
Name: key,
|
||||||
|
Value: url.QueryEscape(val),
|
||||||
|
MaxAge: maxAge,
|
||||||
|
Path: path,
|
||||||
|
Domain: domain,
|
||||||
|
SameSite: http.SameSiteDefaultMode,
|
||||||
|
Secure: secure,
|
||||||
|
HttpOnly: httpOnly,
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) SetStatus(code int) IResponse {
|
||||||
|
ctx.responseWriter.WriteHeader(code)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// set 200
|
||||||
|
func (ctx *Context) SetOkStatus() IResponse {
|
||||||
|
ctx.responseWriter.WriteHeader(http.StatusOK)
|
||||||
|
return ctx
|
||||||
|
}
|
@ -14,10 +14,11 @@ func NewTrie() *Trie {
|
|||||||
return &Trie{root: newNode("")}
|
return &Trie{root: newNode("")}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trie) FindRoute(uri string) []ControllerHandler {
|
func (t *Trie) FindRoute(uri string) *node {
|
||||||
|
uri = strings.ToUpper(uri)
|
||||||
uri = strings.TrimPrefix(uri, "/")
|
uri = strings.TrimPrefix(uri, "/")
|
||||||
if uri == "" {
|
if uri == "" {
|
||||||
return t.root.handlers
|
return t.root
|
||||||
}
|
}
|
||||||
|
|
||||||
found := t.root.findRoute(uri)
|
found := t.root.findRoute(uri)
|
||||||
@ -25,14 +26,15 @@ func (t *Trie) FindRoute(uri string) []ControllerHandler {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return found.handlers
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trie) AddRouter(uri string, handler ControllerHandler) error {
|
func (t *Trie) AddRouter(uri string, handlers []ControllerHandler) error {
|
||||||
|
uri = strings.ToUpper(uri)
|
||||||
uri = strings.TrimPrefix(uri, "/")
|
uri = strings.TrimPrefix(uri, "/")
|
||||||
if uri == "" {
|
if uri == "" {
|
||||||
t.root.isLast = true
|
t.root.isLast = true
|
||||||
t.root.handlers = append(t.root.handlers, handler)
|
t.root.handlers = append(t.root.handlers, handlers...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +46,7 @@ func (t *Trie) AddRouter(uri string, handler ControllerHandler) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The route does not exist, add it to the tree
|
// The route does not exist, add it to the tree
|
||||||
err := t.root.addRoute(upperUri, handler)
|
err := t.root.addRoute(upperUri, handlers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -56,6 +58,29 @@ type node struct {
|
|||||||
segment string
|
segment string
|
||||||
handlers []ControllerHandler
|
handlers []ControllerHandler
|
||||||
children []*node
|
children []*node
|
||||||
|
parent *node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) ParseParamsFromEndNode(uri string) map[string]string {
|
||||||
|
ret := map[string]string{}
|
||||||
|
uri = strings.ToUpper(uri)
|
||||||
|
uri = strings.TrimPrefix(uri, "/")
|
||||||
|
if uri == "" {
|
||||||
|
// root
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
segments := strings.Split(uri, "/")
|
||||||
|
cnt := len(segments)
|
||||||
|
cur := n
|
||||||
|
|
||||||
|
for i := cnt - 1; i >= 0; i-- {
|
||||||
|
if isWildcard(cur.segment) {
|
||||||
|
// set params
|
||||||
|
ret[cur.segment[1:]] = segments[i]
|
||||||
|
}
|
||||||
|
cur = cur.parent
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNode(segment string) *node {
|
func newNode(segment string) *node {
|
||||||
@ -109,7 +134,7 @@ func (n *node) findRoute(uri string) *node {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) addRoute(uri string, handler ControllerHandler) error {
|
func (n *node) addRoute(uri string, handlers []ControllerHandler) error {
|
||||||
splitted := strings.SplitN(uri, "/", 2)
|
splitted := strings.SplitN(uri, "/", 2)
|
||||||
splittedLen := len(splitted)
|
splittedLen := len(splitted)
|
||||||
isLast := splittedLen == 1
|
isLast := splittedLen == 1
|
||||||
@ -125,27 +150,28 @@ func (n *node) addRoute(uri string, handler ControllerHandler) error {
|
|||||||
} else {
|
} else {
|
||||||
// otherwise, set the child
|
// otherwise, set the child
|
||||||
child.isLast = true
|
child.isLast = true
|
||||||
child.handlers = append(child.handlers, handler)
|
child.handlers = append(child.handlers, handlers...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// More segments to check
|
// More segments to check
|
||||||
return child.addRoute(splitted[1], handler)
|
return child.addRoute(splitted[1], handlers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new node
|
// create a new node
|
||||||
new := newNode(splitted[0])
|
new := newNode(splitted[0])
|
||||||
|
new.parent = n
|
||||||
if isLast {
|
if isLast {
|
||||||
// this is the end
|
// this is the end
|
||||||
new.handlers = append(new.handlers, handler)
|
new.handlers = append(new.handlers, handlers...)
|
||||||
new.isLast = true
|
new.isLast = true
|
||||||
n.children = append(n.children, new)
|
n.children = append(n.children, new)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// continue
|
// continue
|
||||||
new.isLast = false
|
new.isLast = false
|
||||||
err := new.addRoute(splitted[1], handler)
|
err := new.addRoute(splitted[1], handlers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -1,3 +1,5 @@
|
|||||||
module git.vinchent.xyz/vinchent/go-web
|
module git.vinchent.xyz/vinchent/go-web
|
||||||
|
|
||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
|
require github.com/spf13/cast v1.7.0
|
||||||
|
12
go.sum
12
go.sum
@ -0,0 +1,12 @@
|
|||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||||
|
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
@ -58,6 +58,7 @@ func FooControllerHandler(ctx *framework.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserLoginController(ctx *framework.Context) error {
|
func UserLoginController(ctx *framework.Context) error {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
ctx.WriteJSON(http.StatusOK, "ok")
|
ctx.WriteJSON(http.StatusOK, "ok")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -74,6 +75,7 @@ func SubjectUpdateController(ctx *framework.Context) error {
|
|||||||
|
|
||||||
func SubjectGetController(ctx *framework.Context) error {
|
func SubjectGetController(ctx *framework.Context) error {
|
||||||
ctx.WriteJSON(http.StatusAccepted, "got")
|
ctx.WriteJSON(http.StatusAccepted, "got")
|
||||||
|
log.Println(ctx.ParamInt("ID", 0))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
main.go
25
main.go
@ -1,8 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.vinchent.xyz/vinchent/go-web/framework"
|
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||||
)
|
)
|
||||||
@ -15,7 +21,22 @@ func main() {
|
|||||||
Handler: core,
|
Handler: core,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := server.ListenAndServe(); err != nil {
|
go func() {
|
||||||
log.Panic(err)
|
server.ListenAndServe()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// create quit channel
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
// set notifier
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
|
||||||
|
<-quit
|
||||||
|
fmt.Println("YOLO")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
log.Fatal("server shutdown: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "git.vinchent.xyz/vinchent/go-web/framework"
|
import (
|
||||||
|
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||||
|
"git.vinchent.xyz/vinchent/go-web/framework/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
func registerRouter(core *framework.Core) {
|
func registerRouter(core *framework.Core) {
|
||||||
|
core.Use(middleware.Test1(), middleware.Test2())
|
||||||
core.Get("/user/login", UserLoginController)
|
core.Get("/user/login", UserLoginController)
|
||||||
|
|
||||||
subjectApi := core.Group("/subject")
|
subjectApi := core.Group("/subject")
|
||||||
{
|
{
|
||||||
|
subjectApi.Use(middleware.Test3())
|
||||||
subjectApi.Delete("/:id", SubjectDelController)
|
subjectApi.Delete("/:id", SubjectDelController)
|
||||||
subjectApi.Put("/:id", SubjectUpdateController)
|
subjectApi.Put("/:id", SubjectUpdateController)
|
||||||
subjectApi.Get("/:id", SubjectGetController)
|
subjectApi.Get("/:id", SubjectGetController)
|
||||||
|
subjectApi.Get("/:id/test", SubjectGetController)
|
||||||
subjectApi.Get("/list/all", SubjectListController)
|
subjectApi.Get("/list/all", SubjectListController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user