wipp
parent
db872df672
commit
c8bd8a591d
|
|
@ -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>
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
2
go.mod
|
|
@ -1,3 +1,5 @@
|
|||
module live-studio-audience
|
||||
|
||||
go 1.21.4
|
||||
|
||||
require golang.org/x/time v0.5.0 // indirect
|
||||
|
|
|
|||
Loading…
Reference in New Issue