publish v12.2.0-alpha6

This commit is contained in:
Gerasimos (Makis) Maropoulos 2022-02-18 22:19:33 +02:00
parent 4899fe95f4
commit 41026c9209
No known key found for this signature in database
GPG Key ID: 66FCC29BD385FCA6
21 changed files with 984 additions and 284 deletions

View File

@ -28,8 +28,13 @@ The codebase for Dependency Injection, Internationalization and localization and
## Fixes and Improvements
- Minor improvements to the [JSON Kitchen Time](x/jsonx/kitchen_time.go).
- A session database can now implement the `EndRequest(ctx *context.Context, session *Session)` method which will be fired at the end of the request-response lifecycle.
- Improvements on JSON and ReadJSON when `Iris.Configuration.EnableOptimizations` is true. The request's Context is used whenever is necessary.
- New [monitor](_examples/monitor/monitor-middleware/main.go) middleware.
- New `RegisterRequestHandler` package-level and client methods to the new `x/client` package. Control or log the request-response lifecycle.
- New `RateLimit` HTTP Client option to the new `x/client` package.
- New `RateLimit` and `Debug` HTTP Client options to the new `x/client` package.
- Push a security fix reported by [Kirill Efimov](https://github.com/kirill89) for older go runtimes.

View File

@ -93,6 +93,7 @@
* [The CSV Formatter](logging/request-logger/accesslog-csv/main.go)
* [Create your own Formatter](logging/request-logger/accesslog-formatter/main.go)
* [Root and Proxy AccessLog instances](logging/request-logger/accesslog-proxy/main.go)
* [Slack integration example](logging/request-logger/accesslog-slack/main.go)
* API Documentation
* [Yaag](apidoc/yaag/main.go)
* [Swagger](https://github.com/iris-contrib/swagger/tree/master/_examples/basic)

View File

@ -0,0 +1,5 @@
package main
func main() {
println("Navigate to: https://github.com/kataras/iris/issues/1808#issuecomment-1013757925")
}

View File

@ -0,0 +1,64 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/monitor"
)
func main() {
app := iris.New()
// Initialize and start the monitor middleware.
m := monitor.New(monitor.Options{
RefreshInterval: 2 * time.Second,
ViewRefreshInterval: 2 * time.Second,
ViewTitle: "MyServer Monitor",
})
// Manually stop monitoring on CMD/CTRL+C.
iris.RegisterOnInterrupt(m.Stop)
// Serve the actual server's process and operating system statistics as JSON.
app.Post("/monitor", m.Stats)
// Render with the default page.
app.Get("/monitor", m.View)
/* You can protect the /monitor under an /admin group of routes
with basic authentication or any type authorization and authentication system.
Example Code:
app.Post("/monitor", myProtectMiddleware, m.Stats)
app.Get("/monitor", myProtectMiddleware, m.View)
*/
/* You can also get the OS statistics using the Holder.GetStats method.
Example Code:
for {
stats := m.Holder.GetStats()
fmt.Printf("%#+v\n", stats)
time.Sleep(time.Second)
}
Note that the same stats are also stored in the expvar metrics:
- pid_cpu
- pid_ram
- pid_conns
- os_cpu
- os_ram
- os_total_ram
- os_load_avg
- os_conns
Check https://github.com/iris-contrib/middleware/tree/master/expmetric
which can be integrated with datadog or other platforms.
*/
app.Get("/", handler)
app.Listen(":8080")
}
func handler(ctx iris.Context) {
ctx.WriteString("Test Index Handler")
}

View File

@ -16,7 +16,7 @@ type BusinessModel struct {
// NewApp returns a new application for showcasing the sessions feature.
func NewApp(sess *sessions.Sessions) *iris.Application {
app := iris.New()
app.Use(sess.Handler()) // session is always non-nil inside handlers now.
app.Use(sess.Handler()) // register the session manager on a group of routes or the root app.
app.Get("/", func(ctx iris.Context) {
session := sessions.Get(ctx) // same as sess.Start(ctx, cookieOptions...)

View File

@ -15,23 +15,21 @@ import (
)
func newApp() *iris.Application {
app := iris.New()
cookieName := "_session_id"
// AES only supports key sizes of 16, 24 or 32 bytes.
// You either need to provide exactly that amount or you derive the key from what you type in.
hashKey := securecookie.GenerateRandomKey(64)
blockKey := securecookie.GenerateRandomKey(32)
s := securecookie.New(hashKey, blockKey)
mySessions := sessions.New(sessions.Config{
Cookie: cookieName,
Encoding: s,
AllowReclaim: true,
})
app = example.NewApp(mySessions)
return app
// mySessions.UseDatabase(...see sessions/database example folder)
return example.NewApp(mySessions)
}
func main() {

View File

@ -30,8 +30,8 @@ import (
"github.com/Shopify/goreferrer"
"github.com/fatih/structs"
gojson "github.com/goccy/go-json"
"github.com/iris-contrib/schema"
jsoniter "github.com/json-iterator/go"
"github.com/kataras/golog"
"github.com/mailru/easyjson"
"github.com/mailru/easyjson/jwriter"
@ -67,6 +67,12 @@ type (
Decode(data []byte) error
}
// BodyDecoderWithContext same as BodyDecoder but it can accept a standard context,
// which is binded to the HTTP request's context.
BodyDecoderWithContext interface {
DecodeContext(ctx stdContext.Context, data []byte) error
}
// Unmarshaler is the interface implemented by types that can unmarshal any raw data.
// TIP INFO: Any pointer to a value which implements the BodyDecoder can be override the unmarshaler.
Unmarshaler interface {
@ -85,7 +91,7 @@ type (
// is terminated and the error is received by the ReadJSONStream method,
// otherwise it continues to read the next available object.
// Look the `Context.ReadJSONStream` method.
DecodeFunc func(outPtr interface{}) error
DecodeFunc func(ctx stdContext.Context, outPtr interface{}) error
)
// Unmarshal parses the X-encoded data and stores the result in the value pointed to by v.
@ -2297,6 +2303,10 @@ func (ctx *Context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) e
return err
}
if decoderWithCtx, ok := outPtr.(BodyDecoderWithContext); ok {
return decoderWithCtx.DecodeContext(ctx.request.Context(), rawData)
}
// check if the v contains its own decode
// in this case the v should be a pointer also,
// but this is up to the user's custom Decode implementation*
@ -2321,36 +2331,6 @@ func (ctx *Context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) e
return ctx.app.Validate(outPtr)
}
// internalBodyDecoder is a generic type of decoder, usually used to export stream reading functionality
// of a JSON request.
type internalBodyDecoder interface {
Decode(outPutr interface{}) error
}
// Same as UnmarshalBody but it operates on body stream.
func (ctx *Context) decodeBody(outPtr interface{}, decoder internalBodyDecoder) error {
// check if the v contains its own decode
// in this case the v should be a pointer also,
// but this is up to the user's custom Decode implementation*
//
// See 'BodyDecoder' for more.
if structDecoder, isDecoder := outPtr.(BodyDecoder); isDecoder {
rawData, err := ctx.GetBody()
if err != nil {
return err
}
return structDecoder.Decode(rawData)
}
err := decoder.Decode(outPtr)
if err != nil {
return err
}
return ctx.app.Validate(outPtr)
}
func (ctx *Context) shouldOptimize() bool {
return ctx.app.ConfigurationReadOnly().GetEnableOptimizations()
}
@ -2379,45 +2359,69 @@ type JSONReader struct { // Note(@kataras): struct instead of optional funcs to
// {"username": "makis"}
// {"username": "george"}
ArrayStream bool
// Optional context cancelation of decoder when Optimize field is enabled.
// On ReadJSON method this is automatically binded to the request context.
Context stdContext.Context
}
type internalJSONDecoder interface {
internalBodyDecoder
DisallowUnknownFields()
Token() (json.Token, error) // gojson.Token is an alias of this, so we are ok.
More() bool
DisallowUnknownFields()
}
func (cfg JSONReader) getDecoder(r io.Reader, globalShouldOptimize bool) (decoder internalJSONDecoder) {
if cfg.Optimize || globalShouldOptimize {
decoder = jsoniter.ConfigCompatibleWithStandardLibrary.NewDecoder(r)
func (options JSONReader) getDecoder(r io.Reader) (internalJSONDecoder, DecodeFunc) {
var (
decoder internalJSONDecoder
decodeFunc DecodeFunc
)
if options.Optimize {
dec := gojson.NewDecoder(r)
decodeFunc = dec.DecodeContext
decoder = dec
} else {
decoder = json.NewDecoder(r)
dec := json.NewDecoder(r)
decodeFunc = func(_ stdContext.Context, outPtr interface{}) error {
return dec.Decode(outPtr)
}
decoder = dec
}
if cfg.DisallowUnknownFields {
if options.DisallowUnknownFields {
decoder.DisallowUnknownFields()
}
return
return decoder, decodeFunc
}
// ReadJSON reads JSON from request's body and binds it to a value of any json-valid type.
//
// Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-json/main.go
func (ctx *Context) ReadJSON(outPtr interface{}, opts ...JSONReader) error {
shouldOptimize := ctx.shouldOptimize()
var options JSONReader
options.Optimize = ctx.shouldOptimize()
if len(opts) > 0 {
cfg := opts[0]
return ctx.decodeBody(outPtr, cfg.getDecoder(ctx.request.Body, shouldOptimize))
options = opts[0]
}
unmarshaler := json.Unmarshal
if shouldOptimize {
unmarshaler = jsoniter.Unmarshal
}
_, decodeFunc := options.getDecoder(ctx.request.Body)
return decodeFunc(ctx.request.Context(), outPtr)
return ctx.UnmarshalBody(outPtr, UnmarshalerFunc(unmarshaler))
/*
b, err := ctx.GetBody()
if err != nil {
return err
}
if options.Optimize {
return gojson.UnmarshalContext(ctx.request.Context(), b, outPtr)
} else {
return json.Unmarshal(b, outPtr)
}
*/
}
// ReadJSONStream is an alternative of ReadJSON which can reduce the memory load
@ -2431,20 +2435,14 @@ func (ctx *Context) ReadJSON(outPtr interface{}, opts ...JSONReader) error {
//
// Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-json-stream/main.go
func (ctx *Context) ReadJSONStream(onDecode func(DecodeFunc) error, opts ...JSONReader) error {
var cfg JSONReader
var options JSONReader
if len(opts) > 0 {
cfg = opts[0]
options = opts[0]
}
// note that only the standard package supports an object
// stream of arrays (when the receiver is not an array).
if cfg.ArrayStream || !cfg.Optimize {
decoder := json.NewDecoder(ctx.request.Body)
if cfg.DisallowUnknownFields {
decoder.DisallowUnknownFields()
}
decodeFunc := decoder.Decode
decoder, decodeFunc := options.getDecoder(ctx.request.Body)
if options.ArrayStream {
_, err := decoder.Token() // read open bracket.
if err != nil {
return err
@ -2460,11 +2458,8 @@ func (ctx *Context) ReadJSONStream(onDecode func(DecodeFunc) error, opts ...JSON
return err
}
dec := cfg.getDecoder(ctx.request.Body, ctx.shouldOptimize())
decodeFunc := dec.Decode
// while the array contains values
for dec.More() {
for decoder.More() {
if err := onDecode(decodeFunc); err != nil {
return err
}
@ -2496,6 +2491,10 @@ var (
return false
}
if errors.Is(err, io.EOF) {
return true
}
if v, ok := err.(*json.SyntaxError); ok {
// standard go json encoder error.
return v.Offset == 0 && v.Error() == "unexpected end of JSON input"
@ -3518,6 +3517,30 @@ type JSON struct {
Secure bool // if true then it prepends a "while(1);" when Go slice (to JSON Array) value.
// proto.Message specific marshal options.
Proto ProtoMarshalOptions
// Optional context cancelation of encoder when Iris optimizations field is enabled.
// On JSON method this is automatically binded to the request context.
Context stdContext.Context
}
// IsDefault reports whether this JSON options structure holds the default values.
func (j *JSON) IsDefault() bool {
return j.StreamingJSON == DefaultJSONOptions.StreamingJSON &&
j.UnescapeHTML == DefaultJSONOptions.UnescapeHTML &&
j.Indent == DefaultJSONOptions.Indent &&
j.Prefix == DefaultJSONOptions.Prefix &&
j.ASCII == DefaultJSONOptions.ASCII &&
j.Secure == DefaultJSONOptions.Secure &&
j.Proto == DefaultJSONOptions.Proto
}
// GetContext returns the option's Context or the HTTP request's one.
func (j *JSON) GetContext(ctx *Context) stdContext.Context {
if j.Context == nil {
return ctx.request.Context()
}
return j.Context
}
// JSONP contains the options for the JSONP (Context's) Renderer.
@ -3558,48 +3581,63 @@ var (
secureJSONPrefix = []byte("while(1);")
)
// WriteJSON marshals the given interface object and writes the JSON response to the 'writer'.
// Ignores StatusCode and StreamingJSON options.
func WriteJSON(writer io.Writer, v interface{}, options JSON, optimize bool) (int, error) {
var (
result []byte
err error
)
func handleJSONResponseValue(w io.Writer, v interface{}, options JSON) (bool, int, error) {
if m, ok := v.(proto.Message); ok {
result, err = options.Proto.Marshal(m)
result, err := options.Proto.Marshal(m)
if err != nil {
return 0, err
return true, 0, err
}
return writer.Write(result)
n, err := w.Write(result)
return true, n, err
}
if easyObject, ok := v.(easyjson.Marshaler); ok {
jw := jwriter.Writer{NoEscapeHTML: !options.UnescapeHTML}
easyObject.MarshalEasyJSON(&jw)
return jw.DumpTo(writer)
n, err := jw.DumpTo(w)
return true, n, err
}
if !optimize && options.Indent == "" {
return false, 0, nil
}
// WriteJSON marshals the given interface object and writes the JSON response to the 'writer'.
// Ignores StatusCode and StreamingJSON options.
func WriteJSON(writer io.Writer, v interface{}, options JSON, shouldOptimize bool) (int, error) {
if handled, n, err := handleJSONResponseValue(writer, v, options); handled {
return n, err
}
var (
result []byte
err error
)
if !shouldOptimize && options.Indent == "" {
options.Indent = " "
}
if indent := options.Indent; indent != "" {
marshalIndent := json.MarshalIndent
if optimize {
marshalIndent = jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent
if shouldOptimize {
// result,err = jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent
result, err = gojson.MarshalIndent(v, "", indent)
} else {
result, err = json.MarshalIndent(v, "", indent)
}
result, err = marshalIndent(v, "", indent)
result = append(result, newLineB...)
} else {
marshal := json.Marshal
if optimize {
marshal = jsoniter.ConfigCompatibleWithStandardLibrary.Marshal
if shouldOptimize {
// result, err = jsoniter.ConfigCompatibleWithStandardLibrary.Marshal
if options.Context != nil {
result, err = gojson.MarshalContext(options.Context, v)
} else {
result, err = gojson.Marshal(v)
}
} else {
result, err = json.Marshal(v)
}
result, err = marshal(v)
}
if err != nil {
@ -3668,22 +3706,43 @@ var DefaultJSONOptions = JSON{}
// If the value is a compatible `proto.Message` one
// then it only uses the options.Proto settings to marshal.
func (ctx *Context) JSON(v interface{}, opts ...JSON) (n int, err error) {
options := DefaultJSONOptions
ctx.ContentType(ContentJSONHeaderValue)
shouldOptimize := ctx.shouldOptimize()
if len(opts) > 0 {
optsLength := len(opts)
if shouldOptimize && optsLength == 0 { // if no options given and optimizations are enabled.
// try handle proto or easyjson.
if handled, n, err := handleJSONResponseValue(ctx, v, DefaultJSONOptions); handled {
return n, err
}
// as soon as possible, use the fast json marshaler with the http request context.
result, err := gojson.MarshalContext(ctx.request.Context(), v)
if err != nil {
return 0, err
}
return ctx.Write(result)
}
options := DefaultJSONOptions
if optsLength > 0 {
options = opts[0]
}
ctx.ContentType(ContentJSONHeaderValue)
if options.StreamingJSON {
if ctx.shouldOptimize() {
jsoniterConfig := jsoniter.Config{
EscapeHTML: !options.UnescapeHTML,
IndentionStep: 4,
}.Froze()
enc := jsoniterConfig.NewEncoder(ctx.writer)
err = enc.Encode(v)
if shouldOptimize {
// jsoniterConfig := jsoniter.Config{
// EscapeHTML: !options.UnescapeHTML,
// IndentionStep: 4,
// }.Froze()
// enc := jsoniterConfig.NewEncoder(ctx.writer)
// err = enc.Encode(v)
enc := gojson.NewEncoder(ctx.writer)
enc.SetEscapeHTML(!options.UnescapeHTML)
enc.SetIndent(options.Prefix, options.Indent)
err = enc.EncodeContext(options.GetContext(ctx), v)
} else {
enc := json.NewEncoder(ctx.writer)
enc.SetEscapeHTML(!options.UnescapeHTML)
@ -3699,7 +3758,7 @@ func (ctx *Context) JSON(v interface{}, opts ...JSON) (n int, err error) {
return ctx.writer.Written(), err
}
n, err = WriteJSON(ctx.writer, v, options, ctx.shouldOptimize())
n, err = WriteJSON(ctx.writer, v, options, shouldOptimize)
if err != nil {
ctx.app.Logger().Debugf("JSON: %v", err)
ctx.StatusCode(http.StatusInternalServerError)
@ -3728,7 +3787,8 @@ func WriteJSONP(writer io.Writer, v interface{}, options JSONP, optimize bool) (
if indent := options.Indent; indent != "" {
marshalIndent := json.MarshalIndent
if optimize {
marshalIndent = jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent
// marshalIndent = jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent
marshalIndent = gojson.MarshalIndent
}
result, err := marshalIndent(v, "", indent)
@ -3741,7 +3801,8 @@ func WriteJSONP(writer io.Writer, v interface{}, options JSONP, optimize bool) (
marshal := json.Marshal
if optimize {
marshal = jsoniter.ConfigCompatibleWithStandardLibrary.Marshal
// marshal = jsoniter.ConfigCompatibleWithStandardLibrary.Marshal
marshal = gojson.Marshal
}
result, err := marshal(v)
@ -5177,21 +5238,30 @@ func (ctx *Context) SetCookieKV(name, value string, options ...CookieOption) {
// returns empty string if nothing was found.
//
// If you want more than the value then:
// cookie, err := ctx.Request().Cookie("name")
// cookie, err := ctx.GetRequestCookie("name")
//
// Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
func (ctx *Context) GetCookie(name string, options ...CookieOption) string {
c, err := ctx.request.Cookie(name)
c, err := ctx.GetRequestCookie(name, options...)
if err != nil {
return ""
}
ctx.applyCookieOptions(c, OpCookieGet, options)
value, _ := url.QueryUnescape(c.Value)
return value
}
// GetRequestCookie returns the request cookie including any context's cookie options (stored or given by this method).
func (ctx *Context) GetRequestCookie(name string, options ...CookieOption) (*http.Cookie, error) {
c, err := ctx.request.Cookie(name)
if err != nil {
return nil, err
}
ctx.applyCookieOptions(c, OpCookieGet, options)
return c, nil
}
var (
// CookieExpireDelete may be set on Cookie.Expire for expiring the given cookie.
CookieExpireDelete = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)

54
go.mod
View File

@ -5,7 +5,7 @@ go 1.17
// retract v12.1.8 // please update to @master
require (
github.com/BurntSushi/toml v0.4.1
github.com/BurntSushi/toml v1.0.0
github.com/CloudyKit/jet/v6 v6.1.0
github.com/Shopify/goreferrer v0.0.0-20210630161223-536fa16abd6f
github.com/andybalholm/brotli v1.0.4
@ -16,6 +16,7 @@ require (
github.com/fatih/structs v1.1.0
github.com/flosch/pongo2/v4 v4.0.2
github.com/go-redis/redis/v8 v8.11.4
github.com/goccy/go-json v0.9.4
github.com/golang/snappy v0.0.4
github.com/google/uuid v1.3.0
github.com/iris-contrib/httpexpect/v2 v2.0.5
@ -29,22 +30,23 @@ require (
github.com/kataras/pio v0.0.10
github.com/kataras/sitemap v0.0.5
github.com/kataras/tunnel v0.0.3
github.com/klauspost/compress v1.13.6
github.com/klauspost/compress v1.14.3
github.com/mailru/easyjson v0.7.7
github.com/microcosm-cc/bluemonday v1.0.16
github.com/microcosm-cc/bluemonday v1.0.18
github.com/russross/blackfriday/v2 v2.1.0
github.com/schollz/closestmatch v2.1.0+incompatible
github.com/tdewolff/minify/v2 v2.9.22
github.com/shirou/gopsutil/v3 v3.22.1
github.com/tdewolff/minify/v2 v2.10.0
github.com/vmihailenco/msgpack/v5 v5.3.5
github.com/yosssi/ace v0.0.5
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
golang.org/x/sys v0.0.0-20220209214540-3681064d5158
golang.org/x/text v0.3.7
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
google.golang.org/protobuf v1.27.1
gopkg.in/ini.v1 v1.66.2
gopkg.in/ini.v1 v1.66.4
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
@ -56,44 +58,50 @@ require (
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.1.0 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/gobwas/ws v1.0.4 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/iris-contrib/go.uuid v2.0.0+incompatible // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mediocregopher/radix/v3 v3.8.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/mediocregopher/radix/v3 v3.6.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nats-io/nats-server/v2 v2.6.5 // indirect
github.com/nats-io/nats.go v1.13.1-0.20211122170419-d7c1d78a50fc // indirect
github.com/nats-io/nats-server/v2 v2.7.2 // indirect
github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d // indirect
github.com/nats-io/nkeys v0.3.0 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/shiyanhui/hero v0.0.2 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/tdewolff/parse/v2 v2.5.22 // indirect
github.com/tdewolff/parse/v2 v2.5.27 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
github.com/yudai/gojsondiff v1.0.0 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
moul.io/http2curl v1.0.0 // indirect
)

118
go.sum generated
View File

@ -1,6 +1,6 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.1.0 h1:hvO96X345XagdH1fAoBjpBYG4a1ghhL/QzalkduPuXk=
@ -24,7 +24,6 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
@ -37,12 +36,10 @@ 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/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
@ -58,19 +55,19 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 h1:YyrUZvJaU8Q0QsoVo+xLFBgWDTam29PKea6GYmwvSiQ=
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI=
github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@ -88,13 +85,12 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -139,12 +135,14 @@ github.com/kataras/tunnel v0.0.3 h1:+8eHXujPD3wLnqTbYtPGa/3/Jc+Eq+bsPwEGTeFBB00=
github.com/kataras/tunnel v0.0.3/go.mod h1:VOlCoaUE5zN1buE+yAjWCkjfQ9hxGuhomKLsjei/5Zs=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.14.3 h1:DQv1WP+iS4srNjibdnHtqu8JNWCDMluj5NzPnFJsnvk=
github.com/klauspost/compress v1.14.3/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@ -153,30 +151,27 @@ github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZb
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mediocregopher/radix/v3 v3.6.0 h1:L18rTxOP19e/S1d+8VW13OEKiVLwUjvskfq7BhJCjCU=
github.com/mediocregopher/radix/v3 v3.6.0/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/mediocregopher/radix/v3 v3.8.0 h1:HI8EgkaM7WzsrFpYAkOXIgUKbjNonb2Ne7K6Le61Pmg=
github.com/mediocregopher/radix/v3 v3.8.0/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.16 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc=
github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo=
github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0=
github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/jwt/v2 v2.1.0 h1:1UbfD5g1xTdWmSeRV8bh/7u+utTiBsRtWhLl1PixZp4=
github.com/nats-io/jwt/v2 v2.1.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
github.com/nats-io/nats-server/v2 v2.6.5 h1:VTG8gdSw4bEqMwKudOHkBLqGwNpNaJOwruj3+rquQlQ=
github.com/nats-io/nats-server/v2 v2.6.5/go.mod h1:LlMieumxNUnCloOTVFv7Wog0YnasScxARUMXVXv9/+M=
github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296 h1:vU9tpM3apjYlLLeY23zRWJ9Zktr5jp+mloR942LEOpY=
github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
github.com/nats-io/nats-server/v2 v2.7.2 h1:+LEN8m0+jdCkiGc884WnDuxR+qj80/5arj+szKuRpRI=
github.com/nats-io/nats-server/v2 v2.7.2/go.mod h1:tckmrt0M6bVaDT3kmh9UrIq/CBOBBse+TpXQi5ldaa8=
github.com/nats-io/nats.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE=
github.com/nats-io/nats.go v1.13.1-0.20211018182449-f2416a8b1483/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
github.com/nats-io/nats.go v1.13.1-0.20211122170419-d7c1d78a50fc h1:SHr4MUUZJ/fAC0uSm2OzWOJYsHpapmR86mpw7q1qPXU=
github.com/nats-io/nats.go v1.13.1-0.20211122170419-d7c1d78a50fc/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d h1:GRSmEJutHkdoxKsRypP575IIdoXe7Bm6yHQF6GcDBnA=
github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.4/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
@ -197,11 +192,12 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -209,9 +205,12 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil/v3 v3.22.1 h1:33y31Q8J32+KstqPfscvFwBlNJ6xLaBy4xqBXzlYV5w=
github.com/shirou/gopsutil/v3 v3.22.1/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
github.com/shiyanhui/hero v0.0.2 h1:RF8fwiIeWbVsdki8LCS905pxLjCQbOz/NcKE0g1ZOJc=
github.com/shiyanhui/hero v0.0.2/go.mod h1:aBlyf5bmklQfvOmVQm5i04lIGFY7t2QYIJdqEMNGJZM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
@ -235,13 +234,16 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify/v2 v2.9.22 h1:PlmaAakaJHdMMdTTwjjsuSwIxKqWPTlvjTj6a/g/ILU=
github.com/tdewolff/minify/v2 v2.9.22/go.mod h1:dNlaFdXaIxgSXh3UFASqjTY0/xjpDkkCsYHA1NCGnmQ=
github.com/tdewolff/parse/v2 v2.5.21/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/parse/v2 v2.5.22 h1:KXMHTyx4VTL6Zu9a94SULQalDMvtP5FQq10mnSfaoGs=
github.com/tdewolff/parse/v2 v2.5.22/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/minify/v2 v2.10.0 h1:ovVAHUcjfGrBDf1EIvsodRUVJiZK/28mMose08B7k14=
github.com/tdewolff/minify/v2 v2.10.0/go.mod h1:6XAjcHM46pFcRE0eztigFPm0Q+Cxsw8YhEWT+rDkcZM=
github.com/tdewolff/parse/v2 v2.5.27 h1:PL3LzzXaOpmdrknnOlIeO2muIBHAwiKp6TxN1RbU5gI=
github.com/tdewolff/parse/v2 v2.5.27/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
@ -249,9 +251,8 @@ github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
@ -268,6 +269,8 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDf
github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -277,9 +280,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -293,8 +296,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c h1:WtYZ93XtWSO5KlOMgPZu7hXY9WhMZpprvlm5VwvAl8c=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
@ -306,29 +309,34 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -353,8 +361,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -51,6 +51,32 @@ func (f *Template) SetOutput(dest io.Writer) {
const defaultTmplText = "{{.Now.Format .TimeFormat}}|{{.Latency}}|{{.Code}}|{{.Method}}|{{.Path}}|{{.IP}}|{{.RequestValuesLine}}|{{.BytesReceivedLine}}|{{.BytesSentLine}}|{{.Request}}|{{.Response}}|\n"
func (f *Template) LogText(log *Log) (string, error) {
var err error
// A template may be executed safely in parallel, although if parallel
// executions share a Writer the output may be interleaved.
// We solve that using a buffer pool, no locks when template is executing (x2 performance boost).
temp := f.ac.bufPool.Get().(*bytes.Buffer)
if f.TmplName != "" {
err = f.Tmpl.ExecuteTemplate(temp, f.TmplName, log)
} else {
err = f.Tmpl.Execute(temp, log)
}
if err != nil {
f.ac.bufPool.Put(temp)
return "", err
}
text := temp.String()
temp.Reset()
f.ac.bufPool.Put(temp)
return text, nil
}
// Format prints the logs in text/template format.
func (f *Template) Format(log *Log) (bool, error) {
var err error

View File

@ -0,0 +1,33 @@
package monitor
import (
"expvar"
"strconv"
"sync/atomic"
)
// Uint64 completes the expvar metric interface, holds an uint64 value.
type Uint64 struct {
value uint64
}
// Set sets v to value.
func (v *Uint64) Set(value uint64) {
atomic.StoreUint64(&v.value, value)
}
// Value returns the underline uint64 value.
func (v *Uint64) Value() uint64 {
return atomic.LoadUint64(&v.value)
}
// String returns the text representation of the underline uint64 value.
func (v *Uint64) String() string {
return strconv.FormatUint(atomic.LoadUint64(&v.value), 10)
}
func newUint64(name string) *Uint64 {
v := new(Uint64)
expvar.Publish(name, v)
return v
}

View File

@ -0,0 +1,105 @@
package monitor
import (
"bytes"
"fmt"
"os"
"time"
"github.com/kataras/iris/v12/context"
"github.com/shirou/gopsutil/v3/process"
)
func init() {
context.SetHandlerName("iris/middleware/monitor.*", "iris.monitor")
}
// Options holds the optional fields for the Monitor structure.
type Options struct {
// Optional process id, defaults to the current one.
PID int32 `json:"pid" yaml:"PID"`
RefreshInterval time.Duration `json:"refresh_interval" yaml:"RefreshInterval"`
ViewRefreshInterval time.Duration `json:"view_refresh_interval" yaml:"ViewRefreshInterval"`
// If more than zero enables line animation. Defaults to zero.
ViewAnimationInterval time.Duration `json:"view_animation_interval" yaml:"ViewAnimationInterval"`
// The title of the monitor HTML document.
ViewTitle string `json:"view_title" yaml:"ViewTitle"`
}
// Monitor tracks and renders the server's process and operating system statistics.
//
// Look its `Stats` and `View` methods.
// Initialize with the `New` package-level function.
type Monitor struct {
opts Options
Holder *StatsHolder
viewBody []byte
}
// New returns a new Monitor.
// Metrics stored through expvar standard package:
// - pid_cpu
// - pid_ram
// - pid_conns
// - os_cpu
// - os_ram
// - os_total_ram
// - os_load_avg
// - os_conns
//
// Check https://github.com/iris-contrib/middleware/tree/master/expmetric
// which can be integrated with datadog or other platforms.
func New(opts Options) *Monitor {
if opts.PID == 0 {
opts.PID = int32(os.Getpid())
}
if opts.RefreshInterval <= 0 {
opts.RefreshInterval = 2 * opts.RefreshInterval
}
if opts.ViewRefreshInterval <= 0 {
opts.ViewRefreshInterval = opts.RefreshInterval
}
viewRefreshIntervalBytes := []byte(fmt.Sprintf("%d", opts.ViewRefreshInterval.Milliseconds()))
viewBody := bytes.Replace(defaultViewBody, viewRefreshIntervalTmplVar, viewRefreshIntervalBytes, 1)
viewAnimationIntervalBytes := []byte(fmt.Sprintf("%d", opts.ViewAnimationInterval.Milliseconds()))
viewBody = bytes.Replace(viewBody, viewAnimationIntervalTmplVar, viewAnimationIntervalBytes, 2)
viewTitleBytes := []byte(opts.ViewTitle)
viewBody = bytes.Replace(viewBody, viewTitleTmplVar, viewTitleBytes, 2)
proc, err := process.NewProcess(opts.PID)
if err != nil {
panic(err)
}
sh := startNewStatsHolder(proc, opts.RefreshInterval)
m := &Monitor{
opts: opts,
Holder: sh,
viewBody: viewBody,
}
return m
}
// Stop terminates the retrieve stats loop for
// the process and the operating system statistics.
// No other monitor instance should be initialized after the first Stop call.
func (m *Monitor) Stop() {
m.Holder.Stop()
}
// Stats sends the stats as json.
func (m *Monitor) Stats(ctx *context.Context) {
ctx.JSON(m.Holder.GetStats())
}
// View renders a default view for the stats.
func (m *Monitor) View(ctx *context.Context) {
ctx.ContentType("text/html")
ctx.Write(m.viewBody)
}

205
middleware/monitor/stats.go Normal file
View File

@ -0,0 +1,205 @@
package monitor
import (
"expvar"
"sync"
"time"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
"github.com/shirou/gopsutil/v3/process"
)
// Stats holds the process and operating system statistics values.
//
// Note that each statistic has its own expvar metric that you can use
// to render e.g. through statsd. Available values:
// * pid_cpu
// * pid_ram
// * pid_conns
// * os_cpu
// * os_ram
// * os_total_ram
// * os_load_avg
// * os_conns
type Stats struct {
PIDCPU float64 `json:"pid_cpu" yaml:"PIDCPU"`
PIDRAM uint64 `json:"pid_ram" yaml:"PIDRAM"`
PIDConns int64 `json:"pid_conns" yaml:"PIDConns"`
OSCPU float64 `json:"os_cpu" yaml:"OSCPU"`
OSRAM uint64 `json:"os_ram" yaml:"OSRAM"`
OSTotalRAM uint64 `json:"os_total_ram" yaml:"OSTotalRAM"`
OSLoadAvg float64 `json:"os_load_avg" yaml:"OSLoadAvg"`
OSConns int64 `json:"os_conns" yaml:"OSConns"`
}
// StatsHolder holds and refreshes the statistics.
type StatsHolder struct {
proc *process.Process
stats *Stats
mu sync.RWMutex
started bool
closeCh chan struct{}
errCh chan error
}
func startNewStatsHolder(proc *process.Process, refreshInterval time.Duration) *StatsHolder {
sh := newStatsHolder(proc)
sh.start(refreshInterval)
return sh
}
func newStatsHolder(proc *process.Process) *StatsHolder {
sh := &StatsHolder{
proc: proc,
stats: new(Stats),
closeCh: make(chan struct{}),
errCh: make(chan error, 1),
}
return sh
}
// Err returns a read-only channel which may be filled with errors
// came from the refresh stats operation.
func (sh *StatsHolder) Err() <-chan error {
return sh.errCh
}
// Stop terminates the routine retrieves the stats.
// Note that no other monitor can be initialized after Stop.
func (sh *StatsHolder) Stop() {
if !sh.started {
return
}
sh.closeCh <- struct{}{}
sh.started = false
}
func (sh *StatsHolder) start(refreshInterval time.Duration) {
if sh.started {
return
}
sh.started = true
once.Do(func() {
go func() {
ticker := time.NewTicker(refreshInterval)
defer ticker.Stop()
for {
select {
case <-sh.closeCh:
// close(sh.errCh)
return
case <-ticker.C:
err := refresh(sh.proc)
if err != nil {
// push the error to the channel and continue the execution,
// the only way to stop it is through its "Stop" method.
sh.errCh <- err
}
}
}
}()
})
}
var (
once = new(sync.Once)
metricPidCPU = expvar.NewFloat("pid_cpu")
metricPidRAM = newUint64("pid_ram")
metricPidConns = expvar.NewInt("pid_conns")
metricOsCPU = expvar.NewFloat("os_cpu")
metricOsRAM = newUint64("os_ram")
metricOsTotalRAM = newUint64("os_total_ram")
metricOsLoadAvg = expvar.NewFloat("os_load_avg")
metricOsConns = expvar.NewInt("os_conns")
)
// refresh updates the process and operating system statistics.
func refresh(proc *process.Process) error {
// Collect the stats.
//
// Process.
pidCPU, err := proc.CPUPercent()
if err != nil {
return err
}
pidRAM, err := proc.MemoryInfo()
if err != nil {
return err
}
pidConns, err := net.ConnectionsPid("tcp", proc.Pid)
if err != nil {
return err
}
// Operating System.
osCPU, err := cpu.Percent(0, false)
if err != nil {
return err
}
osRAM, err := mem.VirtualMemory()
if err != nil {
return err
}
osLoadAvg, err := load.Avg()
if err != nil {
return err
}
osConns, err := net.Connections("tcp")
if err != nil {
return err
}
// Update the fields.
//
// Process.
metricPidCPU.Set(pidCPU / 10)
metricPidRAM.Set(pidRAM.RSS)
metricPidConns.Set(int64(len(pidConns)))
// Operating System.
if len(osCPU) > 0 {
metricOsCPU.Set(osCPU[0])
}
metricOsRAM.Set(osRAM.Used)
metricOsTotalRAM.Set(osRAM.Total)
metricOsLoadAvg.Set(osLoadAvg.Load1)
metricOsConns.Set(int64(len(osConns)))
return nil
}
// GetStats returns a copy of the latest stats available.
func (sh *StatsHolder) GetStats() Stats {
sh.mu.Lock()
statsCopy := Stats{
PIDCPU: metricPidCPU.Value(),
PIDRAM: metricPidRAM.Value(),
PIDConns: metricPidConns.Value(),
OSCPU: metricOsCPU.Value(),
OSRAM: metricOsRAM.Value(),
OSTotalRAM: metricOsTotalRAM.Value(),
OSLoadAvg: metricOsLoadAvg.Value(),
OSConns: metricOsConns.Value(),
}
sh.mu.Unlock()
return statsCopy
}

View File

@ -0,0 +1,15 @@
package monitor
var (
viewRefreshIntervalTmplVar = []byte(`{{.ViewRefreshInterval}}`)
viewAnimationIntervalTmplVar = []byte(`{{.ViewAnimationInterval}}`)
viewTitleTmplVar = []byte(`{{.ViewTitle}}`)
defaultViewBody = []byte(`<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;900&display=swap"rel=stylesheet><script src=https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js></script><script src=https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js></script><title>` + string(viewTitleTmplVar) + `</title><style>body{margin:0;font:16px/1.6 Roboto,sans-serif}.wrapper{max-width:900px;margin:0 auto;padding:30px 0}.title{text-align:center;margin-bottom:2em}.title h1{font-size:2em;padding:0;margin:0}.row{display:flex;margin-bottom:20px;align-items:center}.row .column:first-child{width:35%}.row .column:last-child{width:65%}.metric{color:#777;font-weight:900}h2{padding:0;margin:0;font-size:1.8em}h2 span{font-size:12px;color:#777}h2 span.ram_os{color:rgba(255,150,0,.8)}h2 span.ram_total{color:rgba(0,200,0,.8)}canvas{width:200px;height:180px}</style><section class=wrapper><div class=title><h1>` + string(viewTitleTmplVar) + `</h1></div><section class=charts><div class=row><div class=column><div class=metric>CPU Usage</div><h2 id=cpuMetric>0.00%</h2></div><div class=column><canvas id=cpuChart></canvas></div></div><div class=row><div class=column><div class=metric>Memory Usage</div><h2 id=ramMetric title="PID used / OS used / OS total">0.00 MB</h2></div><div class=column><canvas id=ramChart></canvas></div></div><div class=row><div class=column><div class=metric>Response Time</div><h2 id=rtimeMetric>0ms</h2></div><div class=column><canvas id=rtimeChart></canvas></div></div><div class=row><div class=column><div class=metric>Open Connections</div><h2 id=connsMetric>0</h2></div><div class=column><canvas id=connsChart></canvas></div></div></section></section><script>
Chart.defaults.plugins.legend.display=!1,Chart.defaults.font.size=8,Chart.defaults.elements.line.backgroundColor="rgba(0, 172, 215, 0.25)",Chart.defaults.elements.line.borderColor="rgba(0, 172, 215, 1)",Chart.defaults.elements.line.borderWidth=2,Chart.defaults.plugins.tooltip.enabled=!1,Chart.defaults.elements.line.tension=.2,Chart.defaults.elements.point.radius=0,Chart.defaults.animation.duration="` + string(viewAnimationIntervalTmplVar) + `",Chart.defaults.animation.easing="easeOutQuart";const options={scales:{y:{beginAtZero:!0},x:{type:"time",time:{stepSize:30,unit:"second"},grid:{display:!1}}},responsive:!0,maintainAspectRatio:!1,animation:` + string(viewAnimationIntervalTmplVar) + `>0};
const cpuMetric=document.querySelector("#cpuMetric"),ramMetric=document.querySelector("#ramMetric"),rtimeMetric=document.querySelector("#rtimeMetric"),connsMetric=document.querySelector("#connsMetric"),cpuChartCtx=document.querySelector("#cpuChart").getContext("2d"),ramChartCtx=document.querySelector("#ramChart").getContext("2d"),rtimeChartCtx=document.querySelector("#rtimeChart").getContext("2d"),connsChartCtx=document.querySelector("#connsChart").getContext("2d"),cpuChart=createChart(cpuChartCtx),ramChart=createChart(ramChartCtx),rtimeChart=createChart(rtimeChartCtx),connsChart=createChart(connsChartCtx),charts=[cpuChart,ramChart,rtimeChart,connsChart];
function createChart(t){return new Chart(t,{type:"line",data:{labels:[],datasets:[{label:"",data:[],fill:"start"}]},options:options})}
ramChart.data.datasets.push({data:[],fill:"start",backgroundColor:["rgba(255, 200, 0, .6)"],borderColor:["rgba(255, 150, 0, .8)"]}),ramChart.data.datasets.push({data:[],fill:"start",backgroundColor:["rgba(0, 255, 0, .4)"],borderColor:["rgba(0, 200, 0, .8)"]});
function formatBytes(a,b=2,k=1024){with(Math){let d=floor(log(a)/log(k));return 0==a?"0 Bytes":parseFloat((a/pow(k,d)).toFixed(max(0,b)))+" "+["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"][d]}}
function refreshChart(a,t){cpu=a.pid_cpu.toFixed(1),cpuOS=a.os_cpu.toFixed(1),cpuMetric.innerHTML=cpu+"% <span>"+cpuOS+"%</span>",ramMetric.innerHTML=formatBytes(a.pid_ram)+'<span> / </span><span class="ram_os">'+formatBytes(a.os_ram)+'<span><span> / </span><span class="ram_total">'+formatBytes(a.os_total_ram)+"</span>",rtimeMetric.innerHTML=t+"ms <span>client</span>",connsMetric.innerHTML=a.pid_conns+" <span>"+a.os_conns+"</span>",cpuChart.data.datasets[0].data.push(cpu),ramChart.data.datasets[2].data.push((a.os_total_ram/1e6).toFixed(2)),ramChart.data.datasets[1].data.push((a.os_ram/1e6).toFixed(2)),ramChart.data.datasets[0].data.push((a.pid_ram/1e6).toFixed(2)),rtimeChart.data.datasets[0].data.push(t),connsChart.data.datasets[0].data.push(a.pid_conns);const s=(new Date).getTime();charts.forEach(a=>{a.data.labels.length>50&&(a.data.datasets.forEach(function(a){a.data.shift()}),a.data.labels.shift()),a.data.labels.push(s),a.update()}),setTimeout(fetchData," ` + string(viewRefreshIntervalTmplVar) + `")}
function fetchData(){var e="",n=performance.now();fetch(window.location.href,{method:"POST",headers:{Accept:"application/json"},credentials:"same-origin"}).then(n=>(e=performance.now(),n.json())).then(o=>{refreshChart(o,Math.round(e-n))}).catch(console.error)}fetchData();</script></body></html>`)
)

View File

@ -325,7 +325,7 @@ func TestControllerDependencies(t *testing.T) {
Body().Equal("kataras")
e.POST("/deep").Expect().Status(iris.StatusBadRequest).
Body().Equal("unexpected end of JSON input")
Body().Equal("EOF")
}
type testCtrl0 struct {

View File

@ -6,6 +6,7 @@ import (
"sync"
"time"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/core/memstore"
"github.com/kataras/golog"
@ -61,6 +62,14 @@ type Database interface {
Close() error
}
// DatabaseRequestHandler is an optional interface that a sessions database
// can implement. It contains a single EndRequest method which is fired
// on the very end of the request life cycle. It should be used to Flush
// any local session's values to the client.
type DatabaseRequestHandler interface {
EndRequest(ctx *context.Context, session *Session)
}
type mem struct {
values map[string]*memstore.Store
mu sync.RWMutex

View File

@ -4,6 +4,8 @@ import (
"errors"
"sync"
"time"
"github.com/kataras/iris/v12/context"
)
type (
@ -13,6 +15,7 @@ type (
mu sync.RWMutex
sessions map[string]*Session
db Database
dbRequestHandler DatabaseRequestHandler
destroyListeners []DestroyListener
}
)
@ -35,6 +38,9 @@ func (p *provider) RegisterDatabase(db Database) {
p.mu.Lock() // for any case
p.db = db
if dbreq, ok := db.(DatabaseRequestHandler); ok {
p.dbRequestHandler = dbreq
}
p.mu.Unlock()
}
@ -84,6 +90,12 @@ func (p *provider) Init(man *Sessions, sid string, expires time.Duration) *Sessi
return newSession
}
func (p *provider) EndRequest(ctx *context.Context, session *Session) {
if p.dbRequestHandler != nil {
p.dbRequestHandler.EndRequest(ctx, session)
}
}
// ErrNotFound may be returned from `UpdateExpiration` of a non-existing or
// invalid session entry from memory storage or databases.
// Usage:

View File

@ -2,6 +2,7 @@ package sessions
import (
"net/http"
"net/url"
"time"
"github.com/kataras/iris/v12/context"
@ -98,13 +99,27 @@ func (s *Sessions) upsertCookie(ctx *context.Context, cookie *http.Cookie, cooki
ctx.UpsertCookie(cookie, opts...)
}
func (s *Sessions) getCookie(ctx *context.Context, cookieOptions []context.CookieOption) string {
func (s *Sessions) getCookieValue(ctx *context.Context, cookieOptions []context.CookieOption) string {
c := s.getCookie(ctx, cookieOptions)
if c == nil {
return ""
}
return c.Value
}
func (s *Sessions) getCookie(ctx *context.Context, cookieOptions []context.CookieOption) *http.Cookie {
opts := s.cookieOptions
if len(cookieOptions) > 0 {
opts = append(opts, cookieOptions...)
}
return ctx.GetCookie(s.config.Cookie, opts...)
cookie, err := ctx.GetRequestCookie(s.config.Cookie, opts...)
if err != nil {
return nil
}
cookie.Value, _ = url.QueryUnescape(cookie.Value)
return cookie
}
// Start creates or retrieves an existing session for the particular request.
@ -116,25 +131,46 @@ func (s *Sessions) getCookie(ctx *context.Context, cookieOptions []context.Cooki
//
// NOTE: Use `app.Use(sess.Handler())` instead, avoid using `Start` manually.
func (s *Sessions) Start(ctx *context.Context, cookieOptions ...context.CookieOption) *Session {
cookieValue := s.getCookie(ctx, cookieOptions)
if cookieValue == "" { // cookie doesn't exist, let's generate a session and set a cookie.
sid := s.config.SessionIDGenerator(ctx)
sess := s.provider.Init(s, sid, s.config.Expires)
// n := s.provider.db.Len(sid)
// fmt.Printf("db.Len(%s) = %d\n", sid, n)
// if n > 0 {
// s.provider.db.Visit(sid, func(key string, value interface{}) {
// fmt.Printf("%s=%s\n", key, value)
// })
// }
s.updateCookie(ctx, sid, s.config.Expires, cookieOptions...)
return sess
// cookieValue := s.getCookieValue(ctx, cookieOptions)
cookie := s.getCookie(ctx, cookieOptions)
if cookie != nil {
sid := cookie.Value
if sid == "" { // rare case: a client may contains a cookie with session name but with empty value.
// ctx.RemoveCookie(cookie.Name)
cookie = nil
} else if cookie.Expires.Add(time.Second).After(time.Now()) { // rare case: of custom clients that may hold expired cookies.
s.DestroyByID(sid)
// ctx.RemoveCookie(cookie.Name)
cookie = nil
} else {
// rare case: new expiration configuration that it's lower
// than the previous setting.
expiresTime := time.Now().Add(s.config.Expires)
if cookie.Expires.After(expiresTime) {
s.DestroyByID(sid)
// ctx.RemoveCookie(cookie.Name)
cookie = nil
} else {
// untilExpirationDur := time.Until(cookie.Expires)
// ^ this should be
return s.provider.Read(s, sid, s.config.Expires) // cookie exists and it's valid, let's return its session.
}
}
}
return s.provider.Read(s, cookieValue, s.config.Expires)
// Cookie doesn't exist, let's generate a session and set a cookie.
sid := s.config.SessionIDGenerator(ctx)
sess := s.provider.Init(s, sid, s.config.Expires)
// n := s.provider.db.Len(sid)
// fmt.Printf("db.Len(%s) = %d\n", sid, n)
// if n > 0 {
// s.provider.db.Visit(sid, func(key string, value interface{}) {
// fmt.Printf("%s=%s\n", key, value)
// })
// }
s.updateCookie(ctx, sid, s.config.Expires, cookieOptions...)
return sess
}
const sessionContextKey = "iris.session"
@ -149,6 +185,8 @@ func (s *Sessions) Handler(requestOptions ...context.CookieOption) context.Handl
ctx.Values().Set(sessionContextKey, session)
ctx.Next()
s.provider.EndRequest(ctx, session)
}
}
@ -189,7 +227,7 @@ func (s *Sessions) ShiftExpiration(ctx *context.Context, cookieOptions ...contex
// It will return `ErrNotFound` when trying to update expiration on a non-existence or not valid session entry.
// It will return `ErrNotImplemented` if a database is used and it does not support this feature, yet.
func (s *Sessions) UpdateExpiration(ctx *context.Context, expires time.Duration, cookieOptions ...context.CookieOption) error {
cookieValue := s.getCookie(ctx, cookieOptions)
cookieValue := s.getCookieValue(ctx, cookieOptions)
if cookieValue == "" {
return ErrNotFound
}
@ -223,7 +261,7 @@ func (s *Sessions) OnDestroy(listeners ...DestroyListener) {
// use `Sessions#Start` method for renewal
// or use the Session's Destroy method which does keep the session entry with its values cleared.
func (s *Sessions) Destroy(ctx *context.Context) {
cookieValue := s.getCookie(ctx, nil)
cookieValue := s.getCookieValue(ctx, nil)
if cookieValue == "" { // nothing to destroy
return
}

View File

@ -3,36 +3,57 @@ package jsonx
import (
"fmt"
"strconv"
"strings"
"time"
)
// KitckenTimeLayout represents the "3:04 PM" Go time format, similar to time.Kitcken.
const KitckenTimeLayout = "3:04 PM"
// KitchenTimeLayout represents the "3:04 PM" Go time format, similar to time.Kitchen.
const KitchenTimeLayout = "3:04 PM"
// KitckenTime holds a json "3:04 PM" time.
type KitckenTime time.Time
// KitchenTime holds a json "3:04 PM" time.
type KitchenTime time.Time
// ParseKitchenTime reads from "s" and returns the KitckenTime time.
func ParseKitchenTime(s string) (KitckenTime, error) {
if s == "" || s == "null" {
return KitckenTime{}, nil
var ErrParseKitchenTimeColon = fmt.Errorf("parse kitchen time: missing ':' character")
func parseKitchenTime(s string) (KitchenTime, error) {
// Remove any second,millisecond variable (probably given by postgres 00:00:00.000000).
// required(00:00)remove(:00.000000)
firstIndex := strings.IndexByte(s, ':')
if firstIndex == -1 {
return KitchenTime{}, ErrParseKitchenTimeColon
} else {
nextIndex := strings.LastIndexByte(s, ':')
spaceIdx := strings.LastIndexByte(s, ' ')
if nextIndex > firstIndex && spaceIdx > 0 {
tmp := s[0:nextIndex]
s = tmp + s[spaceIdx:]
}
}
var (
tt time.Time
err error
)
tt, err = time.Parse(KitckenTimeLayout, s)
tt, err := time.Parse(KitchenTimeLayout, s)
if err != nil {
return KitckenTime{}, err
return KitchenTime{}, err
}
return KitckenTime(tt.UTC()), nil
return KitchenTime(tt), nil
}
// UnmarshalJSON binds the json "data" to "t" with the `KitckenTimeLayout`.
func (t *KitckenTime) UnmarshalJSON(data []byte) error {
// ParseKitchenTime reads from "s" and returns the KitchenTime time.
func ParseKitchenTime(s string) (KitchenTime, error) {
if s == "" || s == "null" {
return KitchenTime{}, nil
}
return parseKitchenTime(s)
}
// UnmarshalJSON binds the json "data" to "t" with the `KitchenTimeLayout`.
func (t *KitchenTime) UnmarshalJSON(data []byte) error {
if t == nil {
return fmt.Errorf("kitchen time: dest is nil")
}
if isNull(data) {
return nil
}
@ -43,17 +64,17 @@ func (t *KitckenTime) UnmarshalJSON(data []byte) error {
return nil
}
tt, err := time.Parse(KitckenTimeLayout, string(data))
tt, err := parseKitchenTime(string(data))
if err != nil {
return err
}
*t = KitckenTime(tt)
*t = KitchenTime(tt)
return nil
}
// MarshalJSON returns the json representation of the "t".
func (t KitckenTime) MarshalJSON() ([]byte, error) {
func (t KitchenTime) MarshalJSON() ([]byte, error) {
if s := t.String(); s != "" {
s = strconv.Quote(s)
return []byte(s), nil
@ -64,47 +85,90 @@ func (t KitckenTime) MarshalJSON() ([]byte, error) {
// IsZero reports whether "t" is zero time.
// It completes the pg.Zeroer interface.
func (t KitckenTime) IsZero() bool {
func (t KitchenTime) IsZero() bool {
return t.Value().IsZero()
}
// Value returns the standard time type.
func (t KitckenTime) Value() time.Time {
func (t KitchenTime) Value() time.Time {
return time.Time(t)
}
// String returns the text representation of the date
// formatted based on the `KitckenTimeLayout`.
// formatted based on the `KitchenTimeLayout`.
// If date is zero it returns an empty string.
func (t KitckenTime) String() string {
func (t KitchenTime) String() string {
tt := t.Value()
if tt.IsZero() {
return ""
}
return tt.Format(KitckenTimeLayout)
return tt.Format(KitchenTimeLayout)
}
// Scan completes the pg and native sql driver.Scanner interface
// reading functionality of a custom type.
func (t *KitckenTime) Scan(src interface{}) error {
func (t *KitchenTime) Scan(src interface{}) error {
switch v := src.(type) {
case time.Time: // type was set to timestamp
case time.Time: // type was set to timestamp.
if v.IsZero() {
return nil // don't set zero, ignore it.
}
*t = KitckenTime(v)
case string:
tt, err := ParseKitchenTime(v)
*t = KitchenTime(v)
case string: // type was set to time, input example: 10:00:00.000000
d, err := ParseTimeNotationDuration(v)
if err != nil {
return fmt.Errorf("kitchen time: convert to time notation first: %w", err)
}
s := kitchenTimeStringFromDuration(d.ToDuration())
*t, err = ParseKitchenTime(s)
return err
case int64: // timestamp with integer.
u := time.Unix(v/1000, v%1000)
s := kitchenTimeStringFromHourAndMinute(u.Hour(), u.Minute())
tt, err := ParseKitchenTime(s)
if err != nil {
return err
}
*t = tt
case nil:
*t = KitckenTime(time.Time{})
*t = KitchenTime(time.Time{})
default:
return fmt.Errorf("KitckenTime: unknown type of: %T", v)
return fmt.Errorf("KitchenTime: unknown type of: %T", v)
}
return nil
}
func kitchenTimeStringFromDuration(dt time.Duration) string {
hour := int(dt.Hours())
minute := 0
if totalMins := dt.Minutes(); totalMins > 0 {
minute := int(totalMins / 60)
if minute < 0 {
minute = 0
}
}
return kitchenTimeStringFromHourAndMinute(hour, minute)
}
func kitchenTimeStringFromHourAndMinute(hour, minute int) string {
ampm := "AM"
if hour/12 == 1 {
ampm = "PM"
}
th := hour % 12
hh := strconv.Itoa(th)
if th < 10 {
hh = "0" + hh
}
tm := minute
mm := strconv.Itoa(tm)
if tm < 10 {
mm = "0" + mm
}
return hh + ":" + mm + " " + ampm
}

View File

@ -6,34 +6,50 @@ import (
"time"
)
func TestJSONKitckenTime(t *testing.T) {
data := `{"start": "8:33 AM", "end": "3:04 PM", "nothing": null, "empty": ""}`
v := struct {
Start KitckenTime `json:"start"`
End KitckenTime `json:"end"`
Nothing KitckenTime `json:"nothing"`
Empty KitckenTime `json:"empty"`
}{}
err := json.Unmarshal([]byte(data), &v)
if err != nil {
t.Fatal(err)
func TestJSONKitchenTime(t *testing.T) {
tests := []struct {
rawData string
}{
{
rawData: `{"start": "8:33 AM", "end": "3:04 PM", "nothing": null, "empty": ""}`,
},
{
rawData: `{"start": "08:33 AM", "end": "03:04 PM", "nothing": null, "empty": ""}`,
},
{
rawData: `{"start": "08:33:00.000000 AM", "end": "03:04 PM", "nothing": null, "empty": ""}`,
},
}
if !v.Nothing.IsZero() {
t.Fatalf("expected 'nothing' to be zero but got: %v", v.Nothing)
}
for _, tt := range tests {
v := struct {
Start KitchenTime `json:"start"`
End KitchenTime `json:"end"`
Nothing KitchenTime `json:"nothing"`
Empty KitchenTime `json:"empty"`
}{}
if !v.Empty.IsZero() {
t.Fatalf("expected 'empty' to be zero but got: %v", v.Empty)
}
err := json.Unmarshal([]byte(tt.rawData), &v)
if err != nil {
t.Fatal(err)
}
loc := time.UTC
if !v.Nothing.IsZero() {
t.Fatalf("expected 'nothing' to be zero but got: %v", v.Nothing)
}
if expected, got := time.Date(0, time.January, 1, 8, 33, 0, 0, loc), v.Start.Value(); expected != got {
t.Fatalf("expected 'start' to be: %v but got: %v", expected, got)
}
if !v.Empty.IsZero() {
t.Fatalf("expected 'empty' to be zero but got: %v", v.Empty)
}
if expected, got := time.Date(0, time.January, 1, 15, 4, 0, 0, loc), v.End.Value(); expected != got {
t.Fatalf("expected 'start' to be: %v but got: %v", expected, got)
loc := time.UTC
if expected, got := time.Date(0, time.January, 1, 8, 33, 0, 0, loc), v.Start.Value(); expected != got {
t.Fatalf("expected 'start' to be: %v but got: %v", expected, got)
}
if expected, got := time.Date(0, time.January, 1, 15, 4, 0, 0, loc), v.End.Value(); expected != got {
t.Fatalf("expected 'start' to be: %v but got: %v", expected, got)
}
}
}

View File

@ -20,7 +20,40 @@ func fmtDuration(d time.Duration) string {
h := d / time.Hour
d -= h * time.Hour
m := d / time.Minute
return fmt.Sprintf("%02d:%02d", h, m)
return fmt.Sprintf("%02d:%02d:00", h, m) // Manos doesn't care about the seconds.
}
// ParseTimeNotationDuration parses a string to a time notation duration (00:00:00) hours:minutes:seconds.
func ParseTimeNotationDuration(s string) (TimeNotationDuration, error) {
entries := strings.SplitN(s, ":", 3)
if len(entries) < 3 {
return TimeNotationDuration(0), fmt.Errorf("invalid duration format: expected hours:minutes:seconds (e.g. 01:05:00) but got: %s", s)
}
hours, err := strconv.Atoi(entries[0])
if err != nil {
return TimeNotationDuration(0), err
}
minutes, err := strconv.Atoi(entries[1])
if err != nil {
return TimeNotationDuration(0), err
}
// remove any .0000
secondsStr := strings.TrimSuffix(entries[2], ".000000")
seconds, err := strconv.Atoi(secondsStr)
if err != nil {
return TimeNotationDuration(0), err
}
format := fmt.Sprintf("%02dh%02dm%02ds", hours, minutes, seconds)
v, err := time.ParseDuration(format)
if err != nil {
return TimeNotationDuration(0), err
}
return TimeNotationDuration(v), nil
}
func (d TimeNotationDuration) MarshalJSON() ([]byte, error) {
@ -44,26 +77,11 @@ func (d *TimeNotationDuration) UnmarshalJSON(b []byte) error {
*d = TimeNotationDuration(value)
return nil
case string:
entries := strings.SplitN(value, ":", 2)
if len(entries) < 2 {
return fmt.Errorf("invalid duration format: expected hours:minutes:seconds (e.g. 01:05) but got: %s", value)
}
hours, err := strconv.Atoi(entries[0])
dv, err := ParseTimeNotationDuration(value)
if err != nil {
return err
}
minutes, err := strconv.Atoi(entries[1])
if err != nil {
return err
}
format := fmt.Sprintf("%02dh%02dm", hours, minutes)
v, err := time.ParseDuration(format)
if err != nil {
return err
}
*d = TimeNotationDuration(v)
*d = dv
return nil
default:
return errors.New("invalid duration")
@ -75,7 +93,7 @@ func (d TimeNotationDuration) ToDuration() time.Duration {
}
func (d TimeNotationDuration) Value() (driver.Value, error) {
return int64(d), nil
return d.ToDuration(), nil
}
// Set sets the value of duration in nanoseconds.