Araneae: Get Gin
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | [submodule "framework/gin"] | ||||||
|  | 	path = framework/gin | ||||||
|  | 	url = gitea@git.vinchent.xyz:vinchent/gin.git | ||||||
| @ -1,124 +0,0 @@ | |||||||
| package framework |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"net/http" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Context type is the customized context of Araneae framework |  | ||||||
| // |  | ||||||
| // It packages the internal context.Context with basic "wr" couple. |  | ||||||
| type Context struct { |  | ||||||
| 	ctx            context.Context |  | ||||||
| 	request        *http.Request |  | ||||||
| 	responseWriter http.ResponseWriter |  | ||||||
|  |  | ||||||
| 	handlers []ControllerHandler |  | ||||||
| 	// current handler index |  | ||||||
| 	index int |  | ||||||
|  |  | ||||||
| 	params map[string]string |  | ||||||
|  |  | ||||||
| 	hasTimeout bool |  | ||||||
| 	writerMux  *sync.Mutex |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewContext create a new context |  | ||||||
| func NewContext(w http.ResponseWriter, r *http.Request) *Context { |  | ||||||
| 	return &Context{ |  | ||||||
| 		ctx:            r.Context(), |  | ||||||
| 		request:        r, |  | ||||||
| 		responseWriter: w, |  | ||||||
| 		writerMux:      &sync.Mutex{}, |  | ||||||
| 		index:          -1, // will be set to 0 when at the beginning |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // {{{ Basic functions |  | ||||||
|  |  | ||||||
| // WriterMux returns the writer mutex. |  | ||||||
| // |  | ||||||
| // This is useful when goroutines concurrently write into responseWriter, |  | ||||||
| // while at the same time we are writing into the responseWriter for a |  | ||||||
| // panic or timeout. |  | ||||||
| // We can protect it at the context level. |  | ||||||
| func (ctx *Context) WriterMux() *sync.Mutex { |  | ||||||
| 	return ctx.writerMux |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetRequest returns the original request |  | ||||||
| func (ctx *Context) GetRequest() *http.Request { |  | ||||||
| 	return ctx.request |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetResponseWriter returns the original response writer |  | ||||||
| func (ctx *Context) GetResponseWriter() http.ResponseWriter { |  | ||||||
| 	return ctx.responseWriter |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SetHasTimeout indicates that the context has timeout. |  | ||||||
| // |  | ||||||
| // So that other goroutines won't write into the responseWriter anymore |  | ||||||
| func (ctx *Context) SetHasTimeout() { |  | ||||||
| 	ctx.hasTimeout = true |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // HasTimeout returns whether the context has timeout. |  | ||||||
| func (ctx *Context) HasTimeout() bool { |  | ||||||
| 	return ctx.hasTimeout |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // }}} |  | ||||||
| // {{{ Implements context interface |  | ||||||
|  |  | ||||||
| // BaseContext return a request default Context |  | ||||||
| func (ctx *Context) BaseContext() context.Context { |  | ||||||
| 	return ctx.request.Context() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Done calls the base function |  | ||||||
| func (ctx *Context) Done() <-chan struct{} { |  | ||||||
| 	return ctx.BaseContext().Done() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Deadline calls the base function |  | ||||||
| func (ctx *Context) Deadline() (deadline time.Time, ok bool) { |  | ||||||
| 	return ctx.BaseContext().Deadline() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Err calls the base function |  | ||||||
| func (ctx *Context) Err() error { |  | ||||||
| 	return ctx.BaseContext().Err() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Value calls the base function |  | ||||||
| func (ctx *Context) Value(key any) any { |  | ||||||
| 	return ctx.BaseContext().Value(key) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Next runs the next function in the function chain |  | ||||||
| func (ctx *Context) Next() error { |  | ||||||
| 	ctx.index++ |  | ||||||
| 	if ctx.index >= len(ctx.handlers) { |  | ||||||
| 		// This is the end of the chain |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	// Run this handler |  | ||||||
| 	if err := ctx.handlers[ctx.index](ctx); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SetHandlers sets handlers for context |  | ||||||
| func (ctx *Context) SetHandlers(handlers []ControllerHandler) { |  | ||||||
| 	ctx.handlers = handlers |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (ctx *Context) SetParams(params map[string]string) { |  | ||||||
| 	ctx.params = params |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // }}} |  | ||||||
| @ -1,3 +0,0 @@ | |||||||
| package framework |  | ||||||
|  |  | ||||||
| type ControllerHandler func(c *Context) error |  | ||||||
| @ -1,115 +0,0 @@ | |||||||
| package framework |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"log" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Core is the core struct of the framework |  | ||||||
| type Core struct { |  | ||||||
| 	router      map[string]*Trie |  | ||||||
| 	middlewares []ControllerHandler |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewCore initialize the Core. |  | ||||||
| func NewCore() *Core { |  | ||||||
| 	getRouter := NewTrie() |  | ||||||
| 	postRouter := NewTrie() |  | ||||||
| 	putRouter := NewTrie() |  | ||||||
| 	deleteRouter := NewTrie() |  | ||||||
|  |  | ||||||
| 	router := map[string]*Trie{} |  | ||||||
| 	router["GET"] = getRouter |  | ||||||
| 	router["POST"] = postRouter |  | ||||||
| 	router["PUT"] = putRouter |  | ||||||
| 	router["DELETE"] = deleteRouter |  | ||||||
|  |  | ||||||
| 	return &Core{router: router} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Get is a simple get router |  | ||||||
| func (c *Core) Get(url string, handlers ...ControllerHandler) { |  | ||||||
| 	allHandlers := append(c.middlewares, handlers...) |  | ||||||
| 	if err := c.router["GET"].AddRouter(url, allHandlers); err != nil { |  | ||||||
| 		log.Println(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Post is a simple post router |  | ||||||
| func (c *Core) Post(url string, handlers ...ControllerHandler) { |  | ||||||
| 	allHandlers := append(c.middlewares, handlers...) |  | ||||||
| 	if err := c.router["POST"].AddRouter(url, allHandlers); err != nil { |  | ||||||
| 		log.Println(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Put is a simple put router |  | ||||||
| func (c *Core) Put(url string, handlers ...ControllerHandler) { |  | ||||||
| 	allHandlers := append(c.middlewares, handlers...) |  | ||||||
| 	if err := c.router["PUT"].AddRouter(url, allHandlers); err != nil { |  | ||||||
| 		log.Println(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Delete is a simple delete router |  | ||||||
| func (c *Core) Delete(url string, handlers ...ControllerHandler) { |  | ||||||
| 	allHandlers := append(c.middlewares, handlers...) |  | ||||||
| 	if err := c.router["DELETE"].AddRouter(url, allHandlers); err != nil { |  | ||||||
| 		log.Println(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Use registers middlewares |  | ||||||
| func (c *Core) Use(middlewares ...ControllerHandler) { |  | ||||||
| 	c.middlewares = append(c.middlewares, middlewares...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FindRouteByRequest finds route using the request |  | ||||||
| func (c *Core) FindRouteByRequest(r *http.Request) *node { |  | ||||||
| 	upperMethod := strings.ToUpper(r.Method) |  | ||||||
|  |  | ||||||
| 	mapper, ok := c.router[upperMethod] |  | ||||||
| 	if !ok { |  | ||||||
| 		log.Printf("Method %q is not recognized\n", upperMethod) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	node := mapper.FindRoute(r.URL.Path) |  | ||||||
| 	if node == nil { |  | ||||||
| 		log.Printf("URI %q is not recognized\n", r.URL.Path) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return node |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *Core) Group(prefix string) IGroup { |  | ||||||
| 	return &Group{ |  | ||||||
| 		core:   c, |  | ||||||
| 		prefix: prefix, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ServeHTTP implements the Handler interface |  | ||||||
| func (c *Core) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	log.Println("Welcome to the Araneae framework") |  | ||||||
|  |  | ||||||
| 	ctx := NewContext(w, r) |  | ||||||
|  |  | ||||||
| 	node := c.FindRouteByRequest(r) |  | ||||||
| 	if node == nil { |  | ||||||
| 		ctx.WriteJSON(http.StatusNotFound, "Request not found") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	params := node.ParseParamsFromEndNode(r.URL.Path) |  | ||||||
|  |  | ||||||
| 	ctx.SetParams(params) |  | ||||||
| 	ctx.SetHandlers(node.handlers) |  | ||||||
|  |  | ||||||
| 	if err := ctx.Next(); err != nil { |  | ||||||
| 		ctx.WriteJSON(http.StatusInternalServerError, "Internal error") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										1
									
								
								framework/gin
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								framework/gin
									
									
									
									
									
										Submodule
									
								
							 Submodule framework/gin added at f05f966a08
									
								
							| @ -1,54 +0,0 @@ | |||||||
| package framework |  | ||||||
|  |  | ||||||
| // IGroup prefix routes |  | ||||||
| type IGroup interface { |  | ||||||
| 	Get(string, ...ControllerHandler) |  | ||||||
| 	Post(string, ...ControllerHandler) |  | ||||||
| 	Put(string, ...ControllerHandler) |  | ||||||
| 	Delete(string, ...ControllerHandler) |  | ||||||
| 	Use(...ControllerHandler) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Group is the implementation of IGroup interface |  | ||||||
| type Group struct { |  | ||||||
| 	core        *Core |  | ||||||
| 	prefix      string |  | ||||||
| 	middlewares []ControllerHandler |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewGroup create a new prefix group |  | ||||||
| func NewGroup(core *Core, prefix string) *Group { |  | ||||||
| 	return &Group{ |  | ||||||
| 		core:   core, |  | ||||||
| 		prefix: prefix, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Get is a simple get router of the group |  | ||||||
| func (g *Group) Get(url string, handlers ...ControllerHandler) { |  | ||||||
| 	allHandlers := append(g.middlewares, handlers...) |  | ||||||
| 	g.core.Get(g.prefix+url, allHandlers...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Post is a simple post router of the group |  | ||||||
| func (g *Group) Post(url string, handlers ...ControllerHandler) { |  | ||||||
| 	allHandlers := append(g.middlewares, handlers...) |  | ||||||
| 	g.core.Post(g.prefix+url, allHandlers...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Put is a simple put router of the group |  | ||||||
| func (g *Group) Put(url string, handlers ...ControllerHandler) { |  | ||||||
| 	allHandlers := append(g.middlewares, handlers...) |  | ||||||
| 	g.core.Put(g.prefix+url, allHandlers...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Delete is a simple delete router of the group |  | ||||||
| func (g *Group) Delete(url string, handlers ...ControllerHandler) { |  | ||||||
| 	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...) |  | ||||||
| } |  | ||||||
| @ -3,19 +3,18 @@ package middleware | |||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  |  | ||||||
| 	"git.vinchent.xyz/vinchent/go-web/framework" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Recovery is a middleware that recovers from the panic | // Recovery is a middleware that recovers from the panic | ||||||
| func Recovery() framework.ControllerHandler { | func Recovery() gin.HandlerFunc { | ||||||
| 	return func(c *framework.Context) error { | 	return func(c *gin.Context) { | ||||||
| 		defer func() { | 		defer func() { | ||||||
| 			if err := recover(); err != nil { | 			if err := recover(); err != nil { | ||||||
| 				c.WriteJSON(http.StatusInternalServerError, err) | 				c.JSON(http.StatusInternalServerError, err) | ||||||
| 			} | 			} | ||||||
| 		}() | 		}() | ||||||
|  |  | ||||||
| 		c.Next() | 		c.Next() | ||||||
| 		return nil |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,32 +3,29 @@ package middleware | |||||||
| import ( | import ( | ||||||
| 	"log" | 	"log" | ||||||
|  |  | ||||||
| 	"git.vinchent.xyz/vinchent/go-web/framework" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func Test1() framework.ControllerHandler { | func Test1() gin.HandlerFunc { | ||||||
| 	return func(c *framework.Context) error { | 	return func(c *gin.Context) { | ||||||
| 		log.Println("middleware test1 pre") | 		log.Println("middleware test1 pre") | ||||||
| 		c.Next() | 		c.Next() | ||||||
| 		log.Println("middleware test1 post") | 		log.Println("middleware test1 post") | ||||||
| 		return nil |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func Test2() framework.ControllerHandler { | func Test2() gin.HandlerFunc { | ||||||
| 	return func(c *framework.Context) error { | 	return func(c *gin.Context) { | ||||||
| 		log.Println("middleware test2 pre") | 		log.Println("middleware test2 pre") | ||||||
| 		c.Next() | 		c.Next() | ||||||
| 		log.Println("middleware test2 post") | 		log.Println("middleware test2 post") | ||||||
| 		return nil |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func Test3() framework.ControllerHandler { | func Test3() gin.HandlerFunc { | ||||||
| 	return func(c *framework.Context) error { | 	return func(c *gin.Context) { | ||||||
| 		log.Println("middleware test3 pre") | 		log.Println("middleware test3 pre") | ||||||
| 		c.Next() | 		c.Next() | ||||||
| 		log.Println("middleware test3 post") | 		log.Println("middleware test3 post") | ||||||
| 		return nil |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,15 +6,15 @@ import ( | |||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"git.vinchent.xyz/vinchent/go-web/framework" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func Timeout(d time.Duration) framework.ControllerHandler { | func Timeout(d time.Duration) gin.HandlerFunc { | ||||||
| 	return func(c *framework.Context) error { | 	return func(c *gin.Context) { | ||||||
| 		finish := make(chan struct{}, 1) | 		finish := make(chan struct{}, 1) | ||||||
| 		panicChan := make(chan interface{}, 1) | 		panicChan := make(chan interface{}, 1) | ||||||
|  |  | ||||||
| 		durationCtx, cancel := context.WithTimeout(c.BaseContext(), d) | 		durationCtx, cancel := context.WithTimeout(c.Request.Context(), d) | ||||||
| 		defer cancel() | 		defer cancel() | ||||||
|  |  | ||||||
| 		go func() { | 		go func() { | ||||||
| @ -35,15 +35,12 @@ func Timeout(d time.Duration) framework.ControllerHandler { | |||||||
| 		case p := <-panicChan: | 		case p := <-panicChan: | ||||||
| 			// panic | 			// panic | ||||||
| 			log.Println(p) | 			log.Println(p) | ||||||
| 			c.GetResponseWriter().WriteHeader(http.StatusInternalServerError) | 			c.Status(http.StatusInternalServerError) | ||||||
| 		case <-finish: | 		case <-finish: | ||||||
| 			// finish normally | 			// finish normally | ||||||
| 			log.Println("finish") | 			log.Println("finish") | ||||||
| 		case <-durationCtx.Done(): | 		case <-durationCtx.Done(): | ||||||
| 			c.SetHasTimeout() | 			c.JSON(http.StatusRequestTimeout, "time out") | ||||||
| 			c.GetResponseWriter().WriteHeader(http.StatusRequestTimeout) |  | ||||||
| 			c.GetResponseWriter().Write([]byte("time out")) |  | ||||||
| 		} | 		} | ||||||
| 		return nil |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,30 +8,28 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"git.vinchent.xyz/vinchent/go-web/framework" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestTimeout(t *testing.T) { | 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) | ||||||
|  |  | ||||||
| 		longHandler := func(c *framework.Context) error { | 		longHandler := func(c *gin.Context) { | ||||||
| 			time.Sleep(2 * time.Millisecond) | 			time.Sleep(2 * time.Millisecond) | ||||||
| 			return nil |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		res := prepareMiddlewareTest(t, timeoutHandler, longHandler) | 		res := prepareMiddlewareTest(t, timeoutHandler, longHandler) | ||||||
|  |  | ||||||
| 		assertCode(t, res.StatusCode, http.StatusRequestTimeout) | 		assertCode(t, res.StatusCode, http.StatusRequestTimeout) | ||||||
| 		assertBody(t, res.Body, "time out") | 		assertBody(t, res.Body, "\"time out\"") | ||||||
| 	}) | 	}) | ||||||
| 	t.Run("Test no timeout", func(t *testing.T) { | 	t.Run("Test no timeout", func(t *testing.T) { | ||||||
| 		timeoutHandler := Timeout(2 * time.Millisecond) | 		timeoutHandler := Timeout(2 * time.Millisecond) | ||||||
|  |  | ||||||
| 		quickHandler := func(c *framework.Context) error { | 		quickHandler := func(c *gin.Context) { | ||||||
| 			// time.Sleep(1 * time.Millisecond) | 			// time.Sleep(1 * time.Millisecond) | ||||||
| 			c.WriteJSON(http.StatusOK, "ok") | 			c.JSON(http.StatusOK, "ok") | ||||||
| 			return nil |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		res := prepareMiddlewareTest(t, timeoutHandler, quickHandler) | 		res := prepareMiddlewareTest(t, timeoutHandler, quickHandler) | ||||||
| @ -44,7 +42,7 @@ func TestRecover(t *testing.T) { | |||||||
| 	t.Run("Test panic", func(t *testing.T) { | 	t.Run("Test panic", func(t *testing.T) { | ||||||
| 		recoverer := Recovery() | 		recoverer := Recovery() | ||||||
|  |  | ||||||
| 		panicHandler := func(c *framework.Context) error { | 		panicHandler := func(c *gin.Context) { | ||||||
| 			panic("panic") | 			panic("panic") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @ -55,9 +53,8 @@ func TestRecover(t *testing.T) { | |||||||
| 	t.Run("Test no panic", func(t *testing.T) { | 	t.Run("Test no panic", func(t *testing.T) { | ||||||
| 		recoverer := Recovery() | 		recoverer := Recovery() | ||||||
|  |  | ||||||
| 		normalHandler := func(c *framework.Context) error { | 		normalHandler := func(c *gin.Context) { | ||||||
| 			c.WriteJSON(http.StatusOK, "ok") | 			c.JSON(http.StatusOK, "ok") | ||||||
| 			return nil |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		res := prepareMiddlewareTest(t, recoverer, normalHandler) | 		res := prepareMiddlewareTest(t, recoverer, normalHandler) | ||||||
| @ -68,20 +65,18 @@ func TestRecover(t *testing.T) { | |||||||
|  |  | ||||||
| func prepareMiddlewareTest( | func prepareMiddlewareTest( | ||||||
| 	t testing.TB, | 	t testing.TB, | ||||||
| 	mid framework.ControllerHandler, | 	mid gin.HandlerFunc, | ||||||
| 	in framework.ControllerHandler, | 	in gin.HandlerFunc, | ||||||
| ) *http.Response { | ) *http.Response { | ||||||
| 	t.Helper() | 	t.Helper() | ||||||
| 	request := httptest.NewRequest(http.MethodGet, "/", nil) | 	request := httptest.NewRequest(http.MethodGet, "/", nil) | ||||||
| 	response := httptest.NewRecorder() | 	response := httptest.NewRecorder() | ||||||
| 	c := framework.NewContext(response, request) | 	_, r := gin.CreateTestContext(response) | ||||||
|  |  | ||||||
| 	c.SetHandlers([]framework.ControllerHandler{in}) | 	r.Use(mid) | ||||||
|  | 	r.GET("/", in) | ||||||
|  |  | ||||||
| 	err := mid(c) | 	r.ServeHTTP(response, request) | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	res := response.Result() | 	res := response.Result() | ||||||
| 	return res | 	return res | ||||||
|  | |||||||
| @ -1,411 +0,0 @@ | |||||||
| 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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // }}} |  | ||||||
| @ -1,123 +0,0 @@ | |||||||
| 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 |  | ||||||
| } |  | ||||||
| @ -1,180 +0,0 @@ | |||||||
| package framework |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type Trie struct { |  | ||||||
| 	root *node |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewTrie() *Trie { |  | ||||||
| 	return &Trie{root: newNode("")} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Trie) FindRoute(uri string) *node { |  | ||||||
| 	uri = strings.ToUpper(uri) |  | ||||||
| 	uri = strings.TrimPrefix(uri, "/") |  | ||||||
| 	if uri == "" { |  | ||||||
| 		return t.root |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	found := t.root.findRoute(uri) |  | ||||||
| 	if found == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return found |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Trie) AddRouter(uri string, handlers []ControllerHandler) error { |  | ||||||
| 	uri = strings.ToUpper(uri) |  | ||||||
| 	uri = strings.TrimPrefix(uri, "/") |  | ||||||
| 	if uri == "" { |  | ||||||
| 		t.root.isLast = true |  | ||||||
| 		t.root.handlers = append(t.root.handlers, handlers...) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	upperUri := strings.ToUpper(uri) |  | ||||||
| 	match := t.FindRoute(upperUri) |  | ||||||
| 	if match != nil { |  | ||||||
| 		// existing route |  | ||||||
| 		return fmt.Errorf("existing route for %q", uri) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// The route does not exist, add it to the tree |  | ||||||
| 	err := t.root.addRoute(upperUri, handlers) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type node struct { |  | ||||||
| 	isLast   bool |  | ||||||
| 	segment  string |  | ||||||
| 	handlers []ControllerHandler |  | ||||||
| 	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 { |  | ||||||
| 	return &node{segment: segment} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func isWildcard(s string) bool { |  | ||||||
| 	return strings.HasPrefix(s, ":") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // /user/name |  | ||||||
| // /user/:id/name |  | ||||||
| // /user/3/name |  | ||||||
|  |  | ||||||
| // findRoute finds the handler for the uri if exists. |  | ||||||
| // |  | ||||||
| // we suppose that uri passed here doesn't begin with "/" |  | ||||||
| func (n *node) findRoute(uri string) *node { |  | ||||||
| 	splitted := strings.SplitN(uri, "/", 2) |  | ||||||
| 	splittedLen := len(splitted) |  | ||||||
|  |  | ||||||
| 	if isWildcard(splitted[0]) { |  | ||||||
| 		// input is a wildcard, check if this endpoint has already children |  | ||||||
| 		// if so, return the first one which is not nil. |  | ||||||
| 		nbChildren := len(n.children) |  | ||||||
| 		if nbChildren > 0 && n.children[0].segment != splitted[0] { |  | ||||||
| 			// several nodes exist, return the first one that is not nil |  | ||||||
| 			return n.children[0] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// try to find the value in childre |  | ||||||
| 	for _, child := range n.children { |  | ||||||
| 		if isWildcard(child.segment) || child.segment == splitted[0] { |  | ||||||
| 			if splittedLen == 1 { |  | ||||||
| 				// This is the last value, do the check and return |  | ||||||
| 				if child.isLast { |  | ||||||
| 					// if isLast, that means we have already registered the endpoint |  | ||||||
| 					return child |  | ||||||
| 				} else { |  | ||||||
| 					// otherwise, take it as not registered |  | ||||||
| 					return nil |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			// More segments to check |  | ||||||
| 			return child.findRoute(splitted[1]) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// nothing found in the children |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (n *node) addRoute(uri string, handlers []ControllerHandler) error { |  | ||||||
| 	splitted := strings.SplitN(uri, "/", 2) |  | ||||||
| 	splittedLen := len(splitted) |  | ||||||
| 	isLast := splittedLen == 1 |  | ||||||
|  |  | ||||||
| 	// try to find the value in childre |  | ||||||
| 	for _, child := range n.children { |  | ||||||
| 		if isWildcard(child.segment) || child.segment == splitted[0] { |  | ||||||
| 			if isLast { |  | ||||||
| 				// This is the last value, do the check and return |  | ||||||
| 				if child.isLast { |  | ||||||
| 					// if isLast, that means we have already registered the endpoint |  | ||||||
| 					return errors.New("node exists") |  | ||||||
| 				} else { |  | ||||||
| 					// otherwise, set the child |  | ||||||
| 					child.isLast = true |  | ||||||
| 					child.handlers = append(child.handlers, handlers...) |  | ||||||
| 					return nil |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			// More segments to check |  | ||||||
| 			return child.addRoute(splitted[1], handlers) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// create a new node |  | ||||||
| 	new := newNode(splitted[0]) |  | ||||||
| 	new.parent = n |  | ||||||
| 	if isLast { |  | ||||||
| 		// this is the end |  | ||||||
| 		new.handlers = append(new.handlers, handlers...) |  | ||||||
| 		new.isLast = true |  | ||||||
| 		n.children = append(n.children, new) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	// continue |  | ||||||
| 	new.isLast = false |  | ||||||
| 	err := new.addRoute(splitted[1], handlers) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	n.children = append(n.children, new) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
							
								
								
									
										44
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								go.mod
									
									
									
									
									
								
							| @ -2,4 +2,46 @@ module git.vinchent.xyz/vinchent/go-web | |||||||
|  |  | ||||||
| go 1.22.5 | go 1.22.5 | ||||||
|  |  | ||||||
| require github.com/spf13/cast v1.7.0 | require github.com/gin-gonic/gin v1.10.0 | ||||||
|  |  | ||||||
|  | require ( | ||||||
|  | 	github.com/bytedance/sonic v1.11.6 // indirect | ||||||
|  | 	github.com/bytedance/sonic/loader v0.1.1 // indirect | ||||||
|  | 	github.com/cloudwego/base64x v0.1.4 // indirect | ||||||
|  | 	github.com/cloudwego/iasm v0.2.0 // indirect | ||||||
|  | 	github.com/gabriel-vasile/mimetype v1.4.3 // indirect | ||||||
|  | 	github.com/gin-contrib/sse v0.1.0 // indirect | ||||||
|  | 	github.com/go-playground/locales v0.14.1 // indirect | ||||||
|  | 	github.com/go-playground/universal-translator v0.18.1 // indirect | ||||||
|  | 	github.com/go-playground/validator/v10 v10.20.0 // indirect | ||||||
|  | 	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect | ||||||
|  | 	github.com/goccy/go-json v0.10.2 // indirect | ||||||
|  | 	github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect | ||||||
|  | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
|  | 	github.com/klauspost/cpuid/v2 v2.2.7 // indirect | ||||||
|  | 	github.com/kr/text v0.2.0 // indirect | ||||||
|  | 	github.com/leodido/go-urn v1.4.0 // indirect | ||||||
|  | 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||||
|  | 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||||
|  | 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||||
|  | 	github.com/onsi/ginkgo/v2 v2.9.5 // indirect | ||||||
|  | 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect | ||||||
|  | 	github.com/quic-go/qpack v0.4.0 // indirect | ||||||
|  | 	github.com/quic-go/quic-go v0.43.1 // indirect | ||||||
|  | 	github.com/rogpeppe/go-internal v1.9.0 // indirect | ||||||
|  | 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect | ||||||
|  | 	github.com/ugorji/go/codec v1.2.12 // indirect | ||||||
|  | 	go.uber.org/mock v0.4.0 // indirect | ||||||
|  | 	golang.org/x/arch v0.8.0 // indirect | ||||||
|  | 	golang.org/x/crypto v0.25.0 // indirect | ||||||
|  | 	golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect | ||||||
|  | 	golang.org/x/mod v0.17.0 // indirect | ||||||
|  | 	golang.org/x/net v0.27.0 // indirect | ||||||
|  | 	golang.org/x/sys v0.22.0 // indirect | ||||||
|  | 	golang.org/x/text v0.16.0 // indirect | ||||||
|  | 	golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect | ||||||
|  | 	google.golang.org/protobuf v1.34.1 // indirect | ||||||
|  | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | replace github.com/gin-gonic/gin => ./framework/gin | ||||||
|  | |||||||
							
								
								
									
										127
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										127
									
								
								go.sum
									
									
									
									
									
								
							| @ -1,12 +1,127 @@ | |||||||
| github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= | ||||||
| github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= | ||||||
| github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= | ||||||
| github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= | ||||||
|  | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= | ||||||
|  | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||||||
|  | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | ||||||
|  | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= | ||||||
|  | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= | ||||||
|  | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= | ||||||
|  | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= | ||||||
|  | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||||
|  | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
|  | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= | ||||||
|  | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= | ||||||
|  | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= | ||||||
|  | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= | ||||||
|  | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= | ||||||
|  | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | ||||||
|  | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= | ||||||
|  | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= | ||||||
|  | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= | ||||||
|  | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= | ||||||
|  | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= | ||||||
|  | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= | ||||||
|  | github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= | ||||||
|  | github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= | ||||||
|  | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= | ||||||
|  | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= | ||||||
|  | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= | ||||||
|  | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= | ||||||
|  | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||||||
|  | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||||
|  | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
|  | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
|  | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
|  | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= | ||||||
|  | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||||||
|  | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||||
|  | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | ||||||
|  | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | ||||||
|  | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | ||||||
|  | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= | ||||||
|  | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= | ||||||
|  | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= | ||||||
| github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | ||||||
| github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
|  | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= | ||||||
|  | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= | ||||||
|  | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||||
|  | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||||
|  | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
|  | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | ||||||
|  | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
|  | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= | ||||||
|  | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | ||||||
|  | github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= | ||||||
|  | github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= | ||||||
|  | github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= | ||||||
|  | github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= | ||||||
|  | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
|  | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
|  | github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= | ||||||
|  | github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= | ||||||
|  | github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ= | ||||||
|  | github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= | ||||||
| github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= | 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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= | ||||||
| github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||||
|  | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||||
|  | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= | ||||||
|  | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
|  | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
|  | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
|  | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
|  | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||||
|  | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||||
|  | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||||
|  | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||||
|  | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
|  | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= | ||||||
|  | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= | ||||||
|  | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= | ||||||
|  | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= | ||||||
|  | go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= | ||||||
|  | go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= | ||||||
|  | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | ||||||
|  | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= | ||||||
|  | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= | ||||||
|  | golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= | ||||||
|  | golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= | ||||||
|  | golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= | ||||||
|  | golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= | ||||||
|  | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= | ||||||
|  | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||||
|  | golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= | ||||||
|  | golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= | ||||||
|  | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= | ||||||
|  | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
|  | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= | ||||||
|  | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
|  | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= | ||||||
|  | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= | ||||||
|  | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= | ||||||
|  | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= | ||||||
|  | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= | ||||||
|  | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= | ||||||
|  | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= | ||||||
|  | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= | ||||||
|  | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= | ||||||
|  | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
|  | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= | ||||||
|  | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= | ||||||
|  | |||||||
							
								
								
									
										78
									
								
								handlers.go
									
									
									
									
									
								
							
							
						
						
									
										78
									
								
								handlers.go
									
									
									
									
									
								
							| @ -1,85 +1,29 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" |  | ||||||
| 	"log" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"git.vinchent.xyz/vinchent/go-web/framework" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // func FooControllerHandler(ctx *framework.Context) error { | func UserLoginController(ctx *gin.Context) { | ||||||
| // 	return ctx.WriteJSON(http.StatusOK, map[string]any{ |  | ||||||
| // 		"code": 0, |  | ||||||
| // 	}) |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| func FooControllerHandler(ctx *framework.Context) error { |  | ||||||
| 	durationCtx, cancel := context.WithTimeout(ctx.BaseContext(), time.Duration(1*time.Second)) |  | ||||||
| 	defer cancel() |  | ||||||
|  |  | ||||||
| 	finish := make(chan struct{}, 1) |  | ||||||
| 	panicChan := make(chan interface{}, 1) |  | ||||||
|  |  | ||||||
| 	// some long task |  | ||||||
| 	go func() { |  | ||||||
| 		// Deal with the panic during the work |  | ||||||
| 		defer func() { |  | ||||||
| 			if p := recover(); p != nil { |  | ||||||
| 				panicChan <- p |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
| 		// do the business |  | ||||||
| 		time.Sleep(10 * time.Second) |  | ||||||
| 		ctx.WriteJSON(http.StatusOK, "ok") |  | ||||||
| 		finish <- struct{}{} |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	select { |  | ||||||
| 	case <-panicChan: |  | ||||||
| 		// Protect ResponseWriter for concurrently writing from different |  | ||||||
| 		// goroutines if there are any |  | ||||||
| 		ctx.WriterMux().Lock() |  | ||||||
| 		defer ctx.WriterMux().Unlock() |  | ||||||
| 		log.Println("panicked") |  | ||||||
| 		ctx.WriteJSON(http.StatusInternalServerError, "panicked") |  | ||||||
| 	case <-finish: |  | ||||||
| 		log.Println("finished") |  | ||||||
| 	case <-durationCtx.Done(): |  | ||||||
| 		ctx.WriterMux().Lock() |  | ||||||
| 		defer ctx.WriterMux().Unlock() |  | ||||||
| 		log.Println("Timeout") |  | ||||||
| 		ctx.WriteJSON(http.StatusInternalServerError, "time out") |  | ||||||
| 		ctx.SetHasTimeout() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func UserLoginController(ctx *framework.Context) error { |  | ||||||
| 	time.Sleep(10 * time.Second) | 	time.Sleep(10 * time.Second) | ||||||
| 	ctx.WriteJSON(http.StatusOK, "ok") | 	ctx.JSON(http.StatusOK, "ok") | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func SubjectDelController(ctx *framework.Context) error { | func SubjectDelController(ctx *gin.Context) { | ||||||
| 	ctx.WriteJSON(http.StatusAccepted, "deleted") | 	ctx.JSON(http.StatusAccepted, "deleted") | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func SubjectUpdateController(ctx *framework.Context) error { | func SubjectUpdateController(ctx *gin.Context) { | ||||||
| 	ctx.WriteJSON(http.StatusAccepted, "updated") | 	ctx.JSON(http.StatusAccepted, "updated") | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func SubjectGetController(ctx *framework.Context) error { | func SubjectGetController(ctx *gin.Context) { | ||||||
| 	ctx.WriteJSON(http.StatusAccepted, "got") | 	ctx.JSON(http.StatusAccepted, "got") | ||||||
| 	log.Println(ctx.ParamInt("ID", 0)) |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func SubjectListController(ctx *framework.Context) error { | func SubjectListController(ctx *gin.Context) { | ||||||
| 	ctx.WriteJSON(http.StatusAccepted, "list") | 	ctx.JSON(http.StatusAccepted, "list") | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								main.go
									
									
									
									
									
								
							| @ -10,11 +10,11 @@ import ( | |||||||
| 	"syscall" | 	"syscall" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"git.vinchent.xyz/vinchent/go-web/framework" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
| 	core := framework.NewCore() | 	core := gin.New() | ||||||
| 	registerRouter(core) | 	registerRouter(core) | ||||||
| 	server := &http.Server{ | 	server := &http.Server{ | ||||||
| 		Addr:    ":8080", | 		Addr:    ":8080", | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								routes.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								routes.go
									
									
									
									
									
								
							| @ -1,21 +1,21 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"git.vinchent.xyz/vinchent/go-web/framework" |  | ||||||
| 	"git.vinchent.xyz/vinchent/go-web/framework/middleware" | 	"git.vinchent.xyz/vinchent/go-web/framework/middleware" | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func registerRouter(core *framework.Core) { | func registerRouter(core *gin.Engine) { | ||||||
| 	core.Use(middleware.Test1(), middleware.Test2()) | 	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.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("/:id/test", SubjectGetController) | ||||||
| 		subjectApi.Get("/list/all", SubjectListController) | 		subjectApi.GET("/list/all", SubjectListController) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user