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 }