main
Bel LaPointe 2024-02-20 07:58:59 -07:00
parent db872df672
commit c8bd8a591d
5 changed files with 175 additions and 0 deletions

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<header>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/light.css">
</header>
<body>
<h1>Hello World</h1>
</body>
<footer>
</footer>
</html>

133
cmd/server/main.go Normal file
View File

@ -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")
}

27
cmd/server/main_test.go Normal file
View File

@ -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())
}
})
}

2
go.mod
View File

@ -1,3 +1,5 @@
module live-studio-audience module live-studio-audience
go 1.21.4 go 1.21.4
require golang.org/x/time v0.5.0 // indirect

2
go.sum Normal file
View File

@ -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=