GoWeb: Remove everything to use Gin
This commit is contained in:
parent
c5ab1debdb
commit
722e8191a9
@ -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,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 (
|
||||
"net/http"
|
||||
|
||||
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Recovery is a middleware that recovers from the panic
|
||||
func Recovery() framework.ControllerHandler {
|
||||
return func(c *framework.Context) error {
|
||||
func Recovery() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
c.WriteJSON(http.StatusInternalServerError, err)
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -3,32 +3,29 @@ package middleware
|
||||
import (
|
||||
"log"
|
||||
|
||||
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Test1() framework.ControllerHandler {
|
||||
return func(c *framework.Context) error {
|
||||
func Test1() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
log.Println("middleware test1 pre")
|
||||
c.Next()
|
||||
log.Println("middleware test1 post")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func Test2() framework.ControllerHandler {
|
||||
return func(c *framework.Context) error {
|
||||
func Test2() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
log.Println("middleware test2 pre")
|
||||
c.Next()
|
||||
log.Println("middleware test2 post")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func Test3() framework.ControllerHandler {
|
||||
return func(c *framework.Context) error {
|
||||
func Test3() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
log.Println("middleware test3 pre")
|
||||
c.Next()
|
||||
log.Println("middleware test3 post")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,15 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Timeout(d time.Duration) framework.ControllerHandler {
|
||||
return func(c *framework.Context) error {
|
||||
func Timeout(d time.Duration) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
finish := make(chan struct{}, 1)
|
||||
panicChan := make(chan interface{}, 1)
|
||||
|
||||
durationCtx, cancel := context.WithTimeout(c.BaseContext(), d)
|
||||
durationCtx, cancel := context.WithTimeout(c.Request.Context(), d)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
@ -35,15 +35,12 @@ func Timeout(d time.Duration) framework.ControllerHandler {
|
||||
case p := <-panicChan:
|
||||
// panic
|
||||
log.Println(p)
|
||||
c.GetResponseWriter().WriteHeader(http.StatusInternalServerError)
|
||||
c.Status(http.StatusInternalServerError)
|
||||
case <-finish:
|
||||
// finish normally
|
||||
log.Println("finish")
|
||||
case <-durationCtx.Done():
|
||||
c.SetHasTimeout()
|
||||
c.GetResponseWriter().WriteHeader(http.StatusRequestTimeout)
|
||||
c.GetResponseWriter().Write([]byte("time out"))
|
||||
c.JSON(http.StatusRequestTimeout, "time out")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -8,30 +8,28 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
t.Run("Test timeout handler", func(t *testing.T) {
|
||||
timeoutHandler := Timeout(1 * time.Millisecond)
|
||||
|
||||
longHandler := func(c *framework.Context) error {
|
||||
longHandler := func(c *gin.Context) {
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
res := prepareMiddlewareTest(t, timeoutHandler, longHandler)
|
||||
|
||||
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) {
|
||||
timeoutHandler := Timeout(2 * time.Millisecond)
|
||||
|
||||
quickHandler := func(c *framework.Context) error {
|
||||
quickHandler := func(c *gin.Context) {
|
||||
// time.Sleep(1 * time.Millisecond)
|
||||
c.WriteJSON(http.StatusOK, "ok")
|
||||
return nil
|
||||
c.JSON(http.StatusOK, "ok")
|
||||
}
|
||||
|
||||
res := prepareMiddlewareTest(t, timeoutHandler, quickHandler)
|
||||
@ -44,7 +42,7 @@ func TestRecover(t *testing.T) {
|
||||
t.Run("Test panic", func(t *testing.T) {
|
||||
recoverer := Recovery()
|
||||
|
||||
panicHandler := func(c *framework.Context) error {
|
||||
panicHandler := func(c *gin.Context) {
|
||||
panic("panic")
|
||||
}
|
||||
|
||||
@ -55,9 +53,8 @@ func TestRecover(t *testing.T) {
|
||||
t.Run("Test no panic", func(t *testing.T) {
|
||||
recoverer := Recovery()
|
||||
|
||||
normalHandler := func(c *framework.Context) error {
|
||||
c.WriteJSON(http.StatusOK, "ok")
|
||||
return nil
|
||||
normalHandler := func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, "ok")
|
||||
}
|
||||
|
||||
res := prepareMiddlewareTest(t, recoverer, normalHandler)
|
||||
@ -68,20 +65,18 @@ func TestRecover(t *testing.T) {
|
||||
|
||||
func prepareMiddlewareTest(
|
||||
t testing.TB,
|
||||
mid framework.ControllerHandler,
|
||||
in framework.ControllerHandler,
|
||||
mid gin.HandlerFunc,
|
||||
in gin.HandlerFunc,
|
||||
) *http.Response {
|
||||
t.Helper()
|
||||
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.ServeHTTP(response, request)
|
||||
|
||||
res := response.Result()
|
||||
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
|
||||
}
|
34
go.mod
34
go.mod
@ -2,4 +2,36 @@ module git.vinchent.xyz/vinchent/go-web
|
||||
|
||||
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/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/kr/pretty v0.3.1 // 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/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.25.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
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
96
go.sum
96
go.sum
@ -1,12 +1,96 @@
|
||||
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/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
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/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
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/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
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/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/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/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/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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
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/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=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.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=
|
||||
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/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
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=
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// func FooControllerHandler(ctx *framework.Context) error {
|
||||
// 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 {
|
||||
func UserLoginController(ctx *gin.Context) {
|
||||
time.Sleep(10 * time.Second)
|
||||
ctx.WriteJSON(http.StatusOK, "ok")
|
||||
return nil
|
||||
ctx.JSON(http.StatusOK, "ok")
|
||||
}
|
||||
|
||||
func SubjectDelController(ctx *framework.Context) error {
|
||||
ctx.WriteJSON(http.StatusAccepted, "deleted")
|
||||
return nil
|
||||
func SubjectDelController(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusAccepted, "deleted")
|
||||
}
|
||||
|
||||
func SubjectUpdateController(ctx *framework.Context) error {
|
||||
ctx.WriteJSON(http.StatusAccepted, "updated")
|
||||
return nil
|
||||
func SubjectUpdateController(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusAccepted, "updated")
|
||||
}
|
||||
|
||||
func SubjectGetController(ctx *framework.Context) error {
|
||||
ctx.WriteJSON(http.StatusAccepted, "got")
|
||||
log.Println(ctx.ParamInt("ID", 0))
|
||||
return nil
|
||||
func SubjectGetController(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusAccepted, "got")
|
||||
}
|
||||
|
||||
func SubjectListController(ctx *framework.Context) error {
|
||||
ctx.WriteJSON(http.StatusAccepted, "list")
|
||||
return nil
|
||||
func SubjectListController(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusAccepted, "list")
|
||||
}
|
||||
|
4
main.go
4
main.go
@ -10,11 +10,11 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
core := framework.NewCore()
|
||||
core := gin.New()
|
||||
registerRouter(core)
|
||||
server := &http.Server{
|
||||
Addr: ":8080",
|
||||
|
16
routes.go
16
routes.go
@ -1,21 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.vinchent.xyz/vinchent/go-web/framework"
|
||||
"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.Get("/user/login", UserLoginController)
|
||||
core.GET("/user/login", UserLoginController)
|
||||
|
||||
subjectApi := core.Group("/subject")
|
||||
{
|
||||
subjectApi.Use(middleware.Test3())
|
||||
subjectApi.Delete("/:id", SubjectDelController)
|
||||
subjectApi.Put("/:id", SubjectUpdateController)
|
||||
subjectApi.Get("/:id", SubjectGetController)
|
||||
subjectApi.Get("/:id/test", SubjectGetController)
|
||||
subjectApi.Get("/list/all", SubjectListController)
|
||||
subjectApi.DELETE("/:id", SubjectDelController)
|
||||
subjectApi.PUT("/:id", SubjectUpdateController)
|
||||
subjectApi.GET("/:id", SubjectGetController)
|
||||
subjectApi.GET("/:id/test", SubjectGetController)
|
||||
subjectApi.GET("/list/all", SubjectListController)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user