Compare commits

...

6 Commits

Author SHA1 Message Date
ede0b99d3a v0.0.203 2023-07-24 18:38:04 +02:00
d04ce18eb0 v0.0.202 2023-07-24 18:34:56 +02:00
8ae9a0f107 v0.0.201 2023-07-24 18:22:36 +02:00
a259bb6dbc v0.0.200 2023-07-24 17:42:18 +02:00
adf32568ee v0.0.199 2023-07-24 17:23:38 +02:00
0cfa159cb1 v0.0.198 2023-07-24 14:16:02 +02:00
6 changed files with 240 additions and 20 deletions

View File

@@ -47,6 +47,7 @@ var (
TypeBindFailQuery = ErrorType{"BINDFAIL_QUERY", langext.Ptr(400)} TypeBindFailQuery = ErrorType{"BINDFAIL_QUERY", langext.Ptr(400)}
TypeBindFailJSON = ErrorType{"BINDFAIL_JSON", langext.Ptr(400)} TypeBindFailJSON = ErrorType{"BINDFAIL_JSON", langext.Ptr(400)}
TypeBindFailFormData = ErrorType{"BINDFAIL_FORMDATA", langext.Ptr(400)} TypeBindFailFormData = ErrorType{"BINDFAIL_FORMDATA", langext.Ptr(400)}
TypeBindFailHeader = ErrorType{"BINDFAIL_HEADER", langext.Ptr(400)}
TypeUnauthorized = ErrorType{"UNAUTHORIZED", langext.Ptr(401)} TypeUnauthorized = ErrorType{"UNAUTHORIZED", langext.Ptr(401)}
TypeAuthFailed = ErrorType{"AUTH_FAILED", langext.Ptr(401)} TypeAuthFailed = ErrorType{"AUTH_FAILED", langext.Ptr(401)}

View File

@@ -1,8 +1,14 @@
package ginext package ginext
import ( import (
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/mathext"
"net"
"net/http" "net/http"
"strings"
"time" "time"
) )
@@ -13,6 +19,15 @@ type GinWrapper struct {
allowCors bool allowCors bool
ginDebug bool ginDebug bool
requestTimeout time.Duration requestTimeout time.Duration
routeSpecs []ginRouteSpec
}
type ginRouteSpec struct {
Method string
URL string
Middlewares []string
Handler string
} }
func NewEngine(allowCors bool, ginDebug bool, timeout time.Duration) *GinWrapper { func NewEngine(allowCors bool, ginDebug bool, timeout time.Duration) *GinWrapper {
@@ -33,18 +48,94 @@ func NewEngine(allowCors bool, ginDebug bool, timeout time.Duration) *GinWrapper
engine.Use(CorsMiddleware()) engine.Use(CorsMiddleware())
} }
// do not debug-print routes
gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {}
if ginDebug { if ginDebug {
gin.SetMode(gin.ReleaseMode)
ginlogger := gin.Logger() ginlogger := gin.Logger()
engine.Use(func(context *gin.Context) { engine.Use(func(context *gin.Context) {
if !wrapper.SuppressGinLogs { if !wrapper.SuppressGinLogs {
ginlogger(context) ginlogger(context)
} }
}) })
} else {
gin.SetMode(gin.DebugMode)
} }
return wrapper return wrapper
} }
func (w *GinWrapper) ServeHTTP(writer http.ResponseWriter, request *http.Request) { func (w *GinWrapper) ListenAndServeHTTP(addr string, postInit func(port string)) (chan error, *http.Server) {
w.engine.ServeHTTP(writer, request)
w.DebugPrintRoutes()
httpserver := &http.Server{
Addr: addr,
Handler: w.engine,
}
errChan := make(chan error)
go func() {
ln, err := net.Listen("tcp", httpserver.Addr)
if err != nil {
errChan <- err
return
}
_, port, err := net.SplitHostPort(ln.Addr().String())
if err != nil {
errChan <- err
return
}
log.Info().Str("address", httpserver.Addr).Msg("HTTP-Server started on http://localhost:" + port)
if postInit != nil {
postInit(port) // the net.Listener a few lines above is at this point actually already buffering requests
}
errChan <- httpserver.Serve(ln)
}()
return errChan, httpserver
}
func (w *GinWrapper) DebugPrintRoutes() {
if !w.ginDebug {
return
}
lines := make([][4]string, 0)
pad := [4]int{0, 0, 0, 0}
for _, spec := range w.routeSpecs {
line := [4]string{
spec.Method,
spec.URL,
strings.Join(spec.Middlewares, " --> "),
spec.Method,
}
lines = append(lines, line)
pad[0] = mathext.Max(pad[0], len(line[0]))
pad[1] = mathext.Max(pad[1], len(line[1]))
pad[2] = mathext.Max(pad[2], len(line[2]))
pad[3] = mathext.Max(pad[3], len(line[3]))
}
for _, line := range lines {
fmt.Printf("Gin-Route: [%s] @ %s --> %s --> %s\n",
langext.StrPadRight(line[0], " ", pad[0]),
langext.StrPadRight(line[1], " ", pad[1]),
langext.StrPadRight(line[2], " ", pad[2]),
langext.StrPadRight(line[3], " ", pad[3]))
}
} }

