site.go (1583B)
1 package site 2 3 import ( 4 "bytes" 5 "context" 6 "embed" 7 "fmt" 8 "html/template" 9 "net/http" 10 "path" 11 "strings" 12 "time" 13 14 "code.dwrz.net/src/pkg/log" 15 "code.dwrz.net/src/pkg/randstr" 16 ) 17 18 //go:embed all:static/* 19 var static embed.FS 20 21 type Site struct { 22 debug bool 23 files embed.FS 24 log *log.Logger 25 pages map[string]*bytes.Buffer 26 tmpl *template.Template 27 } 28 29 func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) { 30 now := time.Now() 31 32 // Recover and handle panics. 33 defer func() { 34 if err := recover(); err != nil { 35 w.Header().Set("Connection", "close") 36 37 s.error(w, r, &Error{ 38 Code: http.StatusInternalServerError, 39 Error: fmt.Errorf("%s", err), 40 Message: "Sorry, something went wrong.", 41 }) 42 } 43 }() 44 45 // Generate an id to track the request. 46 // Update the request context to store the id. 47 id := randstr.Charset(randstr.Numeric, 8) 48 r = r.WithContext(context.WithValue(r.Context(), "id", id)) 49 50 // Log the request. 51 var origin = r.RemoteAddr 52 if ip := r.Header.Get("X-Forwarded-For"); ip != "" { 53 origin = ip 54 } 55 s.log.Debug.Printf( 56 "%s → %s %s %s %s", 57 id, origin, 58 r.Proto, r.Method, r.URL.RequestURI(), 59 ) 60 61 // Pass the request on to the multiplexer. 62 base, _ := shiftpath(r.URL.Path) 63 switch base { 64 case "static": 65 s.static(w, r) 66 case "status": 67 s.status(w, r) 68 default: 69 s.page(w, r) 70 } 71 72 s.log.Debug.Printf( 73 "%s → completed in %v", id, time.Since(now), 74 ) 75 } 76 77 func shiftpath(p string) (base, rest string) { 78 p = path.Clean("/" + p) 79 80 i := strings.Index(p[1:], "/") + 1 81 if i <= 0 { 82 return p[1:], "/" 83 } 84 85 return p[1:i], p[i:] 86 }