Compare commits

..

7 Commits

Author SHA1 Message Date
1e66a221e0 feat: add RenderHTML, Redirect 2024-10-25 10:23:52 +08:00
df318682fa fix: nfctl new(project name include '/') 2024-09-22 20:50:19 -07:00
af1e58bce9 update: add resp 418 2024-09-19 01:43:05 -07:00
940e86bd8d chore: update go module, readme 2024-08-27 14:39:24 +08:00
5263cba44a chore: expose default logger
chore: add ctx alias method
2024-08-12 16:14:16 +08:00
63f7516667 fix: c.Writer 2024-07-25 17:23:53 +08:00
9b7f1e4413 chore: X-Trace-Id
fix req context trace id; default logger with trace-id

ci: fix nfctl run
2024-07-17 23:00:45 +08:00
8 changed files with 154 additions and 94 deletions

View File

@ -2,10 +2,10 @@ name: Auto Build
on: on:
push: push:
branches: branches:
- 'master' - 'release/nfctl/*'
env: env:
RELEASE_VERSION: v24.07.14-r3 RELEASE_VERSION: v24.09.23-r1
jobs: jobs:
build-job: build-job:

41
ctx.go
View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/loveuer/nf/internal/sse" "github.com/loveuer/nf/internal/sse"
"html/template"
"io" "io"
"mime/multipart" "mime/multipart"
"net" "net"
@ -51,11 +52,10 @@ func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ct
} }
c := context.WithValue(request.Context(), TraceKey, traceId) c := context.WithValue(request.Context(), TraceKey, traceId)
request.WithContext(c)
ctx := &Ctx{ ctx := &Ctx{
lock: sync.Mutex{}, lock: sync.Mutex{},
Request: request, Request: request.WithContext(c),
path: request.URL.Path, path: request.URL.Path,
method: request.Method, method: request.Method,
StatusCode: 200, StatusCode: 200,
@ -75,6 +75,7 @@ func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ct
} }
ctx.Writer = &ctx.writermem ctx.Writer = &ctx.writermem
ctx.writermem.Header().Set(TraceKey, traceId)
return ctx return ctx
} }
@ -289,23 +290,30 @@ func (c *Ctx) Status(code int) *Ctx {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
c.writermem.WriteHeader(code) c.Writer.WriteHeader(code)
c.StatusCode = c.writermem.status c.StatusCode = c.writermem.status
return c return c
} }
// Set set response header
func (c *Ctx) Set(key string, value string) { func (c *Ctx) Set(key string, value string) {
c.writermem.Header().Set(key, value) c.Writer.Header().Set(key, value)
} }
// AddHeader add response header
func (c *Ctx) AddHeader(key string, value string) {
c.Writer.Header().Add(key, value)
}
// SetHeader set response header
func (c *Ctx) SetHeader(key string, value string) { func (c *Ctx) SetHeader(key string, value string) {
c.writermem.Header().Set(key, value) c.Writer.Header().Set(key, value)
} }
func (c *Ctx) SendStatus(code int) error { func (c *Ctx) SendStatus(code int) error {
c.Status(code) c.Status(code)
c.writermem.WriteHeaderNow() c.Writer.WriteHeaderNow()
return nil return nil
} }
@ -323,7 +331,7 @@ func (c *Ctx) Writef(format string, values ...interface{}) (int, error) {
func (c *Ctx) JSON(data interface{}) error { func (c *Ctx) JSON(data interface{}) error {
c.SetHeader("Content-Type", MIMEApplicationJSON) c.SetHeader("Content-Type", MIMEApplicationJSON)
encoder := json.NewEncoder(&c.writermem) encoder := json.NewEncoder(c.Writer)
if err := encoder.Encode(data); err != nil { if err := encoder.Encode(data); err != nil {
return err return err
@ -355,6 +363,21 @@ func (c *Ctx) HTML(html string) error {
return err return err
} }
func (c *Ctx) Write(data []byte) (int, error) { func (c *Ctx) RenderHTML(name, html string, obj any) error {
return c.writermem.Write(data) c.SetHeader("Content-Type", "text/html")
t, err := template.New(name).Parse(html)
if err != nil {
return err
}
return t.Execute(c.Writer, obj)
}
func (c *Ctx) Redirect(url string, code int) error {
http.Redirect(c.Writer, c.Request, url, code)
return nil
}
func (c *Ctx) Write(data []byte) (int, error) {
return c.Writer.Write(data)
} }

View File

@ -2,11 +2,9 @@ package nf
import ( import (
"fmt" "fmt"
"github.com/google/uuid"
"github.com/loveuer/nf/nft/log" "github.com/loveuer/nf/nft/log"
"os" "os"
"runtime/debug" "runtime/debug"
"strings"
"time" "time"
) )
@ -29,33 +27,19 @@ func NewRecover(enableStackTrace bool) HandlerFunc {
} }
} }
func NewLogger(traceHeader ...string) HandlerFunc { func NewLogger() HandlerFunc {
Header := "X-Trace-ID"
if len(traceHeader) > 0 && traceHeader[0] != "" {
Header = traceHeader[0]
}
return func(c *Ctx) error { return func(c *Ctx) error {
var ( var (
now = time.Now() now = time.Now()
trace = c.Get(Header)
logFn func(msg string, data ...any) logFn func(msg string, data ...any)
ip = c.IP() ip = c.IP()
) )
if trace == "" {
trace = uuid.Must(uuid.NewV7()).String()
}
c.SetHeader(Header, trace)
traces := strings.Split(trace, "-")
shortTrace := traces[len(traces)-1]
err := c.Next() err := c.Next()
duration := time.Since(now) duration := time.Since(now)
msg := fmt.Sprintf("NF | %s | %15s | %3d | %s | %6s | %s", shortTrace, ip, c.StatusCode, HumanDuration(duration.Nanoseconds()), c.Method(), c.Path()) msg := fmt.Sprintf("NF | %v | %15s | %3d | %s | %6s | %s", c.Context().Value(TraceKey), ip, c.StatusCode, HumanDuration(duration.Nanoseconds()), c.Method(), c.Path())
switch { switch {
case c.StatusCode >= 500: case c.StatusCode >= 500:

View File

@ -21,7 +21,7 @@ var (
os.Exit(1) os.Exit(1)
} }
defaultLogger = &logger{ DefaultLogger = &logger{
Mutex: sync.Mutex{}, Mutex: sync.Mutex{},
timeFormat: "2006-01-02T15:04:05", timeFormat: "2006-01-02T15:04:05",
writer: os.Stdout, writer: os.Stdout,
@ -36,32 +36,32 @@ var (
) )
func SetTimeFormat(format string) { func SetTimeFormat(format string) {
defaultLogger.SetTimeFormat(format) DefaultLogger.SetTimeFormat(format)
} }
func SetLogLevel(level LogLevel) { func SetLogLevel(level LogLevel) {
defaultLogger.SetLogLevel(level) DefaultLogger.SetLogLevel(level)
} }
func Debug(msg string, data ...any) { func Debug(msg string, data ...any) {
defaultLogger.Debug(msg, data...) DefaultLogger.Debug(msg, data...)
} }
func Info(msg string, data ...any) { func Info(msg string, data ...any) {
defaultLogger.Info(msg, data...) DefaultLogger.Info(msg, data...)
} }
func Warn(msg string, data ...any) { func Warn(msg string, data ...any) {
defaultLogger.Warn(msg, data...) DefaultLogger.Warn(msg, data...)
} }
func Error(msg string, data ...any) { func Error(msg string, data ...any) {
defaultLogger.Error(msg, data...) DefaultLogger.Error(msg, data...)
} }
func Panic(msg string, data ...any) { func Panic(msg string, data ...any) {
defaultLogger.Panic(msg, data...) DefaultLogger.Panic(msg, data...)
} }
func Fatal(msg string, data ...any) { func Fatal(msg string, data ...any) {
defaultLogger.Fatal(msg, data...) DefaultLogger.Fatal(msg, data...)
} }

View File

@ -3,15 +3,16 @@ package cmd
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/url"
"os"
"path"
"github.com/loveuer/nf/nft/log" "github.com/loveuer/nf/nft/log"
"github.com/loveuer/nf/nft/nfctl/clone" "github.com/loveuer/nf/nft/nfctl/clone"
"github.com/loveuer/nf/nft/nfctl/opt" "github.com/loveuer/nf/nft/nfctl/opt"
"github.com/loveuer/nf/nft/nfctl/tp" "github.com/loveuer/nf/nft/nfctl/tp"
"github.com/loveuer/nf/nft/nfctl/version" "github.com/loveuer/nf/nft/nfctl/version"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"net/url"
"os"
"path"
) )
var ( var (
@ -44,6 +45,7 @@ func initNew() {
err error err error
urlIns *url.URL urlIns *url.URL
pwd string pwd string
moduleName string
projectDir string projectDir string
initBs []byte initBs []byte
renderBs []byte renderBs []byte
@ -58,7 +60,8 @@ func initNew() {
return fmt.Errorf("get work dir err") return fmt.Errorf("get work dir err")
} }
projectDir = path.Join(pwd, args[0]) moduleName = args[0]
projectDir = path.Join(pwd, path.Base(args[0]))
if _, err = os.Stat(projectDir); !errors.Is(err, os.ErrNotExist) { if _, err = os.Stat(projectDir); !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("project folder already exist") return fmt.Errorf("project folder already exist")
@ -101,7 +104,8 @@ func initNew() {
} }
if renderBs, err = tp.RenderVar(initBs, map[string]any{ if renderBs, err = tp.RenderVar(initBs, map[string]any{
"PROJECT_NAME": args[0], "PROJECT_NAME": projectDir,
"MODULE_NAME": moduleName,
}); err != nil { }); err != nil {
return fmt.Errorf("render template init script err: %v", err) return fmt.Errorf("render template init script err: %v", err)
} }

View File

@ -2,9 +2,10 @@ package resp
import ( import (
"fmt" "fmt"
"github.com/loveuer/nf"
"strconv" "strconv"
"strings" "strings"
"github.com/loveuer/nf"
) )
func handleEmptyMsg(status uint32, msg string) string { func handleEmptyMsg(status uint32, msg string) string {
@ -102,6 +103,18 @@ func Resp403(c *nf.Ctx, data any, msgs ...string) error {
return Resp(c, 403, msg, err, data) return Resp(c, 403, msg, err, data)
} }
func Resp418(c *nf.Ctx, data any, msgs ...string) error {
msg := MSG418
err := ""
if len(msgs) > 0 && msgs[0] != "" {
msg = fmt.Sprintf("%s: %s", msg, strings.Join(msgs, "; "))
err = ""
}
return Resp(c, 418, msg, err, data)
}
func Resp429(c *nf.Ctx, data any, msgs ...string) error { func Resp429(c *nf.Ctx, data any, msgs ...string) error {
msg := MSG429 msg := MSG429
err := "" err := ""

View File

@ -7,6 +7,7 @@ const (
MSG401 = "登录已过期, 请重新登录" MSG401 = "登录已过期, 请重新登录"
MSG403 = "请求权限不足" MSG403 = "请求权限不足"
MSG404 = "请求资源未找到" MSG404 = "请求资源未找到"
MSG418 = "请求条件不满足, 请稍后再试"
MSG429 = "请求过于频繁, 请稍后再试" MSG429 = "请求过于频繁, 请稍后再试"
MSG500 = "服务器开小差了, 请稍后再试" MSG500 = "服务器开小差了, 请稍后再试"
MSG501 = "功能开发中, 尽情期待" MSG501 = "功能开发中, 尽情期待"

View File

@ -5,8 +5,9 @@
##### basic usage ##### basic usage
- get param - get param
```go
func main() { ```go
func main() {
app := nf.New() app := nf.New()
app.Get("/hello/:name", func(c *nf.Ctx) error { app.Get("/hello/:name", func(c *nf.Ctx) error {
@ -15,12 +16,13 @@ func main() {
}) })
log.Fatal(app.Run("0.0.0.0:80")) log.Fatal(app.Run("0.0.0.0:80"))
} }
``` ```
- parse request query - parse request query
```go
func handleQuery(c *nf.Ctx) error { ```go
func handleQuery(c *nf.Ctx) error {
type Req struct { type Req struct {
Name string `query:"name"` Name string `query:"name"`
Addr []string `query:"addr"` Addr []string `query:"addr"`
@ -36,12 +38,13 @@ func handleQuery(c *nf.Ctx) error {
} }
return c.JSON(nf.Map{"query": req}) return c.JSON(nf.Map{"query": req})
} }
``` ```
- parse application/json body - parse application/json body
```go
func handlePost(c *nf.Ctx) error { ```go
func handlePost(c *nf.Ctx) error {
type Req struct { type Req struct {
Name string `json:"name"` Name string `json:"name"`
Addr []string `json:"addr"` Addr []string `json:"addr"`
@ -63,5 +66,37 @@ func handlePost(c *nf.Ctx) error {
} }
return c.JSON(nf.Map{"struct": req, "map": reqMap}) return c.JSON(nf.Map{"struct": req, "map": reqMap})
} }
``` ```
- pass local value
```go
type User struct {
Id int
Username string
}
func main() {
app := nf.New()
app.Use(auth())
app.Get("/item/list", list)
}
func auth() nf.HandlerFunc {
return func(c *nf.Ctx) error {
c.Locals("user", &User{Id: 1, Username:"user"})
return c.Next()
}
}
func list(c *nf.Ctx) error {
user, ok := c.Locals("user").(*User)
if !ok {
return c.Status(401).SendString("login required")
}
...
}
```