This repository has been archived on 2023-12-05. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
protect_trans_info/port/http/service.go
2023-08-24 23:40:46 +03:00

145 lines
3.5 KiB
Go

package http
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
)
type LogStorage interface {
Get(ctx context.Context, first, second uint64) (bool, error)
List(ctx context.Context) (string, error)
}
type Response struct {
Dupes *bool `json:"dupes,omitempty"`
Err string `json:"error,omitempty"`
}
func NewService(host string, port uint, log LogStorage, logger *zap.Logger) (*Service, error) {
srv := http.Server{
Addr: fmt.Sprintf("%s:%d", host, port),
ReadTimeout: time.Second,
ReadHeaderTimeout: time.Millisecond * 500,
WriteTimeout: time.Second * 10,
IdleTimeout: time.Minute,
MaxHeaderBytes: 1024 * 16,
}
service := Service{
server: &srv,
log: log,
logger: logger,
}
mux := chi.NewMux()
mux.Use(middleware.StripSlashes)
mux.Get("/{firstID:[0-9]+}/{secondID:[0-9]+}", service.handleDup)
mux.Get("/list", service.List)
srv.Handler = mux
return &service, nil
}
type Service struct {
server *http.Server
log LogStorage
logger *zap.Logger
}
func (s *Service) Start() {
s.logger.Info("starting http service", zap.String("address", s.server.Addr))
if err := s.server.ListenAndServe(); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
s.logger.Error("listen and serve failed", zap.Error(err))
}
}
}
func (s *Service) Stop(ctx context.Context) {
if err := s.server.Shutdown(ctx); err != nil {
s.logger.Error("the graceful shutdown has failed", zap.Error(err))
}
}
func (s *Service) handleDup(w http.ResponseWriter, r *http.Request) {
started := time.Now()
firstIDStr := chi.URLParam(r, "firstID")
firstID, err := strconv.ParseUint(firstIDStr, 10, 64)
if err != nil {
s.badRequest(w, Response{Err: fmt.Sprintf("invalid first id value: %s", firstIDStr)})
return
}
secondIDStr := chi.URLParam(r, "secondID")
secondID, err := strconv.ParseUint(secondIDStr, 10, 64)
if err != nil {
s.badRequest(w, Response{Err: fmt.Sprintf("invalid second id value: %s", secondIDStr)})
return
}
logger := s.logger.With(zap.Uint64("first_id", firstID), zap.Uint64("second_id", secondID))
logger.Debug("new dup request")
if secondID == firstID {
t := true
s.writeResponse(w, Response{Dupes: &t}, http.StatusOK)
return
}
result, err := s.log.Get(r.Context(), firstID, secondID)
if err != nil {
logger.Error("log storage get failed", zap.Error(err))
s.internalError(w, Response{Err: fmt.Sprintf("storage error")})
return
}
logger.Debug("result", zap.Bool("dup", result))
w.Header().Add("Server-Timing", fmt.Sprintf("db;dur=%dus", time.Since(started).Microseconds()))
s.writeResponse(w, Response{Dupes: &result}, http.StatusOK)
}
func (s *Service) badRequest(w http.ResponseWriter, response Response) {
s.writeResponse(w, response, http.StatusBadRequest)
}
func (s *Service) internalError(w http.ResponseWriter, response Response) {
s.writeResponse(w, response, http.StatusInternalServerError)
}
func (s *Service) writeResponse(w http.ResponseWriter, response Response, code int) {
w.WriteHeader(code)
err := json.NewEncoder(w).Encode(response)
if err != nil {
s.logger.Error("encode bad request response", zap.Error(err), zap.Any("response", response))
}
}
func (s *Service) List(w http.ResponseWriter, r *http.Request) {
list, err := s.log.List(r.Context())
if err != nil {
s.internalError(w, Response{Err: err.Error()})
return
}
_, _ = w.Write([]byte(list))
}