Initial QSfera import
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Context context.Context
|
||||
Name string
|
||||
Version string
|
||||
Address string
|
||||
Token string
|
||||
Pprof bool
|
||||
Zpages bool
|
||||
Health http.Handler
|
||||
Ready http.Handler
|
||||
ConfigDump http.Handler
|
||||
CorsAllowedOrigins []string
|
||||
CorsAllowedMethods []string
|
||||
CorsAllowedHeaders []string
|
||||
CorsAllowCredentials bool
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(l log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// Context provides a function to set the context option.
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// Name provides a function to set the name option.
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// Version provides a function to set the version option.
|
||||
func Version(v string) Option {
|
||||
return func(o *Options) {
|
||||
o.Version = v
|
||||
}
|
||||
}
|
||||
|
||||
// Address provides a function to set the address option.
|
||||
func Address(a string) Option {
|
||||
return func(o *Options) {
|
||||
o.Address = a
|
||||
}
|
||||
}
|
||||
|
||||
// Token provides a function to set the token option.
|
||||
func Token(t string) Option {
|
||||
return func(o *Options) {
|
||||
o.Token = t
|
||||
}
|
||||
}
|
||||
|
||||
// Pprof provides a function to set the pprof option.
|
||||
func Pprof(p bool) Option {
|
||||
return func(o *Options) {
|
||||
o.Pprof = p
|
||||
}
|
||||
}
|
||||
|
||||
// Zpages provides a function to set the zpages option.
|
||||
func Zpages(z bool) Option {
|
||||
return func(o *Options) {
|
||||
o.Zpages = z
|
||||
}
|
||||
}
|
||||
|
||||
// Health provides a function to set the health option.
|
||||
func Health(h http.Handler) Option {
|
||||
return func(o *Options) {
|
||||
o.Health = h
|
||||
}
|
||||
}
|
||||
|
||||
// Ready provides a function to set the ready option.
|
||||
func Ready(r http.Handler) Option {
|
||||
return func(o *Options) {
|
||||
o.Ready = r
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigDump to be documented.
|
||||
func ConfigDump(r http.Handler) Option {
|
||||
return func(o *Options) {
|
||||
o.ConfigDump = r
|
||||
}
|
||||
}
|
||||
|
||||
// CorsAllowedOrigins provides a function to set the CorsAllowedOrigin option.
|
||||
func CorsAllowedOrigins(origins []string) Option {
|
||||
return func(o *Options) {
|
||||
o.CorsAllowedOrigins = origins
|
||||
}
|
||||
}
|
||||
|
||||
// CorsAllowedMethods provides a function to set the CorsAllowedMethods option.
|
||||
func CorsAllowedMethods(methods []string) Option {
|
||||
return func(o *Options) {
|
||||
o.CorsAllowedMethods = methods
|
||||
}
|
||||
}
|
||||
|
||||
// CorsAllowedHeaders provides a function to set the CorsAllowedHeaders option.
|
||||
func CorsAllowedHeaders(headers []string) Option {
|
||||
return func(o *Options) {
|
||||
o.CorsAllowedHeaders = headers
|
||||
}
|
||||
}
|
||||
|
||||
// CorsAllowCredentials provides a function to set the CorsAllowAllowCredential option.
|
||||
func CorsAllowCredentials(allow bool) Option {
|
||||
return func(o *Options) {
|
||||
o.CorsAllowCredentials = allow
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/justinas/alice"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/contrib/zpages"
|
||||
|
||||
"github.com/qsfera/server/pkg/cors"
|
||||
"github.com/qsfera/server/pkg/handlers"
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/pkg/middleware"
|
||||
graphMiddleware "github.com/qsfera/server/services/graph/pkg/middleware"
|
||||
)
|
||||
|
||||
var handleProbe = func(mux *http.ServeMux, pattern string, h http.Handler, logger log.Logger) {
|
||||
if h == nil {
|
||||
h = handlers.NewCheckHandler(handlers.NewCheckHandlerConfiguration())
|
||||
logger.Info().
|
||||
Str("endpoint", pattern).
|
||||
Msg("no probe provided, reverting to default (OK)")
|
||||
}
|
||||
|
||||
mux.Handle(pattern, h)
|
||||
}
|
||||
|
||||
// NewService initializes a new debug service.
|
||||
func NewService(opts ...Option) *http.Server {
|
||||
dopts := newOptions(opts...)
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.Handle("/metrics", alice.New(
|
||||
graphMiddleware.Token(
|
||||
dopts.Token,
|
||||
),
|
||||
).Then(
|
||||
promhttp.Handler(),
|
||||
))
|
||||
|
||||
handleProbe(mux, "/healthz", dopts.Health, dopts.Logger) // healthiness check
|
||||
handleProbe(mux, "/readyz", dopts.Ready, dopts.Logger) // readiness check
|
||||
|
||||
if dopts.ConfigDump != nil {
|
||||
mux.Handle("/config", dopts.ConfigDump)
|
||||
}
|
||||
|
||||
if dopts.Pprof {
|
||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
}
|
||||
|
||||
if dopts.Zpages {
|
||||
h := zpages.NewTracezHandler(zpages.NewSpanProcessor())
|
||||
mux.Handle("/debug", h)
|
||||
}
|
||||
|
||||
baseCtx := dopts.Context
|
||||
if baseCtx == nil {
|
||||
baseCtx = context.Background()
|
||||
}
|
||||
|
||||
return &http.Server{
|
||||
Addr: dopts.Address,
|
||||
BaseContext: func(_ net.Listener) context.Context {
|
||||
return baseCtx
|
||||
},
|
||||
Handler: alice.New(
|
||||
chimiddleware.RealIP,
|
||||
chimiddleware.RequestID,
|
||||
middleware.NoCache,
|
||||
middleware.Cors(
|
||||
cors.AllowedOrigins(dopts.CorsAllowedOrigins),
|
||||
cors.AllowedMethods(dopts.CorsAllowedMethods),
|
||||
cors.AllowedHeaders(dopts.CorsAllowedHeaders),
|
||||
cors.AllowCredentials(dopts.CorsAllowCredentials),
|
||||
),
|
||||
middleware.Version(
|
||||
dopts.Name,
|
||||
dopts.Version,
|
||||
),
|
||||
).Then(
|
||||
mux,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
mgrpcc "github.com/go-micro/plugins/v4/client/grpc"
|
||||
mtracer "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry"
|
||||
"github.com/qsfera/server/pkg/registry"
|
||||
"github.com/qsfera/server/pkg/shared"
|
||||
"go-micro.dev/v4/client"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
)
|
||||
|
||||
// ClientOptions represent options (e.g. tls settings) for the grpc clients
|
||||
type ClientOptions struct {
|
||||
tlsMode string
|
||||
caCert string
|
||||
tp trace.TracerProvider
|
||||
}
|
||||
|
||||
// Option is used to pass client options
|
||||
type ClientOption func(opts *ClientOptions)
|
||||
|
||||
// WithTLSMode allows setting the TLSMode option for grpc clients
|
||||
func WithTLSMode(v string) ClientOption {
|
||||
return func(o *ClientOptions) {
|
||||
o.tlsMode = v
|
||||
}
|
||||
}
|
||||
|
||||
// WithTLSCACert allows setting the CA Certificate for grpc clients
|
||||
func WithTLSCACert(v string) ClientOption {
|
||||
return func(o *ClientOptions) {
|
||||
o.caCert = v
|
||||
}
|
||||
}
|
||||
|
||||
// WithTraceProvider allows to set the trace Provider for grpc clients
|
||||
func WithTraceProvider(tp trace.TracerProvider) ClientOption {
|
||||
return func(o *ClientOptions) {
|
||||
if tp != nil {
|
||||
o.tp = tp
|
||||
} else {
|
||||
o.tp = noop.NewTracerProvider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetClientOptions(t *shared.GRPCClientTLS) []ClientOption {
|
||||
opts := []ClientOption{
|
||||
WithTLSMode(t.Mode),
|
||||
WithTLSCACert(t.CACert),
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func NewClient(opts ...ClientOption) (client.Client, error) {
|
||||
var options ClientOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
reg := registry.GetRegistry()
|
||||
var tlsConfig *tls.Config
|
||||
cOpts := []client.Option{
|
||||
client.Registry(reg),
|
||||
client.Wrap(mtracer.NewClientWrapper(
|
||||
mtracer.WithTraceProvider(options.tp),
|
||||
)),
|
||||
}
|
||||
switch options.tlsMode {
|
||||
case "insecure":
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
cOpts = append(cOpts, mgrpcc.AuthTLS(tlsConfig))
|
||||
case "on":
|
||||
tlsConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
// Note: If caCert is empty we use the system's default set of trusted CAs
|
||||
if options.caCert != "" {
|
||||
certs := x509.NewCertPool()
|
||||
pemData, err := os.ReadFile(options.caCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !certs.AppendCertsFromPEM(pemData) {
|
||||
return nil, errors.New("could not initialize client, adding CA cert failed")
|
||||
}
|
||||
tlsConfig.RootCAs = certs
|
||||
}
|
||||
cOpts = append(cOpts, mgrpcc.AuthTLS(tlsConfig))
|
||||
// case "off":
|
||||
// default:
|
||||
}
|
||||
|
||||
return mgrpcc.NewClient(cOpts...), nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package ratelimiter
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go-micro.dev/v4/errors"
|
||||
"go-micro.dev/v4/server"
|
||||
)
|
||||
|
||||
// NewHandlerWrapper creates a blocking server side rate limiter.
|
||||
func NewHandlerWrapper(limit int) server.HandlerWrapper {
|
||||
if limit <= 0 {
|
||||
return func(h server.HandlerFunc) server.HandlerFunc {
|
||||
return h
|
||||
}
|
||||
}
|
||||
|
||||
token := make(chan struct{}, limit)
|
||||
for i := 0; i < limit; i++ {
|
||||
token <- struct{}{}
|
||||
}
|
||||
|
||||
return func(h server.HandlerFunc) server.HandlerFunc {
|
||||
return func(ctx context.Context, req server.Request, rsp any) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case t := <-token:
|
||||
defer func() {
|
||||
token <- t
|
||||
}()
|
||||
return h(ctx, req, rsp)
|
||||
default:
|
||||
return errors.New("go.micro.server", "Rate limit exceeded", 429)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
_serverMaxConnectionAgeEnv = "GRPC_MAX_CONNECTION_AGE"
|
||||
|
||||
// same default as grpc
|
||||
infinity = time.Duration(math.MaxInt64)
|
||||
_defaultMaxConnectionAge = infinity
|
||||
)
|
||||
|
||||
// GetMaxConnectionAge returns the maximum grpc connection age.
|
||||
func GetMaxConnectionAge() time.Duration {
|
||||
d, err := time.ParseDuration(os.Getenv(_serverMaxConnectionAgeEnv))
|
||||
if err != nil {
|
||||
return _defaultMaxConnectionAge
|
||||
}
|
||||
return d
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"go-micro.dev/v4/server"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Namespace string
|
||||
Name string
|
||||
Version string
|
||||
Address string
|
||||
TLSEnabled bool
|
||||
TLSCert string
|
||||
TLSKey string
|
||||
Context context.Context
|
||||
TraceProvider trace.TracerProvider
|
||||
HandlerWrappers []server.HandlerWrapper
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{
|
||||
Namespace: "go.micro.api",
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(l log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace provides a function to set the namespace option.
|
||||
func Namespace(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Namespace = n
|
||||
}
|
||||
}
|
||||
|
||||
// Name provides a function to set the name option.
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// Version provides a function to set the version option.
|
||||
func Version(v string) Option {
|
||||
return func(o *Options) {
|
||||
o.Version = v
|
||||
}
|
||||
}
|
||||
|
||||
// Address provides a function to set the address option.
|
||||
func Address(a string) Option {
|
||||
return func(o *Options) {
|
||||
o.Address = a
|
||||
}
|
||||
}
|
||||
|
||||
// TLSEnabled provides a function to enable/disable TLS
|
||||
func TLSEnabled(v bool) Option {
|
||||
return func(o *Options) {
|
||||
o.TLSEnabled = v
|
||||
}
|
||||
}
|
||||
|
||||
// TLSCert provides a function to set the TLS server certificate and key
|
||||
func TLSCert(c string, k string) Option {
|
||||
return func(o *Options) {
|
||||
o.TLSCert = c
|
||||
o.TLSKey = k
|
||||
}
|
||||
}
|
||||
|
||||
// Context provides a function to set the context option.
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// TraceProvider provides a function to set the trace provider option.
|
||||
func TraceProvider(tp trace.TracerProvider) Option {
|
||||
return func(o *Options) {
|
||||
o.TraceProvider = tp
|
||||
}
|
||||
}
|
||||
|
||||
func HandlerWrappers(w ...server.HandlerWrapper) Option {
|
||||
return func(o *Options) {
|
||||
o.HandlerWrappers = w
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mgrpcs "github.com/go-micro/plugins/v4/server/grpc"
|
||||
"github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus"
|
||||
mtracer "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry"
|
||||
"github.com/rs/zerolog"
|
||||
"go-micro.dev/v4"
|
||||
"go-micro.dev/v4/client"
|
||||
"go-micro.dev/v4/server"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
occrypto "github.com/qsfera/server/pkg/crypto"
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/pkg/registry"
|
||||
)
|
||||
|
||||
// Service simply wraps the go-micro grpc service.
|
||||
type Service struct {
|
||||
micro.Service
|
||||
}
|
||||
|
||||
// NewServiceWithClient initializes a new grpc service with explicit client.
|
||||
func NewServiceWithClient(client client.Client, opts ...Option) (Service, error) {
|
||||
var mServer server.Server
|
||||
sopts := newOptions(opts...)
|
||||
keepaliveParams := grpc.KeepaliveParams(keepalive.ServerParameters{
|
||||
MaxConnectionAge: GetMaxConnectionAge(), // this forces clients to reconnect after 30 seconds, triggering a new DNS lookup to pick up new IPs
|
||||
})
|
||||
tlsConfig := &tls.Config{}
|
||||
|
||||
if sopts.TLSEnabled {
|
||||
var cert tls.Certificate
|
||||
var err error
|
||||
if sopts.TLSCert != "" {
|
||||
cert, err = tls.LoadX509KeyPair(sopts.TLSCert, sopts.TLSKey)
|
||||
if err != nil {
|
||||
sopts.Logger.Error().Err(err).Str("cert", sopts.TLSCert).Str("key", sopts.TLSKey).Msg("error loading server certifcate and key")
|
||||
return Service{}, fmt.Errorf("grpc service error loading server certificate and key: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Generate a self-signed server certificate on the fly. This requires the clients
|
||||
// to connect with InsecureSkipVerify.
|
||||
cert, err = occrypto.GenTempCertForAddr(sopts.Address)
|
||||
if err != nil {
|
||||
return Service{}, fmt.Errorf("grpc service error creating temporary self-signed certificate: %w", err)
|
||||
}
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
mServer = mgrpcs.NewServer(mgrpcs.Options(keepaliveParams), mgrpcs.AuthTLS(tlsConfig))
|
||||
} else {
|
||||
mServer = mgrpcs.NewServer(mgrpcs.Options(keepaliveParams))
|
||||
}
|
||||
|
||||
handlerWrappers := []server.HandlerWrapper{
|
||||
mtracer.NewHandlerWrapper(
|
||||
mtracer.WithTraceProvider(sopts.TraceProvider),
|
||||
),
|
||||
}
|
||||
if sopts.Logger.GetLevel() == zerolog.DebugLevel {
|
||||
handlerWrappers = append(handlerWrappers, LogHandler(&sopts.Logger))
|
||||
}
|
||||
handlerWrappers = append(handlerWrappers, sopts.HandlerWrappers...)
|
||||
|
||||
mopts := []micro.Option{
|
||||
// first add a server because it will reset any options
|
||||
micro.Server(mServer),
|
||||
// also add a client that can be used after initializing the service
|
||||
micro.Client(client),
|
||||
micro.Address(sopts.Address),
|
||||
micro.Name(strings.Join([]string{sopts.Namespace, sopts.Name}, ".")),
|
||||
micro.Version(sopts.Version),
|
||||
micro.Context(sopts.Context),
|
||||
micro.Registry(registry.GetRegistry()),
|
||||
micro.RegisterTTL(registry.GetRegisterTTL()),
|
||||
micro.RegisterInterval(registry.GetRegisterInterval()),
|
||||
micro.WrapHandler(prometheus.NewHandlerWrapper()),
|
||||
micro.WrapClient(mtracer.NewClientWrapper(
|
||||
mtracer.WithTraceProvider(sopts.TraceProvider),
|
||||
)),
|
||||
micro.WrapHandler(handlerWrappers...),
|
||||
micro.WrapSubscriber(mtracer.NewSubscriberWrapper(
|
||||
mtracer.WithTraceProvider(sopts.TraceProvider),
|
||||
)),
|
||||
}
|
||||
|
||||
return Service{micro.NewService(mopts...)}, nil
|
||||
}
|
||||
|
||||
// If used with tracing, please ensure this is registered (by micro.WrapHandler()) after
|
||||
// micro-plugin's opentracing wrapper: `opentracing.NewHandlerWrapper()`
|
||||
func LogHandler(l *log.Logger) func(fn server.HandlerFunc) server.HandlerFunc {
|
||||
return func(fn server.HandlerFunc) server.HandlerFunc {
|
||||
return func(ctx context.Context, req server.Request, rsp any) error {
|
||||
now := time.Now()
|
||||
spanContext := trace.SpanContextFromContext(ctx)
|
||||
defer func() {
|
||||
l.Debug().
|
||||
Str("traceid", spanContext.TraceID().String()).
|
||||
Str("method", req.Method()).
|
||||
Str("endpoint", req.Endpoint()).
|
||||
Str("content-type", req.ContentType()).
|
||||
Str("service", req.Service()).
|
||||
Interface("headers", req.Header()).
|
||||
Dur("duration", time.Since(now)).
|
||||
Msg("grpc call")
|
||||
}()
|
||||
return fn(ctx, req, rsp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/pkg/shared"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
TLSConfig shared.HTTPServiceTLS
|
||||
Namespace string
|
||||
Name string
|
||||
Version string
|
||||
Address string
|
||||
Handler http.Handler
|
||||
Context context.Context
|
||||
Flags []pflag.Flag
|
||||
TraceProvider trace.TracerProvider
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{
|
||||
Namespace: "go.micro.web",
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(l log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace provides a function to set the namespace option.
|
||||
func Namespace(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Namespace = n
|
||||
}
|
||||
}
|
||||
|
||||
// Name provides a function to set the name option.
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// Version provides a function to set the version option.
|
||||
func Version(v string) Option {
|
||||
return func(o *Options) {
|
||||
o.Version = v
|
||||
}
|
||||
}
|
||||
|
||||
// Address provides a function to set the address option.
|
||||
func Address(a string) Option {
|
||||
return func(o *Options) {
|
||||
o.Address = a
|
||||
}
|
||||
}
|
||||
|
||||
// Context provides a function to set the context option.
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// Flags provides a function to set the flags option.
|
||||
func Flags(flags ...pflag.Flag) Option {
|
||||
return func(o *Options) {
|
||||
o.Flags = append(o.Flags, flags...)
|
||||
}
|
||||
}
|
||||
|
||||
// TLSConfig provides a function to set the TLSConfig option.
|
||||
func TLSConfig(config shared.HTTPServiceTLS) Option {
|
||||
return func(o *Options) {
|
||||
o.TLSConfig = config
|
||||
}
|
||||
}
|
||||
|
||||
// TraceProvider provides a function to set the TraceProvider option.
|
||||
func TraceProvider(tp trace.TracerProvider) Option {
|
||||
return func(o *Options) {
|
||||
o.TraceProvider = tp
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qsfera/server/pkg/broker"
|
||||
"github.com/qsfera/server/pkg/registry"
|
||||
|
||||
mhttps "github.com/go-micro/plugins/v4/server/http"
|
||||
mtracer "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry"
|
||||
occrypto "github.com/qsfera/server/pkg/crypto"
|
||||
"go-micro.dev/v4"
|
||||
"go-micro.dev/v4/server"
|
||||
)
|
||||
|
||||
// Service simply wraps the go-micro web service.
|
||||
type Service struct {
|
||||
micro.Service
|
||||
}
|
||||
|
||||
// NewService initializes a new http service.
|
||||
func NewService(opts ...Option) (Service, error) {
|
||||
noopBroker := broker.NoOp{}
|
||||
sopts := newOptions(opts...)
|
||||
var mServer server.Server
|
||||
if sopts.TLSConfig.Enabled {
|
||||
var cert tls.Certificate
|
||||
var err error
|
||||
if sopts.TLSConfig.Cert != "" {
|
||||
cert, err = tls.LoadX509KeyPair(sopts.TLSConfig.Cert, sopts.TLSConfig.Key)
|
||||
if err != nil {
|
||||
sopts.Logger.Error().Err(err).
|
||||
Str("cert", sopts.TLSConfig.Cert).
|
||||
Str("key", sopts.TLSConfig.Key).
|
||||
Msg("error loading server certifcate and key")
|
||||
return Service{}, fmt.Errorf("error loading server certificate and key: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Generate a self-signed server certificate on the fly. This requires the clients
|
||||
// to connect with InsecureSkipVerify.
|
||||
sopts.Logger.Warn().Str("address", sopts.Address).
|
||||
Msg("No server certificate configured. Generating a temporary self-signed certificate")
|
||||
cert, err = occrypto.GenTempCertForAddr(sopts.Address)
|
||||
if err != nil {
|
||||
return Service{}, fmt.Errorf("error creating temporary self-signed certificate: %w", err)
|
||||
}
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
mServer = mhttps.NewServer(server.TLSConfig(tlsConfig))
|
||||
} else {
|
||||
mServer = mhttps.NewServer()
|
||||
}
|
||||
|
||||
wopts := []micro.Option{
|
||||
micro.Server(mServer),
|
||||
micro.Broker(noopBroker),
|
||||
micro.Address(sopts.Address),
|
||||
micro.Name(strings.Join([]string{sopts.Namespace, sopts.Name}, ".")),
|
||||
micro.Version(sopts.Version),
|
||||
micro.Context(sopts.Context),
|
||||
// TODO: clarify if this is actually used on the go-micro side
|
||||
//micro.Flags(sopts.Flags...),
|
||||
micro.Registry(registry.GetRegistry()),
|
||||
micro.RegisterTTL(registry.GetRegisterTTL()),
|
||||
micro.RegisterInterval(registry.GetRegisterInterval()),
|
||||
micro.WrapClient(mtracer.NewClientWrapper(
|
||||
mtracer.WithTraceProvider(sopts.TraceProvider),
|
||||
)),
|
||||
micro.WrapHandler(mtracer.NewHandlerWrapper(
|
||||
mtracer.WithTraceProvider(sopts.TraceProvider),
|
||||
)),
|
||||
micro.WrapSubscriber(mtracer.NewSubscriberWrapper(
|
||||
mtracer.WithTraceProvider(sopts.TraceProvider),
|
||||
)),
|
||||
}
|
||||
if sopts.TLSConfig.Enabled {
|
||||
wopts = append(wopts, micro.Metadata(map[string]string{"use_tls": "true"}))
|
||||
}
|
||||
|
||||
return Service{micro.NewService(wopts...)}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user