github.com/yandex/pandora@v0.5.32/examples/http/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"log/slog"
     9  	"mime"
    10  	"net"
    11  	"net/http"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  
    16  	"github.com/yandex/pandora/lib/str"
    17  )
    18  
    19  const (
    20  	defaultPort = "8091"
    21  
    22  	userCount         = 10
    23  	userMultiplicator = 1000
    24  	itemMultiplicator = 100
    25  )
    26  
    27  type StatisticBodyResponse struct {
    28  	Code200 map[int64]uint64 `json:"200"`
    29  	Code400 uint64           `json:"400"`
    30  	Code500 uint64           `json:"500"`
    31  }
    32  
    33  type StatisticResponse struct {
    34  	Auth StatisticBodyResponse `json:"auth"`
    35  	List StatisticBodyResponse `json:"list"`
    36  	Item StatisticBodyResponse `json:"item"`
    37  }
    38  
    39  func checkContentTypeAndMethod(r *http.Request, methods []string) (int, error) {
    40  	contentType := r.Header.Get("Content-Type")
    41  	mt, _, err := mime.ParseMediaType(contentType)
    42  	if err != nil {
    43  		return http.StatusBadRequest, errors.New("malformed Content-Type header")
    44  	}
    45  
    46  	if mt != "application/json" {
    47  		return http.StatusUnsupportedMediaType, errors.New("header Content-Type must be application/json")
    48  	}
    49  
    50  	for _, method := range methods {
    51  		if r.Method == method {
    52  			return 0, nil
    53  		}
    54  	}
    55  	return http.StatusMethodNotAllowed, errors.New("method not allowed")
    56  }
    57  
    58  func (s *Server) checkAuthorization(r *http.Request) (int64, int, error) {
    59  	authHeader := r.Header.Get("Authorization")
    60  	authHeader = strings.Replace(authHeader, "Bearer ", "", 1)
    61  	s.mu.RLock()
    62  	userID := s.keys[authHeader]
    63  	s.mu.RUnlock()
    64  
    65  	if userID == 0 {
    66  		return 0, http.StatusUnauthorized, errors.New("StatusUnauthorized")
    67  	}
    68  	return userID, 0, nil
    69  }
    70  
    71  func (s *Server) authHandler(w http.ResponseWriter, r *http.Request) {
    72  	code, err := checkContentTypeAndMethod(r, []string{http.MethodPost})
    73  	if err != nil {
    74  		if code >= 500 {
    75  			s.stats.IncAuth500()
    76  		} else {
    77  			s.stats.IncAuth400()
    78  		}
    79  		http.Error(w, err.Error(), code)
    80  		return
    81  	}
    82  
    83  	user := struct {
    84  		UserID int64 `json:"user_id"`
    85  	}{}
    86  	err = json.NewDecoder(r.Body).Decode(&user)
    87  	if err != nil {
    88  		s.stats.IncAuth500()
    89  		http.Error(w, "Incorrect body", http.StatusNotAcceptable)
    90  		return
    91  	}
    92  	if user.UserID > userCount {
    93  		s.stats.IncAuth400()
    94  		http.Error(w, "Incorrect user_id", http.StatusBadRequest)
    95  		return
    96  	}
    97  
    98  	s.stats.IncAuth200(user.UserID)
    99  
   100  	var authKey string
   101  	s.mu.RLock()
   102  	for k, v := range s.keys {
   103  		if v == user.UserID {
   104  			authKey = k
   105  			break
   106  		}
   107  	}
   108  	s.mu.RUnlock()
   109  
   110  	w.Header().Set("Content-Type", "application/json")
   111  	w.Header().Set("Authorization", "Bearer "+authKey)
   112  	_, _ = w.Write([]byte(`{"result":"ok"}`))
   113  }
   114  
   115  func (s *Server) listHandler(w http.ResponseWriter, r *http.Request) {
   116  	code, err := checkContentTypeAndMethod(r, []string{http.MethodGet})
   117  	if err != nil {
   118  		if code >= 500 {
   119  			s.stats.IncList500()
   120  		} else {
   121  			s.stats.IncList400()
   122  		}
   123  		http.Error(w, err.Error(), code)
   124  		return
   125  	}
   126  
   127  	userID, code, err := s.checkAuthorization(r)
   128  	if err != nil {
   129  		http.Error(w, err.Error(), code)
   130  		return
   131  	}
   132  
   133  	s.stats.IncList200(userID)
   134  
   135  	// Logic
   136  	userID *= userMultiplicator
   137  	result := make([]string, itemMultiplicator)
   138  	for i := int64(0); i < itemMultiplicator; i++ {
   139  		result[i] = strconv.FormatInt(userID+i, 10)
   140  	}
   141  
   142  	w.Header().Set("Content-Type", "application/json")
   143  	_, _ = w.Write([]byte(fmt.Sprintf(`{"items": [%s]}`, strings.Join(result, ","))))
   144  }
   145  
   146  func (s *Server) orderHandler(w http.ResponseWriter, r *http.Request) {
   147  	code, err := checkContentTypeAndMethod(r, []string{http.MethodPost})
   148  	if err != nil {
   149  		if code >= 500 {
   150  			s.stats.IncOrder500()
   151  		} else {
   152  			s.stats.IncOrder400()
   153  		}
   154  		http.Error(w, err.Error(), code)
   155  		return
   156  	}
   157  
   158  	userID, code, err := s.checkAuthorization(r)
   159  	if err != nil {
   160  		http.Error(w, err.Error(), code)
   161  		return
   162  	}
   163  
   164  	// Logic
   165  	itm := struct {
   166  		ItemID int64 `json:"item_id"`
   167  	}{}
   168  	err = json.NewDecoder(r.Body).Decode(&itm)
   169  	if err != nil {
   170  		s.stats.IncOrder500()
   171  		http.Error(w, "Incorrect body", http.StatusNotAcceptable)
   172  		return
   173  	}
   174  
   175  	ranger := userID * userMultiplicator
   176  	if itm.ItemID < ranger || itm.ItemID >= ranger+itemMultiplicator {
   177  		s.stats.IncOrder400()
   178  		http.Error(w, "Incorrect user_id", http.StatusBadRequest)
   179  		return
   180  	}
   181  
   182  	s.stats.IncOrder200(userID)
   183  
   184  	w.Header().Set("Content-Type", "application/json")
   185  	_, _ = w.Write([]byte(fmt.Sprintf(`{"item": %d}`, itm.ItemID)))
   186  }
   187  
   188  func (s *Server) resetHandler(w http.ResponseWriter, r *http.Request) {
   189  	if r.Method != http.MethodPost {
   190  		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
   191  		return
   192  	}
   193  	s.stats.Reset()
   194  
   195  	w.Header().Set("Content-Type", "application/json")
   196  	_, _ = w.Write([]byte(`{"status": "ok"}`))
   197  }
   198  
   199  func (s *Server) statisticHandler(w http.ResponseWriter, r *http.Request) {
   200  	response := StatisticResponse{
   201  		Auth: StatisticBodyResponse{
   202  			Code200: s.stats.Auth200,
   203  			Code400: s.stats.auth400.Load(),
   204  			Code500: s.stats.auth500.Load(),
   205  		},
   206  		List: StatisticBodyResponse{
   207  			Code200: s.stats.List200,
   208  			Code400: s.stats.list400.Load(),
   209  			Code500: s.stats.list500.Load(),
   210  		},
   211  		Item: StatisticBodyResponse{
   212  			Code200: s.stats.Order200,
   213  			Code400: s.stats.order400.Load(),
   214  			Code500: s.stats.order500.Load(),
   215  		},
   216  	}
   217  	b, err := json.Marshal(response)
   218  	if err != nil {
   219  		http.Error(w, "Internal error", http.StatusInternalServerError)
   220  		return
   221  	}
   222  	w.Header().Set("Content-Type", "application/json")
   223  	_, _ = w.Write(b)
   224  }
   225  
   226  func NewServer(addr string, log *slog.Logger, seed int64) *Server {
   227  	keys := make(map[string]int64, userCount)
   228  	for i := int64(1); i <= userCount; i++ {
   229  		keys[str.RandStringRunes(64, "")] = i
   230  	}
   231  
   232  	result := &Server{Log: log, stats: newStats(userCount), keys: keys}
   233  	mux := http.NewServeMux()
   234  
   235  	mux.Handle("/auth", http.HandlerFunc(result.authHandler))
   236  	mux.Handle("/list", http.HandlerFunc(result.listHandler))
   237  	mux.Handle("/order", http.HandlerFunc(result.orderHandler))
   238  	mux.Handle("/stats", http.HandlerFunc(result.statisticHandler))
   239  	mux.Handle("/reset", http.HandlerFunc(result.resetHandler))
   240  
   241  	ctx := context.Background()
   242  	result.srv = &http.Server{
   243  		Addr:    addr,
   244  		Handler: mux,
   245  		BaseContext: func(l net.Listener) context.Context {
   246  			return ctx
   247  		},
   248  	}
   249  	log.Info("New server created", slog.String("addr", addr), slog.Any("keys", keys))
   250  
   251  	return result
   252  }
   253  
   254  type Server struct {
   255  	srv *http.Server
   256  
   257  	Log   *slog.Logger
   258  	stats *Stats
   259  	keys  map[string]int64
   260  	mu    sync.RWMutex
   261  
   262  	runErr chan error
   263  	finish bool
   264  }
   265  
   266  func (s *Server) Err() <-chan error {
   267  	return s.runErr
   268  }
   269  
   270  func (s *Server) ServeAsync() {
   271  	go func() {
   272  		err := s.srv.ListenAndServe()
   273  		if err != nil {
   274  			s.runErr <- err
   275  		} else {
   276  			s.runErr <- nil
   277  		}
   278  		s.finish = true
   279  	}()
   280  }
   281  
   282  func (s *Server) Shutdown(ctx context.Context) error {
   283  	return s.srv.Shutdown(ctx)
   284  }
   285  
   286  func (s *Server) Stats() *Stats {
   287  	return s.stats
   288  }