View File

@@ -17,6 +17,7 @@ type PreContext struct {
query any query any
body any body any
form any form any
header any
} }
func (pctx *PreContext) URI(uri any) *PreContext { func (pctx *PreContext) URI(uri any) *PreContext {
@@ -39,6 +40,11 @@ func (pctx *PreContext) Form(form any) *PreContext {
return pctx return pctx
} }
func (pctx *PreContext) Header(header any) *PreContext {
pctx.header = header
return pctx
}
func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) { func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
if pctx.uri != nil { if pctx.uri != nil {
if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil { if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil {
@@ -94,6 +100,16 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
} }
} }
if pctx.header != nil {
if err := pctx.ginCtx.ShouldBindHeader(pctx.query); err != nil {
err = exerr.Wrap(err, "Failed to read header").
WithType(exerr.TypeBindFailHeader).
Str("struct_type", fmt.Sprintf("%T", pctx.query)).
Build()
return nil, nil, langext.Ptr(APIError(pctx.ginCtx, err))
}
}
ictx, cancel := context.WithTimeout(context.Background(), pctx.wrapper.requestTimeout) ictx, cancel := context.WithTimeout(context.Background(), pctx.wrapper.requestTimeout)
actx := CreateAppContext(pctx.ginCtx, ictx, cancel) actx := CreateAppContext(pctx.ginCtx, ictx, cancel)

View File

@@ -7,50 +7,93 @@ import (
json "gogs.mikescher.com/BlackForestBytes/goext/gojson" json "gogs.mikescher.com/BlackForestBytes/goext/gojson"
) )
type headerval struct {
Key string
Val string
}
type HTTPResponse interface { type HTTPResponse interface {
Write(g *gin.Context) Write(g *gin.Context)
WithHeader(k string, v string) HTTPResponse
} }
type jsonHTTPResponse struct { type jsonHTTPResponse struct {
statusCode int statusCode int
data any data any
headers []headerval
} }
func (j jsonHTTPResponse) Write(g *gin.Context) { func (j jsonHTTPResponse) Write(g *gin.Context) {
for _, v := range j.headers {
g.Header(v.Key, v.Val)
}
g.Render(j.statusCode, json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true}) g.Render(j.statusCode, json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true})
} }
func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse {
j.headers = append(j.headers, headerval{k, v})
return j
}
type emptyHTTPResponse struct { type emptyHTTPResponse struct {
statusCode int statusCode int
headers []headerval
} }
func (j emptyHTTPResponse) Write(g *gin.Context) { func (j emptyHTTPResponse) Write(g *gin.Context) {
for _, v := range j.headers {
g.Header(v.Key, v.Val)
}
g.Status(j.statusCode) g.Status(j.statusCode)
} }
func (j emptyHTTPResponse) WithHeader(k string, v string) HTTPResponse {
j.headers = append(j.headers, headerval{k, v})
return j
}
type textHTTPResponse struct { type textHTTPResponse struct {
statusCode int statusCode int
data string data string
headers []headerval
} }
func (j textHTTPResponse) Write(g *gin.Context) { func (j textHTTPResponse) Write(g *gin.Context) {
for _, v := range j.headers {
g.Header(v.Key, v.Val)
}
g.String(j.statusCode, "%s", j.data) g.String(j.statusCode, "%s", j.data)
} }
func (j textHTTPResponse) WithHeader(k string, v string) HTTPResponse {
j.headers = append(j.headers, headerval{k, v})
return j
}
type dataHTTPResponse struct { type dataHTTPResponse struct {
statusCode int statusCode int
data []byte data []byte
contentType string contentType string
headers []headerval
} }
func (j dataHTTPResponse) Write(g *gin.Context) { func (j dataHTTPResponse) Write(g *gin.Context) {
for _, v := range j.headers {
g.Header(v.Key, v.Val)
}
g.Data(j.statusCode, j.contentType, j.data) g.Data(j.statusCode, j.contentType, j.data)
} }
func (j dataHTTPResponse) WithHeader(k string, v string) HTTPResponse {
j.headers = append(j.headers, headerval{k, v})
return j
}
type fileHTTPResponse struct { type fileHTTPResponse struct {
mimetype string mimetype string
filepath string filepath string
filename *string filename *string
headers []headerval
} }
func (j fileHTTPResponse) Write(g *gin.Context) { func (j fileHTTPResponse) Write(g *gin.Context) {
@@ -59,26 +102,46 @@ func (j fileHTTPResponse) Write(g *gin.Context) {
g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename)) g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename))
} }
for _, v := range j.headers {
g.Header(v.Key, v.Val)
}
g.File(j.filepath) g.File(j.filepath)
} }
func (j fileHTTPResponse) WithHeader(k string, v string) HTTPResponse {
j.headers = append(j.headers, headerval{k, v})
return j
}
type redirectHTTPResponse struct { type redirectHTTPResponse struct {
statusCode int statusCode int
url string url string
headers []headerval
} }
func (j redirectHTTPResponse) Write(g *gin.Context) { func (j redirectHTTPResponse) Write(g *gin.Context) {
g.Redirect(j.statusCode, j.url) g.Redirect(j.statusCode, j.url)
} }
func (j redirectHTTPResponse) WithHeader(k string, v string) HTTPResponse {
j.headers = append(j.headers, headerval{k, v})
return j
}
type jsonAPIErrResponse struct { type jsonAPIErrResponse struct {
err *exerr.ExErr err *exerr.ExErr
headers []headerval
} }
func (j jsonAPIErrResponse) Write(g *gin.Context) { func (j jsonAPIErrResponse) Write(g *gin.Context) {
j.err.Output(g) j.err.Output(g)
} }
func (j jsonAPIErrResponse) WithHeader(k string, v string) HTTPResponse {
j.headers = append(j.headers, headerval{k, v})
return j
}
func Status(sc int) HTTPResponse { func Status(sc int) HTTPResponse {
return &emptyHTTPResponse{statusCode: sc} return &emptyHTTPResponse{statusCode: sc}
} }

