145 lines
3.5 KiB
Go
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))
|
|
}
|