mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-23 00:38:33 +02:00
refactor: move some utility functions to goutils and update references
This commit is contained in:
2
goutils
2
goutils
Submodule goutils updated: cfb8d74b17...dc10bf40f9
@@ -14,7 +14,6 @@ import (
|
|||||||
"github.com/yusing/godoxy/internal/logging/accesslog"
|
"github.com/yusing/godoxy/internal/logging/accesslog"
|
||||||
"github.com/yusing/godoxy/internal/maxmind"
|
"github.com/yusing/godoxy/internal/maxmind"
|
||||||
"github.com/yusing/godoxy/internal/notif"
|
"github.com/yusing/godoxy/internal/notif"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
strutils "github.com/yusing/goutils/strings"
|
strutils "github.com/yusing/goutils/strings"
|
||||||
"github.com/yusing/goutils/task"
|
"github.com/yusing/goutils/task"
|
||||||
@@ -82,7 +81,7 @@ var ActiveConfig atomic.Pointer[Config]
|
|||||||
const cacheTTL = 1 * time.Minute
|
const cacheTTL = 1 * time.Minute
|
||||||
|
|
||||||
func (c *checkCache) Expired() bool {
|
func (c *checkCache) Expired() bool {
|
||||||
return c.created.Add(cacheTTL).Before(utils.TimeNow())
|
return c.created.Add(cacheTTL).Before(time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add stats
|
// TODO: add stats
|
||||||
@@ -180,7 +179,7 @@ func (c *Config) cacheRecord(info *maxmind.IPInfo, allow bool) {
|
|||||||
c.ipCache.Store(info.Str, &checkCache{
|
c.ipCache.Store(info.Str, &checkCache{
|
||||||
IPInfo: info,
|
IPInfo: info,
|
||||||
allow: allow,
|
allow: allow,
|
||||||
created: utils.TimeNow(),
|
created: time.Now(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/yusing/godoxy/internal/common"
|
"github.com/yusing/godoxy/internal/common"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
apitypes "github.com/yusing/goutils/apitypes"
|
apitypes "github.com/yusing/goutils/apitypes"
|
||||||
|
"github.com/yusing/goutils/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListFilesResponse struct {
|
type ListFilesResponse struct {
|
||||||
@@ -35,7 +35,7 @@ func List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// config/
|
// config/
|
||||||
files, err := utils.ListFiles(common.ConfigBasePath, 0, true)
|
files, err := fs.ListFiles(common.ConfigBasePath, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error(apitypes.InternalServerError(err, "failed to list files"))
|
c.Error(apitypes.InternalServerError(err, "failed to list files"))
|
||||||
return
|
return
|
||||||
@@ -48,7 +48,7 @@ func List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// config/middlewares/
|
// config/middlewares/
|
||||||
mids, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0, true)
|
mids, err := fs.ListFiles(common.MiddlewareComposeBasePath, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error(apitypes.InternalServerError(err, "failed to list files"))
|
c.Error(apitypes.InternalServerError(err, "failed to list files"))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/yusing/godoxy/internal/common"
|
"github.com/yusing/godoxy/internal/common"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -96,7 +95,7 @@ func (cfg *Config) Validate() gperr.Error {
|
|||||||
if cfg.Provider != ProviderCustom {
|
if cfg.Provider != ProviderCustom {
|
||||||
b.Add(ErrUnknownProvider.
|
b.Add(ErrUnknownProvider.
|
||||||
Subject(cfg.Provider).
|
Subject(cfg.Provider).
|
||||||
With(gperr.DoYouMean(utils.NearestField(cfg.Provider, Providers))))
|
With(gperr.DoYouMeanField(cfg.Provider, Providers)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
provider, err := providerConstructor(cfg.Options)
|
provider, err := providerConstructor(cfg.Options)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
"github.com/yusing/godoxy/internal/serialization"
|
"github.com/yusing/godoxy/internal/serialization"
|
||||||
"github.com/yusing/godoxy/internal/types"
|
"github.com/yusing/godoxy/internal/types"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -223,7 +222,7 @@ func setPrivateHostname(c *types.Container, helper containerHelper) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nearest := gperr.DoYouMean(utils.NearestField(c.Network, helper.NetworkSettings.Networks))
|
nearest := gperr.DoYouMeanField(c.Network, helper.NetworkSettings.Networks)
|
||||||
addError(c, fmt.Errorf("network %q not found, %w", c.Network, nearest))
|
addError(c, fmt.Errorf("network %q not found, %w", c.Network, nearest))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||||
"github.com/yusing/godoxy/internal/utils/pool"
|
"github.com/yusing/goutils/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type route interface {
|
type route interface {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/yusing/godoxy/internal/logging/accesslog"
|
. "github.com/yusing/godoxy/internal/logging/accesslog"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
"github.com/yusing/goutils/mockable"
|
||||||
"github.com/yusing/goutils/task"
|
"github.com/yusing/goutils/task"
|
||||||
expect "github.com/yusing/goutils/testing"
|
expect "github.com/yusing/goutils/testing"
|
||||||
)
|
)
|
||||||
@@ -57,7 +57,7 @@ func fmtLog(cfg *RequestLoggerConfig) (ts string, line string) {
|
|||||||
|
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
logger := NewMockAccessLogger(testTask, cfg)
|
logger := NewMockAccessLogger(testTask, cfg)
|
||||||
utils.MockTimeNow(t)
|
mockable.MockTimeNow(t)
|
||||||
buf = logger.(RequestFormatter).AppendRequestLog(buf, req, resp)
|
buf = logger.(RequestFormatter).AppendRequestLog(buf, req, resp)
|
||||||
return t.Format(LogTimeFormat), string(buf)
|
return t.Format(LogTimeFormat), string(buf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
maxmind "github.com/yusing/godoxy/internal/maxmind/types"
|
maxmind "github.com/yusing/godoxy/internal/maxmind/types"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
"github.com/yusing/goutils/mockable"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -67,7 +67,7 @@ func (f *CommonFormatter) AppendRequestLog(line []byte, req *http.Request, res *
|
|||||||
line = append(line, clientIP(req)...)
|
line = append(line, clientIP(req)...)
|
||||||
line = append(line, " - - ["...)
|
line = append(line, " - - ["...)
|
||||||
|
|
||||||
line = utils.TimeNow().AppendFormat(line, LogTimeFormat)
|
line = mockable.TimeNow().AppendFormat(line, LogTimeFormat)
|
||||||
line = append(line, `] "`...)
|
line = append(line, `] "`...)
|
||||||
|
|
||||||
line = append(line, req.Method...)
|
line = append(line, req.Method...)
|
||||||
@@ -103,7 +103,7 @@ func (f *JSONFormatter) AppendRequestLog(line []byte, req *http.Request, res *ht
|
|||||||
writer := bytes.NewBuffer(line)
|
writer := bytes.NewBuffer(line)
|
||||||
logger := zerolog.New(writer)
|
logger := zerolog.New(writer)
|
||||||
event := logger.Info().
|
event := logger.Info().
|
||||||
Str("time", utils.TimeNow().Format(LogTimeFormat)).
|
Str("time", mockable.TimeNow().Format(LogTimeFormat)).
|
||||||
Str("ip", clientIP(req)).
|
Str("ip", clientIP(req)).
|
||||||
Str("method", req.Method).
|
Str("method", req.Method).
|
||||||
Str("scheme", scheme(req)).
|
Str("scheme", scheme(req)).
|
||||||
@@ -136,7 +136,7 @@ func (f ACLLogFormatter) AppendACLLog(line []byte, info *maxmind.IPInfo, blocked
|
|||||||
writer := bytes.NewBuffer(line)
|
writer := bytes.NewBuffer(line)
|
||||||
logger := zerolog.New(writer)
|
logger := zerolog.New(writer)
|
||||||
event := logger.Info().
|
event := logger.Info().
|
||||||
Str("time", utils.TimeNow().Format(LogTimeFormat)).
|
Str("time", mockable.TimeNow().Format(LogTimeFormat)).
|
||||||
Str("ip", info.Str)
|
Str("ip", info.Str)
|
||||||
if blocked {
|
if blocked {
|
||||||
event.Str("action", "block")
|
event.Str("action", "block")
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
|
"github.com/yusing/goutils/mockable"
|
||||||
strutils "github.com/yusing/goutils/strings"
|
strutils "github.com/yusing/goutils/strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,14 +81,14 @@ func rotateLogFile(file supportRotate, config *Retention, result *RotateResult)
|
|||||||
|
|
||||||
func rotateLogFileByPolicy(file supportRotate, config *Retention, result *RotateResult) (rotated bool, err error) {
|
func rotateLogFileByPolicy(file supportRotate, config *Retention, result *RotateResult) (rotated bool, err error) {
|
||||||
var shouldStop func() bool
|
var shouldStop func() bool
|
||||||
t := utils.TimeNow()
|
t := mockable.TimeNow()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case config.Last > 0:
|
case config.Last > 0:
|
||||||
shouldStop = func() bool { return result.NumLinesKeep-result.NumLinesInvalid == int(config.Last) }
|
shouldStop = func() bool { return result.NumLinesKeep-result.NumLinesInvalid == int(config.Last) }
|
||||||
// not needed to parse time for last N lines
|
// not needed to parse time for last N lines
|
||||||
case config.Days > 0:
|
case config.Days > 0:
|
||||||
cutoff := utils.TimeNow().AddDate(0, 0, -int(config.Days)+1)
|
cutoff := mockable.TimeNow().AddDate(0, 0, -int(config.Days)+1)
|
||||||
shouldStop = func() bool { return t.Before(cutoff) }
|
shouldStop = func() bool { return t.Before(cutoff) }
|
||||||
default:
|
default:
|
||||||
return false, nil // should not happen
|
return false, nil // should not happen
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/yusing/godoxy/internal/logging/accesslog"
|
. "github.com/yusing/godoxy/internal/logging/accesslog"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
"github.com/yusing/goutils/mockable"
|
||||||
strutils "github.com/yusing/goutils/strings"
|
strutils "github.com/yusing/goutils/strings"
|
||||||
"github.com/yusing/goutils/task"
|
"github.com/yusing/goutils/task"
|
||||||
expect "github.com/yusing/goutils/testing"
|
expect "github.com/yusing/goutils/testing"
|
||||||
@@ -56,7 +56,7 @@ func TestRotateKeepLast(t *testing.T) {
|
|||||||
for _, format := range ReqLoggerFormats {
|
for _, format := range ReqLoggerFormats {
|
||||||
t.Run(string(format)+" keep last", func(t *testing.T) {
|
t.Run(string(format)+" keep last", func(t *testing.T) {
|
||||||
file := NewMockFile(true)
|
file := NewMockFile(true)
|
||||||
utils.MockTimeNow(testTime)
|
mockable.MockTimeNow(testTime)
|
||||||
logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{
|
logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||||
Format: format,
|
Format: format,
|
||||||
})
|
})
|
||||||
@@ -93,7 +93,7 @@ func TestRotateKeepLast(t *testing.T) {
|
|||||||
expect.Nil(t, logger.Config().Retention)
|
expect.Nil(t, logger.Config().Retention)
|
||||||
nLines := 10
|
nLines := 10
|
||||||
for i := range nLines {
|
for i := range nLines {
|
||||||
utils.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
||||||
logger.Log(req, resp)
|
logger.Log(req, resp)
|
||||||
}
|
}
|
||||||
logger.Flush()
|
logger.Flush()
|
||||||
@@ -105,7 +105,7 @@ func TestRotateKeepLast(t *testing.T) {
|
|||||||
expect.Equal(t, retention.KeepSize, 0)
|
expect.Equal(t, retention.KeepSize, 0)
|
||||||
logger.Config().Retention = retention
|
logger.Config().Retention = retention
|
||||||
|
|
||||||
utils.MockTimeNow(testTime)
|
mockable.MockTimeNow(testTime)
|
||||||
var result RotateResult
|
var result RotateResult
|
||||||
rotated, err := logger.(AccessLogRotater).Rotate(&result)
|
rotated, err := logger.(AccessLogRotater).Rotate(&result)
|
||||||
expect.NoError(t, err)
|
expect.NoError(t, err)
|
||||||
@@ -139,7 +139,7 @@ func TestRotateKeepFileSize(t *testing.T) {
|
|||||||
expect.Nil(t, logger.Config().Retention)
|
expect.Nil(t, logger.Config().Retention)
|
||||||
nLines := 10
|
nLines := 10
|
||||||
for i := range nLines {
|
for i := range nLines {
|
||||||
utils.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
||||||
logger.Log(req, resp)
|
logger.Log(req, resp)
|
||||||
}
|
}
|
||||||
logger.Flush()
|
logger.Flush()
|
||||||
@@ -151,7 +151,7 @@ func TestRotateKeepFileSize(t *testing.T) {
|
|||||||
expect.Equal(t, retention.Last, 0)
|
expect.Equal(t, retention.Last, 0)
|
||||||
logger.Config().Retention = retention
|
logger.Config().Retention = retention
|
||||||
|
|
||||||
utils.MockTimeNow(testTime)
|
mockable.MockTimeNow(testTime)
|
||||||
var result RotateResult
|
var result RotateResult
|
||||||
rotated, err := logger.(AccessLogRotater).Rotate(&result)
|
rotated, err := logger.(AccessLogRotater).Rotate(&result)
|
||||||
expect.NoError(t, err)
|
expect.NoError(t, err)
|
||||||
@@ -171,7 +171,7 @@ func TestRotateKeepFileSize(t *testing.T) {
|
|||||||
expect.Nil(t, logger.Config().Retention)
|
expect.Nil(t, logger.Config().Retention)
|
||||||
nLines := 100
|
nLines := 100
|
||||||
for i := range nLines {
|
for i := range nLines {
|
||||||
utils.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
||||||
logger.Log(req, resp)
|
logger.Log(req, resp)
|
||||||
}
|
}
|
||||||
logger.Flush()
|
logger.Flush()
|
||||||
@@ -183,7 +183,7 @@ func TestRotateKeepFileSize(t *testing.T) {
|
|||||||
expect.Equal(t, retention.Last, 0)
|
expect.Equal(t, retention.Last, 0)
|
||||||
logger.Config().Retention = retention
|
logger.Config().Retention = retention
|
||||||
|
|
||||||
utils.MockTimeNow(testTime)
|
mockable.MockTimeNow(testTime)
|
||||||
var result RotateResult
|
var result RotateResult
|
||||||
rotated, err := logger.(AccessLogRotater).Rotate(&result)
|
rotated, err := logger.(AccessLogRotater).Rotate(&result)
|
||||||
expect.NoError(t, err)
|
expect.NoError(t, err)
|
||||||
@@ -205,7 +205,7 @@ func TestRotateSkipInvalidTime(t *testing.T) {
|
|||||||
expect.Nil(t, logger.Config().Retention)
|
expect.Nil(t, logger.Config().Retention)
|
||||||
nLines := 10
|
nLines := 10
|
||||||
for i := range nLines {
|
for i := range nLines {
|
||||||
utils.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
||||||
logger.Log(req, resp)
|
logger.Log(req, resp)
|
||||||
logger.Flush()
|
logger.Flush()
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ func BenchmarkRotate(b *testing.B) {
|
|||||||
Format: FormatJSON,
|
Format: FormatJSON,
|
||||||
})
|
})
|
||||||
for i := range 100 {
|
for i := range 100 {
|
||||||
utils.MockTimeNow(testTime.AddDate(0, 0, -100+i+1))
|
mockable.MockTimeNow(testTime.AddDate(0, 0, -100+i+1))
|
||||||
logger.Log(req, resp)
|
logger.Log(req, resp)
|
||||||
}
|
}
|
||||||
logger.Flush()
|
logger.Flush()
|
||||||
@@ -282,7 +282,7 @@ func BenchmarkRotateWithInvalidTime(b *testing.B) {
|
|||||||
Format: FormatJSON,
|
Format: FormatJSON,
|
||||||
})
|
})
|
||||||
for i := range 10000 {
|
for i := range 10000 {
|
||||||
utils.MockTimeNow(testTime.AddDate(0, 0, -10000+i+1))
|
mockable.MockTimeNow(testTime.AddDate(0, 0, -10000+i+1))
|
||||||
logger.Log(req, resp)
|
logger.Log(req, resp)
|
||||||
if i%10 == 0 {
|
if i%10 == 0 {
|
||||||
_, _ = file.Write([]byte("invalid time\n"))
|
_, _ = file.Write([]byte("invalid time\n"))
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
|
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
|
||||||
"github.com/yusing/godoxy/internal/types"
|
"github.com/yusing/godoxy/internal/types"
|
||||||
"github.com/yusing/godoxy/internal/utils/pool"
|
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
|
"github.com/yusing/goutils/pool"
|
||||||
"github.com/yusing/goutils/task"
|
"github.com/yusing/goutils/task"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
"github.com/yusing/godoxy/internal/jsonstore"
|
"github.com/yusing/godoxy/internal/jsonstore"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CaptchaSession struct {
|
type CaptchaSession struct {
|
||||||
@@ -22,7 +21,7 @@ var CaptchaSessions = jsonstore.Store[*CaptchaSession]("captcha_sessions")
|
|||||||
func newCaptchaSession(p Provider) *CaptchaSession {
|
func newCaptchaSession(p Provider) *CaptchaSession {
|
||||||
buf := make([]byte, 32)
|
buf := make([]byte, 32)
|
||||||
_, _ = rand.Read(buf)
|
_, _ = rand.Read(buf)
|
||||||
now := utils.TimeNow()
|
now := time.Now()
|
||||||
return &CaptchaSession{
|
return &CaptchaSession{
|
||||||
ID: hex.EncodeToString(buf),
|
ID: hex.EncodeToString(buf),
|
||||||
Expiry: now.Add(p.SessionExpiry()),
|
Expiry: now.Add(p.SessionExpiry()),
|
||||||
@@ -30,5 +29,5 @@ func newCaptchaSession(p Provider) *CaptchaSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CaptchaSession) expired() bool {
|
func (s *CaptchaSession) expired() bool {
|
||||||
return utils.TimeNow().After(s.Expiry)
|
return time.Now().After(s.Expiry)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
"github.com/puzpuzpuz/xsync/v4"
|
"github.com/puzpuzpuz/xsync/v4"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/yusing/godoxy/internal/common"
|
"github.com/yusing/godoxy/internal/common"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
"github.com/yusing/godoxy/internal/watcher"
|
"github.com/yusing/godoxy/internal/watcher"
|
||||||
"github.com/yusing/godoxy/internal/watcher/events"
|
"github.com/yusing/godoxy/internal/watcher/events"
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
|
"github.com/yusing/goutils/fs"
|
||||||
"github.com/yusing/goutils/task"
|
"github.com/yusing/goutils/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ func GetErrorPageByStatus(statusCode int) (content []byte, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadContent() {
|
func loadContent() {
|
||||||
files, err := utils.ListFiles(errPagesBasePath, 0)
|
files, err := fs.ListFiles(errPagesBasePath, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("failed to list error page resources")
|
log.Err(err).Msg("failed to list error page resources")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/yusing/godoxy/internal/common"
|
"github.com/yusing/godoxy/internal/common"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
|
fsutils "github.com/yusing/goutils/fs"
|
||||||
strutils "github.com/yusing/goutils/strings"
|
strutils "github.com/yusing/goutils/strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ func Get(name string) (*Middleware, Error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrUnknownMiddleware.
|
return nil, ErrUnknownMiddleware.
|
||||||
Subject(name).
|
Subject(name).
|
||||||
With(gperr.DoYouMean(utils.NearestField(name, allMiddlewares)))
|
With(gperr.DoYouMeanField(name, allMiddlewares))
|
||||||
}
|
}
|
||||||
return middleware, nil
|
return middleware, nil
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ func All() map[string]*Middleware {
|
|||||||
|
|
||||||
func LoadComposeFiles() {
|
func LoadComposeFiles() {
|
||||||
errs := gperr.NewBuilder("middleware compile errors")
|
errs := gperr.NewBuilder("middleware compile errors")
|
||||||
middlewareDefs, err := utils.ListFiles(common.MiddlewareComposeBasePath, 0)
|
middlewareDefs, err := fsutils.ListFiles(common.MiddlewareComposeBasePath, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ import (
|
|||||||
urlPkg "net/url"
|
urlPkg "net/url"
|
||||||
|
|
||||||
"github.com/bytedance/sonic"
|
"github.com/bytedance/sonic"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type URL struct {
|
type URL struct {
|
||||||
_ utils.NoCopy
|
|
||||||
urlPkg.URL
|
urlPkg.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/bytedance/sonic"
|
"github.com/bytedance/sonic"
|
||||||
"github.com/luthermonson/go-proxmox"
|
"github.com/luthermonson/go-proxmox"
|
||||||
"github.com/yusing/godoxy/internal/utils/pool"
|
"github.com/yusing/goutils/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/yusing/godoxy/internal/types"
|
"github.com/yusing/godoxy/internal/types"
|
||||||
"github.com/yusing/godoxy/internal/utils/pool"
|
"github.com/yusing/goutils/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/puzpuzpuz/xsync/v4"
|
"github.com/puzpuzpuz/xsync/v4"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
gi "github.com/yusing/gointernals"
|
gi "github.com/yusing/gointernals"
|
||||||
"github.com/yusing/goutils/env"
|
"github.com/yusing/goutils/env"
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
@@ -300,7 +299,7 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat
|
|||||||
errs.Add(err.Subject(k))
|
errs.Add(err.Subject(k))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errs.Add(ErrUnknownField.Subject(k).With(gperr.DoYouMean(utils.NearestField(k, info.fieldNames))))
|
errs.Add(ErrUnknownField.Subject(k).With(gperr.DoYouMeanField(k, info.fieldNames)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if info.hasValidateTag && checkValidateTag {
|
if info.hasValidateTag && checkValidateTag {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/yusing/ds/ordered"
|
"github.com/yusing/ds/ordered"
|
||||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||||
"github.com/yusing/godoxy/internal/utils"
|
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,8 +13,6 @@ type (
|
|||||||
|
|
||||||
PortMapping = map[int]container.Port
|
PortMapping = map[int]container.Port
|
||||||
Container struct {
|
Container struct {
|
||||||
_ utils.NoCopy
|
|
||||||
|
|
||||||
DockerHost string `json:"docker_host"`
|
DockerHost string `json:"docker_host"`
|
||||||
Image *ContainerImage `json:"image"`
|
Image *ContainerImage `json:"image"`
|
||||||
ContainerName string `json:"container_name"`
|
ContainerName string `json:"container_name"`
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"github.com/yusing/godoxy/internal/homepage"
|
"github.com/yusing/godoxy/internal/homepage"
|
||||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||||
provider "github.com/yusing/godoxy/internal/route/provider/types"
|
provider "github.com/yusing/godoxy/internal/route/provider/types"
|
||||||
"github.com/yusing/godoxy/internal/utils/pool"
|
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
"github.com/yusing/goutils/http/reverseproxy"
|
"github.com/yusing/goutils/http/reverseproxy"
|
||||||
|
"github.com/yusing/goutils/pool"
|
||||||
"github.com/yusing/goutils/task"
|
"github.com/yusing/goutils/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Recursively lists all files in a directory until `maxDepth` is reached
|
|
||||||
// Returns a slice of file paths relative to `dir`.
|
|
||||||
func ListFiles(dir string, maxDepth int, hideHidden ...bool) ([]string, error) {
|
|
||||||
entries, err := os.ReadDir(dir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error listing directory %s: %w", dir, err)
|
|
||||||
}
|
|
||||||
hideHiddenFiles := len(hideHidden) > 0 && hideHidden[0]
|
|
||||||
files := make([]string, 0)
|
|
||||||
for _, entry := range entries {
|
|
||||||
if hideHiddenFiles && entry.Name()[0] == '.' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if entry.IsDir() {
|
|
||||||
if maxDepth <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
subEntries, err := ListFiles(path.Join(dir, entry.Name()), maxDepth-1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files = append(files, subEntries...)
|
|
||||||
} else {
|
|
||||||
files = append(files, path.Join(dir, entry.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
strutils "github.com/yusing/goutils/strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NearestField(input string, s any) string {
|
|
||||||
minDistance := -1
|
|
||||||
nearestField := ""
|
|
||||||
var fields []string
|
|
||||||
switch s := s.(type) {
|
|
||||||
case []string:
|
|
||||||
fields = s
|
|
||||||
default:
|
|
||||||
t := reflect.TypeOf(s)
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
fields = make([]string, 0)
|
|
||||||
for i := range t.NumField() {
|
|
||||||
jsonTag, ok := t.Field(i).Tag.Lookup("json")
|
|
||||||
if ok {
|
|
||||||
fields = append(fields, jsonTag)
|
|
||||||
} else {
|
|
||||||
fields = append(fields, t.Field(i).Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Map:
|
|
||||||
keys := reflect.ValueOf(s).MapKeys()
|
|
||||||
fields = make([]string, len(keys))
|
|
||||||
for i, key := range keys {
|
|
||||||
fields[i] = key.String()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("NearestField unsupported type: " + t.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, field := range fields {
|
|
||||||
distance := strutils.LevenshteinDistance(input, field)
|
|
||||||
if minDistance == -1 || distance < minDistance {
|
|
||||||
minDistance = distance
|
|
||||||
nearestField = field
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nearestField
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
package pool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v4"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Pool[T Object] struct {
|
|
||||||
m *xsync.Map[string, T]
|
|
||||||
name string
|
|
||||||
disableLog atomic.Bool
|
|
||||||
}
|
|
||||||
// Preferable allows an object to express deterministic replacement preference
|
|
||||||
// when multiple objects with the same key are added to the pool.
|
|
||||||
// If new.PreferOver(old) returns true, the new object replaces the old one.
|
|
||||||
Preferable interface {
|
|
||||||
PreferOver(other any) bool
|
|
||||||
}
|
|
||||||
Object interface {
|
|
||||||
Key() string
|
|
||||||
Name() string
|
|
||||||
}
|
|
||||||
ObjectWithDisplayName interface {
|
|
||||||
Object
|
|
||||||
DisplayName() string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func New[T Object](name string) Pool[T] {
|
|
||||||
return Pool[T]{m: xsync.NewMap[string, T](), name: name}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) ToggleLog(v bool) {
|
|
||||||
p.disableLog.Store(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) Name() string {
|
|
||||||
return p.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) Add(obj T) {
|
|
||||||
p.AddKey(obj.Key(), obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) AddKey(key string, obj T) {
|
|
||||||
if cur, exists := p.m.Load(key); exists {
|
|
||||||
if newPref, ok := any(obj).(Preferable); ok {
|
|
||||||
if !newPref.PreferOver(cur) {
|
|
||||||
// keep existing
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.checkExists(key)
|
|
||||||
p.m.Store(key, obj)
|
|
||||||
p.logAction("added", obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) AddIfNotExists(obj T) (actual T, added bool) {
|
|
||||||
actual, loaded := p.m.LoadOrStore(obj.Key(), obj)
|
|
||||||
if !loaded {
|
|
||||||
p.logAction("added", obj)
|
|
||||||
}
|
|
||||||
return actual, !loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) Del(obj T) {
|
|
||||||
p.m.Delete(obj.Key())
|
|
||||||
p.logAction("removed", obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) DelKey(key string) {
|
|
||||||
if v, exists := p.m.LoadAndDelete(key); exists {
|
|
||||||
p.logAction("removed", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) Get(key string) (T, bool) {
|
|
||||||
return p.m.Load(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) Size() int {
|
|
||||||
return p.m.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) Clear() {
|
|
||||||
p.m.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) Iter(fn func(k string, v T) bool) {
|
|
||||||
p.m.Range(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) Slice() []T {
|
|
||||||
slice := make([]T, 0, p.m.Size())
|
|
||||||
for _, v := range p.m.Range {
|
|
||||||
slice = append(slice, v)
|
|
||||||
}
|
|
||||||
sort.Slice(slice, func(i, j int) bool {
|
|
||||||
return slice[i].Name() < slice[j].Name()
|
|
||||||
})
|
|
||||||
return slice
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool[T]) logAction(action string, obj T) {
|
|
||||||
if p.disableLog.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if withName, ok := any(obj).(ObjectWithDisplayName); ok {
|
|
||||||
disp, name := withName.DisplayName(), withName.Name()
|
|
||||||
if disp != name {
|
|
||||||
log.Info().Msgf("%s: %s %s (%s)", p.name, action, disp, name)
|
|
||||||
} else {
|
|
||||||
log.Info().Msgf("%s: %s %s", p.name, action, name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Info().Msgf("%s: %s %s", p.name, action, obj.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//go:build debug
|
|
||||||
|
|
||||||
package pool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p Pool[T]) checkExists(key string) {
|
|
||||||
if _, ok := p.m.Load(key); ok {
|
|
||||||
log.Warn().Msgf("%s: key %s already exists\nstacktrace: %s", p.name, key, string(debug.Stack()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
//go:build !debug
|
|
||||||
|
|
||||||
package pool
|
|
||||||
|
|
||||||
func (p Pool[T]) checkExists(key string) {
|
|
||||||
// no-op in production
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
TimeNow = DefaultTimeNow
|
|
||||||
shouldCallTimeNow atomic.Bool
|
|
||||||
timeNowTicker = time.NewTicker(shouldCallTimeNowInterval)
|
|
||||||
lastTimeNow = atomic.NewTime(time.Now())
|
|
||||||
)
|
|
||||||
|
|
||||||
const shouldCallTimeNowInterval = 100 * time.Millisecond
|
|
||||||
|
|
||||||
func MockTimeNow(t time.Time) {
|
|
||||||
TimeNow = func() time.Time {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultTimeNow is a time.Now wrapper that reduces the number of calls to time.Now
|
|
||||||
// by caching the result and only allow calling time.Now when the ticker fires.
|
|
||||||
//
|
|
||||||
// Returned value may have +-100ms error.
|
|
||||||
func DefaultTimeNow() time.Time {
|
|
||||||
swapped := shouldCallTimeNow.CompareAndSwap(false, true)
|
|
||||||
if swapped { // first call
|
|
||||||
now := time.Now()
|
|
||||||
lastTimeNow.Store(now)
|
|
||||||
return now
|
|
||||||
}
|
|
||||||
return lastTimeNow.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
go func() {
|
|
||||||
for range timeNowTicker.C {
|
|
||||||
shouldCallTimeNow.Store(true)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var sink time.Time
|
|
||||||
|
|
||||||
func BenchmarkTimeNow(b *testing.B) {
|
|
||||||
b.Run("default", func(b *testing.B) {
|
|
||||||
for b.Loop() {
|
|
||||||
sink = time.Now()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
b.Run("reduced_call", func(b *testing.B) {
|
|
||||||
for b.Loop() {
|
|
||||||
sink = DefaultTimeNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultTimeNow(t *testing.T) {
|
|
||||||
// Get initial time
|
|
||||||
t1 := DefaultTimeNow()
|
|
||||||
|
|
||||||
// Second call should return the same time without calling time.Now
|
|
||||||
t2 := DefaultTimeNow()
|
|
||||||
|
|
||||||
if !t1.Equal(t2) {
|
|
||||||
t.Errorf("Expected t1 == t2, got t1 = %v, t2 = %v", t1, t2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set shouldCallTimeNow to true
|
|
||||||
shouldCallTimeNow.Store(true)
|
|
||||||
|
|
||||||
// This should update the lastTimeNow
|
|
||||||
t3 := DefaultTimeNow()
|
|
||||||
|
|
||||||
// The time should have changed
|
|
||||||
if t2.Equal(t3) {
|
|
||||||
t.Errorf("Expected t2 != t3, got t2 = %v, t3 = %v", t2, t3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fourth call should return the same time as third call
|
|
||||||
t4 := DefaultTimeNow()
|
|
||||||
|
|
||||||
if !t3.Equal(t4) {
|
|
||||||
t.Errorf("Expected t3 == t4, got t3 = %v, t4 = %v", t3, t4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMockTimeNow(t *testing.T) {
|
|
||||||
// Save the original TimeNow function to restore later
|
|
||||||
originalTimeNow := TimeNow
|
|
||||||
defer func() {
|
|
||||||
TimeNow = originalTimeNow
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Create a fixed time
|
|
||||||
fixedTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)
|
|
||||||
|
|
||||||
// Mock the time
|
|
||||||
MockTimeNow(fixedTime)
|
|
||||||
|
|
||||||
// TimeNow should return the fixed time
|
|
||||||
result := TimeNow()
|
|
||||||
|
|
||||||
if !result.Equal(fixedTime) {
|
|
||||||
t.Errorf("Expected %v, got %v", fixedTime, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeNowTicker(t *testing.T) {
|
|
||||||
// This test verifies that the ticker properly updates shouldCallTimeNow
|
|
||||||
|
|
||||||
// Reset the flag
|
|
||||||
shouldCallTimeNow.Store(false)
|
|
||||||
|
|
||||||
// Wait for the ticker to tick (slightly more than the interval)
|
|
||||||
time.Sleep(shouldCallTimeNowInterval + 10*time.Millisecond)
|
|
||||||
|
|
||||||
// The ticker should have set shouldCallTimeNow to true
|
|
||||||
if !shouldCallTimeNow.Load() {
|
|
||||||
t.Error("Expected shouldCallTimeNow to be true after ticker interval")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call DefaultTimeNow which should reset the flag
|
|
||||||
DefaultTimeNow()
|
|
||||||
|
|
||||||
// Check that the flag is reset
|
|
||||||
if shouldCallTimeNow.Load() {
|
|
||||||
t.Error("Expected shouldCallTimeNow to be false after calling DefaultTimeNow")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
BenchmarkTimeNow
|
|
||||||
BenchmarkTimeNow/default
|
|
||||||
BenchmarkTimeNow/default-20 48158628 24.86 ns/op 0 B/op 0 allocs/op
|
|
||||||
BenchmarkTimeNow/reduced_call
|
|
||||||
BenchmarkTimeNow/reduced_call-20 1000000000 1.000 ns/op 0 B/op 0 allocs/op
|
|
||||||
*/
|
|
||||||
Reference in New Issue
Block a user