View File

@@ -2,7 +2,11 @@ package ginext
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"net/http" "net/http"
"reflect"
"runtime"
"strings"
) )
var anyMethods = []string{ var anyMethods = []string{
@@ -14,12 +18,13 @@ var anyMethods = []string{
type GinRoutesWrapper struct { type GinRoutesWrapper struct {
wrapper *GinWrapper wrapper *GinWrapper
routes gin.IRouter routes gin.IRouter
defaultHandler []gin.HandlerFunc
} }
type GinRouteBuilder struct { type GinRouteBuilder struct {
routes *GinRoutesWrapper routes *GinRoutesWrapper
methods []string method string
relPath string relPath string
handlers []gin.HandlerFunc handlers []gin.HandlerFunc
} }
@@ -29,43 +34,49 @@ func (w *GinWrapper) Routes() *GinRoutesWrapper {
} }
func (w *GinRoutesWrapper) Group(relativePath string) *GinRoutesWrapper { func (w *GinRoutesWrapper) Group(relativePath string) *GinRoutesWrapper {
return &GinRoutesWrapper{wrapper: w.wrapper, routes: w.routes.Group(relativePath)} return &GinRoutesWrapper{wrapper: w.wrapper, routes: w.routes.Group(relativePath), defaultHandler: langext.ArrCopy(w.defaultHandler)}
}
func (w *GinRoutesWrapper) Use(middleware ...gin.HandlerFunc) *GinRoutesWrapper {
defHandler := langext.ArrCopy(w.defaultHandler)
defHandler = append(defHandler, middleware...)
return &GinRoutesWrapper{wrapper: w.wrapper, routes: w.routes, defaultHandler: defHandler}
} }
func (w *GinRoutesWrapper) GET(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) GET(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodGet}, relPath: relativePath, handlers: make([]gin.HandlerFunc, 0)} return &GinRouteBuilder{routes: w, method: http.MethodGet, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) POST(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) POST(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodPost}, relPath: relativePath, handlers: make([]gin.HandlerFunc, 0)} return &GinRouteBuilder{routes: w, method: http.MethodPost, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) DELETE(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) DELETE(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodDelete}, relPath: relativePath, handlers: make([]gin.HandlerFunc, 0)} return &GinRouteBuilder{routes: w, method: http.MethodDelete, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) PATCH(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) PATCH(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodPatch}, relPath: relativePath, handlers: make([]gin.HandlerFunc, 0)} return &GinRouteBuilder{routes: w, method: http.MethodPatch, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) PUT(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) PUT(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodPut}, relPath: relativePath, handlers: make([]gin.HandlerFunc, 0)} return &GinRouteBuilder{routes: w, method: http.MethodPut, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) OPTIONS(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) OPTIONS(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodOptions}, relPath: relativePath, handlers: make([]gin.HandlerFunc, 0)} return &GinRouteBuilder{routes: w, method: http.MethodOptions, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) HEAD(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) HEAD(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{http.MethodHead}, relPath: relativePath, handlers: make([]gin.HandlerFunc, 0)} return &GinRouteBuilder{routes: w, method: http.MethodHead, relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) COUNT(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) COUNT(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: []string{"COUNT"}, relPath: relativePath, handlers: make([]gin.HandlerFunc, 0)} return &GinRouteBuilder{routes: w, method: "COUNT", relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRoutesWrapper) Any(relativePath string) *GinRouteBuilder { func (w *GinRoutesWrapper) Any(relativePath string) *GinRouteBuilder {
return &GinRouteBuilder{routes: w, methods: anyMethods, relPath: relativePath, handlers: make([]gin.HandlerFunc, 0)} return &GinRouteBuilder{routes: w, method: "*", relPath: relativePath, handlers: langext.ArrCopy(w.defaultHandler)}
} }
func (w *GinRouteBuilder) Use(middleware ...gin.HandlerFunc) *GinRouteBuilder { func (w *GinRouteBuilder) Use(middleware ...gin.HandlerFunc) *GinRouteBuilder {
@@ -74,12 +85,50 @@ func (w *GinRouteBuilder) Use(middleware ...gin.HandlerFunc) *GinRouteBuilder {
} }
func (w *GinRouteBuilder) Handle(handler WHandlerFunc) { func (w *GinRouteBuilder) Handle(handler WHandlerFunc) {
middlewareNames := langext.ArrMap(w.handlers, func(v gin.HandlerFunc) string { return nameOfFunction(v) })
handlerName := nameOfFunction(handler)
w.handlers = append(w.handlers, Wrap(w.routes.wrapper, handler)) w.handlers = append(w.handlers, Wrap(w.routes.wrapper, handler))
for _, m := range w.methods {
w.routes.routes.Handle(m, w.relPath, w.handlers...) methodName := w.method
if w.method == "*" {
methodName = "ANY"
for _, method := range anyMethods {
w.routes.routes.Handle(method, w.relPath, w.handlers...)
} }
} else {
w.routes.routes.Handle(w.method, w.relPath, w.handlers...)
}
w.routes.wrapper.routeSpecs = append(w.routes.wrapper.routeSpecs, ginRouteSpec{
Method: methodName,
URL: w.relPath,
Middlewares: middlewareNames,
Handler: handlerName,
})
} }
func (w *GinWrapper) NoRoute(handler WHandlerFunc) { func (w *GinWrapper) NoRoute(handler WHandlerFunc) {
w.engine.NoRoute(Wrap(w, handler)) w.engine.NoRoute(Wrap(w, handler))
w.routeSpecs = append(w.routeSpecs, ginRouteSpec{
Method: "ANY",
URL: "[NO_ROUTE]",
Middlewares: nil,
Handler: nameOfFunction(handler),
})
}
func nameOfFunction(f any) string {
n := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
split := strings.Split(n, "/")
if len(split) == 0 {
return ""
}
return split[len(split)-1]
} }

View File

@@ -1,5 +1,5 @@
package goext package goext
const GoextVersion = "0.0.197" const GoextVersion = "0.0.203"
const GoextVersionTimestamp = "2023-07-24T12:27:06+0200" const GoextVersionTimestamp = "2023-07-24T18:38:04+0200"