[hw-1] implement cart service

This commit is contained in:
Никита Шубин
2025-05-25 15:49:17 +00:00
parent 3d3f10647b
commit 5077f04b0c
28 changed files with 1151 additions and 2 deletions

View 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
}

View 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)
}

View 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)
}

View 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
}