package viewer import ( "errors" "fmt" "io" "mime" "net/http" "strings" "reanimator/chart/web" ) type HandlerOptions struct { Title string Standalone bool } func NewHandler(opts HandlerOptions) http.Handler { opts.Standalone = false return newHandler(opts) } func NewStandaloneHandler(opts HandlerOptions) http.Handler { opts.Standalone = true return newHandler(opts) } func newHandler(opts HandlerOptions) http.Handler { title := strings.TrimSpace(opts.Title) if title == "" { title = "Reanimator Chart" } mux := http.NewServeMux() mux.Handle("GET /static/", http.StripPrefix("/static/", web.Static())) mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { var ( html []byte err error ) if opts.Standalone { html, err = web.RenderUpload(pageData{Title: title}) } else { html, err = RenderHTML(nil, title) } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write(html) }) mux.HandleFunc("POST /render", func(w http.ResponseWriter, r *http.Request) { payload, err := readSnapshotPayload(r) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } page, err := buildPageData([]byte(payload), title) if err != nil { if opts.Standalone { html, renderErr := web.RenderUpload(pageData{ Title: title, Error: err.Error(), }) if renderErr != nil { http.Error(w, renderErr.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write(html) return } page = pageData{Title: title, Error: err.Error()} } html, renderErr := web.Render(page) if renderErr != nil { http.Error(w, renderErr.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write(html) }) return mux } func readSnapshotPayload(r *http.Request) (string, error) { mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) if err != nil { return "", fmt.Errorf("parse content type: %w", err) } switch mediaType { case "application/json": body, err := io.ReadAll(r.Body) if err != nil { return "", fmt.Errorf("read request body: %w", err) } return string(body), nil case "multipart/form-data": if err := r.ParseMultipartForm(32 << 20); err != nil { return "", fmt.Errorf("parse multipart form: %w", err) } payload, err := readSnapshotFile(r, "snapshot_file") if err == nil && strings.TrimSpace(payload) != "" { return payload, nil } if err != nil && !errors.Is(err, http.ErrMissingFile) { return "", err } return r.FormValue("snapshot"), nil default: if err := r.ParseForm(); err != nil { return "", fmt.Errorf("parse form: %w", err) } return r.FormValue("snapshot"), nil } } func readSnapshotFile(r *http.Request, field string) (string, error) { file, _, err := r.FormFile(field) if err != nil { return "", fmt.Errorf("read uploaded file: %w", err) } defer file.Close() body, err := io.ReadAll(file) if err != nil { return "", fmt.Errorf("read uploaded file contents: %w", err) } return string(body), nil }