Add custom request context thing

This commit is contained in:
Ben Visness 2021-03-17 21:14:06 -05:00
parent cc521e0245
commit 465c877241
3 changed files with 99 additions and 26 deletions

View File

@ -46,6 +46,10 @@ func Fatal() *zerolog.Event {
return log.Fatal().Timestamp().Stack()
}
func With() zerolog.Context {
return log.With()
}
type PrettyZerologWriter struct {
wd string
}

View File

@ -0,0 +1,82 @@
package website
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"git.handmade.network/hmn/hmn/src/logging"
"git.handmade.network/hmn/hmn/src/templates"
"github.com/julienschmidt/httprouter"
"github.com/rs/zerolog"
)
type HMNRouter struct {
HttpRouter *httprouter.Router
}
func (r *HMNRouter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
r.HttpRouter.ServeHTTP(rw, req)
}
func (r *HMNRouter) Handle(method, route string, handler HMNHandler) {
r.HttpRouter.Handle(method, route, handleHmnHandler(route, handler))
}
func (r *HMNRouter) GET(route string, handler HMNHandler) {
r.Handle(http.MethodGet, route, handler)
}
func (r *HMNRouter) ServeFiles(path string, root http.FileSystem) {
r.HttpRouter.ServeFiles(path, root)
}
type HMNHandler func(c *RequestContext, p httprouter.Params)
type RequestContext struct {
StatusCode int
Body io.ReadWriter
Logger zerolog.Context
rw http.ResponseWriter
req *http.Request
}
func newRequestContext(rw http.ResponseWriter, req *http.Request, route string) *RequestContext {
return &RequestContext{
StatusCode: http.StatusOK,
Body: new(bytes.Buffer),
Logger: logging.With().Str("route", route),
rw: rw,
req: req,
}
}
func (c *RequestContext) Context() context.Context {
return c.req.Context()
}
func (c *RequestContext) URL() *url.URL {
return c.req.URL
}
func (c *RequestContext) Headers() http.Header {
return c.rw.Header()
}
func (c *RequestContext) WriteTemplate(name string, data interface{}) error {
return templates.Templates[name].Execute(c.Body, data)
}
func handleHmnHandler(route string, h HMNHandler) httprouter.Handle {
return func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
c := newRequestContext(rw, r, route)
h(c, p)
rw.WriteHeader(c.StatusCode)
io.Copy(rw, c.Body)
}
}

View File

@ -14,15 +14,15 @@ import (
)
type websiteRoutes struct {
*httprouter.Router
*HMNRouter
conn *pgxpool.Pool
}
func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
routes := &websiteRoutes{
Router: httprouter.New(),
conn: conn,
HMNRouter: &HMNRouter{HttpRouter: httprouter.New()},
conn: conn,
}
routes.GET("/", routes.Index)
@ -33,21 +33,8 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
return routes
}
/*
TODO: Make a custom context thing so that routes won't directly use a response writer.
This should store up a body, desired headers, status codes, etc. Doing this allows us to
make middleware that can write headers after an aborted request.
This context should also provide a sub-logger with request fields so we can easily see
which URLs are having problems.
*/
// TODO: Make all these routes automatically pull general template data
// TODO:
func (s *websiteRoutes) Index(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
err := templates.Templates["index.html"].Execute(rw, templates.BaseData{
func (s *websiteRoutes) Index(c *RequestContext, p httprouter.Params) {
err := c.WriteTemplate("index.html", templates.BaseData{
Project: templates.Project{
Name: "Handmade Network",
Color: "cd4e31",
@ -66,7 +53,7 @@ func (s *websiteRoutes) Index(rw http.ResponseWriter, r *http.Request, p httprou
}
}
func (s *websiteRoutes) Project(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
func (s *websiteRoutes) Project(c *RequestContext, p httprouter.Params) {
id := p.ByName("id")
row := s.conn.QueryRow(context.Background(), "SELECT name FROM handmade_project WHERE id = $1", p.ByName("id"))
var name string
@ -75,14 +62,14 @@ func (s *websiteRoutes) Project(rw http.ResponseWriter, r *http.Request, p httpr
panic(err)
}
rw.Write([]byte(fmt.Sprintf("(%s) %s\n", id, name)))
c.Body.Write([]byte(fmt.Sprintf("(%s) %s\n", id, name)))
}
func (s *websiteRoutes) ProjectCSS(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
color := r.URL.Query().Get("color")
func (s *websiteRoutes) ProjectCSS(c *RequestContext, p httprouter.Params) {
color := c.URL().Query().Get("color")
if color == "" {
rw.WriteHeader(http.StatusBadRequest)
rw.Write([]byte("You must provide a 'color' parameter.\n"))
c.StatusCode = http.StatusBadRequest
c.Body.Write([]byte("You must provide a 'color' parameter.\n"))
return
}
@ -94,8 +81,8 @@ func (s *websiteRoutes) ProjectCSS(rw http.ResponseWriter, r *http.Request, p ht
Theme: "dark",
}
rw.Header().Add("Content-Type", "text/css")
err := templates.Templates["project.css"].Execute(rw, templateData)
c.Headers().Add("Content-Type", "text/css")
err := c.WriteTemplate("project.css", templateData)
if err != nil {
logging.Error().Err(err).Msg("failed to generate project CSS")
return