Compare commits

..

No commits in common. "v3" and "v3.11.8" have entirely different histories.
v3 ... v3.11.8

24 changed files with 184 additions and 2113 deletions

24
.gitignore vendored
View File

@ -1,24 +0,0 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
# General
.DS_Store
.idea
.vscode

22
go.mod
View File

@ -1,24 +1,16 @@
module go.unistack.org/micro-server-http/v3 module go.unistack.org/micro-server-http/v3
go 1.21 go 1.18
toolchain go1.23.1
require ( require (
go.unistack.org/micro-client-http/v3 v3.9.12 go.unistack.org/micro-proto/v3 v3.3.1
go.unistack.org/micro-codec-yaml/v3 v3.10.1 go.unistack.org/micro/v3 v3.10.14
go.unistack.org/micro-proto/v3 v3.4.1 golang.org/x/net v0.7.0
go.unistack.org/micro/v3 v3.10.88
golang.org/x/net v0.29.0
) )
require ( require (
github.com/google/gnostic v0.7.0 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/gnostic v0.6.9 // indirect
golang.org/x/sys v0.25.0 // indirect google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.66.2 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
) )

1470
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -6,21 +6,14 @@ import (
"io" "io"
"net/http" "net/http"
"reflect" "reflect"
"slices"
"strconv"
"strings" "strings"
"sync" "sync"
"time"
"go.unistack.org/micro/v3/errors" "go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register" "go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/semconv"
"go.unistack.org/micro/v3/server" "go.unistack.org/micro/v3/server"
"go.unistack.org/micro/v3/tracer"
rhttp "go.unistack.org/micro/v3/util/http" rhttp "go.unistack.org/micro/v3/util/http"
rflutil "go.unistack.org/micro/v3/util/reflect" rflutil "go.unistack.org/micro/v3/util/reflect"
) )
@ -122,14 +115,14 @@ func (h *Server) HTTPHandlerFunc(handler interface{}) (http.HandlerFunc, error)
md["Method"] = r.Method md["Method"] = r.Method
md["URL"] = r.URL.String() md["URL"] = r.URL.String()
md["Proto"] = r.Proto md["Proto"] = r.Proto
md["Content-Length"] = fmt.Sprintf("%d", r.ContentLength) md["ContentLength"] = fmt.Sprintf("%d", r.ContentLength)
md["Transfer-Encoding"] = strings.Join(r.TransferEncoding, ",") md["TransferEncoding"] = strings.Join(r.TransferEncoding, ",")
md["Host"] = r.Host md["Host"] = r.Host
md["RequestURI"] = r.RequestURI md["RequestURI"] = r.RequestURI
if r.TLS != nil { if r.TLS != nil {
md["TLS"] = "true" md["TLS"] = "true"
md["TLS-ALPN"] = r.TLS.NegotiatedProtocol md["TLS_ALPN"] = r.TLS.NegotiatedProtocol
md["TLS-ServerName"] = r.TLS.ServerName md["TLS_ServerName"] = r.TLS.ServerName
} }
ctx = metadata.NewIncomingContext(ctx, md) ctx = metadata.NewIncomingContext(ctx, md)
@ -279,11 +272,9 @@ func (h *Server) HTTPHandlerFunc(handler interface{}) (http.HandlerFunc, error)
} }
// wrap the handler func // wrap the handler func
h.opts.Hooks.EachNext(func(hook options.Hook) { for i := len(handler.sopts.HdlrWrappers); i > 0; i-- {
if h, ok := hook.(server.HookHandler); ok { fn = handler.sopts.HdlrWrappers[i-1](fn)
fn = h(fn) }
}
})
if ct == "application/x-www-form-urlencoded" { if ct == "application/x-www-form-urlencoded" {
cf, err = h.newCodec(DefaultContentType) cf, err = h.newCodec(DefaultContentType)
@ -354,11 +345,8 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ct = htype ct = htype
} }
ts := time.Now()
ctx := context.WithValue(r.Context(), rspCodeKey{}, &rspCodeVal{}) ctx := context.WithValue(r.Context(), rspCodeKey{}, &rspCodeVal{})
ctx = context.WithValue(ctx, rspHeaderKey{}, &rspHeaderVal{}) ctx = context.WithValue(ctx, rspHeaderKey{}, &rspHeaderVal{})
md, ok := metadata.FromIncomingContext(ctx) md, ok := metadata.FromIncomingContext(ctx)
if !ok { if !ok {
md = metadata.New(len(r.Header) + 8) md = metadata.New(len(r.Header) + 8)
@ -433,112 +421,21 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
} }
var sp tracer.Span
if !match && h.hd != nil { if !match && h.hd != nil {
if hdlr, ok := h.hd.Handler().(http.Handler); ok { if hdlr, ok := h.hd.Handler().(http.Handler); ok {
if !slices.Contains(tracer.DefaultSkipEndpoints, h.hd.Name()) { hdlr.ServeHTTP(w, r)
ctx, sp = h.opts.Tracer.Start(ctx, h.hd.Name()+" rpc-server",
tracer.WithSpanKind(tracer.SpanKindServer),
tracer.WithSpanLabels(
"endpoint", h.hd.Name(),
),
)
defer func() {
n := GetRspCode(ctx)
if s, _ := sp.Status(); s != tracer.SpanStatusError && n > 399 {
sp.SetStatus(tracer.SpanStatusError, http.StatusText(n))
}
sp.Finish()
}()
}
if !slices.Contains(meter.DefaultSkipEndpoints, h.hd.Name()) {
h.opts.Meter.Counter(semconv.ServerRequestInflight, "endpoint", h.hd.Name()).Inc()
defer func() {
n := GetRspCode(ctx)
if n > 399 {
h.opts.Meter.Counter(semconv.ServerRequestTotal, "endpoint", h.hd.Name(), "status", "success", "code", strconv.Itoa(n)).Inc()
} else {
h.opts.Meter.Counter(semconv.ServerRequestTotal, "endpoint", h.hd.Name(), "status", "failure", "code", strconv.Itoa(n)).Inc()
}
te := time.Since(ts)
h.opts.Meter.Summary(semconv.ServerRequestLatencyMicroseconds, "endpoint", h.hd.Name()).Update(te.Seconds())
h.opts.Meter.Histogram(semconv.ServerRequestDurationSeconds, "endpoint", h.hd.Name()).Update(te.Seconds())
h.opts.Meter.Counter(semconv.ServerRequestInflight, "endpoint", h.hd.Name()).Dec()
}()
}
hdlr.ServeHTTP(w, r.WithContext(ctx))
return return
} }
} else if !match { } else if !match {
// check for http.HandlerFunc handlers // check for http.HandlerFunc handlers
if !slices.Contains(tracer.DefaultSkipEndpoints, r.URL.Path) {
ctx, sp = h.opts.Tracer.Start(ctx, r.URL.Path+" rpc-server",
tracer.WithSpanKind(tracer.SpanKindServer),
tracer.WithSpanLabels(
"endpoint", r.URL.Path,
),
)
defer func() {
if n := GetRspCode(ctx); n > 399 {
sp.SetStatus(tracer.SpanStatusError, http.StatusText(n))
} else {
sp.SetStatus(tracer.SpanStatusError, http.StatusText(http.StatusNotFound))
}
sp.Finish()
}()
}
if ph, _, err := h.pathHandlers.Search(r.Method, r.URL.Path); err == nil { if ph, _, err := h.pathHandlers.Search(r.Method, r.URL.Path); err == nil {
ph.(http.HandlerFunc)(w, r.WithContext(ctx)) ph.(http.HandlerFunc)(w, r)
return return
} }
h.errorHandler(ctx, nil, w, r, fmt.Errorf("not matching route found"), http.StatusNotFound) h.errorHandler(ctx, nil, w, r, fmt.Errorf("not matching route found"), http.StatusNotFound)
return return
} }
endpointName := fmt.Sprintf("%s.%s", hldr.name, hldr.mtype.method.Name)
topts := []tracer.SpanOption{
tracer.WithSpanKind(tracer.SpanKindServer),
tracer.WithSpanLabels(
"endpoint", endpointName,
),
}
if slices.Contains(tracer.DefaultSkipEndpoints, endpointName) {
topts = append(topts, tracer.WithSpanRecord(false))
}
ctx, sp = h.opts.Tracer.Start(ctx, endpointName+" rpc-server", topts...)
if !slices.Contains(meter.DefaultSkipEndpoints, handler.name) {
defer func() {
te := time.Since(ts)
h.opts.Meter.Summary(semconv.ServerRequestLatencyMicroseconds, "endpoint", handler.name).Update(te.Seconds())
h.opts.Meter.Histogram(semconv.ServerRequestDurationSeconds, "endpoint", handler.name).Update(te.Seconds())
h.opts.Meter.Counter(semconv.ServerRequestInflight, "endpoint", handler.name).Dec()
n := GetRspCode(ctx)
if n > 399 {
h.opts.Meter.Counter(semconv.ServerRequestTotal, "endpoint", handler.name, "status", "failure", "code", strconv.Itoa(n)).Inc()
} else {
h.opts.Meter.Counter(semconv.ServerRequestTotal, "endpoint", handler.name, "status", "success", "code", strconv.Itoa(n)).Inc()
}
}()
}
defer func() {
n := GetRspCode(ctx)
if n > 399 {
if s, _ := sp.Status(); s != tracer.SpanStatusError {
sp.SetStatus(tracer.SpanStatusError, http.StatusText(n))
}
}
sp.Finish()
}()
// get fields from url values // get fields from url values
if len(r.URL.RawQuery) > 0 { if len(r.URL.RawQuery) > 0 {
umd, cerr := rflutil.URLMap(r.URL.RawQuery) umd, cerr := rflutil.URLMap(r.URL.RawQuery)
@ -634,18 +531,13 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
metadata.SetOutgoingContext(ctx, md) metadata.SetOutgoingContext(ctx, md)
if err != nil && sp != nil {
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
return err return err
} }
h.opts.Hooks.EachNext(func(hook options.Hook) { // wrap the handler func
if h, ok := hook.(server.HookHandler); ok { for i := len(handler.sopts.HdlrWrappers); i > 0; i-- {
fn = h(fn) fn = handler.sopts.HdlrWrappers[i-1](fn)
} }
})
if ct == "application/x-www-form-urlencoded" { if ct == "application/x-www-form-urlencoded" {
cf, err = h.newCodec(DefaultContentType) cf, err = h.newCodec(DefaultContentType)
@ -695,7 +587,7 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
if err != nil && handler.sopts.Logger.V(logger.ErrorLevel) { if err != nil && handler.sopts.Logger.V(logger.ErrorLevel) {
handler.sopts.Logger.Error(handler.sopts.Context, "handler error", err) handler.sopts.Logger.Errorf(handler.sopts.Context, "handler err: %v", err)
return return
} }
@ -705,6 +597,6 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(scode) w.WriteHeader(scode)
if _, cerr := w.Write(buf); cerr != nil { if _, cerr := w.Write(buf); cerr != nil {
handler.sopts.Logger.Error(ctx, "respoonse write error", cerr) handler.sopts.Logger.Errorf(ctx, "write failed: %v", cerr)
} }
} }

View File

@ -1,77 +0,0 @@
//go:build ignore
package graphql_handler
import (
"context"
"fmt"
"github.com/99designs/gqlgen/graphql"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/store"
)
var _ graphql.Cache = (*cacheWrapper)(nil)
type Handler struct {
opts Options
}
type Option func(*Options)
type Options struct {
cache *cacheWrapper
Path string
}
type cacheWrapper struct {
s store.Store
l logger.Logger
}
func (c *cacheWrapper) Get(ctx context.Context, key string) (interface{}, bool) {
var val interface{}
if err := c.s.Read(ctx, key, val); err != nil && err != store.ErrNotFound {
c.l.Error(ctx, fmt.Sprintf("cache.Get %s failed", key), err)
return nil, false
}
return val, true
}
func (c *cacheWrapper) Add(ctx context.Context, key string, val interface{}) {
if err := c.s.Write(ctx, key, val); err != nil {
c.l.Error(ctx, fmt.Sprintf("cache.Add %s failed", key), err)
}
}
func Store(s store.Store) Option {
return func(o *Options) {
if o.cache == nil {
o.cache = &cacheWrapper{}
}
o.cache.s = s
}
}
func Logger(l logger.Logger) Option {
return func(o *Options) {
if o.cache == nil {
o.cache = &cacheWrapper{}
}
o.cache.l = l
}
}
func Path(path string) Option {
return func(o *Options) {
o.Path = path
}
}
func NewHandler(opts ...Option) *Handler {
options := Options{}
for _, o := range opts {
o(&options)
}
return &Handler{opts: options}
}

View File

@ -1,4 +1,4 @@
package health_handler package health // import "go.unistack.org/micro-server-http/v3/handler/health"
import ( import (
"context" "context"

View File

@ -1,7 +1,7 @@
syntax = "proto3"; syntax = "proto3";
package micro.server.http.v3.handler.health; package micro.server.http.v3.handler.health;
option go_package = "go.unistack.org/micro-server-http/v3/handler/health;health_handler"; option go_package = "go.unistack.org/micro-server-http/v3/handler/health;health";
import "api/annotations.proto"; import "api/annotations.proto";
import "openapiv3/annotations.proto"; import "openapiv3/annotations.proto";

View File

@ -1,26 +1,45 @@
// Code generated by protoc-gen-go-micro. DO NOT EDIT. // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-micro v3.10.4 // - protoc-gen-go-micro v3.10.2
// - protoc v5.26.1 // - protoc v3.21.12
// source: health/health.proto // source: health.proto
package health_handler package health
import ( import (
context "context" context "context"
codec "go.unistack.org/micro-proto/v3/codec" codec "go.unistack.org/micro-proto/v3/codec"
client "go.unistack.org/micro/v3/client" v3 "go.unistack.org/micro-server-http/v3"
) )
var ( var (
HealthServiceName = "HealthService" HealthServiceName = "HealthService"
) )
var (
type HealthServiceClient interface { HealthServiceServerEndpoints = []v3.EndpointMetadata{
Live(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) {
Ready(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) Name: "HealthService.Live",
Version(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) Path: "/live",
} Method: "GET",
Body: "",
Stream: false,
},
{
Name: "HealthService.Ready",
Path: "/ready",
Method: "GET",
Body: "",
Stream: false,
},
{
Name: "HealthService.Version",
Path: "/version",
Method: "GET",
Body: "",
Stream: false,
},
}
)
type HealthServiceServer interface { type HealthServiceServer interface {
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error

View File

@ -1,108 +1,16 @@
// Code generated by protoc-gen-go-micro. DO NOT EDIT. // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// protoc-gen-go-micro version: v3.10.4 // protoc-gen-go-micro version: v3.10.2
// source: health/health.proto // source: health.proto
package health_handler package health
import ( import (
context "context" context "context"
v31 "go.unistack.org/micro-client-http/v3"
codec "go.unistack.org/micro-proto/v3/codec" codec "go.unistack.org/micro-proto/v3/codec"
v3 "go.unistack.org/micro-server-http/v3" v3 "go.unistack.org/micro-server-http/v3"
client "go.unistack.org/micro/v3/client"
server "go.unistack.org/micro/v3/server" server "go.unistack.org/micro/v3/server"
http "net/http"
) )
var (
HealthServiceServerEndpoints = []v3.EndpointMetadata{
{
Name: "HealthService.Live",
Path: "/live",
Method: "GET",
Body: "",
Stream: false,
},
{
Name: "HealthService.Ready",
Path: "/ready",
Method: "GET",
Body: "",
Stream: false,
},
{
Name: "HealthService.Version",
Path: "/version",
Method: "GET",
Body: "",
Stream: false,
},
}
)
type healthServiceClient struct {
c client.Client
name string
}
func NewHealthServiceClient(name string, c client.Client) HealthServiceClient {
return &healthServiceClient{c: c, name: name}
}
func (c *healthServiceClient) Live(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) {
errmap := make(map[string]interface{}, 1)
errmap["default"] = &codec.Frame{}
opts = append(opts,
v31.ErrorMap(errmap),
)
opts = append(opts,
v31.Method(http.MethodGet),
v31.Path("/live"),
)
rsp := &codec.Frame{}
err := c.c.Call(ctx, c.c.NewRequest(c.name, "HealthService.Live", req), rsp, opts...)
if err != nil {
return nil, err
}
return rsp, nil
}
func (c *healthServiceClient) Ready(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) {
errmap := make(map[string]interface{}, 1)
errmap["default"] = &codec.Frame{}
opts = append(opts,
v31.ErrorMap(errmap),
)
opts = append(opts,
v31.Method(http.MethodGet),
v31.Path("/ready"),
)
rsp := &codec.Frame{}
err := c.c.Call(ctx, c.c.NewRequest(c.name, "HealthService.Ready", req), rsp, opts...)
if err != nil {
return nil, err
}
return rsp, nil
}
func (c *healthServiceClient) Version(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) {
errmap := make(map[string]interface{}, 1)
errmap["default"] = &codec.Frame{}
opts = append(opts,
v31.ErrorMap(errmap),
)
opts = append(opts,
v31.Method(http.MethodGet),
v31.Path("/version"),
)
rsp := &codec.Frame{}
err := c.c.Call(ctx, c.c.NewRequest(c.name, "HealthService.Version", req), rsp, opts...)
if err != nil {
return nil, err
}
return rsp, nil
}
type healthServiceServer struct { type healthServiceServer struct {
HealthServiceServer HealthServiceServer
} }

View File

@ -1,36 +1,14 @@
package meter_handler package meter // import "go.unistack.org/micro-server-http/v3/handler/meter"
import ( import (
"bytes" "bytes"
"compress/gzip"
"context" "context"
"io"
"strings"
"sync"
codecpb "go.unistack.org/micro-proto/v3/codec" codecpb "go.unistack.org/micro-proto/v3/codec"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
) )
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)
},
}
// guard to fail early // guard to fail early
var _ MeterServiceServer = &Handler{} var _ MeterServiceServer = &Handler{}
@ -41,10 +19,9 @@ type Handler struct {
type Option func(*Options) type Option func(*Options)
type Options struct { type Options struct {
Meter meter.Meter Meter meter.Meter
Name string Name string
MeterOptions []meter.Option MeterOptions []meter.Option
DisableCompress bool
} }
func Meter(m meter.Meter) Option { func Meter(m meter.Meter) Option {
@ -59,12 +36,6 @@ func Name(name string) Option {
} }
} }
func DisableCompress(g bool) Option {
return func(o *Options) {
o.DisableCompress = g
}
}
func MeterOptions(opts ...meter.Option) Option { func MeterOptions(opts ...meter.Option) Option {
return func(o *Options) { return func(o *Options) {
o.MeterOptions = append(o.MeterOptions, opts...) o.MeterOptions = append(o.MeterOptions, opts...)
@ -72,7 +43,7 @@ func MeterOptions(opts ...meter.Option) Option {
} }
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{Meter: meter.DefaultMeter, DisableCompress: false} options := Options{Meter: meter.DefaultMeter}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
@ -85,48 +56,12 @@ func NewHandler(opts ...Option) *Handler {
} }
func (h *Handler) Metrics(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error { func (h *Handler) Metrics(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
log, ok := logger.FromContext(ctx) buf := bytes.NewBuffer(nil)
if !ok { if err := h.opts.Meter.Write(buf, h.opts.MeterOptions...); err != nil {
log = logger.DefaultLogger return errors.InternalServerError(h.opts.Name, "%v", err)
}
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)
buf.Reset()
w := io.Writer(buf)
if md, ok := metadata.FromOutgoingContext(ctx); gzipAccepted(md) && ok && !h.opts.DisableCompress {
omd, _ := metadata.FromOutgoingContext(ctx)
omd.Set(contentEncodingHeader, "gzip")
gz := gzipPool.Get().(*gzip.Writer)
defer gzipPool.Put(gz)
gz.Reset(w)
defer gz.Close()
w = gz
gz.Flush()
}
if err := h.opts.Meter.Write(w, h.opts.MeterOptions...); err != nil {
log.Error(ctx, "http/meter write failed", err)
return nil
} }
rsp.Data = buf.Bytes() rsp.Data = buf.Bytes()
return nil return nil
} }
// 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
}

View File

@ -1,7 +1,7 @@
syntax = "proto3"; syntax = "proto3";
package micro.server.http.v3.handler.meter; package micro.server.http.v3.handler.meter;
option go_package = "go.unistack.org/micro-server-http/v3/handler/meter;meter_handler"; option go_package = "go.unistack.org/micro-server-http/v3/handler/meter;meter";
import "api/annotations.proto"; import "api/annotations.proto";
import "openapiv3/annotations.proto"; import "openapiv3/annotations.proto";

View File

@ -1,24 +1,31 @@
// Code generated by protoc-gen-go-micro. DO NOT EDIT. // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-micro v3.10.4 // - protoc-gen-go-micro v3.10.2
// - protoc v5.26.1 // - protoc v3.21.12
// source: meter/meter.proto // source: meter.proto
package meter_handler package meter
import ( import (
context "context" context "context"
codec "go.unistack.org/micro-proto/v3/codec" codec "go.unistack.org/micro-proto/v3/codec"
client "go.unistack.org/micro/v3/client" v3 "go.unistack.org/micro-server-http/v3"
) )
var ( var (
MeterServiceName = "MeterService" MeterServiceName = "MeterService"
) )
var (
type MeterServiceClient interface { MeterServiceServerEndpoints = []v3.EndpointMetadata{
Metrics(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) {
} Name: "MeterService.Metrics",
Path: "/metrics",
Method: "GET",
Body: "",
Stream: false,
},
}
)
type MeterServiceServer interface { type MeterServiceServer interface {
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error

View File

@ -1,58 +1,16 @@
// Code generated by protoc-gen-go-micro. DO NOT EDIT. // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// protoc-gen-go-micro version: v3.10.4 // protoc-gen-go-micro version: v3.10.2
// source: meter/meter.proto // source: meter.proto
package meter_handler package meter
import ( import (
context "context" context "context"
v31 "go.unistack.org/micro-client-http/v3"
codec "go.unistack.org/micro-proto/v3/codec" codec "go.unistack.org/micro-proto/v3/codec"
v3 "go.unistack.org/micro-server-http/v3" v3 "go.unistack.org/micro-server-http/v3"
client "go.unistack.org/micro/v3/client"
server "go.unistack.org/micro/v3/server" server "go.unistack.org/micro/v3/server"
http "net/http"
) )
var (
MeterServiceServerEndpoints = []v3.EndpointMetadata{
{
Name: "MeterService.Metrics",
Path: "/metrics",
Method: "GET",
Body: "",
Stream: false,
},
}
)
type meterServiceClient struct {
c client.Client
name string
}
func NewMeterServiceClient(name string, c client.Client) MeterServiceClient {
return &meterServiceClient{c: c, name: name}
}
func (c *meterServiceClient) Metrics(ctx context.Context, req *codec.Frame, opts ...client.CallOption) (*codec.Frame, error) {
errmap := make(map[string]interface{}, 1)
errmap["default"] = &codec.Frame{}
opts = append(opts,
v31.ErrorMap(errmap),
)
opts = append(opts,
v31.Method(http.MethodGet),
v31.Path("/metrics"),
)
rsp := &codec.Frame{}
err := c.c.Call(ctx, c.c.NewRequest(c.name, "MeterService.Metrics", req), rsp, opts...)
if err != nil {
return nil, err
}
return rsp, nil
}
type meterServiceServer struct { type meterServiceServer struct {
MeterServiceServer MeterServiceServer
} }

View File

@ -1,46 +0,0 @@
package pprof_handler
import (
"expvar"
"net/http"
"net/http/pprof"
"path"
"strings"
)
func NewHandler(prefixPath string, initFuncs ...func()) http.HandlerFunc {
for _, fn := range initFuncs {
fn()
}
return func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.EqualFold(r.RequestURI, prefixPath) && r.RequestURI[len(r.RequestURI)-1] != '/':
http.Redirect(w, r, r.RequestURI+"/", http.StatusMovedPermanently)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "cmdline")):
pprof.Cmdline(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "profile")):
pprof.Profile(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "symbol")):
pprof.Symbol(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "trace")):
pprof.Trace(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "goroutine")):
pprof.Handler("goroutine").ServeHTTP(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "threadcreate")):
pprof.Handler("threadcreate").ServeHTTP(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "mutex")):
pprof.Handler("mutex").ServeHTTP(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "heap")):
pprof.Handler("heap").ServeHTTP(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "block")):
pprof.Handler("block").ServeHTTP(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "allocs")):
pprof.Handler("allocs").ServeHTTP(w, r)
case strings.HasPrefix(r.RequestURI, path.Join(prefixPath, "vars")):
expvar.Handler().ServeHTTP(w, r)
default:
pprof.Index(w, r)
}
}
}

