feat: allocate ctx by pool

This commit is contained in:
loveuer 2024-12-16 22:50:51 +08:00
parent 1e66a221e0
commit d8d771aec6
5 changed files with 81 additions and 72 deletions

39
app.go
View File

@ -5,13 +5,15 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"github.com/loveuer/nf/internal/bytesconv"
"io" "io"
"log" "log"
"net" "net"
"net/http" "net/http"
"path" "path"
"regexp" "regexp"
"sync"
"github.com/loveuer/nf/internal/bytesconv"
) )
var ( var (
@ -29,6 +31,8 @@ type App struct {
trees methodTrees trees methodTrees
pool *sync.Pool
maxParams uint16 maxParams uint16
maxSections uint16 maxSections uint16
@ -40,13 +44,34 @@ type App struct {
removeExtraSlash bool // false removeExtraSlash bool // false
} }
func (a *App) allocateContext() *Ctx {
var (
skippedNodes = make([]skippedNode, 0, a.maxSections)
v = make(Params, 0, a.maxParams)
)
ctx := Ctx{
lock: sync.Mutex{},
app: a,
index: -1,
locals: make(map[string]any),
handlers: make([]HandlerFunc, 0),
skippedNodes: &skippedNodes,
params: &v,
}
return &ctx
}
func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) { func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
var ( var (
err error err error
c = newContext(a, writer, request) c = a.pool.Get().(*Ctx)
nfe = new(Err) nfe = new(Err)
) )
c.reset(writer, request)
if err = c.verify(); err != nil { if err = c.verify(); err != nil {
if errors.As(err, nfe) { if errors.As(err, nfe) {
_ = c.Status(nfe.Status).SendString(nfe.Msg) _ = c.Status(nfe.Status).SendString(nfe.Msg)
@ -58,6 +83,8 @@ func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
} }
a.handleHTTPRequest(c) a.handleHTTPRequest(c)
a.pool.Put(c)
} }
func (a *App) run(ln net.Listener) error { func (a *App) run(ln net.Listener) error {
@ -137,9 +164,7 @@ func (a *App) addRoute(method, path string, handlers ...HandlerFunc) {
} }
func (a *App) handleHTTPRequest(c *Ctx) { func (a *App) handleHTTPRequest(c *Ctx) {
var ( var err error
err error
)
httpMethod := c.Request.Method httpMethod := c.Request.Method
rPath := c.Request.URL.Path rPath := c.Request.URL.Path
@ -263,7 +288,7 @@ func redirectFixedPath(c *Ctx, root *node, trailingSlash bool) bool {
func redirectRequest(c *Ctx) { func redirectRequest(c *Ctx) {
req := c.Request req := c.Request
//rPath := req.URL.Path // rPath := req.URL.Path
rURL := req.URL.String() rURL := req.URL.String()
code := http.StatusMovedPermanently // Permanent redirect, request with GET method code := http.StatusMovedPermanently // Permanent redirect, request with GET method
@ -271,7 +296,7 @@ func redirectRequest(c *Ctx) {
code = http.StatusTemporaryRedirect code = http.StatusTemporaryRedirect
} }
//debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL) // debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
http.Redirect(c.Writer, req, rURL, code) http.Redirect(c.Writer, req, rURL, code)
c.writermem.WriteHeaderNow() c.writermem.WriteHeaderNow()

63
ctx.go
View File

@ -6,8 +6,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/google/uuid"
"github.com/loveuer/nf/internal/sse"
"html/template" "html/template"
"io" "io"
"mime/multipart" "mime/multipart"
@ -15,11 +13,12 @@ import (
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
"github.com/google/uuid"
"github.com/loveuer/nf/internal/sse"
) )
var ( var forwardHeaders = []string{"CF-Connecting-IP", "X-Forwarded-For", "X-Real-Ip"}
forwardHeaders = []string{"CF-Connecting-IP", "X-Forwarded-For", "X-Real-Ip"}
)
type Ctx struct { type Ctx struct {
lock sync.Mutex lock sync.Mutex
@ -39,45 +38,29 @@ type Ctx struct {
fullPath string fullPath string
} }
func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ctx { func (c *Ctx) reset(w http.ResponseWriter, r *http.Request) {
traceId := r.Header.Get(TraceKey)
var ( if traceId == "" {
traceId string
skippedNodes = make([]skippedNode, 0, app.maxSections)
v = make(Params, 0, app.maxParams)
)
if traceId = request.Header.Get(TraceKey); traceId == "" {
traceId = uuid.Must(uuid.NewV7()).String() traceId = uuid.Must(uuid.NewV7()).String()
} }
c := context.WithValue(request.Context(), TraceKey, traceId) c.writermem.reset(w)
ctx := &Ctx{ c.Request = r.WithContext(context.WithValue(r.Context(), TraceKey, traceId))
lock: sync.Mutex{}, c.Writer = &c.writermem
Request: request.WithContext(c), c.handlers = nil
path: request.URL.Path, c.index = -1
method: request.Method, c.path = r.URL.Path
StatusCode: 200, c.method = r.Method
c.StatusCode = 200
app: app, c.fullPath = ""
index: -1, *c.params = (*c.params)[:0]
locals: map[string]interface{}{}, *c.skippedNodes = (*c.skippedNodes)[:0]
handlers: make([]HandlerFunc, 0), for key := range c.locals {
skippedNodes: &skippedNodes, delete(c.locals, key)
params: &v,
} }
c.writermem.Header().Set(TraceKey, traceId)
ctx.writermem = responseWriter{
ResponseWriter: writer,
size: -1,
status: 0,
}
ctx.Writer = &ctx.writermem
ctx.writermem.Header().Set(TraceKey, traceId)
return ctx
} }
func (c *Ctx) Locals(key string, value ...interface{}) interface{} { func (c *Ctx) Locals(key string, value ...interface{}) interface{} {
@ -109,9 +92,7 @@ func (c *Ctx) Path(overWrite ...string) string {
} }
func (c *Ctx) Cookies(key string, defaultValue ...string) string { func (c *Ctx) Cookies(key string, defaultValue ...string) string {
var ( dv := ""
dv = ""
)
if len(defaultValue) > 0 { if len(defaultValue) > 0 {
dv = defaultValue[0] dv = defaultValue[0]

38
nf.go
View File

@ -1,5 +1,7 @@
package nf package nf
import "sync"
const ( const (
banner = " _ _ _ ___ _ \n | \\| |___| |_ | __|__ _ _ _ _ __| |\n | .` / _ \\ _| | _/ _ \\ || | ' \\/ _` |\n |_|\\_\\___/\\__| |_|\\___/\\_,_|_||_\\__,_|\n " banner = " _ _ _ ___ _ \n | \\| |___| |_ | __|__ _ _ _ _ __| |\n | .` / _ \\ _| | _/ _ \\ || | ' \\/ _` |\n |_|\\_\\___/\\__| |_|\\___/\\_,_|_||_\\__,_|\n "
_404 = "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1\"><meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"><title>Not Found</title><style>body{background:#333;margin:0;color:#ccc;display:flex;align-items:center;max-height:100vh;height:100vh;justify-content:center}textarea{min-height:5rem;min-width:20rem;text-align:center;border:none;background:0 0;color:#ccc;resize:none;user-input:none;user-select:none;cursor:default;-webkit-user-select:none;-webkit-touch-callout:none;-moz-user-select:none;-ms-user-select:none;outline:0}</style></head><body><textarea id=\"banner\" readonly=\"readonly\"></textarea><script type=\"text/javascript\">let htmlCodes = [\n ' _ _ _ ___ _ ',\n '| \\\\| |___| |_ | __|__ _ _ _ _ __| |',\n '| .` / _ \\\\ _| | _/ _ \\\\ || | \\' \\\\/ _` |',\n '|_|\\\\_\\\\___/\\\\__| |_|\\\\___/\\\\_,_|_||_\\\\__,_|'\n].join('\\n');\ndocument.querySelector('#banner').value = htmlCodes</script></body></html>" _404 = "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1\"><meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"><title>Not Found</title><style>body{background:#333;margin:0;color:#ccc;display:flex;align-items:center;max-height:100vh;height:100vh;justify-content:center}textarea{min-height:5rem;min-width:20rem;text-align:center;border:none;background:0 0;color:#ccc;resize:none;user-input:none;user-select:none;cursor:default;-webkit-user-select:none;-webkit-touch-callout:none;-moz-user-select:none;-ms-user-select:none;outline:0}</style></head><body><textarea id=\"banner\" readonly=\"readonly\"></textarea><script type=\"text/javascript\">let htmlCodes = [\n ' _ _ _ ___ _ ',\n '| \\\\| |___| |_ | __|__ _ _ _ _ __| |',\n '| .` / _ \\\\ _| | _/ _ \\\\ || | \\' \\\\/ _` |',\n '|_|\\\\_\\\\___/\\\\__| |_|\\\\___/\\\\_,_|_||_\\\\__,_|'\n].join('\\n');\ndocument.querySelector('#banner').value = htmlCodes</script></body></html>"
@ -23,26 +25,24 @@ type Config struct {
DisableRecover bool `json:"-"` DisableRecover bool `json:"-"`
DisableHttpErrorLog bool `json:"-"` DisableHttpErrorLog bool `json:"-"`
//EnableNotImplementHandler bool `json:"-"` // EnableNotImplementHandler bool `json:"-"`
NotFoundHandler HandlerFunc `json:"-"` NotFoundHandler HandlerFunc `json:"-"`
MethodNotAllowedHandler HandlerFunc `json:"-"` MethodNotAllowedHandler HandlerFunc `json:"-"`
} }
var ( var defaultConfig = &Config{
defaultConfig = &Config{ BodyLimit: 4 * 1024 * 1024,
BodyLimit: 4 * 1024 * 1024, NotFoundHandler: func(c *Ctx) error {
NotFoundHandler: func(c *Ctx) error { c.Set("Content-Type", MIMETextHTML)
c.Set("Content-Type", MIMETextHTML) _, err := c.Status(404).Write([]byte(_404))
_, err := c.Status(404).Write([]byte(_404)) return err
return err },
}, MethodNotAllowedHandler: func(c *Ctx) error {
MethodNotAllowedHandler: func(c *Ctx) error { c.Set("Content-Type", MIMETextPlain)
c.Set("Content-Type", MIMETextPlain) _, err := c.Status(405).Write([]byte(_405))
_, err := c.Status(405).Write([]byte(_405)) return err
return err },
}, }
}
)
func New(config ...Config) *App { func New(config ...Config) *App {
app := &App{ app := &App{
@ -52,6 +52,8 @@ func New(config ...Config) *App {
root: true, root: true,
}, },
pool: &sync.Pool{},
redirectTrailingSlash: true, // true redirectTrailingSlash: true, // true
redirectFixedPath: false, // false redirectFixedPath: false, // false
handleMethodNotAllowed: true, // false handleMethodNotAllowed: true, // false
@ -89,5 +91,9 @@ func New(config ...Config) *App {
app.Use(NewRecover(true)) app.Use(NewRecover(true))
} }
app.pool.New = func() any {
return app.allocateContext()
}
return app return app
} }

View File

@ -30,12 +30,12 @@ nfctl new {project} --template http://username:token@my.gitlab.com/my-zone/my-re
disableInit bool disableInit bool
preTemplateMap = map[string]string{ preTemplateMap = map[string]string{
"ultone": "https://gitcode.com/loveuer/ultone.git", "ultone": "https://gitea.loveuer.com/loveuer/ultone.git",
} }
) )
func initNew() { func initNew() {
cmdNew.Flags().StringVarP(&template, "template", "t", "", "template name/url[example:ultone, https://github.com/xxx/yyy.git]") cmdNew.Flags().StringVarP(&template, "template", "t", "ultone", "template name/url[example:ultone, https://github.com/xxx/yyy.git]")
cmdNew.Flags().BoolVar(&disableInit, "without-init", false, "don't run template init script") cmdNew.Flags().BoolVar(&disableInit, "without-init", false, "don't run template init script")
cmdNew.RunE = func(cmd *cobra.Command, args []string) error { cmdNew.RunE = func(cmd *cobra.Command, args []string) error {
@ -67,7 +67,7 @@ func initNew() {
return fmt.Errorf("project folder already exist") return fmt.Errorf("project folder already exist")
} }
if err = os.MkdirAll(projectDir, 0750); err != nil { if err = os.MkdirAll(projectDir, 0o750); err != nil {
return fmt.Errorf("create project dir err: %v", err) return fmt.Errorf("create project dir err: %v", err)
} }

View File

@ -2,18 +2,15 @@ package main
import ( import (
"context" "context"
"github.com/loveuer/nf/nft/nfctl/cmd"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/loveuer/nf/nft/nfctl/cmd"
) )
func main() { func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
defer cancel() defer cancel()
//if !(len(os.Args) >= 2 && os.Args[1] == "version") {
// version.Check(5)
//}
_ = cmd.Root.ExecuteContext(ctx) _ = cmd.Root.ExecuteContext(ctx)
} }