Extract error rendering

This commit is contained in:
Daan Sprenkels 2019-09-15 21:34:41 +02:00
parent ddd674e88a
commit 372d7c0487
8 changed files with 54 additions and 41 deletions

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>{{block "title" .}}{{end}}</title>{{block "head-append" .}}{{end}} <title>{{block "title" .}}rushlink{{end}}</title>{{block "head-append" .}}{{end}}
</head> </head>
<body> <body>
{{block "body" .}}{{end}} {{block "body" .}}{{end}}

View File

@ -0,0 +1,8 @@
{{define "title"}}
Error - rushlink
{{end}}
{{define "body"}}
<pre>
{{.Message}}
</pre>
{{end}}

View File

@ -0,0 +1 @@
{{.Message}}

View File

@ -6,7 +6,6 @@ import (
"crypto/subtle" "crypto/subtle"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
@ -81,28 +80,23 @@ func IndexGetHandler(w http.ResponseWriter, r *http.Request) {
func IndexPostHandler(w http.ResponseWriter, r *http.Request) { func IndexPostHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(50 * 1000 * 1000); err != nil { if err := r.ParseMultipartForm(50 * 1000 * 1000); 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 return
} }
// Determine what kind of post this is, currently only `shorten=...` // Determine what kind of post this is, currently only `shorten=...`
if len(r.PostForm) == 0 { if len(r.PostForm) == 0 {
w.WriteHeader(http.StatusBadRequest) RenderError(w, r, http.StatusBadRequest, "empty body in POST request\n")
var buf []byte
r.Body.Read(buf)
io.WriteString(w, "empty body in POST request\n")
return return
} }
shorten_values, prs := r.PostForm["shorten"] shorten_values, prs := r.PostForm["shorten"]
if !prs { if !prs {
w.WriteHeader(http.StatusBadRequest) RenderError(w, r, http.StatusBadRequest, "no 'shorten' param given\n")
io.WriteString(w, "no 'shorten' param supplied\n")
return return
} }
if len(shorten_values) != 1 { if len(shorten_values) != 1 {
w.WriteHeader(http.StatusBadRequest) RenderError(w, r, http.StatusBadRequest, "only one 'shorten' param is allowed per request\n")
io.WriteString(w, "only one 'shorten' param is allowed per request\n")
return return
} }
@ -130,30 +124,26 @@ func pasteGetHandlerInner(w http.ResponseWriter, r *http.Request, noRedirect, sh
storedPaste, err = getURL(tx, []byte(key)) storedPaste, err = getURL(tx, []byte(key))
return err return err
}); err != nil { }); err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("error: %v\n", err) log.Printf("error: %v\n", err)
fmt.Fprintf(w, "internal server error: %v\n", err) RenderInternalServerError(w, r, err)
return return
} }
if storedPaste == nil { if storedPaste == nil {
w.WriteHeader(http.StatusNotFound) RenderError(w, r, http.StatusNotFound, "url key not found in the database")
fmt.Fprintf(w, "url key not found in the database\n")
return return
} }
if showMeta { if showMeta {
typeString, err := storedPaste.Type.String() typeString, err := storedPaste.Type.String()
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("error: %v\n", err) log.Printf("error: %v\n", err)
fmt.Fprintf(w, "internal server error: %v\n", err) RenderInternalServerError(w, r, err)
return return
} }
stateString, err := storedPaste.State.String() stateString, err := storedPaste.State.String()
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("error: %v\n", err) log.Printf("error: %v\n", err)
fmt.Fprintf(w, "internal server error: %v\n", err) RenderInternalServerError(w, r, err)
return return
} }
isOwner := "no" isOwner := "no"
@ -162,6 +152,7 @@ func pasteGetHandlerInner(w http.ResponseWriter, r *http.Request, noRedirect, sh
isOwner = "yes" isOwner = "yes"
} }
// TODO(dsprenkels) This should be put into a template
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "key: %v\n", string(storedPaste.Key)) fmt.Fprintf(w, "key: %v\n", string(storedPaste.Key))
fmt.Fprintf(w, "type: %v\n", typeString) 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) rawurl := string(storedPaste.Content)
urlParse, err := url.Parse(rawurl) urlParse, err := url.Parse(rawurl)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("error: invalid URL ('%v') in database for key '%v': %v\n", rawurl, storedPaste.Key, err) 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 return
} }
http.Redirect(w, r, urlParse.String(), http.StatusSeeOther) http.Redirect(w, r, urlParse.String(), http.StatusSeeOther)
} }
w.Write(storedPaste.Content) w.Write(storedPaste.Content)
case StateDeleted: case StateDeleted:
w.WriteHeader(http.StatusGone) RenderError(w, r, http.StatusGone, "key has been deleted")
fmt.Fprintf(w, "key has been deleted\n")
default: default:
w.WriteHeader(http.StatusInternalServerError)
log.Printf("error: invalid storedPaste.State (%v) for key '%v'\n", storedPaste.State, storedPaste.Key) 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") rawurl := r.PostForm.Get("shorten")
userURL, err := url.ParseRequestURI(rawurl) userURL, err := url.ParseRequestURI(rawurl)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) msg := fmt.Sprintf("invalid url (%v): %v", err, rawurl)
fmt.Fprintf(w, "invalid url (%v): %v\n", err, rawurl) RenderError(w, r, http.StatusBadRequest, msg)
return return
} }
if userURL.Scheme == "" { if userURL.Scheme == "" {
w.WriteHeader(http.StatusBadRequest) RenderError(w, r, http.StatusBadRequest, "invalid url (unspecified scheme)")
fmt.Fprintf(w, "invalid url (unspecified scheme)\n", rawurl)
return return
} }
if userURL.Host == "" { if userURL.Host == "" {
w.WriteHeader(http.StatusBadRequest) RenderError(w, r, http.StatusBadRequest, "invalid url (unspecified host)")
fmt.Fprintf(w, "invalid url (unspecified host)\n", rawurl)
return return
} }
@ -229,9 +216,8 @@ func ShortenPostHandler(w http.ResponseWriter, r *http.Request) {
storedPaste = sp storedPaste = sp
return err return err
}); err != nil { }); err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("error: %v\n", err) log.Printf("error: %v\n", err)
fmt.Fprintf(w, "internal server error: %v\n", err) RenderInternalServerError(w, r, err)
return return
} }
@ -239,12 +225,13 @@ func ShortenPostHandler(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
err = errors.Wrap(err, "parsing url") err = errors.Wrap(err, "parsing url")
log.Printf("error: %v\n", err) log.Printf("error: %v\n", err)
fmt.Fprintf(w, "internal server error: %v\n", err) RenderInternalServerError(w, r, err)
return return
} }
var base64OwnerToken = make([]byte, 24) var base64OwnerToken = make([]byte, 24)
base64Encoder.Encode(base64OwnerToken, storedPaste.OwnerToken[:]) base64Encoder.Encode(base64OwnerToken, storedPaste.OwnerToken[:])
// TODO(dsprenkels) Put this into a template
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "URL saved at %v\n", saveURL) fmt.Fprintf(w, "URL saved at %v\n", saveURL)
isNotPrint := func(r rune) bool { return !unicode.IsPrint(r) } isNotPrint := func(r rune) bool { return !unicode.IsPrint(r) }

