Skip to content

Commit

Permalink
docd: return JSON errors when things fail (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathaningram authored Sep 20, 2022
1 parent c6fca32 commit f481425
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 64 deletions.
122 changes: 122 additions & 0 deletions docd/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"

"cloud.google.com/go/errorreporting"

"code.sajari.com/docconv"
"code.sajari.com/docconv/docd/internal"
)

type convertServer struct {
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
if *logLevel >= 2 {
log.Println("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)
}

if *logLevel >= 1 {
log.Printf("Received file: %v (%v)", info.Filename, 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...),
})

log.Printf(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)

log.Printf("%v", 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 {
panic(fmt.Errorf("could not write to response (failed after %d bytes): %w", n, err))
}
}
71 changes: 7 additions & 64 deletions docd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
Expand Down Expand Up @@ -53,6 +52,10 @@ func main() {
}
}

cs := &convertServer{
er: er,
}

// TODO: Improve this (remove the need for it!)
docconv.HTMLReadabilityOptionsValues = docconv.HTMLReadabilityOptions{
LengthLow: *readabilityLengthLow,
Expand All @@ -73,73 +76,13 @@ func main() {
return
}

serve(er)
}

func convert(w http.ResponseWriter, r *http.Request) {
// Readability flag. Currently only used for HTML
var readability bool
if r.FormValue("readability") == "1" {
readability = true
if *logLevel >= 2 {
log.Println("Readability is on")
}
}

path := r.FormValue("path")
if path != "" {
b, err := docconv.ConvertPathReadability(path, readability)
if err != nil {
// TODO: return a sensible status code for errors like this.
log.Printf("error converting path '%v': %v", path, err)
return
}
w.Write(b)
return
}

// Get uploaded file
file, info, err := r.FormFile("input")
if err != nil {
log.Println("File upload", err)
return
}
defer file.Close()

// Abort if file doesn't have a mime type
if len(info.Header["Content-Type"]) == 0 {
log.Println("No content type", 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)
}

if *logLevel >= 1 {
log.Println("Received file: " + info.Filename + " (" + mimeType + ")")
}

data, err := docconv.Convert(file, mimeType, readability)
if err != nil {
log.Printf("error converting data: %v", err)
data = &docconv.Response{
Error: err.Error(),
}
}

if err := json.NewEncoder(w).Encode(data); err != nil {
log.Printf("error marshaling JSON data: %v", err)
return
}
serve(er, cs)
}

// Start the conversion web service
func serve(er internal.ErrorReporter) {
func serve(er internal.ErrorReporter, cs *convertServer) {
r := mux.NewRouter()
r.HandleFunc("/convert", convert)
r.HandleFunc("/convert", cs.convert)

// Start webserver
log.Println("Setting log level to", *logLevel)
Expand Down

0 comments on commit f481425

Please sign in to comment.