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=