2023-02-26 14:51:34 +03:00
|
|
|
package meter // import "go.unistack.org/micro-server-http/v3/handler/meter"
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2024-03-11 13:21:11 +03:00
|
|
|
"compress/gzip"
|
2023-02-26 14:51:34 +03:00
|
|
|
"context"
|
2024-03-11 13:21:11 +03:00
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
2023-02-26 14:51:34 +03:00
|
|
|
|
|
|
|
codecpb "go.unistack.org/micro-proto/v3/codec"
|
2024-03-11 13:21:11 +03:00
|
|
|
"go.unistack.org/micro/v3/logger"
|
|
|
|
"go.unistack.org/micro/v3/metadata"
|
2023-02-26 14:51:34 +03:00
|
|
|
"go.unistack.org/micro/v3/meter"
|
|
|
|
)
|
|
|
|
|
2024-03-11 13:21:11 +03:00
|
|
|
const (
|
|
|
|
contentEncodingHeader = "Content-Encoding"
|
|
|
|
acceptEncodingHeader = "Accept-Encoding"
|
|
|
|
)
|
|
|
|
|
|
|
|
var gzipPool = sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
return gzip.NewWriter(nil)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var bufPool = sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
return bytes.NewBuffer(nil)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-02-26 14:51:34 +03:00
|
|
|
// guard to fail early
|
|
|
|
var _ MeterServiceServer = &Handler{}
|
|
|
|
|
|
|
|
type Handler struct {
|
|
|
|
opts Options
|
|
|
|
}
|
|
|
|
|
|
|
|
type Option func(*Options)
|
|
|
|
|
|
|
|
type Options struct {
|
|
|
|
Meter meter.Meter
|
|
|
|
Name string
|
|
|
|
MeterOptions []meter.Option
|
|
|
|
}
|
|
|
|
|
|
|
|
func Meter(m meter.Meter) Option {
|
|
|
|
return func(o *Options) {
|
|
|
|
o.Meter = m
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Name(name string) Option {
|
|
|
|
return func(o *Options) {
|
|
|
|
o.Name = name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func MeterOptions(opts ...meter.Option) Option {
|
|
|
|
return func(o *Options) {
|
|
|
|
o.MeterOptions = append(o.MeterOptions, opts...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewOptions(opts ...Option) Options {
|
|
|
|
options := Options{Meter: meter.DefaultMeter}
|
|
|
|
for _, o := range opts {
|
|
|
|
o(&options)
|
|
|
|
}
|
|
|
|
return options
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewHandler(opts ...Option) *Handler {
|
|
|
|
options := NewOptions(opts...)
|
|
|
|
return &Handler{opts: options}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *Handler) Metrics(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
|
2024-03-11 13:21:11 +03:00
|
|
|
log, ok := logger.FromContext(ctx)
|
|
|
|
if !ok {
|
2024-03-12 00:03:51 +03:00
|
|
|
log = logger.DefaultLogger
|
2024-03-11 13:21:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
buf := bufPool.Get().(*bytes.Buffer)
|
|
|
|
defer bufPool.Put(buf)
|
|
|
|
buf.Reset()
|
|
|
|
|
|
|
|
w := io.Writer(buf)
|
|
|
|
|
|
|
|
if md, ok := metadata.FromIncomingContext(ctx); gzipAccepted(md) && ok {
|
|
|
|
md.Set(contentEncodingHeader, "gzip")
|
2024-03-18 16:14:34 +03:00
|
|
|
ctx = metadata.NewIncomingContext(ctx, md)
|
2024-03-11 13:21:11 +03:00
|
|
|
gz := gzipPool.Get().(*gzip.Writer)
|
|
|
|
defer gzipPool.Put(gz)
|
|
|
|
|
|
|
|
gz.Reset(w)
|
|
|
|
defer gz.Close()
|
|
|
|
|
|
|
|
w = gz
|
2024-03-18 16:14:34 +03:00
|
|
|
gz.Flush()
|
2024-03-11 13:21:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := h.opts.Meter.Write(w, h.opts.MeterOptions...); err != nil {
|
2024-03-12 00:03:51 +03:00
|
|
|
log.Error(ctx, "http/meter write failed", err)
|
2024-03-11 13:21:11 +03:00
|
|
|
return nil
|
2023-02-26 14:51:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
rsp.Data = buf.Bytes()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2024-03-11 13:21:11 +03:00
|
|
|
|
|
|
|
// gzipAccepted returns whether the client will accept gzip-encoded content.
|
|
|
|
func gzipAccepted(md metadata.Metadata) bool {
|
|
|
|
a, ok := md.Get(acceptEncodingHeader)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if strings.Contains(a, "gzip") {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|