im-kulikov/helium

View on GitHub
web/servers.go

Summary

Maintainability
A
0 mins
Test Coverage
package web

import (
    "net"
    "net/http"
    "time"

    "github.com/spf13/viper"
    "go.uber.org/dig"
    "go.uber.org/zap"
    "google.golang.org/grpc"

    "github.com/im-kulikov/helium/internal"
    "github.com/im-kulikov/helium/module"
    "github.com/im-kulikov/helium/service"
)

type (
    // APIParams struct.
    APIParams struct {
        dig.In

        Config   *viper.Viper
        Logger   *zap.Logger
        Handler  http.Handler `optional:"true"`
        Listener net.Listener `name:"api_listener" optional:"true"`
    }

    // HTTPParams struct.
    HTTPParams struct {
        dig.In

        Config   *viper.Viper
        Logger   *zap.Logger
        Name     string       `name:"http_name" optional:"true"`
        Key      string       `name:"http_config" optional:"true"`
        Handler  http.Handler `name:"http_handler" optional:"true"`
        Listener net.Listener `name:"http_listener" optional:"true"`
    }

    // ServerResult struct.
    ServerResult struct {
        dig.Out

        Server service.Service `group:"services"`
    }

    grpcParams struct {
        dig.In

        Logger   *zap.Logger
        Viper    *viper.Viper
        Name     string       `name:"grpc_name" optional:"true"`
        Key      string       `name:"grpc_config" optional:"true"`
        Server   *grpc.Server `name:"grpc_server" optional:"true"`
        Listener net.Listener `name:"grpc_listener" optional:"true"`
    }
)

const (
    apiServer  = "api"
    gRPCServer = "grpc"

    // ErrEmptyLogger is raised when empty logger passed into New function.
    ErrEmptyLogger = internal.Error("empty logger")
)

var (
    // DefaultServersModule of web base structs.
    // nolint:gochecknoglobals
    DefaultServersModule = module.Combine(
        DefaultGRPCModule,
        OpsModule,
        APIModule,
    )

    // APIModule defines API server module.
    // nolint:gochecknoglobals
    APIModule = module.New(NewAPIServer)

    // DefaultGRPCModule defines default gRPC server module.
    // nolint:gochecknoglobals
    DefaultGRPCModule = module.New(newDefaultGRPCServer)
)

// NewAPIServer creates api server by http.Handler from DI container.
func NewAPIServer(p APIParams) (ServerResult, error) {
    return NewHTTPServer(HTTPParams{
        Config:   p.Config,
        Logger:   p.Logger,
        Name:     apiServer,
        Key:      apiServer,
        Handler:  p.Handler,
        Listener: p.Listener,
    })
}

func newDefaultGRPCServer(p grpcParams) (ServerResult, error) {
    if p.Key == "" {
        p.Key = gRPCServer
    }

    if p.Name == "" {
        p.Name = "default_grpc"
    }

    switch {
    case p.Logger == nil:
        return ServerResult{}, ErrEmptyLogger
    case p.Viper == nil:
        p.Logger.Info("Empty config for gRPC server, skip")

        return ServerResult{}, nil
    case p.Viper.GetBool(p.Key + ".disabled"):
        p.Logger.Info("Server disabled",
            zap.String("name", p.Name))

        return ServerResult{}, nil
    case p.Server == nil:
        p.Logger.Info("Empty server, skip",
            zap.String("name", p.Name))

        return ServerResult{}, nil
    }

    var address string

    options := []GRPCOption{
        GRPCName(p.Name),
        GRPCWithLogger(p.Logger),
        GRPCListener(p.Listener),
    }

    if p.Viper.GetBool(p.Key + ".skip_errors") {
        options = append(options, GRPCSkipErrors())
    }

    if p.Viper.IsSet(p.Key + ".network") {
        options = append(options, GRPCListenNetwork(p.Viper.GetString(p.Key+".network")))
    }

    if p.Viper.IsSet(p.Key + ".address") {
        address = p.Viper.GetString(p.Key + ".address")
        options = append(options, GRPCListenAddress(address))
    }

    if p.Listener != nil {
        address = p.Listener.Addr().String()
    }

    serve, err := NewGRPCService(p.Server, options...)

    p.Logger.Info("Creates gRPC server", zap.String("name", p.Key), zap.String("address", address))

    return ServerResult{Server: serve}, err
}

// NewHTTPServer creates http-server that will be embedded into multiple server.
func NewHTTPServer(p HTTPParams) (ServerResult, error) {
    switch {
    case p.Logger == nil:
        return ServerResult{}, ErrEmptyLogger
    case p.Key == "" || p.Config == nil:
        p.Logger.Info("Empty config or key for http server, skip", zap.String("key", p.Key))

        return ServerResult{}, nil
    case p.Handler == nil:
        p.Logger.Info("Empty handler, skip", zap.String("name", p.Key))

        return ServerResult{}, nil
    case p.Config.GetBool(p.Key + ".disabled"):
        p.Logger.Info("Server disabled", zap.String("name", p.Key))

        return ServerResult{}, nil
    }

    options := []HTTPOption{
        HTTPName(p.Key),
        HTTPListener(p.Listener),
        HTTPWithLogger(p.Logger),
    }

    var address string
    if p.Config.IsSet(p.Key + ".address") {
        address = p.Config.GetString(p.Key + ".address")
        options = append(options, HTTPListenAddress(address))
    }

    if p.Config.IsSet(p.Key + ".network") {
        options = append(options, HTTPListenNetwork(p.Config.GetString(p.Key+".network")))
    }

    if p.Config.IsSet(p.Key + ".skip_errors") {
        options = append(options, HTTPSkipErrors())
    }

    hServer := &http.Server{Handler: p.Handler, ReadHeaderTimeout: time.Second}
    if p.Config.IsSet(p.Key + ".read_timeout") {
        hServer.ReadTimeout = p.Config.GetDuration(p.Key + ".read_timeout")
    }

    if p.Config.IsSet(p.Key + ".read_header_timeout") {
        hServer.ReadHeaderTimeout = p.Config.GetDuration(p.Key + ".read_header_timeout")
    }

    if p.Config.IsSet(p.Key + ".write_timeout") {
        hServer.WriteTimeout = p.Config.GetDuration(p.Key + ".write_timeout")
    }

    if p.Config.IsSet(p.Key + ".idle_timeout") {
        hServer.IdleTimeout = p.Config.GetDuration(p.Key + ".idle_timeout")
    }

    if p.Config.IsSet(p.Key + ".max_header_bytes") {
        hServer.MaxHeaderBytes = p.Config.GetInt(p.Key + ".max_header_bytes")
    }

    if p.Listener != nil {
        address = p.Listener.Addr().String()
    }

    serve, err := NewHTTPService(hServer, options...)
    if err != nil {
        return ServerResult{}, err
    }

    p.Logger.Info("creating http server", zap.String("name", p.Name), zap.String("address", address))

    return ServerResult{Server: serve}, nil
}