mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
publish v12.2.0-alpha6
This commit is contained in:
parent
4899fe95f4
commit
41026c9209
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
5
_examples/logging/request-logger/accesslog-slack/main.go
Normal file
5
_examples/logging/request-logger/accesslog-slack/main.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
println("Navigate to: https://github.com/kataras/iris/issues/1808#issuecomment-1013757925")
|
||||
}
|
64
_examples/monitor/monitor-middleware/main.go
Normal file
64
_examples/monitor/monitor-middleware/main.go
Normal 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")
|
||||
}
|
|
@ -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...)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
54
go.mod
|
@ -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
118
go.sum
generated
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
|
|
33
middleware/monitor/expvar_uint64.go
Normal file
33
middleware/monitor/expvar_uint64.go
Normal 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
|
||||
}
|
105
middleware/monitor/monitor.go
Normal file
105
middleware/monitor/monitor.go
Normal 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
205
middleware/monitor/stats.go
Normal 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
|
||||
}
|
15
middleware/monitor/view.go
Normal file
15
middleware/monitor/view.go
Normal 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>`)
|
||||
)
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue
Block a user