mirror of
https://github.com/3ybactuk/marketplace-go-service-project.git
synced 2025-10-30 22:13:44 +03:00
[hw-1] implement cart service
This commit is contained in:
64
cart/internal/infra/config/config.go
Normal file
64
cart/internal/infra/config/config.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Service struct {
|
||||
Host string `yaml:"host"`
|
||||
Port string `yaml:"port"`
|
||||
LogLevel string `yaml:"log_level"`
|
||||
Workers int `yaml:"workers"`
|
||||
} `yaml:"service"`
|
||||
|
||||
Jaeger struct {
|
||||
Host string `yaml:"host"`
|
||||
Port string `yaml:"port"`
|
||||
} `yaml:"jaeger"`
|
||||
|
||||
ProductService struct {
|
||||
Host string `yaml:"host"`
|
||||
Port string `yaml:"port"`
|
||||
Token string `yaml:"token"`
|
||||
Limit int `yaml:"limit"`
|
||||
Burst int `yaml:"burst"`
|
||||
} `yaml:"product_service"`
|
||||
|
||||
LomsService struct {
|
||||
Host string `yaml:"host"`
|
||||
Port string `yaml:"port"`
|
||||
} `yaml:"loms_service"`
|
||||
}
|
||||
|
||||
func LoadConfig(filename string) (*Config, error) {
|
||||
workDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgRoot := filepath.Join(workDir, "configs")
|
||||
absCfgRoot, _ := filepath.Abs(cfgRoot)
|
||||
|
||||
filePath := filepath.Join(absCfgRoot, filepath.Clean(filename))
|
||||
if !strings.HasPrefix(filePath, absCfgRoot) {
|
||||
return nil, fmt.Errorf("invalid path")
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
config := &Config{}
|
||||
if err := yaml.NewDecoder(f).Decode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
26
cart/internal/infra/http/middlewares/time_middleware.go
Normal file
26
cart/internal/infra/http/middlewares/time_middleware.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type TimerMiddleware struct {
|
||||
h http.Handler
|
||||
}
|
||||
|
||||
func NewTimerMiddleware(h http.Handler) http.Handler {
|
||||
return &TimerMiddleware{
|
||||
h: h,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TimerMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer func(now time.Time) {
|
||||
log.Debug().Msgf("%s %s spent %s", r.Method, r.URL.String(), time.Since(now))
|
||||
}(time.Now())
|
||||
|
||||
m.h.ServeHTTP(w, r)
|
||||
}
|
||||
23
cart/internal/infra/http/round_trippers/log_rt.go
Normal file
23
cart/internal/infra/http/round_trippers/log_rt.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package round_trippers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type LogRoundTripper struct {
|
||||
rt http.RoundTripper
|
||||
}
|
||||
|
||||
func NewLogRoundTripper(rt http.RoundTripper) http.RoundTripper {
|
||||
return &LogRoundTripper{
|
||||
rt: rt,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LogRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
log.Debug().Msgf("%s called", r.URL.String())
|
||||
|
||||
return l.rt.RoundTrip(r)
|
||||
}
|
||||
67
cart/internal/infra/http/round_trippers/retry_rt.go
Normal file
67
cart/internal/infra/http/round_trippers/retry_rt.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package round_trippers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type RetryRoundTripper struct {
|
||||
rt http.RoundTripper
|
||||
maxRetries int
|
||||
initialDelaySec int
|
||||
}
|
||||
|
||||
func NewRetryRoundTripper(rt http.RoundTripper, maxRetries int, initialDelaySec int) http.RoundTripper {
|
||||
return &RetryRoundTripper{
|
||||
rt: rt,
|
||||
maxRetries: maxRetries,
|
||||
initialDelaySec: initialDelaySec,
|
||||
}
|
||||
}
|
||||
|
||||
func (rrt *RetryRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
for attempt := 0; attempt <= rrt.maxRetries; attempt++ {
|
||||
if attempt > 0 {
|
||||
// exponential retry time (e.g.: [1s, 2s, 4s])
|
||||
delay := time.Duration(rrt.initialDelaySec<<uint(attempt-1)) * time.Second
|
||||
|
||||
log.Debug().Msgf("retrying, delay=%d", delay)
|
||||
|
||||
timer := time.NewTimer(delay)
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-r.Context().Done():
|
||||
timer.Stop()
|
||||
|
||||
return nil, r.Context().Err()
|
||||
}
|
||||
}
|
||||
|
||||
resp, err = rrt.rt.RoundTrip(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusTooManyRequests, 420:
|
||||
resp.Body.Close()
|
||||
|
||||
if attempt == rrt.maxRetries {
|
||||
return nil, fmt.Errorf("request returned %d after %d retries", resp.StatusCode, rrt.maxRetries)
|
||||
}
|
||||
|
||||
continue
|
||||
default:
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
Reference in New Issue
Block a user