mirror of
https://github.com/sajari/docconv.git
synced 2026-10-18 06:31:24 +08:00
08652136d5
Example log entry that is panicking: > could not write to response (failed after 3965 bytes): write tcp 172.17.0.5:8080->172.17.0.4:61528: write: broken pipe
128 lines
3.2 KiB
Go
128 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"syscall"
|
|
|
|
"cloud.google.com/go/errorreporting"
|
|
|
|
"code.sajari.com/docconv/v2"
|
|
"code.sajari.com/docconv/v2/docd/internal"
|
|
)
|
|
|
|
type convertServer struct {
|
|
l *slog.Logger
|
|
er internal.ErrorReporter
|
|
}
|
|
|
|
func (s *convertServer) convert(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
// Readability flag. Currently only used for HTML
|
|
var readability bool
|
|
if r.FormValue("readability") == "1" {
|
|
readability = true
|
|
s.l.DebugContext(ctx, "Readability is on")
|
|
}
|
|
|
|
path := r.FormValue("path")
|
|
if path != "" {
|
|
mimeType := docconv.MimeTypeByExtension(path)
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
s.serverError(ctx, w, r, fmt.Errorf("could not open file: %w", err))
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
data, err := docconv.Convert(f, mimeType, readability)
|
|
if err != nil {
|
|
s.serverError(ctx, w, r, fmt.Errorf("could not convert file from path %v: %w", path, err))
|
|
return
|
|
}
|
|
|
|
s.respond(ctx, w, r, http.StatusOK, data)
|
|
return
|
|
}
|
|
|
|
// Get uploaded file
|
|
file, info, err := r.FormFile("input")
|
|
if err != nil {
|
|
s.serverError(ctx, w, r, fmt.Errorf("could not get input file: %w", err))
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// Abort if file doesn't have a mime type
|
|
if len(info.Header["Content-Type"]) == 0 {
|
|
s.clientError(ctx, w, r, http.StatusUnprocessableEntity, "input file %v does not have a Content-Type header", info.Filename)
|
|
return
|
|
}
|
|
|
|
// If a generic mime type was provided then use file extension to determine mimetype
|
|
mimeType := info.Header["Content-Type"][0]
|
|
if mimeType == "application/octet-stream" {
|
|
mimeType = docconv.MimeTypeByExtension(info.Filename)
|
|
}
|
|
|
|
s.l.InfoContext(ctx, "Received file", "filename", info.Filename, "mimeType", mimeType)
|
|
|
|
data, err := docconv.Convert(file, mimeType, readability)
|
|
if err != nil {
|
|
s.serverError(ctx, w, r, fmt.Errorf("could not convert file: %w", err))
|
|
return
|
|
}
|
|
|
|
s.respond(ctx, w, r, http.StatusOK, data)
|
|
}
|
|
|
|
func (s *convertServer) clientError(ctx context.Context, w http.ResponseWriter, r *http.Request, code int, pattern string, args ...interface{}) {
|
|
s.respond(ctx, w, r, code, &docconv.Response{
|
|
Error: fmt.Sprintf(pattern, args...),
|
|
})
|
|
|
|
s.l.InfoContext(ctx, fmt.Sprintf(pattern, args...))
|
|
}
|
|
|
|
func (s *convertServer) serverError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(`{"error":"internal server error"}`))
|
|
|
|
e := errorreporting.Entry{
|
|
Error: err,
|
|
Req: r,
|
|
}
|
|
s.er.Report(e)
|
|
|
|
s.l.ErrorContext(ctx, err.Error(), "error", err)
|
|
}
|
|
|
|
func (s *convertServer) respond(ctx context.Context, w http.ResponseWriter, r *http.Request, code int, resp interface{}) {
|
|
buf := &bytes.Buffer{}
|
|
err := json.NewEncoder(buf).Encode(resp)
|
|
if err != nil {
|
|
s.serverError(ctx, w, r, fmt.Errorf("could not marshal JSON response: %w", err))
|
|
return
|
|
}
|
|
w.WriteHeader(code)
|
|
n, err := io.Copy(w, buf)
|
|
if err != nil {
|
|
// Avoid panicking on broken pipe errors.
|
|
// See https://gosamples.dev/broken-pipe/
|
|
if errors.Is(err, syscall.EPIPE) {
|
|
s.l.DebugContext(ctx, err.Error(), "error", err)
|
|
return
|
|
}
|
|
panic(fmt.Errorf("could not write to response (failed after %d bytes): %w", n, err))
|
|
}
|
|
}
|