From c8bd8a591d118b95119fe43844b0b062c29f9369 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Tue, 20 Feb 2024 07:58:59 -0700 Subject: [PATCH] wipp --- cmd/server/internal/public/index.html | 11 +++ cmd/server/main.go | 133 ++++++++++++++++++++++++++ cmd/server/main_test.go | 27 ++++++ go.mod | 2 + go.sum | 2 + 5 files changed, 175 insertions(+) create mode 100644 cmd/server/internal/public/index.html create mode 100644 cmd/server/main.go create mode 100644 cmd/server/main_test.go create mode 100644 go.sum diff --git a/cmd/server/internal/public/index.html b/cmd/server/internal/public/index.html new file mode 100644 index 0000000..8279ac0 --- /dev/null +++ b/cmd/server/internal/public/index.html @@ -0,0 +1,11 @@ + + +
+ +
+ +

Hello World

+ + + diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..ca758a5 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,133 @@ +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 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()) + + if ok, err := h.auth(r); err != nil { + return err + } else if !ok { + w.Header().Set("WWW-Authenticate", "Basic realm=xyz") + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`IDENTIFY YOURSELF!`)) + return nil + } + + return h.handle(w, r) +} + +func (h Handler) auth(r *http.Request) (bool, error) { + user, pass, ok := r.BasicAuth() + if !ok { + return false, nil + } + session := Session{} + session.User.ID = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, pass))) + session.User.Name = user + h.putSession(r, session) + return true, nil +} + +func (h Handler) putSession(r *http.Request, session Session) { + ctx := r.Context() + ctx = context.WithValue(ctx, "session", session) + *r = *r.WithContext(ctx) +} + +func (h Handler) getSession(r *http.Request) Session { + ctx := r.Context() + v := ctx.Value("session") + session, _ := v.(Session) + return session +} + +func (h Handler) handle(w http.ResponseWriter, r *http.Request) error { + return errors.New("not impl") +} diff --git a/cmd/server/main_test.go b/cmd/server/main_test.go new file mode 100644 index 0000000..2187353 --- /dev/null +++ b/cmd/server/main_test.go @@ -0,0 +1,27 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestRunHTTP(t *testing.T) { + cfg := Config{} + + h := cfg.NewHandler() + + t.Run("requires auth", func(t *testing.T) { + r := httptest.NewRequest(http.MethodGet, "/", nil) + w := httptest.NewRecorder() + t.Logf("%s %s", r.Method, r.URL) + h.ServeHTTP(w, r) + t.Logf("(%d) %s", w.Code, w.Body.Bytes()) + if w.Code != 401 { + t.Error(w.Code) + } + if w.Header().Get("WWW-Authenticate") == "" { + t.Errorf("expected WWW-Authenticate header but got %+v", w.Header()) + } + }) +} diff --git a/go.mod b/go.mod index 45a44b3..9221629 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module live-studio-audience go 1.21.4 + +require golang.org/x/time v0.5.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a2652c5 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=