View File

@ -22,8 +22,8 @@ import (
) )
// Plain text templates // Plain text templates
var TextBaseTemplate *text.Template = text.Must(text.New("Empty").Parse(string(MustAsset("templates/base.txt.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("Empty").Parse(string(MustAsset("templates/base.html.tmpl")))) var HTMLBaseTemplate *html.Template = html.Must(html.New("").Parse(string(MustAsset("templates/html/base.html.tmpl"))))
// Template collections // Template collections
var TextTemplates = make(map[string]*text.Template, 0) 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 // HTML templates
func init() { func init() {
for _, tmplPath := range AssetNames() { for _, tmplPath := range AssetNames() {
if mustMatch("templates/*.txt.tmpl", tmplPath) { if mustMatch("templates/txt/*.txt.tmpl", tmplPath) {
tmpl := text.Must(TextBaseTemplate.Parse(string(MustAsset(tmplPath)))) base := text.Must(TextBaseTemplate.Clone())
tmpl := text.Must(base.Parse(string(MustAsset(tmplPath))))
tmplName := strings.TrimSuffix(filepath.Base(tmplPath), ".txt.tmpl") tmplName := strings.TrimSuffix(filepath.Base(tmplPath), ".txt.tmpl")
TextTemplates[tmplName] = tmpl TextTemplates[tmplName] = tmpl
continue continue
} }
if mustMatch("templates/*.html.tmpl", tmplPath) { if mustMatch("templates/html/*.html.tmpl", tmplPath) {
tmpl := html.Must(HTMLBaseTemplate.Parse(string(MustAsset(tmplPath)))) base := html.Must(HTMLBaseTemplate.Clone())
tmpl := html.Must(base.Parse(string(MustAsset(tmplPath))))
tmplName := strings.TrimSuffix(filepath.Base(tmplPath), ".html.tmpl") tmplName := strings.TrimSuffix(filepath.Base(tmplPath), ".html.tmpl")
HTMLTemplates[tmplName] = tmpl HTMLTemplates[tmplName] = tmpl
continue 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 { 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. // 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 // This is done by reading from the `types` argument. If one of them matches