This repository has been archived by the owner on Mar 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #102 from im-kulikov/FIX-100_create_ops_server
Create OPS server
- Loading branch information
Showing
7 changed files
with
445 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
package web | ||
|
||
import ( | ||
"context" | ||
"expvar" | ||
"net/http" | ||
"net/http/pprof" | ||
"time" | ||
|
||
"github.com/prometheus/client_golang/prometheus/promhttp" | ||
"github.com/spf13/viper" | ||
"go.uber.org/dig" | ||
"go.uber.org/zap" | ||
|
||
"github.com/im-kulikov/helium/internal" | ||
"github.com/im-kulikov/helium/module" | ||
"github.com/im-kulikov/helium/service" | ||
) | ||
|
||
// ProbeChecker used by ops-server ready and health handler. | ||
type ProbeChecker func(context.Context) error | ||
|
||
// HTTPConfig . | ||
type HTTPConfig struct { | ||
Logger *zap.Logger `mapstructure:"-"` | ||
Handler http.Handler `mapstructure:"-"` | ||
|
||
Name string `mapstructure:"name"` | ||
Address string `mapstructure:"address"` | ||
Network string `mapstructure:"network"` | ||
|
||
ReadTimeout time.Duration `mapstructure:"read_timeout"` | ||
ReadHeaderTimeout time.Duration `mapstructure:"read_header_timeout"` | ||
WriteTimeout time.Duration `mapstructure:"write_timeout"` | ||
IdleTimeout time.Duration `mapstructure:"idle_timeout"` | ||
MaxHeaderBytes int `mapstructure:"max_header_bytes"` | ||
} | ||
|
||
// OpsConfig . | ||
type OpsConfig struct { | ||
HTTPConfig `mapstructure:",squash"` | ||
|
||
DisableMetrics bool `mapstructure:"disable_metrics"` | ||
DisableProfile bool `mapstructure:"disable_pprof"` | ||
DisableHealthy bool `mapstructure:"disable_healthy"` | ||
} | ||
|
||
// OpsProbeParams allows setting health and ready probes for ops server. | ||
type OpsProbeParams struct { | ||
dig.In | ||
|
||
HealthProbes []ProbeChecker `group:"health_probes"` | ||
ReadyProbes []ProbeChecker `group:"ready_probes"` | ||
} | ||
|
||
const ( | ||
// ErrEmptyConfig is raised when empty configuration passed into functions that requires it. | ||
ErrEmptyConfig = internal.Error("empty configuration") | ||
|
||
opsDefaultName = "ops-server" | ||
|
||
opsDefaultAddress = ":8081" | ||
opsDefaultNetwork = "tcp" | ||
|
||
cfgOpsAddress = "ops.address" | ||
cfgOpsNetwork = "ops.network" | ||
cfgOpsReadTimeout = "ops.read_timeout" | ||
cfgOpsReadHeaderTimeout = "ops.read_header_timeout" | ||
cfgOpsWriteTimeout = "ops.write_timeout" | ||
cfgOpsIdleTimeout = "ops.idle_timeout" | ||
cfgOpsMaxHeaderBytes = "ops.max_header_bytes" | ||
cfgOpsDisableMetrics = "ops.disable_metrics" | ||
cfgOpsDisableProfile = "ops.disable_profile" | ||
cfgOpsDisableHealthy = "ops.disable_healthy" | ||
|
||
opsPathMetrics = "/metrics" | ||
opsPathDebugVars = "/debug/vars" | ||
opsPathProfileIndex = "/debug/pprof/" | ||
opsPathProfileCMDLine = "/debug/pprof/cmdline" | ||
opsPathProfileProfile = "/debug/pprof/profile" | ||
opsPathProfileSymbol = "/debug/pprof/symbol" | ||
opsPathProfileTrace = "/debug/pprof/trace" | ||
opsPathAppReady = "/-/ready" | ||
opsPathAppHealthy = "/-/healthy" | ||
) | ||
|
||
var _ = OpsModule | ||
|
||
// OpsModule allows import ops http.Server. | ||
// nolint: gochecknoglobals | ||
var OpsModule = module.New(NewOpsServer, dig.Group("services")).AppendConstructor(NewOpsConfig) | ||
|
||
// OpsDefaults allows setting default settings for ops server. | ||
func OpsDefaults(v *viper.Viper) { | ||
v.SetDefault(cfgOpsAddress, opsDefaultAddress) | ||
v.SetDefault(cfgOpsNetwork, opsDefaultNetwork) | ||
|
||
v.SetDefault(cfgOpsDisableMetrics, false) | ||
v.SetDefault(cfgOpsDisableProfile, false) | ||
v.SetDefault(cfgOpsDisableHealthy, false) | ||
} | ||
|
||
// PrepareHTTPService creates http.Server as service.Service. | ||
func PrepareHTTPService(cfg HTTPConfig) (service.Service, error) { | ||
serve := &http.Server{ | ||
Handler: cfg.Handler, | ||
|
||
ReadTimeout: cfg.ReadTimeout, | ||
ReadHeaderTimeout: cfg.ReadHeaderTimeout, | ||
WriteTimeout: cfg.WriteTimeout, | ||
IdleTimeout: cfg.IdleTimeout, | ||
MaxHeaderBytes: cfg.MaxHeaderBytes, | ||
} | ||
|
||
cfg.Logger.Info("creating http.Server", | ||
zap.String("name", cfg.Name), | ||
zap.String("address", cfg.Address)) | ||
|
||
return NewHTTPService(serve, | ||
HTTPName(cfg.Name), | ||
HTTPWithLogger(cfg.Logger), | ||
HTTPListenAddress(cfg.Address), | ||
HTTPListenNetwork(cfg.Network)) | ||
} | ||
|
||
// NewOpsConfig creates OpsConfig and should be moved to settings module in the future. | ||
func NewOpsConfig(v *viper.Viper, l *zap.Logger) (*OpsConfig, error) { | ||
switch { | ||
case l == nil: | ||
return nil, ErrEmptyLogger | ||
case v == nil: | ||
return nil, ErrEmptyConfig | ||
case !v.IsSet(cfgOpsAddress): | ||
return nil, ErrEmptyHTTPAddress | ||
} | ||
|
||
hc := HTTPConfig{ | ||
Logger: l, | ||
Name: opsDefaultName, | ||
Address: v.GetString(cfgOpsAddress), | ||
Network: v.GetString(cfgOpsNetwork), | ||
ReadTimeout: v.GetDuration(cfgOpsReadTimeout), | ||
ReadHeaderTimeout: v.GetDuration(cfgOpsReadHeaderTimeout), | ||
WriteTimeout: v.GetDuration(cfgOpsWriteTimeout), | ||
IdleTimeout: v.GetDuration(cfgOpsIdleTimeout), | ||
MaxHeaderBytes: v.GetInt(cfgOpsMaxHeaderBytes), | ||
} | ||
|
||
return &OpsConfig{ | ||
HTTPConfig: hc, | ||
DisableMetrics: v.GetBool(cfgOpsDisableMetrics), | ||
DisableProfile: v.GetBool(cfgOpsDisableProfile), | ||
DisableHealthy: v.GetBool(cfgOpsDisableHealthy), | ||
}, nil | ||
} | ||
|
||
// NewOpsServer creates ops server. | ||
func NewOpsServer(cfg *OpsConfig, probe OpsProbeParams) (service.Service, error) { | ||
mux := http.NewServeMux() | ||
|
||
mux.Handle("/", http.NotFoundHandler()) | ||
|
||
if !cfg.DisableMetrics { | ||
mux.Handle(opsPathMetrics, promhttp.Handler()) | ||
} | ||
|
||
if !cfg.DisableProfile { | ||
mux.Handle(opsPathDebugVars, expvar.Handler()) | ||
|
||
mux.HandleFunc(opsPathProfileIndex, pprof.Index) | ||
mux.HandleFunc(opsPathProfileCMDLine, pprof.Cmdline) | ||
mux.HandleFunc(opsPathProfileProfile, pprof.Profile) | ||
mux.HandleFunc(opsPathProfileSymbol, pprof.Symbol) | ||
mux.HandleFunc(opsPathProfileTrace, pprof.Trace) | ||
} | ||
|
||
if !cfg.DisableHealthy { | ||
mux.HandleFunc(opsPathAppReady, probeChecker(probe.ReadyProbes)) | ||
mux.HandleFunc(opsPathAppHealthy, probeChecker(probe.HealthProbes)) | ||
} | ||
|
||
return PrepareHTTPService(HTTPConfig{ | ||
Logger: cfg.Logger, | ||
Handler: mux, | ||
Name: cfg.Name, | ||
Address: cfg.Address, | ||
Network: cfg.Network, | ||
}) | ||
} | ||
|
||
func probeChecker(probes []ProbeChecker) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
for i := range probes { | ||
if probes[i] == nil { | ||
continue | ||
} | ||
|
||
if err := probes[i](r.Context()); err != nil { | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
|
||
return | ||
} | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
} | ||
} |
Oops, something went wrong.