check / check (push) Successful in 3m15s
Each row of the recent events on the webhook page links to the
event's own page, /hook/{id}/events/{eventID}, and expands to show its
body; only the newest starts expanded. The event's page shows its
details, its whole body and every delivery with its attempts.
One renderer, newBodyView with templates/event_body.html, shows a body
in all three places: whole up to 32 KiB, cut there in the lists with a
link to the event's page, JSON pretty-printed, a body of more than 200
lines or 32 KiB in a scrolling box, and a body that is not text left
out beside its download link. A resubmitted copy links to its
original's page.
Model: opus-5-5
148 lines
3.9 KiB
Go
148 lines
3.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// maxRenderedBodyBytes is the most of one event's body that the
|
|
// recent events on a webhook's page and the event log show; a larger
|
|
// body is cut there and shown whole only on the event's own page.
|
|
// Bodies come from the unauthenticated receiver under its 1 MB cap,
|
|
// and renderTemplate buffers a whole page before writing it, so a list
|
|
// of events cannot show every body whole.
|
|
const maxRenderedBodyBytes = 32 << 10
|
|
|
|
// maxInlineBodyLines is the most lines a body is shown at its full
|
|
// height with. A body with more lines, or larger than
|
|
// maxRenderedBodyBytes, is shown in a box of fixed height that
|
|
// scrolls, so that it does not make the page huge.
|
|
const maxInlineBodyLines = 200
|
|
|
|
// maxIndentGrowth is how many times its size a JSON body may grow
|
|
// when it is pretty-printed; past that it is shown as received.
|
|
// Indenting grows with nesting depth as well as with size: 20 KB of
|
|
// nested brackets indents to some 200 MB.
|
|
const maxIndentGrowth = 4
|
|
|
|
// jsonIndent is the indent of a pretty-printed JSON body.
|
|
const jsonIndent = " "
|
|
|
|
// BodyView is an event's body as the pages show it. newBodyView
|
|
// decides it and templates/event_body.html shows it, the same way in
|
|
// the recent events on a webhook's page, in the event log and on the
|
|
// event's own page.
|
|
type BodyView struct {
|
|
// EventURL is the event's own page. The stored body downloads
|
|
// from EventURL/body.
|
|
EventURL string
|
|
|
|
// Text is the body as shown, pretty-printed when it is JSON.
|
|
Text string
|
|
|
|
// Size is the stored body's size in bytes, and ShownBytes how
|
|
// many of them Text holds when Cut.
|
|
Size int64
|
|
ShownBytes int
|
|
|
|
// Cut reports that Text is only the start of the body.
|
|
Cut bool
|
|
|
|
// Binary reports a body that is not text. It is not shown.
|
|
Binary bool
|
|
|
|
// Scroll reports a body to show in a box that scrolls.
|
|
Scroll bool
|
|
}
|
|
|
|
// newBodyView decides how to show an event's body. body is the
|
|
// stored body, or its first maxRenderedBodyBytes when only those were
|
|
// read, and size is the stored body's size.
|
|
func newBodyView(eventURL string, body []byte, size int64) BodyView {
|
|
v := BodyView{EventURL: eventURL, Size: size}
|
|
|
|
if size > int64(len(body)) {
|
|
v.Cut = true
|
|
body = trimPartialRune(body)
|
|
v.ShownBytes = len(body)
|
|
}
|
|
|
|
// html/template shows invalid UTF-8 and NUL bytes as replacement
|
|
// characters, so a body holding either is not text.
|
|
if !utf8.Valid(body) || bytes.IndexByte(body, 0) >= 0 {
|
|
v.Binary = true
|
|
|
|
return v
|
|
}
|
|
|
|
// A cut JSON document is no longer valid JSON.
|
|
if !v.Cut {
|
|
body = indentJSON(body)
|
|
}
|
|
|
|
lines := bytes.Count(body, []byte("\n")) + 1
|
|
|
|
v.Text = string(body)
|
|
v.Scroll = lines > maxInlineBodyLines || size > maxRenderedBodyBytes
|
|
|
|
return v
|
|
}
|
|
|
|
// indentJSON returns body pretty-printed when it is a JSON document,
|
|
// and unchanged when it is not or would grow more than
|
|
// maxIndentGrowth times.
|
|
func indentJSON(body []byte) []byte {
|
|
if !json.Valid(body) || !indentFits(body) {
|
|
return body
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
|
|
err := json.Indent(&out, body, "", jsonIndent)
|
|
if err != nil {
|
|
return body
|
|
}
|
|
|
|
return out.Bytes()
|
|
}
|
|
|
|
// indentFits reports whether pretty-printing the JSON document body
|
|
// keeps it within maxIndentGrowth times its size. It adds up an upper
|
|
// bound instead of indenting: each token starts at most one line,
|
|
// indented once per enclosing object or array, and a key gains the
|
|
// space after its colon.
|
|
func indentFits(body []byte) bool {
|
|
limit := maxIndentGrowth * len(body)
|
|
size, depth := len(body), 0
|
|
|
|
dec := json.NewDecoder(bytes.NewReader(body))
|
|
|
|
// A number too large for a float64 is still valid JSON.
|
|
dec.UseNumber()
|
|
|
|
for size <= limit {
|
|
tok, err := dec.Token()
|
|
if errors.Is(err, io.EOF) {
|
|
return true
|
|
}
|
|
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
switch tok {
|
|
case json.Delim('{'), json.Delim('['):
|
|
depth++
|
|
case json.Delim('}'), json.Delim(']'):
|
|
depth--
|
|
}
|
|
|
|
size += len("\n") + depth*len(jsonIndent) + len(" ")
|
|
}
|
|
|
|
return false
|
|
}
|