Initial commit
This commit is contained in:
144
port/http/service.go
Normal file
144
port/http/service.go
Normal file
@@ -0,0 +1,144 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user