Compare commits

...

2 Commits

Author SHA1 Message Date
Muyao CHEN
5f3074e9bf Araneae: Get Gin 2024-09-28 11:13:18 +02:00
Muyao CHEN
c5ab1debdb stop: use graceful shutdown for 5s 2024-09-26 23:05:05 +02:00
18 changed files with 224 additions and 1138 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "framework/gin"]
path = framework/gin
url = gitea@git.vinchent.xyz:vinchent/gin.git

View File

@ -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
}
// }}}

View File

@ -1,3 +0,0 @@
package framework
type ControllerHandler func(c *Context) error

View File

@ -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

@ -0,0 +1 @@
Subproject commit f05f966a0824b1d302ee556183e2579c91954266

View File

@ -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...)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
// }}}

View File

@ -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
}

View File

@ -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
View File

@ -2,4 +2,46 @@ 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/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
View File

@ -1,12 +1,127 @@
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/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/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/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/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.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=

View File

@ -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
func UserLoginController(ctx *gin.Context) {
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
ctx.JSON(http.StatusOK, "ok")
}
func UserLoginController(ctx *framework.Context) error {
time.Sleep(10 * time.Second)
ctx.WriteJSON(http.StatusOK, "ok")
return nil
func SubjectDelController(ctx *gin.Context) {
ctx.JSON(http.StatusAccepted, "deleted")
}
func SubjectDelController(ctx *framework.Context) error {
ctx.WriteJSON(http.StatusAccepted, "deleted")
return nil
func SubjectUpdateController(ctx *gin.Context) {
ctx.JSON(http.StatusAccepted, "updated")
}
func SubjectUpdateController(ctx *framework.Context) error {
ctx.WriteJSON(http.StatusAccepted, "updated")
return nil
func SubjectGetController(ctx *gin.Context) {
ctx.JSON(http.StatusAccepted, "got")
}
func SubjectGetController(ctx *framework.Context) error {
ctx.WriteJSON(http.StatusAccepted, "got")
log.Println(ctx.ParamInt("ID", 0))
return nil
}
func SubjectListController(ctx *framework.Context) error {
ctx.WriteJSON(http.StatusAccepted, "list")
return nil
func SubjectListController(ctx *gin.Context) {
ctx.JSON(http.StatusAccepted, "list")
}

View File

@ -8,12 +8,13 @@ import (
"os"
"os/signal"
"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",
@ -33,7 +34,9 @@ func main() {
<-quit
fmt.Println("YOLO")
if err := server.Shutdown(context.Background()); err != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal("server shutdown: ", err)
}
}

View File

@ -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)
}
}