package main import ( "context" "encoding/base64" "errors" "flag" "fmt" "log" "net/http" "os" "os/signal" "syscall" "golang.org/x/time/rate" ) type Config struct { Addr string RPS int } type Handler struct { cfg Config limiter *rate.Limiter } type Session struct { User struct { ID string Name string } } func main() { ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT) defer can() if err := run(ctx); err != nil { panic(err) } } func run(ctx context.Context) error { cfg, err := newConfig() if err != nil { return err } return runHTTP(ctx, cfg) } func newConfig() (Config, error) { cfg := Config{} fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) fs.StringVar(&cfg.Addr, "addr", ":8080", "address to listen on") fs.IntVar(&cfg.RPS, "rps", 100, "requests per second to serve") err := fs.Parse(os.Args[1:]) return cfg, err } func (cfg Config) NewHandler() Handler { return Handler{ cfg: cfg, limiter: rate.NewLimiter(rate.Limit(cfg.RPS), 10), } } func (s Session) Empty() bool { return s == (Session{}) } func runHTTP(ctx context.Context, cfg Config) error { server := &http.Server{ Addr: cfg.Addr, Handler: cfg.NewHandler(), } go func() { <-ctx.Done() server.Close() }() log.Println("listening on", cfg.Addr) if err := server.ListenAndServe(); err != nil && ctx.Err() == nil { return err } return nil } func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err := h.serveHTTP(w, r); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func (h Handler) serveHTTP(w http.ResponseWriter, r *http.Request) error { h.limiter.Wait(r.Context()) session, err := h.auth(r) if err != nil { return err } if session.Empty() { w.Header().Set("WWW-Authenticate", "Basic realm=xyz") w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`IDENTIFY YOURSELF!`)) return nil } return h.handle(session, w, r) } func (h Handler) auth(r *http.Request) (Session, error) { user, pass, ok := r.BasicAuth() if !ok { return Session{}, nil } session := Session{} session.User.ID = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, pass))) session.User.Name = user return session, nil } func (h Handler) handle(session Session, w http.ResponseWriter, r *http.Request) error { return errors.New("not impl") }