From 372d7c04875361753095c59373dcf051dbeb213a Mon Sep 17 00:00:00 2001 From: Daan Sprenkels Date: Sun, 15 Sep 2019 21:34:41 +0200 Subject: [PATCH] Extract error rendering --- assets/templates/{ => html}/base.html.tmpl | 2 +- assets/templates/html/error.html.tmpl | 8 +++ assets/templates/{ => html}/index.html.tmpl | 0 assets/templates/{ => txt}/base.txt.tmpl | 0 assets/templates/txt/error.txt.tmpl | 1 + assets/templates/{ => txt}/index.txt.tmpl | 0 handlers/handlers.go | 55 ++++++++------------- handlers/views.go | 29 ++++++++--- 8 files changed, 54 insertions(+), 41 deletions(-) rename assets/templates/{ => html}/base.html.tmpl (61%) create mode 100644 assets/templates/html/error.html.tmpl rename assets/templates/{ => html}/index.html.tmpl (100%) rename assets/templates/{ => txt}/base.txt.tmpl (100%) create mode 100644 assets/templates/txt/error.txt.tmpl rename assets/templates/{ => txt}/index.txt.tmpl (100%) diff --git a/assets/templates/base.html.tmpl b/assets/templates/html/base.html.tmpl similarity index 61% rename from assets/templates/base.html.tmpl rename to assets/templates/html/base.html.tmpl index 1004384..71e2614 100644 --- a/assets/templates/base.html.tmpl +++ b/assets/templates/html/base.html.tmpl @@ -2,7 +2,7 @@ - {{block "title" .}}{{end}}{{block "head-append" .}}{{end}} + {{block "title" .}}rushlink{{end}}{{block "head-append" .}}{{end}} {{block "body" .}}{{end}} diff --git a/assets/templates/html/error.html.tmpl b/assets/templates/html/error.html.tmpl new file mode 100644 index 0000000..ec247f5 --- /dev/null +++ b/assets/templates/html/error.html.tmpl @@ -0,0 +1,8 @@ +{{define "title"}} +Error - rushlink +{{end}} +{{define "body"}} +
+{{.Message}}
+
+{{end}} \ No newline at end of file diff --git a/assets/templates/index.html.tmpl b/assets/templates/html/index.html.tmpl similarity index 100% rename from assets/templates/index.html.tmpl rename to assets/templates/html/index.html.tmpl diff --git a/assets/templates/base.txt.tmpl b/assets/templates/txt/base.txt.tmpl similarity index 100% rename from assets/templates/base.txt.tmpl rename to assets/templates/txt/base.txt.tmpl diff --git a/assets/templates/txt/error.txt.tmpl b/assets/templates/txt/error.txt.tmpl new file mode 100644 index 0000000..e55a763 --- /dev/null +++ b/assets/templates/txt/error.txt.tmpl @@ -0,0 +1 @@ +{{.Message}} \ No newline at end of file diff --git a/assets/templates/index.txt.tmpl b/assets/templates/txt/index.txt.tmpl similarity index 100% rename from assets/templates/index.txt.tmpl rename to assets/templates/txt/index.txt.tmpl diff --git a/handlers/handlers.go b/handlers/handlers.go index 164a2f4..4f5b97e 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -6,7 +6,6 @@ import ( "crypto/subtle" "encoding/base64" "fmt" - "io" "log" "net/http" "net/url" @@ -81,28 +80,23 @@ func IndexGetHandler(w http.ResponseWriter, r *http.Request) { func IndexPostHandler(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(50 * 1000 * 1000); err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Internal server error: %v\n", err) + log.Printf("error: %v\n", err) + RenderInternalServerError(w, r, err) return } // Determine what kind of post this is, currently only `shorten=...` if len(r.PostForm) == 0 { - w.WriteHeader(http.StatusBadRequest) - var buf []byte - r.Body.Read(buf) - io.WriteString(w, "empty body in POST request\n") + RenderError(w, r, http.StatusBadRequest, "empty body in POST request\n") return } shorten_values, prs := r.PostForm["shorten"] if !prs { - w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "no 'shorten' param supplied\n") + RenderError(w, r, http.StatusBadRequest, "no 'shorten' param given\n") return } if len(shorten_values) != 1 { - w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "only one 'shorten' param is allowed per request\n") + RenderError(w, r, http.StatusBadRequest, "only one 'shorten' param is allowed per request\n") return } @@ -130,30 +124,26 @@ func pasteGetHandlerInner(w http.ResponseWriter, r *http.Request, noRedirect, sh storedPaste, err = getURL(tx, []byte(key)) return err }); err != nil { - w.WriteHeader(http.StatusInternalServerError) log.Printf("error: %v\n", err) - fmt.Fprintf(w, "internal server error: %v\n", err) + RenderInternalServerError(w, r, err) return } if storedPaste == nil { - w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, "url key not found in the database\n") + RenderError(w, r, http.StatusNotFound, "url key not found in the database") return } if showMeta { typeString, err := storedPaste.Type.String() if err != nil { - w.WriteHeader(http.StatusInternalServerError) log.Printf("error: %v\n", err) - fmt.Fprintf(w, "internal server error: %v\n", err) + RenderInternalServerError(w, r, err) return } stateString, err := storedPaste.State.String() if err != nil { - w.WriteHeader(http.StatusInternalServerError) log.Printf("error: %v\n", err) - fmt.Fprintf(w, "internal server error: %v\n", err) + RenderInternalServerError(w, r, err) return } isOwner := "no" @@ -162,6 +152,7 @@ func pasteGetHandlerInner(w http.ResponseWriter, r *http.Request, noRedirect, sh isOwner = "yes" } + // TODO(dsprenkels) This should be put into a template w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "key: %v\n", string(storedPaste.Key)) fmt.Fprintf(w, "type: %v\n", typeString) @@ -177,21 +168,19 @@ func pasteGetHandlerInner(w http.ResponseWriter, r *http.Request, noRedirect, sh rawurl := string(storedPaste.Content) urlParse, err := url.Parse(rawurl) if err != nil { - w.WriteHeader(http.StatusInternalServerError) log.Printf("error: invalid URL ('%v') in database for key '%v': %v\n", rawurl, storedPaste.Key, err) - fmt.Fprintf(w, "internal server error: invalid url in database\n") + RenderInternalServerError(w, r, "invalid url in database") return } http.Redirect(w, r, urlParse.String(), http.StatusSeeOther) } w.Write(storedPaste.Content) case StateDeleted: - w.WriteHeader(http.StatusGone) - fmt.Fprintf(w, "key has been deleted\n") + RenderError(w, r, http.StatusGone, "key has been deleted") default: - w.WriteHeader(http.StatusInternalServerError) log.Printf("error: invalid storedPaste.State (%v) for key '%v'\n", storedPaste.State, storedPaste.Key) - fmt.Fprintf(w, "internal server error: invalid storedPaste.State (%v\n)", storedPaste.State) + msg := fmt.Sprintf("internal server error: invalid storedPaste.State (%v\n)", storedPaste.State) + RenderInternalServerError(w, r, msg) } } @@ -199,18 +188,16 @@ func ShortenPostHandler(w http.ResponseWriter, r *http.Request) { rawurl := r.PostForm.Get("shorten") userURL, err := url.ParseRequestURI(rawurl) if err != nil { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "invalid url (%v): %v\n", err, rawurl) + msg := fmt.Sprintf("invalid url (%v): %v", err, rawurl) + RenderError(w, r, http.StatusBadRequest, msg) return } if userURL.Scheme == "" { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "invalid url (unspecified scheme)\n", rawurl) + RenderError(w, r, http.StatusBadRequest, "invalid url (unspecified scheme)") return } if userURL.Host == "" { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "invalid url (unspecified host)\n", rawurl) + RenderError(w, r, http.StatusBadRequest, "invalid url (unspecified host)") return } @@ -229,9 +216,8 @@ func ShortenPostHandler(w http.ResponseWriter, r *http.Request) { storedPaste = sp return err }); err != nil { - w.WriteHeader(http.StatusInternalServerError) log.Printf("error: %v\n", err) - fmt.Fprintf(w, "internal server error: %v\n", err) + RenderInternalServerError(w, r, err) return } @@ -239,12 +225,13 @@ func ShortenPostHandler(w http.ResponseWriter, r *http.Request) { if err != nil { err = errors.Wrap(err, "parsing url") log.Printf("error: %v\n", err) - fmt.Fprintf(w, "internal server error: %v\n", err) + RenderInternalServerError(w, r, err) return } var base64OwnerToken = make([]byte, 24) base64Encoder.Encode(base64OwnerToken, storedPaste.OwnerToken[:]) + // TODO(dsprenkels) Put this into a template w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "URL saved at %v\n", saveURL) isNotPrint := func(r rune) bool { return !unicode.IsPrint(r) } diff --git a/handlers/views.go b/handlers/views.go index 81364cb..6fd9079 100644 --- a/handlers/views.go +++ b/handlers/views.go @@ -22,8 +22,8 @@ import ( ) // Plain text templates -var TextBaseTemplate *text.Template = text.Must(text.New("Empty").Parse(string(MustAsset("templates/base.txt.tmpl")))) -var HTMLBaseTemplate *html.Template = html.Must(html.New("Empty").Parse(string(MustAsset("templates/base.html.tmpl")))) +var TextBaseTemplate *text.Template = text.Must(text.New("").Parse(string(MustAsset("templates/txt/base.txt.tmpl")))) +var HTMLBaseTemplate *html.Template = html.Must(html.New("").Parse(string(MustAsset("templates/html/base.html.tmpl")))) // Template collections var TextTemplates = make(map[string]*text.Template, 0) @@ -37,19 +37,26 @@ var acceptHeaderWeight = regexp.MustCompile(`^\s*q=0(?:\.([0-9]{0,3}))|1(?:\.0{0 // HTML templates func init() { for _, tmplPath := range AssetNames() { - if mustMatch("templates/*.txt.tmpl", tmplPath) { - tmpl := text.Must(TextBaseTemplate.Parse(string(MustAsset(tmplPath)))) + if mustMatch("templates/txt/*.txt.tmpl", tmplPath) { + base := text.Must(TextBaseTemplate.Clone()) + tmpl := text.Must(base.Parse(string(MustAsset(tmplPath)))) tmplName := strings.TrimSuffix(filepath.Base(tmplPath), ".txt.tmpl") TextTemplates[tmplName] = tmpl continue } - if mustMatch("templates/*.html.tmpl", tmplPath) { - tmpl := html.Must(HTMLBaseTemplate.Parse(string(MustAsset(tmplPath)))) + if mustMatch("templates/html/*.html.tmpl", tmplPath) { + base := html.Must(HTMLBaseTemplate.Clone()) + tmpl := html.Must(base.Parse(string(MustAsset(tmplPath)))) tmplName := strings.TrimSuffix(filepath.Base(tmplPath), ".html.tmpl") HTMLTemplates[tmplName] = tmpl continue } } + + // Sanity check. Both maps should not be empty + if len(TextTemplates) == 0 || len(HTMLTemplates) == 0 { + panic("template loading failed") + } } func mustMatch(pattern, name string) bool { @@ -99,6 +106,16 @@ func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interf } } +func RenderError(w http.ResponseWriter, r *http.Request, status int, msg string) { + w.WriteHeader(status) + Render(w, r, "error", struct{ Message string }{msg}) +} + +func RenderInternalServerError(w http.ResponseWriter, r *http.Request, err interface{}) { + msg := fmt.Sprintf("internal server error: %v", err) + RenderError(w, r, http.StatusInternalServerError, msg) +} + // Try to resolve the preferred content-type for the response to this request. // // This is done by reading from the `types` argument. If one of them matches