View File

@ -0,0 +1,20 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "{{ .SWAGGER }}",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};

View File

@ -1,4 +1,4 @@
package swaggerui_handler package swaggerui // import "go.unistack.org/micro-server-http/v3/handler/swagger-ui"
import ( import (
"embed" "embed"

View File

@ -1,4 +1,4 @@
package swaggerui_handler package swaggerui
import ( import (
"net/http" "net/http"

View File

@ -1,4 +1,4 @@
package swagger_handler package swagger
import ( import (
"io/fs" "io/fs"

114
http.go
View File

@ -191,11 +191,6 @@ func (h *Server) NewHandler(handler interface{}, opts ...server.HandlerOption) s
} }
*/ */
registerCORS := false
if v, ok := options.Context.Value(registerCORSHandlerKey{}).(bool); ok && v {
registerCORS = true
}
for hn, md := range options.Metadata { for hn, md := range options.Metadata {
var method reflect.Method var method reflect.Method
mname := hn[strings.Index(hn, ".")+1:] mname := hn[strings.Index(hn, ".")+1:]
@ -209,7 +204,7 @@ func (h *Server) NewHandler(handler interface{}, opts ...server.HandlerOption) s
} }
if method.Name == "" && h.opts.Logger.V(logger.ErrorLevel) { if method.Name == "" && h.opts.Logger.V(logger.ErrorLevel) {
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("nil method for %s", mname)) h.opts.Logger.Errorf(h.opts.Context, "nil method for %s", mname)
continue continue
} }
@ -218,7 +213,7 @@ func (h *Server) NewHandler(handler interface{}, opts ...server.HandlerOption) s
h.opts.Logger.Errorf(h.opts.Context, "%v", err) h.opts.Logger.Errorf(h.opts.Context, "%v", err)
continue continue
} else if mtype == nil { } else if mtype == nil {
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("nil mtype for %s", mname)) h.opts.Logger.Errorf(h.opts.Context, "nil mtype for %s", mname)
continue continue
} }
@ -228,23 +223,14 @@ func (h *Server) NewHandler(handler interface{}, opts ...server.HandlerOption) s
pth := &patHandler{mtype: mtype, name: name, rcvr: rcvr} pth := &patHandler{mtype: mtype, name: name, rcvr: rcvr}
hdlr.name = name hdlr.name = name
methods := []string{md["Method"]} if err := hdlr.handlers.Insert([]string{md["Method"]}, md["Path"], pth); err != nil {
if registerCORS { h.opts.Logger.Errorf(h.opts.Context, "cant add handler for %s %s", md["Method"], md["Path"])
methods = append(methods, http.MethodOptions)
}
if err := hdlr.handlers.Insert(methods, md["Path"], pth); err != nil {
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("cant add handler for %v %s", methods, md["Path"]))
} }
if h.registerRPC { if h.registerRPC {
methods := []string{http.MethodPost} h.opts.Logger.Infof(h.opts.Context, "register rpc handler for http.MethodPost %s /%s", hn, hn)
if registerCORS { if err := hdlr.handlers.Insert([]string{http.MethodPost}, "/"+hn, pth); err != nil {
methods = append(methods, http.MethodOptions) h.opts.Logger.Errorf(h.opts.Context, "cant add rpc handler for http.MethodPost %s /%s", hn, hn)
}
if err := hdlr.handlers.Insert(methods, "/"+hn, pth); err != nil {
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("cant add rpc handler for http.MethodPost %s /%s", hn, hn))
} }
} }
} }
@ -268,7 +254,7 @@ func (h *Server) NewHandler(handler interface{}, opts ...server.HandlerOption) s
} }
if method.Name == "" && h.opts.Logger.V(logger.ErrorLevel) { if method.Name == "" && h.opts.Logger.V(logger.ErrorLevel) {
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("nil method for %s", mname)) h.opts.Logger.Errorf(h.opts.Context, "nil method for %s", mname)
continue continue
} }
@ -277,7 +263,7 @@ func (h *Server) NewHandler(handler interface{}, opts ...server.HandlerOption) s
h.opts.Logger.Errorf(h.opts.Context, "%v", err) h.opts.Logger.Errorf(h.opts.Context, "%v", err)
continue continue
} else if mtype == nil { } else if mtype == nil {
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("nil mtype for %s", mname)) h.opts.Logger.Errorf(h.opts.Context, "nil mtype for %s", mname)
continue continue
} }
@ -287,24 +273,14 @@ func (h *Server) NewHandler(handler interface{}, opts ...server.HandlerOption) s
pth := &patHandler{mtype: mtype, name: name, rcvr: rcvr} pth := &patHandler{mtype: mtype, name: name, rcvr: rcvr}
hdlr.name = name hdlr.name = name
methods := []string{md.Method} if err := hdlr.handlers.Insert([]string{md.Method}, md.Path, pth); err != nil {
if registerCORS { h.opts.Logger.Errorf(h.opts.Context, "cant add handler for %s %s", md.Method, md.Path)
methods = append(methods, http.MethodOptions)
}
if err := hdlr.handlers.Insert(methods, md.Path, pth); err != nil {
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("cant add handler for %s %s", md.Method, md.Path))
} }
if h.registerRPC { if h.registerRPC {
methods := []string{http.MethodPost} h.opts.Logger.Infof(h.opts.Context, "register rpc handler for http.MethodPost %s /%s", hn, hn)
if registerCORS { if err := hdlr.handlers.Insert([]string{http.MethodPost}, "/"+hn, pth); err != nil {
methods = append(methods, http.MethodOptions) h.opts.Logger.Errorf(h.opts.Context, "cant add rpc handler for http.MethodPost %s /%s", hn, hn)
}
h.opts.Logger.Info(h.opts.Context, fmt.Sprintf("register rpc handler for http.MethodPost %s /%s", hn, hn))
if err := hdlr.handlers.Insert(methods, "/"+hn, pth); err != nil {
h.opts.Logger.Error(h.opts.Context, fmt.Sprintf("cant add rpc handler for http.MethodPost %s /%s", hn, hn))
} }
} }
} }
@ -387,7 +363,7 @@ func (h *Server) Register() error {
if !registered { if !registered {
if config.Logger.V(logger.InfoLevel) { if config.Logger.V(logger.InfoLevel) {
config.Logger.Info(config.Context, fmt.Sprintf("Register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].ID)) config.Logger.Infof(config.Context, "Register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].ID)
} }
} }
@ -450,7 +426,7 @@ func (h *Server) Deregister() error {
} }
if config.Logger.V(logger.InfoLevel) { if config.Logger.V(logger.InfoLevel) {
config.Logger.Info(config.Context, "Deregistering node: "+service.Nodes[0].ID) config.Logger.Infof(config.Context, "Deregistering node: %s", service.Nodes[0].ID)
} }
if err := server.DefaultDeregisterFunc(service, config); err != nil { if err := server.DefaultDeregisterFunc(service, config); err != nil {
@ -474,10 +450,10 @@ func (h *Server) Deregister() error {
} }
for _, sub := range subs { for _, sub := range subs {
config.Logger.Info(config.Context, "Unsubscribing from topic: "+sub.Topic()) config.Logger.Infof(config.Context, "Unsubscribing from topic: %s", sub.Topic())
if err := sub.Unsubscribe(subCtx); err != nil { if err := sub.Unsubscribe(subCtx); err != nil {
h.Unlock() h.Unlock()
config.Logger.Error(config.Context, fmt.Sprintf("failed to unsubscribe topic: %s, error", sb.Topic()), err) config.Logger.Errorf(config.Context, "failed to unsubscribe topic: %s, error: %v", sb.Topic(), err)
return err return err
} }
} }
@ -517,7 +493,7 @@ func (h *Server) Start() error {
} }
if config.Logger.V(logger.InfoLevel) { if config.Logger.V(logger.InfoLevel) {
config.Logger.Info(config.Context, "Listening on "+ts.Addr().String()) config.Logger.Infof(config.Context, "Listening on %s", ts.Addr().String())
} }
h.Lock() h.Lock()
@ -525,6 +501,7 @@ func (h *Server) Start() error {
h.Unlock() h.Unlock()
var handler http.Handler var handler http.Handler
var srvFunc func(net.Listener) error
// nolint: nestif // nolint: nestif
if h.opts.Context != nil { if h.opts.Context != nil {
@ -561,7 +538,7 @@ func (h *Server) Start() error {
if err := config.RegisterCheck(h.opts.Context); err != nil { if err := config.RegisterCheck(h.opts.Context); err != nil {
if config.Logger.V(logger.ErrorLevel) { if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register check error", config.Name, config.ID), err) config.Logger.Errorf(config.Context, "Server %s-%s register check error: %s", config.Name, config.ID, err)
} }
} else { } else {
if err = h.Register(); err != nil { if err = h.Register(); err != nil {
@ -575,7 +552,6 @@ func (h *Server) Start() error {
fn := handler fn := handler
var hs *http.Server
if h.opts.Context != nil { if h.opts.Context != nil {
if mwf, ok := h.opts.Context.Value(middlewareKey{}).([]func(http.Handler) http.Handler); ok && len(mwf) > 0 { if mwf, ok := h.opts.Context.Value(middlewareKey{}).([]func(http.Handler) http.Handler); ok && len(mwf) > 0 {
// wrap the handler func // wrap the handler func
@ -583,19 +559,25 @@ func (h *Server) Start() error {
fn = mwf[i-1](fn) fn = mwf[i-1](fn)
} }
} }
var ok bool if hs, ok := h.opts.Context.Value(serverKey{}).(*http.Server); ok && hs != nil {
if hs, ok = h.opts.Context.Value(serverKey{}).(*http.Server); ok && hs != nil {
hs.Handler = fn hs.Handler = fn
} else { srvFunc = hs.Serve
hs = &http.Server{Handler: fn}
} }
} }
go func() { if srvFunc != nil {
if cerr := hs.Serve(ts); cerr != nil && !errors.Is(cerr, net.ErrClosed) { go func() {
h.opts.Logger.Error(h.opts.Context, "serve error", cerr) if cerr := srvFunc(ts); cerr != nil && !errors.Is(cerr, net.ErrClosed) {
} h.opts.Logger.Error(h.opts.Context, cerr)
}() }
}()
} else {
go func() {
if cerr := http.Serve(ts, fn); cerr != nil && !errors.Is(cerr, net.ErrClosed) {
h.opts.Logger.Error(h.opts.Context, cerr)
}
}()
}
go func() { go func() {
t := new(time.Ticker) t := new(time.Ticker)
@ -621,28 +603,28 @@ func (h *Server) Start() error {
// nolint: nestif // nolint: nestif
if rerr != nil && registered { if rerr != nil && registered {
if config.Logger.V(logger.ErrorLevel) { if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register check error, deregister it", config.Name, config.ID), rerr) config.Logger.Errorf(config.Context, "Server %s-%s register check error: %s, deregister it", config.Name, config.ID, rerr)
} }
// deregister self in case of error // deregister self in case of error
if err := h.Deregister(); err != nil { if err := h.Deregister(); err != nil {
if config.Logger.V(logger.ErrorLevel) { if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s deregister error", config.Name, config.ID), err) config.Logger.Errorf(config.Context, "Server %s-%s deregister error: %s", config.Name, config.ID, err)
} }
} }
} else if rerr != nil && !registered { } else if rerr != nil && !registered {
if config.Logger.V(logger.ErrorLevel) { if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register check error", config.Name, config.ID), rerr) config.Logger.Errorf(config.Context, "Server %s-%s register check error: %s", config.Name, config.ID, rerr)
} }
continue continue
} }
if err := h.Register(); err != nil { if err := h.Register(); err != nil {
if config.Logger.V(logger.ErrorLevel) { if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error(config.Context, fmt.Sprintf("Server %s-%s register error", config.Name, config.ID), err) config.Logger.Errorf(config.Context, "Server %s-%s register error: %s", config.Name, config.ID, err)
} }
} }
if err := h.Register(); err != nil { if err := h.Register(); err != nil {
config.Logger.Error(config.Context, "Server register error", err) config.Logger.Errorf(config.Context, "Server register error: %s", err)
} }
// wait for exit // wait for exit
case ch = <-h.exit: case ch = <-h.exit:
@ -652,22 +634,14 @@ func (h *Server) Start() error {
// deregister // deregister
if err := h.Deregister(); err != nil { if err := h.Deregister(); err != nil {
config.Logger.Error(config.Context, "Server deregister error", err) config.Logger.Errorf(config.Context, "Server deregister error: %s", err)
} }
if err := config.Broker.Disconnect(config.Context); err != nil { if err := config.Broker.Disconnect(config.Context); err != nil {
config.Logger.Error(config.Context, "Broker disconnect error", err) config.Logger.Errorf(config.Context, "Broker disconnect error: %s", err)
} }
ctx, cancel := context.WithTimeout(context.Background(), h.opts.GracefulTimeout) ch <- ts.Close()
defer cancel()
err := hs.Shutdown(ctx)
if err != nil {
err = hs.Close()
}
ch <- err
}() }()
return nil return nil

View File

@ -11,6 +11,7 @@ type httpMessage struct {
header metadata.Metadata header metadata.Metadata
topic string topic string
contentType string contentType string
body []byte
} }
func (r *httpMessage) Topic() string { func (r *httpMessage) Topic() string {

View File

@ -69,7 +69,7 @@ func getRspHeader(ctx context.Context) http.Header {
// GetRspCode used internally by generated http server handler // GetRspCode used internally by generated http server handler
func GetRspCode(ctx context.Context) int { func GetRspCode(ctx context.Context) int {
code := int(200) var code int
if rsp, ok := ctx.Value(rspCodeKey{}).(*rspCodeVal); ok { if rsp, ok := ctx.Value(rspCodeKey{}).(*rspCodeVal); ok {
code = rsp.code code = rsp.code
} }
@ -133,13 +133,6 @@ func RegisterRPCHandler(b bool) server.Option {
return server.SetOption(registerRPCHandlerKey{}, b) return server.SetOption(registerRPCHandlerKey{}, b)
} }
type registerCORSHandlerKey struct{}
// RegisterCORSHandler registers cors endpoints with /ServiceName.ServiceEndpoint method POPTIONSOST
func RegisterCORSHandler(b bool) server.HandlerOption {
return server.SetHandlerOption(registerCORSHandlerKey{}, b)
}
type handlerEndpointsKey struct{} type handlerEndpointsKey struct{}
type EndpointMetadata struct { type EndpointMetadata struct {

View File

@ -58,7 +58,11 @@ func (r *rpcRequest) Header() metadata.Metadata {
} }
func (r *rpcRequest) Read() ([]byte, error) { func (r *rpcRequest) Read() ([]byte, error) {
return nil, nil f := &codec.Frame{}
if err := r.codec.ReadBody(r.rw, f); err != nil {
return nil, err
}
return f.Data, nil
} }
func (r *rpcRequest) Stream() bool { func (r *rpcRequest) Stream() bool {

View File

@ -1,14 +1,15 @@
package http package http
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
"go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register" "go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/server" "go.unistack.org/micro/v3/server"
) )
@ -130,7 +131,13 @@ func (s *Server) createSubHandler(sb *httpSubscriber, opts server.Options) broke
req = req.Elem() req = req.Elem()
} }
if err := cf.Unmarshal(msg.Body, req.Interface()); err != nil { buf := bytes.NewBuffer(msg.Body)
if err := cf.ReadHeader(buf, &codec.Message{}, codec.Event); err != nil {
return err
}
if err := cf.ReadBody(buf, req.Interface()); err != nil {
return err return err
} }
@ -152,11 +159,9 @@ func (s *Server) createSubHandler(sb *httpSubscriber, opts server.Options) broke
return nil return nil
} }
opts.Hooks.EachNext(func(hook options.Hook) { for i := len(opts.SubWrappers); i > 0; i-- {
if h, ok := hook.(server.HookSubHandler); ok { fn = opts.SubWrappers[i-1](fn)
fn = h(fn) }
}
})
go func() { go func() {
results <- fn(ctx, &httpMessage{ results <- fn(ctx, &httpMessage{

View File

@ -8,52 +8,8 @@ import (
"testing" "testing"
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/server"
) )
func Test_Hook(t *testing.T) {
opts := server.Options{}
var fn server.HandlerFunc = func(fctx context.Context, req server.Request, rsp interface{}) (err error) {
// fmt.Println("1")
return nil
}
var fn2 server.HandlerWrapper = func(next server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
// fmt.Println("2")
return next(ctx, req, rsp)
}
}
var fn3 server.HandlerWrapper = func(next server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
// fmt.Println("3")
return next(ctx, req, rsp)
}
}
var fn4 server.HandlerWrapper = func(next server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
// fmt.Println("4")
return next(ctx, req, rsp)
}
}
opts.Hooks = append(opts.Hooks, fn2, fn3, fn4)
opts.Hooks.EachNext(func(hook options.Hook) {
if h, ok := hook.(server.HandlerWrapper); ok {
// fmt.Printf("h %#+v\n", h)
fn = h(fn)
}
})
err := fn(nil, nil, nil)
if err != nil {
t.Fatal(err)
}
}
func TestFillrequest(t *testing.T) { func TestFillrequest(t *testing.T) {
md := metadata.New(1) md := metadata.New(1)
md.Set("ClientID", "xxx") md.Set("ClientID", "xxx")