Compare commits
No commits in common. "c108679fc92338b307950ec170a4158cf0759fc7" and "7cf7ec32ac30ead7e99afe47e51c2e82d1caa678" have entirely different histories.
c108679fc9
...
7cf7ec32ac
62
.github/workflows/nfctl.yml
vendored
62
.github/workflows/nfctl.yml
vendored
@ -1,62 +0,0 @@
|
||||
name: Auto Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'release/nfctl/*'
|
||||
|
||||
env:
|
||||
RELEASE_VERSION: v24.09.23-r1
|
||||
|
||||
jobs:
|
||||
build-job:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
repository-projects: write
|
||||
steps:
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: install golang
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: build linux amd64
|
||||
run: CGO_ENABLE=0 GOOS=linux GOARCH=amd64 go build -ldflags='-s -w' -o "dist/nfctl-linux_amd64-${{ env.RELEASE_VERSION }}" nft/nfctl/main.go
|
||||
|
||||
- name: build linux arm64
|
||||
run: CGO_ENABLE=0 GOOS=linux GOARCH=arm64 go build -ldflags='-s -w' -o "dist/nfctl-linux_arm64-${{ env.RELEASE_VERSION }}" nft/nfctl/main.go
|
||||
|
||||
- name: build windows amd64
|
||||
run: CGO_ENABLE=0 GOOS=windows GOARCH=amd64 go build -ldflags='-s -w' -o "dist/nfctl-win_amd64-${{ env.RELEASE_VERSION }}.exe" nft/nfctl/main.go
|
||||
|
||||
- name: build windows arm64
|
||||
run: CGO_ENABLE=0 GOOS=windows GOARCH=arm64 go build -ldflags='-s -w' -o "dist/nfctl-win_arm64-${{ env.RELEASE_VERSION }}.exe" nft/nfctl/main.go
|
||||
|
||||
- name: build darwin amd64
|
||||
run: CGO_ENABLE=0 GOOS=darwin GOARCH=amd64 go build -ldflags='-s -w' -o "dist/nfctl-darwin_amd64-${{ env.RELEASE_VERSION }}" nft/nfctl/main.go
|
||||
|
||||
- name: build darwin arm64
|
||||
run: CGO_ENABLE=0 GOOS=darwin GOARCH=arm64 go build -ldflags='-s -w' -o "dist/nfctl-darwin_arm64-${{ env.RELEASE_VERSION }}" nft/nfctl/main.go
|
||||
|
||||
- name: show all builds
|
||||
run: ls -lash dist
|
||||
|
||||
- name: create releases
|
||||
id: create_releases
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
automatic_release_tag: "Release-nfctl-${{ env.RELEASE_VERSION }}"
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
title: "Release_${{ env.RELEASE_VERSION }}"
|
||||
prerelease: false
|
||||
files: |
|
||||
dist/nfctl-linux_amd64-${{ env.RELEASE_VERSION }}
|
||||
dist/nfctl-linux_arm64-${{ env.RELEASE_VERSION }}
|
||||
dist/nfctl-win_amd64-${{ env.RELEASE_VERSION }}.exe
|
||||
dist/nfctl-win_arm64-${{ env.RELEASE_VERSION }}.exe
|
||||
dist/nfctl-darwin_amd64-${{ env.RELEASE_VERSION }}
|
||||
dist/nfctl-darwin_arm64-${{ env.RELEASE_VERSION }}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
.idea
|
||||
.vscode
|
||||
.DS_Store
|
||||
xtest
|
57
app.go
57
app.go
@ -5,15 +5,13 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/loveuer/nf/internal/bytesconv"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/loveuer/nf/internal/bytesconv"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -21,6 +19,8 @@ var (
|
||||
|
||||
regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
|
||||
regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
|
||||
|
||||
mimePlain = []string{"text/plain"}
|
||||
)
|
||||
|
||||
type App struct {
|
||||
@ -31,8 +31,6 @@ type App struct {
|
||||
|
||||
trees methodTrees
|
||||
|
||||
pool *sync.Pool
|
||||
|
||||
maxParams uint16
|
||||
maxSections uint16
|
||||
|
||||
@ -44,34 +42,13 @@ type App struct {
|
||||
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) {
|
||||
var (
|
||||
err error
|
||||
c = a.pool.Get().(*Ctx)
|
||||
c = newContext(a, writer, request)
|
||||
nfe = new(Err)
|
||||
)
|
||||
|
||||
c.reset(writer, request)
|
||||
|
||||
if err = c.verify(); err != nil {
|
||||
if errors.As(err, nfe) {
|
||||
_ = c.Status(nfe.Status).SendString(nfe.Msg)
|
||||
@ -83,8 +60,6 @@ func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
|
||||
a.handleHTTPRequest(c)
|
||||
|
||||
a.pool.Put(c)
|
||||
}
|
||||
|
||||
func (a *App) run(ln net.Listener) error {
|
||||
@ -164,7 +139,9 @@ func (a *App) addRoute(method, path string, handlers ...HandlerFunc) {
|
||||
}
|
||||
|
||||
func (a *App) handleHTTPRequest(c *Ctx) {
|
||||
var err error
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
httpMethod := c.Request.Method
|
||||
rPath := c.Request.URL.Path
|
||||
@ -188,9 +165,8 @@ func (a *App) handleHTTPRequest(c *Ctx) {
|
||||
// Find route in tree
|
||||
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
|
||||
if value.params != nil {
|
||||
c.params = value.params
|
||||
c.Params = *value.params
|
||||
}
|
||||
|
||||
if value.handlers != nil {
|
||||
c.handlers = value.handlers
|
||||
c.fullPath = value.fullPath
|
||||
@ -199,6 +175,7 @@ func (a *App) handleHTTPRequest(c *Ctx) {
|
||||
serveError(c, errorHandler)
|
||||
}
|
||||
|
||||
c.writermem.WriteHeaderNow()
|
||||
return
|
||||
}
|
||||
if httpMethod != http.MethodConnect && rPath != "/" {
|
||||
@ -227,19 +204,17 @@ func (a *App) handleHTTPRequest(c *Ctx) {
|
||||
}
|
||||
|
||||
if len(allowed) > 0 {
|
||||
c.handlers = a.combineHandlers(a.config.MethodNotAllowedHandler)
|
||||
c.handlers = a.combineHandlers()
|
||||
|
||||
_ = c.Next()
|
||||
serveError(c, a.config.MethodNotAllowedHandler)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.handlers = a.combineHandlers(a.config.NotFoundHandler)
|
||||
c.handlers = a.combineHandlers()
|
||||
|
||||
_ = c.Next()
|
||||
|
||||
return
|
||||
serveError(c, a.config.NotFoundHandler)
|
||||
}
|
||||
|
||||
func errorHandler(c *Ctx) error {
|
||||
@ -288,7 +263,7 @@ func redirectFixedPath(c *Ctx, root *node, trailingSlash bool) bool {
|
||||
|
||||
func redirectRequest(c *Ctx) {
|
||||
req := c.Request
|
||||
// rPath := req.URL.Path
|
||||
//rPath := req.URL.Path
|
||||
rURL := req.URL.String()
|
||||
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
@ -296,8 +271,8 @@ func redirectRequest(c *Ctx) {
|
||||
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()
|
||||
}
|
||||
|
191
ctx.go
191
ctx.go
@ -2,31 +2,24 @@ package nf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/loveuer/nf/internal/sse"
|
||||
)
|
||||
|
||||
var forwardHeaders = []string{"CF-Connecting-IP", "X-Forwarded-For", "X-Real-Ip"}
|
||||
|
||||
type Ctx struct {
|
||||
lock sync.Mutex
|
||||
writermem responseWriter
|
||||
Writer ResponseWriter
|
||||
Request *http.Request
|
||||
path string
|
||||
method string
|
||||
writermem responseWriter
|
||||
// origin objects
|
||||
writer http.ResponseWriter
|
||||
Request *http.Request
|
||||
// request info
|
||||
path string
|
||||
method string
|
||||
// response info
|
||||
StatusCode int
|
||||
|
||||
app *App
|
||||
@ -36,31 +29,37 @@ type Ctx struct {
|
||||
locals map[string]interface{}
|
||||
skippedNodes *[]skippedNode
|
||||
fullPath string
|
||||
Params Params
|
||||
}
|
||||
|
||||
func (c *Ctx) reset(w http.ResponseWriter, r *http.Request) {
|
||||
traceId := r.Header.Get(TraceKey)
|
||||
if traceId == "" {
|
||||
traceId = uuid.Must(uuid.NewV7()).String()
|
||||
func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ctx {
|
||||
|
||||
skippedNodes := make([]skippedNode, 0, app.maxSections)
|
||||
v := make(Params, 0, app.maxParams)
|
||||
|
||||
ctx := &Ctx{
|
||||
writer: writer,
|
||||
writermem: responseWriter{},
|
||||
Request: request,
|
||||
path: request.URL.Path,
|
||||
method: request.Method,
|
||||
StatusCode: 200,
|
||||
|
||||
app: app,
|
||||
index: -1,
|
||||
locals: map[string]interface{}{},
|
||||
handlers: make([]HandlerFunc, 0),
|
||||
skippedNodes: &skippedNodes,
|
||||
params: &v,
|
||||
}
|
||||
|
||||
c.writermem.reset(w)
|
||||
|
||||
c.Request = r.WithContext(context.WithValue(r.Context(), TraceKey, traceId))
|
||||
c.Writer = &c.writermem
|
||||
c.handlers = nil
|
||||
c.index = -1
|
||||
c.path = r.URL.Path
|
||||
c.method = r.Method
|
||||
c.StatusCode = 200
|
||||
|
||||
c.fullPath = ""
|
||||
*c.params = (*c.params)[:0]
|
||||
*c.skippedNodes = (*c.skippedNodes)[:0]
|
||||
for key := range c.locals {
|
||||
delete(c.locals, key)
|
||||
ctx.writermem = responseWriter{
|
||||
ResponseWriter: ctx.writer,
|
||||
size: -1,
|
||||
status: 0,
|
||||
}
|
||||
c.writermem.Header().Set(TraceKey, traceId)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *Ctx) Locals(key string, value ...interface{}) interface{} {
|
||||
@ -92,7 +91,9 @@ func (c *Ctx) Path(overWrite ...string) string {
|
||||
}
|
||||
|
||||
func (c *Ctx) Cookies(key string, defaultValue ...string) string {
|
||||
dv := ""
|
||||
var (
|
||||
dv = ""
|
||||
)
|
||||
|
||||
if len(defaultValue) > 0 {
|
||||
dv = defaultValue[0]
|
||||
@ -106,22 +107,15 @@ func (c *Ctx) Cookies(key string, defaultValue ...string) string {
|
||||
return cookie.Value
|
||||
}
|
||||
|
||||
func (c *Ctx) Context() context.Context {
|
||||
return c.Request.Context()
|
||||
}
|
||||
|
||||
func (c *Ctx) Next() error {
|
||||
c.index++
|
||||
|
||||
if c.index >= len(c.handlers) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
handler = c.handlers[c.index]
|
||||
)
|
||||
|
||||
//for c.index < len(c.handlers) {
|
||||
if handler != nil {
|
||||
if err = handler(c); err != nil {
|
||||
return err
|
||||
@ -129,6 +123,7 @@ func (c *Ctx) Next() error {
|
||||
}
|
||||
|
||||
c.index++
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -147,39 +142,18 @@ func (c *Ctx) verify() error {
|
||||
}
|
||||
|
||||
func (c *Ctx) Param(key string) string {
|
||||
return c.params.ByName(key)
|
||||
}
|
||||
|
||||
func (c *Ctx) SetParam(key, value string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
params := append(*c.params, Param{Key: key, Value: value})
|
||||
c.params = ¶ms
|
||||
return c.Params.ByName(key)
|
||||
}
|
||||
|
||||
func (c *Ctx) Form(key string) string {
|
||||
return c.Request.FormValue(key)
|
||||
}
|
||||
|
||||
// FormValue fiber ctx function
|
||||
func (c *Ctx) FormValue(key string) string {
|
||||
return c.Request.FormValue(key)
|
||||
}
|
||||
|
||||
func (c *Ctx) FormFile(key string) (*multipart.FileHeader, error) {
|
||||
_, fh, err := c.Request.FormFile(key)
|
||||
return fh, err
|
||||
}
|
||||
|
||||
func (c *Ctx) MultipartForm() (*multipart.Form, error) {
|
||||
if err := c.Request.ParseMultipartForm(c.app.config.BodyLimit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Request.MultipartForm, nil
|
||||
}
|
||||
|
||||
func (c *Ctx) Query(key string) string {
|
||||
return c.Request.URL.Query().Get(key)
|
||||
}
|
||||
@ -193,24 +167,11 @@ func (c *Ctx) Get(key string, defaultValue ...string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
func (c *Ctx) IP(useProxyHeader ...bool) string {
|
||||
func (c *Ctx) IP() string {
|
||||
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(useProxyHeader) > 0 && useProxyHeader[0] {
|
||||
for _, h := range forwardHeaders {
|
||||
for _, rip := range strings.Split(c.Request.Header.Get(h), ",") {
|
||||
realIP := net.ParseIP(strings.Replace(rip, " ", "", -1))
|
||||
if check := net.ParseIP(realIP.String()); check != nil {
|
||||
ip = realIP.String()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
@ -260,6 +221,11 @@ func (c *Ctx) BodyParser(out interface{}) error {
|
||||
}
|
||||
|
||||
func (c *Ctx) QueryParser(out interface{}) error {
|
||||
//v := reflect.ValueOf(out)
|
||||
//
|
||||
//if v.Kind() == reflect.Ptr && v.Elem().Kind() != reflect.Map {
|
||||
//}
|
||||
|
||||
return parseToStruct("query", out, c.Request.URL.Query())
|
||||
}
|
||||
|
||||
@ -268,34 +234,17 @@ func (c *Ctx) QueryParser(out interface{}) error {
|
||||
=============================================================== */
|
||||
|
||||
func (c *Ctx) Status(code int) *Ctx {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.Writer.WriteHeader(code)
|
||||
c.writermem.WriteHeader(code)
|
||||
c.StatusCode = c.writermem.status
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Set set response header
|
||||
func (c *Ctx) Set(key string, value string) {
|
||||
c.Writer.Header().Set(key, value)
|
||||
c.writermem.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) {
|
||||
c.Writer.Header().Set(key, value)
|
||||
}
|
||||
|
||||
func (c *Ctx) SendStatus(code int) error {
|
||||
c.Status(code)
|
||||
c.Writer.WriteHeaderNow()
|
||||
return nil
|
||||
c.writermem.Header().Set(key, value)
|
||||
}
|
||||
|
||||
func (c *Ctx) SendString(data string) error {
|
||||
@ -306,13 +255,13 @@ func (c *Ctx) SendString(data string) error {
|
||||
|
||||
func (c *Ctx) Writef(format string, values ...interface{}) (int, error) {
|
||||
c.SetHeader("Content-Type", "text/plain")
|
||||
return c.Write([]byte(fmt.Sprintf(format, values...)))
|
||||
return c.writer.Write([]byte(fmt.Sprintf(format, values...)))
|
||||
}
|
||||
|
||||
func (c *Ctx) JSON(data interface{}) error {
|
||||
c.SetHeader("Content-Type", MIMEApplicationJSON)
|
||||
|
||||
encoder := json.NewEncoder(c.Writer)
|
||||
encoder := json.NewEncoder(&c.writermem)
|
||||
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
return err
|
||||
@ -321,44 +270,16 @@ func (c *Ctx) JSON(data interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Ctx) SSEvent(event string, data interface{}) error {
|
||||
c.Set("Content-Type", "text/event-stream")
|
||||
c.Set("Cache-Control", "no-cache")
|
||||
c.Set("Transfer-Encoding", "chunked")
|
||||
|
||||
return sse.Encode(c.Writer, sse.Event{Event: event, Data: data})
|
||||
func (c *Ctx) RawWriter() http.ResponseWriter {
|
||||
return c.writer
|
||||
}
|
||||
|
||||
func (c *Ctx) Flush() error {
|
||||
if f, ok := c.Writer.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("http.Flusher is not implemented")
|
||||
func (c *Ctx) Write(data []byte) (int, error) {
|
||||
return c.writermem.Write(data)
|
||||
}
|
||||
|
||||
func (c *Ctx) HTML(html string) error {
|
||||
c.SetHeader("Content-Type", "text/html")
|
||||
_, err := c.Write([]byte(html))
|
||||
_, err := c.writer.Write([]byte(html))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Ctx) RenderHTML(name, html string, obj any) error {
|
||||
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)
|
||||
}
|
||||
|
36
go.mod
36
go.mod
@ -1,39 +1,3 @@
|
||||
module github.com/loveuer/nf
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.16.2
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
151
go.sum
151
go.sum
@ -1,151 +0,0 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
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/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
|
||||
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
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.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
@ -1,106 +0,0 @@
|
||||
package sse
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Server-Sent Events
|
||||
// W3C Working Draft 29 October 2009
|
||||
// http://www.w3.org/TR/2009/WD-eventsource-20091029/
|
||||
|
||||
const ContentType = "text/event-stream"
|
||||
|
||||
var contentType = []string{ContentType}
|
||||
var noCache = []string{"no-cache"}
|
||||
|
||||
var fieldReplacer = strings.NewReplacer(
|
||||
"\n", "\\n",
|
||||
"\r", "\\r")
|
||||
|
||||
var dataReplacer = strings.NewReplacer(
|
||||
"\n", "\ndata:",
|
||||
"\r", "\\r")
|
||||
|
||||
type Event struct {
|
||||
Event string
|
||||
Id string
|
||||
Retry uint
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
func Encode(writer io.Writer, event Event) error {
|
||||
w := checkWriter(writer)
|
||||
writeId(w, event.Id)
|
||||
writeEvent(w, event.Event)
|
||||
writeRetry(w, event.Retry)
|
||||
return writeData(w, event.Data)
|
||||
}
|
||||
|
||||
func writeId(w stringWriter, id string) {
|
||||
if len(id) > 0 {
|
||||
w.WriteString("id:")
|
||||
fieldReplacer.WriteString(w, id)
|
||||
w.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func writeEvent(w stringWriter, event string) {
|
||||
if len(event) > 0 {
|
||||
w.WriteString("event:")
|
||||
fieldReplacer.WriteString(w, event)
|
||||
w.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func writeRetry(w stringWriter, retry uint) {
|
||||
if retry > 0 {
|
||||
w.WriteString("retry:")
|
||||
w.WriteString(strconv.FormatUint(uint64(retry), 10))
|
||||
w.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func writeData(w stringWriter, data interface{}) error {
|
||||
w.WriteString("data:")
|
||||
switch kindOfData(data) {
|
||||
case reflect.Struct, reflect.Slice, reflect.Map:
|
||||
err := json.NewEncoder(w).Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteString("\n")
|
||||
default:
|
||||
dataReplacer.WriteString(w, fmt.Sprint(data))
|
||||
w.WriteString("\n\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Event) Render(w http.ResponseWriter) error {
|
||||
r.WriteContentType(w)
|
||||
return Encode(w, r)
|
||||
}
|
||||
|
||||
func (r Event) WriteContentType(w http.ResponseWriter) {
|
||||
header := w.Header()
|
||||
header["Content-Type"] = contentType
|
||||
|
||||
if _, exist := header["Cache-Control"]; !exist {
|
||||
header["Cache-Control"] = noCache
|
||||
}
|
||||
}
|
||||
|
||||
func kindOfData(data interface{}) reflect.Kind {
|
||||
value := reflect.ValueOf(data)
|
||||
valueType := value.Kind()
|
||||
if valueType == reflect.Ptr {
|
||||
valueType = value.Elem().Kind()
|
||||
}
|
||||
return valueType
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package sse
|
||||
|
||||
import "io"
|
||||
|
||||
type stringWriter interface {
|
||||
io.Writer
|
||||
WriteString(string) (int, error)
|
||||
}
|
||||
|
||||
type stringWrapper struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (w stringWrapper) WriteString(str string) (int, error) {
|
||||
return w.Writer.Write([]byte(str))
|
||||
}
|
||||
|
||||
func checkWriter(writer io.Writer) stringWriter {
|
||||
if w, ok := writer.(stringWriter); ok {
|
||||
return w
|
||||
} else {
|
||||
return stringWrapper{writer}
|
||||
}
|
||||
}
|
@ -2,11 +2,10 @@ package nf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
)
|
||||
|
||||
func NewRecover(enableStackTrace bool) HandlerFunc {
|
||||
@ -19,7 +18,7 @@ func NewRecover(enableStackTrace bool) HandlerFunc {
|
||||
os.Stderr.WriteString(fmt.Sprintf("recovered from panic: %v\n", r))
|
||||
}
|
||||
|
||||
// serveError(c, 500, []byte(fmt.Sprint(r)))
|
||||
//serveError(c, 500, []byte(fmt.Sprint(r)))
|
||||
_ = c.Status(500).SendString(fmt.Sprint(r))
|
||||
}
|
||||
}()
|
||||
@ -29,28 +28,50 @@ func NewRecover(enableStackTrace bool) HandlerFunc {
|
||||
}
|
||||
|
||||
func NewLogger() HandlerFunc {
|
||||
return func(c *Ctx) error {
|
||||
l := log.New(os.Stdout, "[NF] ", 0)
|
||||
|
||||
durationFormat := func(num int64) string {
|
||||
var (
|
||||
now = time.Now()
|
||||
logFn func(msg string, data ...any)
|
||||
ip = c.IP()
|
||||
unit = "ns"
|
||||
)
|
||||
|
||||
err := c.Next()
|
||||
duration := time.Since(now)
|
||||
|
||||
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 {
|
||||
case c.StatusCode >= 500:
|
||||
logFn = log.Error
|
||||
case c.StatusCode >= 400:
|
||||
logFn = log.Warn
|
||||
default:
|
||||
logFn = log.Info
|
||||
if num > 1000 {
|
||||
num = num / 1000
|
||||
unit = "µs"
|
||||
}
|
||||
|
||||
logFn(msg)
|
||||
if num > 1000 {
|
||||
num = num / 1000
|
||||
unit = "ms"
|
||||
}
|
||||
|
||||
if num > 1000 {
|
||||
num = num / 1000
|
||||
unit = "s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%3d %2s", num, unit)
|
||||
}
|
||||
|
||||
return func(c *Ctx) error {
|
||||
start := time.Now()
|
||||
|
||||
err := c.Next()
|
||||
|
||||
var (
|
||||
duration = time.Now().Sub(start).Nanoseconds()
|
||||
status = c.StatusCode
|
||||
path = c.path
|
||||
method = c.Request.Method
|
||||
)
|
||||
|
||||
l.Printf("%s | %5s | %d | %s | %s",
|
||||
start.Format("06/01/02T15:04:05"),
|
||||
method,
|
||||
status,
|
||||
durationFormat(duration),
|
||||
path,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
47
nf.go
47
nf.go
@ -1,13 +1,10 @@
|
||||
package nf
|
||||
|
||||
import "sync"
|
||||
|
||||
const (
|
||||
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>"
|
||||
_405 = `405 Method Not Allowed`
|
||||
_500 = `500 Internal Server Error`
|
||||
TraceKey = "X-Trace-Id"
|
||||
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>"
|
||||
_405 = `405 Method Not Allowed`
|
||||
_500 = `500 Internal Server Error`
|
||||
)
|
||||
|
||||
type Map map[string]interface{}
|
||||
@ -25,24 +22,26 @@ type Config struct {
|
||||
DisableRecover bool `json:"-"`
|
||||
DisableHttpErrorLog bool `json:"-"`
|
||||
|
||||
// EnableNotImplementHandler bool `json:"-"`
|
||||
//EnableNotImplementHandler bool `json:"-"`
|
||||
NotFoundHandler HandlerFunc `json:"-"`
|
||||
MethodNotAllowedHandler HandlerFunc `json:"-"`
|
||||
}
|
||||
|
||||
var defaultConfig = &Config{
|
||||
BodyLimit: 4 * 1024 * 1024,
|
||||
NotFoundHandler: func(c *Ctx) error {
|
||||
c.Set("Content-Type", MIMETextHTML)
|
||||
_, err := c.Status(404).Write([]byte(_404))
|
||||
return err
|
||||
},
|
||||
MethodNotAllowedHandler: func(c *Ctx) error {
|
||||
c.Set("Content-Type", MIMETextPlain)
|
||||
_, err := c.Status(405).Write([]byte(_405))
|
||||
return err
|
||||
},
|
||||
}
|
||||
var (
|
||||
defaultConfig = &Config{
|
||||
BodyLimit: 4 * 1024 * 1024,
|
||||
NotFoundHandler: func(c *Ctx) error {
|
||||
c.Set("Content-Type", MIMETextHTML)
|
||||
_, err := c.Status(404).Write([]byte(_404))
|
||||
return err
|
||||
},
|
||||
MethodNotAllowedHandler: func(c *Ctx) error {
|
||||
c.Set("Content-Type", MIMETextPlain)
|
||||
_, err := c.Status(405).Write([]byte(_405))
|
||||
return err
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func New(config ...Config) *App {
|
||||
app := &App{
|
||||
@ -52,8 +51,6 @@ func New(config ...Config) *App {
|
||||
root: true,
|
||||
},
|
||||
|
||||
pool: &sync.Pool{},
|
||||
|
||||
redirectTrailingSlash: true, // true
|
||||
redirectFixedPath: false, // false
|
||||
handleMethodNotAllowed: true, // false
|
||||
@ -91,9 +88,5 @@ func New(config ...Config) *App {
|
||||
app.Use(NewRecover(true))
|
||||
}
|
||||
|
||||
app.pool.New = func() any {
|
||||
return app.allocateContext()
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
@ -1,123 +0,0 @@
|
||||
package loading
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Type int
|
||||
|
||||
const (
|
||||
TypeProcessing Type = iota
|
||||
TypeInfo
|
||||
TypeSuccess
|
||||
TypeWarning
|
||||
TypeError
|
||||
)
|
||||
|
||||
func (t Type) Symbol() string {
|
||||
switch t {
|
||||
case TypeSuccess:
|
||||
return "✔️ "
|
||||
case TypeWarning:
|
||||
return "❗ "
|
||||
case TypeError:
|
||||
return "❌ "
|
||||
case TypeInfo:
|
||||
return "❕ "
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type _msg struct {
|
||||
msg string
|
||||
t Type
|
||||
}
|
||||
|
||||
var frames = []string{"|", "/", "-", "\\"}
|
||||
|
||||
func Do(ctx context.Context, fn func(ctx context.Context, print func(msg string, types ...Type)) error) (err error) {
|
||||
start := time.Now()
|
||||
ch := make(chan *_msg)
|
||||
|
||||
go func() {
|
||||
var (
|
||||
m *_msg
|
||||
ok bool
|
||||
processing string
|
||||
)
|
||||
|
||||
defer func() {
|
||||
fmt.Printf("\r\033[K")
|
||||
}()
|
||||
|
||||
for {
|
||||
for _, frame := range frames {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case m, ok = <-ch:
|
||||
if !ok || m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch m.t {
|
||||
case TypeProcessing:
|
||||
if m.msg != "" {
|
||||
processing = m.msg
|
||||
}
|
||||
case TypeInfo,
|
||||
TypeSuccess,
|
||||
TypeWarning,
|
||||
TypeError:
|
||||
// Clear the loading animation
|
||||
fmt.Printf("\r\033[K")
|
||||
fmt.Printf("%s%s\n", m.t.Symbol(), m.msg)
|
||||
}
|
||||
default:
|
||||
elapsed := time.Since(start).Seconds()
|
||||
if processing != "" {
|
||||
fmt.Printf("\r\033[K%s %s (%.2fs)", frame, processing, elapsed)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
printFn := func(msg string, types ...Type) {
|
||||
if msg == "" {
|
||||
return
|
||||
}
|
||||
|
||||
m := &_msg{
|
||||
msg: msg,
|
||||
t: TypeProcessing,
|
||||
}
|
||||
|
||||
if len(types) > 0 {
|
||||
m.t = types[0]
|
||||
}
|
||||
|
||||
ch <- m
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
if err = fn(ctx, printFn); err != nil {
|
||||
ch <- &_msg{msg: err.Error(), t: TypeError}
|
||||
}
|
||||
|
||||
close(ch)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-done:
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package loading
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLoadingPrint(t *testing.T) {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
defer cancel()
|
||||
|
||||
Do(ctx, func(ctx context.Context, print func(msg string, types ...Type)) error {
|
||||
print("start task 1...")
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
print("warning...1", TypeWarning)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
nilLogger = func(prefix, timestamp, msg string, data ...any) {}
|
||||
normalLogger = func(prefix, timestamp, msg string, data ...any) {
|
||||
fmt.Printf(prefix+"| "+timestamp+" | "+msg+"\n", data...)
|
||||
}
|
||||
|
||||
panicLogger = func(prefix, timestamp, msg string, data ...any) {
|
||||
panic(fmt.Sprintf(prefix+"| "+timestamp+" | "+msg+"\n", data...))
|
||||
}
|
||||
|
||||
fatalLogger = func(prefix, timestamp, msg string, data ...any) {
|
||||
fmt.Printf(prefix+"| "+timestamp+" | "+msg+"\n", data...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
DefaultLogger = &logger{
|
||||
Mutex: sync.Mutex{},
|
||||
timeFormat: "2006-01-02T15:04:05",
|
||||
writer: os.Stdout,
|
||||
level: LogLevelInfo,
|
||||
debug: nilLogger,
|
||||
info: normalLogger,
|
||||
warn: normalLogger,
|
||||
error: normalLogger,
|
||||
panic: panicLogger,
|
||||
fatal: fatalLogger,
|
||||
}
|
||||
)
|
||||
|
||||
func SetTimeFormat(format string) {
|
||||
DefaultLogger.SetTimeFormat(format)
|
||||
}
|
||||
|
||||
func SetLogLevel(level LogLevel) {
|
||||
DefaultLogger.SetLogLevel(level)
|
||||
}
|
||||
|
||||
func Debug(msg string, data ...any) {
|
||||
DefaultLogger.Debug(msg, data...)
|
||||
}
|
||||
func Info(msg string, data ...any) {
|
||||
DefaultLogger.Info(msg, data...)
|
||||
}
|
||||
|
||||
func Warn(msg string, data ...any) {
|
||||
DefaultLogger.Warn(msg, data...)
|
||||
}
|
||||
|
||||
func Error(msg string, data ...any) {
|
||||
DefaultLogger.Error(msg, data...)
|
||||
}
|
||||
|
||||
func Panic(msg string, data ...any) {
|
||||
DefaultLogger.Panic(msg, data...)
|
||||
}
|
||||
|
||||
func Fatal(msg string, data ...any) {
|
||||
DefaultLogger.Fatal(msg, data...)
|
||||
}
|
115
nft/log/log.go
115
nft/log/log.go
@ -1,115 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogLevel uint32
|
||||
|
||||
const (
|
||||
LogLevelDebug = iota
|
||||
LogLevelInfo
|
||||
LogLevelWarn
|
||||
LogLevelError
|
||||
LogLevelPanic
|
||||
LogLevelFatal
|
||||
)
|
||||
|
||||
type logger struct {
|
||||
sync.Mutex
|
||||
timeFormat string
|
||||
writer io.Writer
|
||||
level LogLevel
|
||||
debug func(prefix, timestamp, msg string, data ...any)
|
||||
info func(prefix, timestamp, msg string, data ...any)
|
||||
warn func(prefix, timestamp, msg string, data ...any)
|
||||
error func(prefix, timestamp, msg string, data ...any)
|
||||
panic func(prefix, timestamp, msg string, data ...any)
|
||||
fatal func(prefix, timestamp, msg string, data ...any)
|
||||
}
|
||||
|
||||
var (
|
||||
red = color.New(color.FgRed)
|
||||
hired = color.New(color.FgHiRed)
|
||||
green = color.New(color.FgGreen)
|
||||
yellow = color.New(color.FgYellow)
|
||||
white = color.New(color.FgWhite)
|
||||
)
|
||||
|
||||
func (l *logger) SetTimeFormat(format string) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
l.timeFormat = format
|
||||
}
|
||||
|
||||
func (l *logger) SetLogLevel(level LogLevel) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if level > LogLevelDebug {
|
||||
l.debug = nilLogger
|
||||
} else {
|
||||
l.debug = normalLogger
|
||||
}
|
||||
|
||||
if level > LogLevelInfo {
|
||||
l.info = nilLogger
|
||||
} else {
|
||||
l.info = normalLogger
|
||||
}
|
||||
|
||||
if level > LogLevelWarn {
|
||||
l.warn = nilLogger
|
||||
} else {
|
||||
l.warn = normalLogger
|
||||
}
|
||||
|
||||
if level > LogLevelError {
|
||||
l.error = nilLogger
|
||||
} else {
|
||||
l.error = normalLogger
|
||||
}
|
||||
|
||||
if level > LogLevelPanic {
|
||||
l.panic = nilLogger
|
||||
} else {
|
||||
l.panic = panicLogger
|
||||
}
|
||||
|
||||
if level > LogLevelFatal {
|
||||
l.fatal = nilLogger
|
||||
} else {
|
||||
l.fatal = fatalLogger
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logger) Debug(msg string, data ...any) {
|
||||
l.debug(white.Sprint("Debug "), time.Now().Format(l.timeFormat), msg, data...)
|
||||
}
|
||||
|
||||
func (l *logger) Info(msg string, data ...any) {
|
||||
l.info(green.Sprint("Info "), time.Now().Format(l.timeFormat), msg, data...)
|
||||
}
|
||||
|
||||
func (l *logger) Warn(msg string, data ...any) {
|
||||
l.warn(yellow.Sprint("Warn "), time.Now().Format(l.timeFormat), msg, data...)
|
||||
}
|
||||
|
||||
func (l *logger) Error(msg string, data ...any) {
|
||||
l.error(red.Sprint("Error "), time.Now().Format(l.timeFormat), msg, data...)
|
||||
}
|
||||
|
||||
func (l *logger) Panic(msg string, data ...any) {
|
||||
l.panic(hired.Sprint("Panic "), time.Now().Format(l.timeFormat), msg, data...)
|
||||
}
|
||||
|
||||
func (l *logger) Fatal(msg string, data ...any) {
|
||||
l.fatal(hired.Sprint("Fatal "), time.Now().Format(l.timeFormat), msg, data...)
|
||||
}
|
||||
|
||||
type WroteLogger interface {
|
||||
Info(msg string, data ...any)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func New() *logger {
|
||||
return &logger{
|
||||
Mutex: sync.Mutex{},
|
||||
timeFormat: "2006-01-02T15:04:05",
|
||||
writer: os.Stdout,
|
||||
level: LogLevelInfo,
|
||||
debug: nilLogger,
|
||||
info: normalLogger,
|
||||
warn: normalLogger,
|
||||
error: normalLogger,
|
||||
panic: panicLogger,
|
||||
fatal: fatalLogger,
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/loveuer/nf/nft/loading"
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"github.com/loveuer/nf/nft/nfctl/internal/opt"
|
||||
"github.com/loveuer/nf/nft/tool"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var newCmd = &cobra.Command{
|
||||
Use: "new",
|
||||
Short: "new a nf project",
|
||||
Example: "nfctl new <project> -t ultone [options]",
|
||||
RunE: doNew,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
|
||||
func initNew() *cobra.Command {
|
||||
newCmd.Flags().StringVarP(&opt.Cfg.New.Template, "template", "t", "ultone", "template name/url[example:ultone, https://gitea.loveuer.com/loveuer/ultone.git]")
|
||||
newCmd.Flags().BoolVar(&opt.Cfg.New.DisableInitScript, "disable-init-script", false, "disable init script(.nfctl)")
|
||||
return newCmd
|
||||
}
|
||||
|
||||
func doNew(cmd *cobra.Command, args []string) (err error) {
|
||||
if len(args) == 0 {
|
||||
return errors.New("必须提供 project 名称")
|
||||
}
|
||||
|
||||
if strings.HasSuffix(args[0], "/") {
|
||||
return errors.New("project 名称不能以 / 结尾")
|
||||
}
|
||||
|
||||
base := path.Base(args[0])
|
||||
if strings.HasPrefix(base, ".") {
|
||||
return errors.New("project 名称不能以 . 开头")
|
||||
}
|
||||
|
||||
return loading.Do(cmd.Context(), func(ctx context.Context, print func(msg string, types ...loading.Type)) error {
|
||||
print("开始新建项目: "+args[0], loading.TypeInfo)
|
||||
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
moduleName := args[0]
|
||||
pwd = path.Join(filepath.ToSlash(pwd), base)
|
||||
|
||||
log.Debug("cmd.new: new project, pwd = %s, name = %s, template = %s", pwd, moduleName, opt.Cfg.New.Template)
|
||||
|
||||
print("开始下载模板: "+opt.Cfg.New.Template, loading.TypeProcessing)
|
||||
|
||||
repo := opt.Cfg.New.Template
|
||||
if v, ok := opt.TemplateMap[repo]; ok {
|
||||
repo = v
|
||||
}
|
||||
|
||||
if err = tool.Clone(pwd, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print("下载模板完成: "+opt.Cfg.New.Template, loading.TypeSuccess)
|
||||
|
||||
if err = os.RemoveAll(path.Join(pwd, ".git")); err != nil {
|
||||
print(err.Error(), loading.TypeWarning)
|
||||
}
|
||||
|
||||
print("开始初始化项目: "+args[0], loading.TypeProcessing)
|
||||
|
||||
if err = filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "go.mod") {
|
||||
var content []byte
|
||||
if content, err = os.ReadFile(path); err != nil {
|
||||
print("初始化文件失败: "+err.Error(), loading.TypeWarning)
|
||||
print("开始初始化项目: "+args[0], loading.TypeProcessing)
|
||||
return nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(content))
|
||||
replaced := make([]string, 0, 16)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// 操作 go.mod 文件时, 忽略 toolchain 行, 以更好的兼容 go1.20
|
||||
if strings.HasSuffix(path, "go.mod") && strings.HasPrefix(line, "toolchain") {
|
||||
continue
|
||||
}
|
||||
replaced = append(replaced, strings.ReplaceAll(line, opt.Cfg.New.Template, moduleName))
|
||||
}
|
||||
if err = os.WriteFile(path, []byte(strings.Join(replaced, "\n")), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
render *template.Template
|
||||
rf *os.File
|
||||
)
|
||||
|
||||
if render, err = template.New(base).Parse(opt.README); err != nil {
|
||||
log.Debug("cmd.new: new text template err, err = %s", err.Error())
|
||||
print("生成 readme 失败", loading.TypeWarning)
|
||||
goto END
|
||||
}
|
||||
|
||||
if rf, err = os.OpenFile(path.Join(pwd, "readme.md"), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0o644); err != nil {
|
||||
log.Debug("cmd.new: new readme file err, err = %s", err.Error())
|
||||
print("生成 readme 失败", loading.TypeWarning)
|
||||
goto END
|
||||
}
|
||||
defer rf.Close()
|
||||
|
||||
if err = render.Execute(rf, map[string]any{
|
||||
"project_name": base,
|
||||
}); err != nil {
|
||||
log.Debug("cmd.new: template execute err, err = %s", err.Error())
|
||||
print("生成 readme 失败", loading.TypeWarning)
|
||||
}
|
||||
|
||||
END:
|
||||
print(fmt.Sprintf("项目: %s 初始化成功", args[0]), loading.TypeSuccess)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"github.com/loveuer/nf/nft/nfctl/internal/opt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "nfctl",
|
||||
Short: "nfctl is a tool for quick start a nf projects",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if opt.Cfg.Debug {
|
||||
log.SetLogLevel(log.LogLevelDebug)
|
||||
}
|
||||
|
||||
if opt.Cfg.Version {
|
||||
doVersion(cmd, args)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if !opt.Cfg.DisableUpdate {
|
||||
doUpdate(cmd.Context())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
DisableSuggestions: true,
|
||||
SilenceUsage: true,
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
func initRoot(cmds ...*cobra.Command) {
|
||||
rootCmd.PersistentFlags().BoolVar(&opt.Cfg.Debug, "debug", false, "debug mode")
|
||||
rootCmd.PersistentFlags().BoolVar(&opt.Cfg.DisableUpdate, "disable-update", false, "disable self update")
|
||||
rootCmd.PersistentFlags().BoolVarP(&opt.Cfg.Version, "version", "v", false, "print nfctl version")
|
||||
rootCmd.AddCommand(cmds...)
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
resty "github.com/go-resty/resty/v2"
|
||||
"github.com/loveuer/nf/nft/loading"
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"github.com/loveuer/nf/nft/nfctl/internal/opt"
|
||||
"github.com/loveuer/nf/nft/tool"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "update nfctl self",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func initUpdate() *cobra.Command {
|
||||
return updateCmd
|
||||
}
|
||||
|
||||
func doUpdate(ctx context.Context) (err error) {
|
||||
return loading.Do(tool.TimeoutCtx(ctx, 30), func(ctx context.Context, print func(msg string, types ...loading.Type)) error {
|
||||
print("正在检查更新...")
|
||||
tip := "❗ 请尝试手动更新: go install github.com/loveuer/nf/nft/nfctl@master"
|
||||
version := ""
|
||||
|
||||
var rr *resty.Response
|
||||
if rr, err = resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).R().
|
||||
SetContext(ctx).
|
||||
Get(opt.VersionURL); err != nil {
|
||||
err = fmt.Errorf("检查更新失败: %s\n%s", err.Error(), tip)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("cmd.update: url = %s, raw_response = %s", opt.VersionURL, rr.String())
|
||||
|
||||
if rr.StatusCode() != 200 {
|
||||
err = fmt.Errorf("检查更新失败: %s\n%s", rr.Status(), tip)
|
||||
return err
|
||||
}
|
||||
|
||||
reg := regexp.MustCompile(`const Version = "v\d{2}\.\d{2}\.\d{2}-r\d{1,2}"`)
|
||||
for _, line := range strings.Split(rr.String(), "\n") {
|
||||
if reg.MatchString(line) {
|
||||
version = strings.TrimSpace(strings.TrimPrefix(line, "const Version = "))
|
||||
version = version[1 : len(version)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if version == "" {
|
||||
err = fmt.Errorf("检查更新失败: 未找到版本信息\n%s", tip)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("cmd.update: find version = %s, now_version = %s", version, opt.Version)
|
||||
|
||||
if version <= opt.Version {
|
||||
print(fmt.Sprintf("已是最新版本: %s", opt.Version), loading.TypeSuccess)
|
||||
return nil
|
||||
}
|
||||
|
||||
print(fmt.Sprintf("发现新版本: %s", version), loading.TypeInfo)
|
||||
|
||||
print(fmt.Sprintf("正在更新到 %s ...", version))
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
print("暂时无法自动更新, 请尝试手动更新: go install github.com/loveuer/nf/nft/nfctl@master", loading.TypeWarning)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/loveuer/nf/nft/nfctl/internal/opt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func initVersion() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Run: doVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func doVersion(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("%s\nnfctl: %s\n\n", opt.Banner, opt.Version)
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
initRoot(
|
||||
initVersion(),
|
||||
initUpdate(),
|
||||
initNew(),
|
||||
)
|
||||
}
|
||||
|
||||
func Run(ctx context.Context) {
|
||||
if err := rootCmd.ExecuteContext(ctx); err != nil {
|
||||
fmt.Printf("❌ %s\n", err.Error())
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package opt
|
||||
|
||||
type _new struct {
|
||||
Template string
|
||||
DisableInitScript bool
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Debug bool
|
||||
DisableUpdate bool
|
||||
Version bool
|
||||
New _new
|
||||
}
|
||||
|
||||
var Cfg = &config{}
|
||||
|
||||
var TemplateMap = map[string]string{
|
||||
"ultone": "https://gitea.loveuer.com/loveuer/ultone.git",
|
||||
}
|
||||
|
||||
const README = "# {{ .project_name }}\n\n### 启动\n- `go run . --help`\n- `go run .`\n\n### 构建\n- `go build -ldflags '-s -w' -o dist/{{ .project_name}}_app .`\n- `docker build -t <image> -f Dockerfile .`"
|
@ -1,13 +0,0 @@
|
||||
package opt
|
||||
|
||||
const Version = "v24.12.27-r03"
|
||||
|
||||
// const VersionURL = "https://github.com/loveuer/nf/nft/nfctl/internal/opt/version.go"
|
||||
|
||||
const VersionURL = "https://raw.githubusercontent.com/loveuer/nf/refs/heads/master/nft/nfctl/internal/opt/version.go"
|
||||
|
||||
const Banner = ` ___ __ __
|
||||
___ / _/___/ /_/ /
|
||||
/ _ \/ _/ __/ __/ /
|
||||
/_//_/_/ \__/\__/_/
|
||||
`
|
@ -1,10 +0,0 @@
|
||||
package opt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBanner(t *testing.T) {
|
||||
fmt.Printf("%s\nnfctl: %s\n\n", Banner, Version)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/loveuer/nf/nft/nfctl/internal/cmd"
|
||||
)
|
||||
|
||||
func init() {
|
||||
time.Local = time.FixedZone("CST", 8*3600)
|
||||
cmd.Init()
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
defer cancel()
|
||||
|
||||
cmd.Run(ctx)
|
||||
}
|
@ -2,10 +2,9 @@ package resp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/loveuer/nf"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/loveuer/nf"
|
||||
)
|
||||
|
||||
func handleEmptyMsg(status uint32, msg string) string {
|
||||
@ -103,18 +102,6 @@ func Resp403(c *nf.Ctx, data any, msgs ...string) error {
|
||||
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 {
|
||||
msg := MSG429
|
||||
err := ""
|
||||
|
@ -7,7 +7,6 @@ const (
|
||||
MSG401 = "登录已过期, 请重新登录"
|
||||
MSG403 = "请求权限不足"
|
||||
MSG404 = "请求资源未找到"
|
||||
MSG418 = "请求条件不满足, 请稍后再试"
|
||||
MSG429 = "请求过于频繁, 请稍后再试"
|
||||
MSG500 = "服务器开小差了, 请稍后再试"
|
||||
MSG501 = "功能开发中, 尽情期待"
|
||||
|
@ -1,40 +0,0 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
)
|
||||
|
||||
func Clone(projectDir string, repoURL string) error {
|
||||
ins, err := url.Parse(repoURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s://%s%s", ins.Scheme, ins.Host, ins.Path)
|
||||
|
||||
opt := &git.CloneOptions{
|
||||
URL: uri,
|
||||
Depth: 1,
|
||||
InsecureSkipTLS: true,
|
||||
SingleBranch: true,
|
||||
}
|
||||
|
||||
if ins.User != nil {
|
||||
password, _ := ins.User.Password()
|
||||
opt.Auth = &http.BasicAuth{
|
||||
Username: ins.User.Username(),
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
_, err = git.PlainClone(projectDir, false, opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Timeout(seconds ...int) (ctx context.Context) {
|
||||
var duration time.Duration
|
||||
|
||||
if len(seconds) > 0 && seconds[0] > 0 {
|
||||
duration = time.Duration(seconds[0]) * time.Second
|
||||
} else {
|
||||
duration = time.Duration(30) * time.Second
|
||||
}
|
||||
|
||||
ctx, _ = context.WithTimeout(context.Background(), duration)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TimeoutCtx(ctx context.Context, seconds ...int) context.Context {
|
||||
var duration time.Duration
|
||||
|
||||
if len(seconds) > 0 && seconds[0] > 0 {
|
||||
duration = time.Duration(seconds[0]) * time.Second
|
||||
} else {
|
||||
duration = time.Duration(30) * time.Second
|
||||
}
|
||||
|
||||
nctx, _ := context.WithTimeout(ctx, duration)
|
||||
|
||||
return nctx
|
||||
}
|
135
readme.md
135
readme.md
@ -5,98 +5,63 @@
|
||||
##### basic usage
|
||||
|
||||
- get param
|
||||
```go
|
||||
func main() {
|
||||
app := nf.New()
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := nf.New()
|
||||
app.Get("/hello/:name", func(c *nf.Ctx) error {
|
||||
name := c.Param("name")
|
||||
return c.JSON(nf.Map{"status": 200, "data": "hello, " + name})
|
||||
})
|
||||
|
||||
app.Get("/hello/:name", func(c *nf.Ctx) error {
|
||||
name := c.Param("name")
|
||||
return c.JSON(nf.Map{"status": 200, "data": "hello, " + name})
|
||||
})
|
||||
|
||||
log.Fatal(app.Run("0.0.0.0:80"))
|
||||
}
|
||||
```
|
||||
log.Fatal(app.Run("0.0.0.0:80"))
|
||||
}
|
||||
```
|
||||
|
||||
- parse request query
|
||||
```go
|
||||
func handleQuery(c *nf.Ctx) error {
|
||||
type Req struct {
|
||||
Name string `query:"name"`
|
||||
Addr []string `query:"addr"`
|
||||
}
|
||||
|
||||
```go
|
||||
func handleQuery(c *nf.Ctx) error {
|
||||
type Req struct {
|
||||
Name string `query:"name"`
|
||||
Addr []string `query:"addr"`
|
||||
}
|
||||
var (
|
||||
err error
|
||||
req = Req{}
|
||||
)
|
||||
|
||||
var (
|
||||
err error
|
||||
req = Req{}
|
||||
)
|
||||
if err = c.QueryParser(&req); err != nil {
|
||||
return nf.NewNFError(400, err.Error())
|
||||
}
|
||||
|
||||
if err = c.QueryParser(&req); err != nil {
|
||||
return nf.NewNFError(400, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(nf.Map{"query": req})
|
||||
}
|
||||
```
|
||||
return c.JSON(nf.Map{"query": req})
|
||||
}
|
||||
```
|
||||
|
||||
- parse application/json body
|
||||
```go
|
||||
func handlePost(c *nf.Ctx) error {
|
||||
type Req struct {
|
||||
Name string `json:"name"`
|
||||
Addr []string `json:"addr"`
|
||||
}
|
||||
|
||||
```go
|
||||
func handlePost(c *nf.Ctx) error {
|
||||
type Req struct {
|
||||
Name string `json:"name"`
|
||||
Addr []string `json:"addr"`
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
req = Req{}
|
||||
reqMap = make(map[string]interface{})
|
||||
)
|
||||
|
||||
if err = c.BodyParser(&req); err != nil {
|
||||
return nf.NewNFError(400, err.Error())
|
||||
}
|
||||
|
||||
// can parse body multi times
|
||||
if err = c.BodyParser(&reqMap); err != nil {
|
||||
return nf.NewNFError(400, err.Error())
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
var (
|
||||
err error
|
||||
req = Req{}
|
||||
reqMap = make(map[string]interface{})
|
||||
)
|
||||
|
||||
if err = c.BodyParser(&req); err != nil {
|
||||
return nf.NewNFError(400, err.Error())
|
||||
}
|
||||
|
||||
// can parse body multi times
|
||||
if err = c.BodyParser(&reqMap); err != nil {
|
||||
return nf.NewNFError(400, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(nf.Map{"struct": req, "map": reqMap})
|
||||
}
|
||||
```
|
@ -3,7 +3,6 @@ package nf
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
@ -42,9 +41,8 @@ type ResponseWriter interface {
|
||||
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
written bool
|
||||
size int
|
||||
status int
|
||||
size int
|
||||
status int
|
||||
}
|
||||
|
||||
var _ ResponseWriter = (*responseWriter)(nil)
|
||||
@ -62,7 +60,7 @@ func (w *responseWriter) reset(writer http.ResponseWriter) {
|
||||
func (w *responseWriter) WriteHeader(code int) {
|
||||
if code > 0 && w.status != code {
|
||||
if w.Written() {
|
||||
log.Printf("[NF] WARNING: Headers were already written. Wanted to override status code %d with %d", w.status, code)
|
||||
// todo: debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
|
||||
return
|
||||
}
|
||||
w.status = code
|
||||
@ -104,7 +102,7 @@ func (w *responseWriter) Size() int {
|
||||
}
|
||||
|
||||
func (w *responseWriter) Written() bool {
|
||||
return w.size != noWritten || w.written
|
||||
return w.size != noWritten
|
||||
}
|
||||
|
||||
// Hijack implements the http.Hijacker interface.
|
||||
|
21
util.go
21
util.go
@ -202,24 +202,3 @@ func bufApp(buf *[]byte, s string, w int, c byte) {
|
||||
}
|
||||
b[w] = c
|
||||
}
|
||||
|
||||
func HumanDuration(nano int64) string {
|
||||
duration := float64(nano)
|
||||
unit := "ns"
|
||||
if duration >= 1000 {
|
||||
duration /= 1000
|
||||
unit = "us"
|
||||
}
|
||||
|
||||
if duration >= 1000 {
|
||||
duration /= 1000
|
||||
unit = "ms"
|
||||
}
|
||||
|
||||
if duration >= 1000 {
|
||||
duration /= 1000
|
||||
unit = " s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%6.2f%s", duration, unit)
|
||||
}
|
||||
|
14
xtest/basic/basic.http
Normal file
14
xtest/basic/basic.http
Normal file
@ -0,0 +1,14 @@
|
||||
### basic - get
|
||||
GET http://127.0.0.1/hello/nf
|
||||
|
||||
|
||||
### test resp error
|
||||
GET http://127.0.0.1/error
|
||||
|
||||
### test basic post
|
||||
POST http://127.0.0.1/data
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "nice"
|
||||
}
|
17
xtest/basic/main.go
Normal file
17
xtest/basic/main.go
Normal file
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/loveuer/nf"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := nf.New(nf.Config{EnableNotImplementHandler: true})
|
||||
|
||||
api := app.Group("/api")
|
||||
api.Get("/1", func(c *nf.Ctx) error {
|
||||
return c.SendString("nice")
|
||||
})
|
||||
|
||||
log.Fatal(app.Run(":80"))
|
||||
}
|
9
xtest/bodyLimit/body_limit.http
Normal file
9
xtest/bodyLimit/body_limit.http
Normal file
@ -0,0 +1,9 @@
|
||||
### body_limit
|
||||
POST http://127.0.0.1/data
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "zyp",
|
||||
"age": 19,
|
||||
"likes": ["2233"]
|
||||
}
|
50
xtest/bodyLimit/main.go
Normal file
50
xtest/bodyLimit/main.go
Normal file
@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/loveuer/nf"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := nf.New(nf.Config{BodyLimit: 30})
|
||||
|
||||
app.Post("/data", func(c *nf.Ctx) error {
|
||||
type Req struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Likes []string `json:"likes"`
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
req = new(Req)
|
||||
)
|
||||
|
||||
if err = c.BodyParser(req); err != nil {
|
||||
return c.JSON(nf.Map{"status": 400, "err": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(nf.Map{"status": 200, "data": req})
|
||||
})
|
||||
|
||||
app.Post("/url", func(c *nf.Ctx) error {
|
||||
type Req struct {
|
||||
Name string `form:"name"`
|
||||
Age int `form:"age"`
|
||||
Likes []string `form:"likes"`
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
req = new(Req)
|
||||
)
|
||||
|
||||
if err = c.BodyParser(req); err != nil {
|
||||
return c.JSON(nf.Map{"status": 400, "err": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(nf.Map{"status": 200, "data": req})
|
||||
})
|
||||
|
||||
log.Fatal(app.Run("0.0.0.0:80"))
|
||||
}
|
28
xtest/midd/main.go
Normal file
28
xtest/midd/main.go
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/loveuer/nf"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := nf.New(nf.Config{DisableLogger: false})
|
||||
|
||||
app.Get("/hello", func(c *nf.Ctx) error {
|
||||
return c.SendString("world")
|
||||
})
|
||||
|
||||
app.Use(ml())
|
||||
|
||||
log.Fatal(app.Run(":80"))
|
||||
}
|
||||
|
||||
func ml() nf.HandlerFunc {
|
||||
return func(c *nf.Ctx) error {
|
||||
index := []byte(`<h1>my not found</h1>`)
|
||||
c.Set("Content-Type", "text/html")
|
||||
c.Status(403)
|
||||
_, err := c.Write(index)
|
||||
return err
|
||||
}
|
||||
}
|
34
xtest/midd2/main.go
Normal file
34
xtest/midd2/main.go
Normal file
@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/loveuer/nf"
|
||||
"github.com/loveuer/nf/nft/resp"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := nf.New()
|
||||
|
||||
api := app.Group("/api")
|
||||
|
||||
api.Get("/hello",
|
||||
auth(),
|
||||
func(c *nf.Ctx) error {
|
||||
return resp.Resp403(c, errors.New("in hello"))
|
||||
},
|
||||
)
|
||||
|
||||
log.Fatal(app.Run(":80"))
|
||||
}
|
||||
|
||||
func auth() nf.HandlerFunc {
|
||||
return func(c *nf.Ctx) error {
|
||||
token := c.Query("token")
|
||||
if token != "zyp" {
|
||||
return resp.Resp401(c, errors.New("no auth"))
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
28
xtest/multihandler/main.go
Normal file
28
xtest/multihandler/main.go
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/loveuer/nf"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := nf.New()
|
||||
|
||||
app.Get("/nice", h1, h2)
|
||||
|
||||
log.Fatal(app.Run(":80"))
|
||||
}
|
||||
|
||||
func h1(c *nf.Ctx) error {
|
||||
you := c.Query("to")
|
||||
if you == "you" {
|
||||
return c.JSON(nf.Map{"status": 201, "msg": "nice to meet you"})
|
||||
}
|
||||
|
||||
//return c.Next()
|
||||
return nil
|
||||
}
|
||||
|
||||
func h2(c *nf.Ctx) error {
|
||||
return c.JSON(nf.Map{"status": 200, "msg": "hello world"})
|
||||
}
|
5
xtest/multihandler/req.http
Normal file
5
xtest/multihandler/req.http
Normal file
@ -0,0 +1,5 @@
|
||||
### test multi handlers no next
|
||||
GET http://127.0.0.1:3333/nice?to=you
|
||||
|
||||
### test multi handlers do next
|
||||
GET http://127.0.0.1:3333/nice?to=nf
|
24
xtest/panic/main.go
Normal file
24
xtest/panic/main.go
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/loveuer/nf"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := nf.New(nf.Config{
|
||||
DisableRecover: false,
|
||||
})
|
||||
|
||||
app.Get("/hello/:name", func(c *nf.Ctx) error {
|
||||
name := c.Param("name")
|
||||
|
||||
if name == "nf" {
|
||||
panic("name is nf")
|
||||
}
|
||||
|
||||
return c.JSON("nice")
|
||||
})
|
||||
|
||||
log.Fatal(app.Run("0.0.0.0:80"))
|
||||
}
|
5
xtest/panic/panic.http
Normal file
5
xtest/panic/panic.http
Normal file
@ -0,0 +1,5 @@
|
||||
### panic test
|
||||
GET http://127.0.0.1/hello/nf
|
||||
|
||||
### if covered?
|
||||
GET http://127.0.0.1/hello/world
|
36
xtest/queryParser/main.go
Normal file
36
xtest/queryParser/main.go
Normal file
@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/loveuer/nf"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := nf.New()
|
||||
|
||||
app.Get("/hello", func(c *nf.Ctx) error {
|
||||
type Req struct {
|
||||
Name string `query:"name"`
|
||||
Age int `query:"age"`
|
||||
Likes []string `query:"likes"`
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
req = new(Req)
|
||||
rm = make(map[string]interface{})
|
||||
)
|
||||
|
||||
//if err = c.QueryParser(req); err != nil {
|
||||
// return nf.NewNFError(400, "1:"+err.Error())
|
||||
//}
|
||||
|
||||
if err = c.QueryParser(&rm); err != nil {
|
||||
return nf.NewNFError(400, "2:"+err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(nf.Map{"status": 200, "data": req, "map": rm})
|
||||
})
|
||||
|
||||
log.Fatal(app.Run("0.0.0.0:80"))
|
||||
}
|
52
xtest/quit/main.go
Normal file
52
xtest/quit/main.go
Normal file
@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/loveuer/nf"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
app = nf.New()
|
||||
quit = make(chan bool)
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app.Get("/name", handleGet)
|
||||
|
||||
go func() {
|
||||
err := app.Run(":80")
|
||||
log.Print("run with err=", err)
|
||||
quit <- true
|
||||
}()
|
||||
|
||||
<-quit
|
||||
}
|
||||
|
||||
func handleGet(c *nf.Ctx) error {
|
||||
type Req struct {
|
||||
Name string `query:"name"`
|
||||
Addr []string `query:"addr"`
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
req = Req{}
|
||||
)
|
||||
|
||||
if err = c.QueryParser(&req); err != nil {
|
||||
return nf.NewNFError(400, err.Error())
|
||||
}
|
||||
|
||||
if req.Name == "quit" {
|
||||
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
log.Print("app quit = ", app.Shutdown(context.TODO()))
|
||||
}()
|
||||
}
|
||||
|
||||
return c.JSON(nf.Map{"req_map": req})
|
||||
}
|
119
xtest/tls/main.go
Normal file
119
xtest/tls/main.go
Normal file
@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"github.com/loveuer/nf"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := nf.New(nf.Config{
|
||||
DisableHttpErrorLog: true,
|
||||
})
|
||||
|
||||
app.Get("/hello/:name", func(c *nf.Ctx) error {
|
||||
return c.SendString("hello, " + c.Param("name"))
|
||||
})
|
||||
|
||||
st, _, _ := GenerateTlsConfig()
|
||||
log.Fatal(app.RunTLS(":443", st))
|
||||
}
|
||||
|
||||
func GenerateTlsConfig() (serverTLSConf *tls.Config, clientTLSConf *tls.Config, err error) {
|
||||
ca := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(2019),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Company, INC."},
|
||||
Country: []string{"US"},
|
||||
Province: []string{""},
|
||||
Locality: []string{"San Francisco"},
|
||||
StreetAddress: []string{"Golden Gate Bridge"},
|
||||
PostalCode: []string{"94016"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(99, 0, 0),
|
||||
IsCA: true,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
// create our private and public key
|
||||
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// create the CA
|
||||
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// pem encode
|
||||
caPEM := new(bytes.Buffer)
|
||||
pem.Encode(caPEM, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: caBytes,
|
||||
})
|
||||
caPrivKeyPEM := new(bytes.Buffer)
|
||||
pem.Encode(caPrivKeyPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
|
||||
})
|
||||
// set up our server certificate
|
||||
cert := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(2019),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Company, INC."},
|
||||
Country: []string{"US"},
|
||||
Province: []string{""},
|
||||
Locality: []string{"San Francisco"},
|
||||
StreetAddress: []string{"Golden Gate Bridge"},
|
||||
PostalCode: []string{"94016"},
|
||||
},
|
||||
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||
SubjectKeyId: []byte{1, 2, 3, 4, 6},
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
certPEM := new(bytes.Buffer)
|
||||
pem.Encode(certPEM, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certBytes,
|
||||
})
|
||||
certPrivKeyPEM := new(bytes.Buffer)
|
||||
pem.Encode(certPrivKeyPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
|
||||
})
|
||||
serverCert, err := tls.X509KeyPair(certPEM.Bytes(), certPrivKeyPEM.Bytes())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
serverTLSConf = &tls.Config{
|
||||
Certificates: []tls.Certificate{serverCert},
|
||||
}
|
||||
certpool := x509.NewCertPool()
|
||||
certpool.AppendCertsFromPEM(caPEM.Bytes())
|
||||
clientTLSConf = &tls.Config{
|
||||
RootCAs: certpool,
|
||||
}
|
||||
return
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user