polish HTML debug view

This commit is contained in:
Michael Stapelberg
2020-06-20 10:21:50 +02:00
parent 8bf9d10964
commit 0d97aa3ca1
2 changed files with 229 additions and 51 deletions

165
cmd/qrbill-api/api.go Normal file
View File

@@ -0,0 +1,165 @@
package main
import (
"bytes"
"flag"
"fmt"
"image/png"
"io"
"log"
"net/http"
"github.com/davecgh/go-spew/spew"
"github.com/stapelberg/qrbill"
_ "net/http/pprof"
)
func ifEmpty(s, alternative string) string {
if s == "" {
return alternative
}
return s
}
func qrchFromRequest(r *http.Request) *qrbill.QRCH {
return &qrbill.QRCH{
CdtrInf: qrbill.QRCHCdtrInf{
IBAN: ifEmpty(r.FormValue("criban"), "CH0209000000870913543"),
Cdtr: qrbill.Address{
AdrTp: qrbill.AddressTypeStructured,
Name: ifEmpty(r.FormValue("crname"), "Legalize it!"),
StrtNmOrAdrLine1: ifEmpty(r.FormValue("craddr1"), "Quellenstrasse 25"),
BldgNbOrAdrLine2: ifEmpty(r.FormValue("craddr2"), ""),
PstCd: ifEmpty(r.FormValue("crpost"), "8005"),
TwnNm: ifEmpty(r.FormValue("crcity"), "Zürich"),
Ctry: ifEmpty(r.FormValue("crcountry"), "CH"),
},
},
CcyAmt: qrbill.QRCHCcyAmt{
Amt: "",
Ccy: "CHF",
},
UltmtDbtr: qrbill.Address{
AdrTp: qrbill.AddressTypeStructured,
Name: ifEmpty(r.FormValue("udname"), "Michael Stapelberg"),
StrtNmOrAdrLine1: ifEmpty(r.FormValue("udaddr1"), "Brahmsstrasse 21"),
BldgNbOrAdrLine2: ifEmpty(r.FormValue("udaddr2"), ""),
PstCd: ifEmpty(r.FormValue("udpost"), "8003"),
TwnNm: ifEmpty(r.FormValue("udcity"), "Zürich"),
Ctry: ifEmpty(r.FormValue("udcountry"), "CH"),
},
RmtInf: qrbill.QRCHRmtInf{
Tp: "NON", // Reference type
Ref: "", // Reference
AddInf: qrbill.QRCHRmtInfAddInf{
Ustrd: ifEmpty(r.FormValue("message"), "Spende 6141"),
},
},
}
}
func logic() error {
var listen = flag.String("listen", "localhost:9933", "[host]:port to listen on")
flag.Parse()
http.HandleFunc("/qr", func(w http.ResponseWriter, r *http.Request) {
prefix := "[" + r.RemoteAddr + "]"
format := r.FormValue("format")
log.Printf("%s handling request for %s, format=%s", prefix, r.URL.Path, format)
defer log.Printf("%s request completed (%s)", prefix, r.URL.Path)
if format == "" {
msg := fmt.Sprintf("no ?format= parameter specified. Try %s",
"http://"+*listen+"/qr?format=html")
log.Printf("%s %s", prefix, msg)
http.Error(w, msg, http.StatusBadRequest)
return
}
if format != "png" &&
format != "svg" &&
format != "txt" &&
format != "html" {
msg := fmt.Sprintf("format (%q) must be one of png, svg, txt or html", format)
log.Printf("%s %s", prefix, msg)
http.Error(w, msg, http.StatusBadRequest)
return
}
qrch := qrchFromRequest(r)
bill, err := qrch.Encode()
if err != nil {
log.Printf("%s %s", prefix, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var b []byte
switch format {
case "png":
code, err := bill.EncodeToImage()
if err != nil {
log.Printf("%s %s", prefix, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var buf bytes.Buffer
if err := png.Encode(&buf, code); err != nil {
log.Printf("%s %s", prefix, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
b = buf.Bytes()
w.Header().Add("Content-Type", "image/png")
case "svg":
var err error
b, err = bill.EncodeToSVG()
if err != nil {
log.Printf("%s %s", prefix, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "image/svg+xml")
case "txt":
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
spew.Fdump(w, qrch.Fill())
case "html":
debugHTML(w, r, prefix, qrch)
}
// TODO: add cache control headers
if _, err := io.Copy(w, bytes.NewReader(b)); err != nil {
log.Printf("%s %s", prefix, err)
return
}
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.Error(w, "not found", http.StatusNotFound)
return
}
w.Header().Add("Content-Type", "text/html; charset=utf-8")
// TODO: add explanation for how to construct a URL
// e.g. for usage in filemaker web view
fmt.Fprintf(w, "<ul>")
fmt.Fprintf(w, `<li>PNG referenz: <a href="/qr.png">qr.png</a>`+"\n")
fmt.Fprintf(w, `<li>SVG scalable: <a href="/qr.svg">qr.svg</a>`+"\n")
fmt.Fprintf(w, `<li>debug: <a href="/qr.html">qr.html</a>, or <a href="/qr.txt">qr.txt</a>`+"\n")
})
log.Printf("QR Bill generation URL: http://%s/qr?format=html", *listen)
return http.ListenAndServe(*listen, nil)
}
func main() {
if err := logic(); err != nil {
log.Fatal(err)
}
}

195
cmd/qrbill-api/debughtml.go Normal file
View File

@@ -0,0 +1,195 @@
package main
import (
"bytes"
"fmt"
"html/template"
"log"
"net/http"
"regexp"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/stapelberg/qrbill"
)
var (
fieldNameRe = regexp.MustCompile(`<br>(&nbsp;)*([^:]+):`)
stringLiteralRe = regexp.MustCompile(`"([^"]*)"`)
parenRe = regexp.MustCompile(`\(([^)]+)\)&nbsp;`)
)
var tmpl = template.Must(template.New("").Parse(`<!DOCTYPE html>
<html lang="en">
<head>
<title>QR Bill HTML Debug Page</title>
<style type="text/css">
.fieldname { font-weight: bold; }
.stringliteral { color: blue; }
#spews { display: flex; }
#spews div { border: 1px solid black; margin: 1em; padding: 1em; }
.qrch { font-family: monospace; }
th { text-align: left; }
#params tr td:nth-child(2) { color: blue; }
#params tr td:nth-child(1)::before { content: "&"; }
#params tr td:nth-child(1)::after { content: "="; }
</style>
</head>
<body>
<div id="spews">
<div class="qrch">
<h1>URL parameters</h1>
<table id="params">
<tr>
<th>Parameter</th>
<th>Value</th>
</tr>
<tr>
<td>criban</td>
<td>{{ .Criban }}</td>
</tr>
<tr>
<td>crname</td>
<td>{{ .Crname }}</td>
</tr>
<tr>
<td>craddr1</td>
<td>{{ .Craddr1 }}</td>
</tr>
<tr>
<td>craddr2</td>
<td>{{ .Craddr2 }}</td>
</tr>
<tr>
<td>crpost</td>
<td>{{ .Crpost }}</td>
</tr>
<tr>
<td>crcity</td>
<td>{{ .Crcity }}</td>
</tr>
<tr>
<td>crcountry</td>
<td>{{ .Crcountry }}</td>
</tr>
<tr>
<td>udname</td>
<td>{{ .Udname }}</td>
</tr>
<tr>
<td>udaddr1</td>
<td>{{ .Udaddr1 }}</td>
</tr>
<tr>
<td>udaddr2</td>
<td>{{ .Udaddr2 }}</td>
</tr>
<tr>
<td>udpost</td>
<td>{{ .Udpost }}</td>
</tr>
<tr>
<td>udcity</td>
<td>{{ .Udcity }}</td>
</tr>
<tr>
<td>udcountry</td>
<td>{{ .Udcountry }}</td>
</tr>
<tr>
<td>message</td>
<td>{{ .Message }}</td>
</tr>
</table>
</div>
`))
func debugHTML(w http.ResponseWriter, r *http.Request, prefix string, qrch *qrbill.QRCH) {
w.Header().Add("Content-Type", "text/html; charset=utf-8")
var buf bytes.Buffer
err := tmpl.Execute(&buf, struct {
Criban string
Crname string
Craddr1 string
Craddr2 string
Crpost string
Crcity string
Crcountry string
Udname string
Udaddr1 string
Udaddr2 string
Udpost string
Udcity string
Udcountry string
Message string
}{
Criban: r.FormValue("criban"),
Crname: r.FormValue("crname"),
Craddr1: r.FormValue("craddr1"),
Craddr2: r.FormValue("craddr2"),
Crpost: r.FormValue("crpost"),
Crcity: r.FormValue("crcity"),
Crcountry: r.FormValue("crcountry"),
Udname: r.FormValue("udname"),
Udaddr1: r.FormValue("udaddr1"),
Udaddr2: r.FormValue("udaddr2"),
Udpost: r.FormValue("udpost"),
Udcity: r.FormValue("udcity"),
Udcountry: r.FormValue("udcountry"),
Message: r.FormValue("message"),
})
if err != nil {
log.Printf("%s %s", prefix, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "%s", buf.String())
spew := func(vars ...interface{}) string {
sp := spew.Sdump(vars...)
sp = strings.ReplaceAll(sp, "\n", "<br>")
sp = strings.ReplaceAll(sp, " ", "&nbsp;")
sp = parenRe.ReplaceAllString(sp, "")
sp = stringLiteralRe.ReplaceAllStringFunc(sp, func(stringLiteral string) string {
return `<span class="stringliteral">` + stringLiteral + "</span>"
})
sp = fieldNameRe.ReplaceAllStringFunc(sp, func(fieldName string) string {
return `<span class="fieldname">` + fieldName + "</span>"
})
return sp
}
fmt.Fprintf(w, `<div class="qrch"><h1>input</h1>%s</div>`, spew(qrch))
fmt.Fprintf(w, `<div class="qrch"><h1>validated</h1>%s</div>`, spew(qrch.Fill()))
r.URL.Path = "/qr"
v := r.URL.Query()
v.Set("format", "png")
r.URL.RawQuery = v.Encode()
fmt.Fprintf(w, `<div class="qrch"><h1>QR Bill</h1><img src="%s" width="200" height="200"></div>`, r.URL.String())
}