Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions client/go/image/kwkhtmltoimage_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) 2019 ACSONE SA/NV
// Distributed under the MIT License (http://opensource.org/licenses/MIT)

package main

import (
"bytes"
"errors"
"io"
"mime/multipart"
"net/http"
"os"
"strings"
)

const chunkSize = 32 * 1024

func addOption(w *multipart.Writer, option string) error {
return w.WriteField("option", option)
}

func addFile(w *multipart.Writer, filename string) error {
writer, err := w.CreateFormFile("file", filename)
if err != nil {
return err
}
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
return err
}

func do() error {
var err error
var out *os.File

serverURL := os.Getenv("KWKHTMLTOPDF_SERVER_URL")
if serverURL == "" {
return errors.New("KWKHTMLTOPDF_SERVER_URL not set")
}

// detect if last argument is output file, and create it
args := os.Args[1:]
if len(args) == 0 {
args = []string{"-h"}
}
if len(args) >= 2 && !strings.HasPrefix(args[len(args)-1], "-") && !strings.HasPrefix(args[len(args)-2], "-") {
out, err = os.Create(args[len(args)-1])
if err != nil {
return err
}
defer out.Close()
args = args[:len(args)-1]
} else {
out = os.Stdout
}

// prepare request
var postBuf bytes.Buffer
w := multipart.NewWriter(&postBuf)
for _, arg := range args {
if arg == "-" {
return errors.New("stdin/stdout input is not implemented")
} else if strings.HasPrefix(arg, "-") {
err = addOption(w, arg)
} else if strings.HasPrefix(arg, "https://") {
err = addOption(w, arg)
} else if strings.HasPrefix(arg, "http://") {
err = addOption(w, arg)
} else if strings.HasPrefix(arg, "file://") {
err = addFile(w, arg[7:])
} else if _, err := os.Stat(arg); err == nil {
// TODO: better way to detect file arguments
err = addFile(w, arg)
} else {
err = addOption(w, arg)
}
if err != nil {
return err
}
}
w.Close()

// post request to image endpoint
endpoint := serverURL + "/image"
resp, err := http.Post(endpoint, w.FormDataContentType(), &postBuf)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New("server error, consult server log for details")
}

// read response
respBuf := make([]byte, chunkSize)
for {
nr, er := resp.Body.Read(respBuf)
if er != nil && er != io.EOF {
return errors.New("server error, consult server log for details")
}
if nr > 0 {
_, ew := out.Write(respBuf[0:nr])
if ew != nil {
return ew
}
}
if er == io.EOF {
break
}
}

return nil
}

func main() {
err := do()
if err != nil {
os.Stderr.WriteString(err.Error())
os.Stderr.WriteString("\n")
os.Exit(-1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ func do() error {
w.Close()

// post request
resp, err := http.Post(serverURL, w.FormDataContentType(), &postBuf)
endpoint := serverURL + "/pdf"
resp, err := http.Post(endpoint, w.FormDataContentType(), &postBuf)
if err != nil {
return err
}
Expand Down
28 changes: 25 additions & 3 deletions server/kwkhtmltopdf_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ func wkhtmltopdfBin() string {
return "wkhtmltopdf"
}

func wkhtmltoimageBin() string {
bin := os.Getenv("KWKHTMLTOIMAGE_BIN")
if bin != "" {
return bin
}
return "wkhtmltoimage"
}

func isDocOption(arg string) bool {
switch arg {
case
Expand Down Expand Up @@ -96,8 +104,8 @@ func handler(w http.ResponseWriter, r *http.Request) {
httpError(w, errors.New("http method not allowed: "+r.Method), http.StatusMethodNotAllowed)
return
}
if r.URL.Path != "/" && r.URL.Path != "/pdf" {
// handle / and /pdf, keep the rest for future use
if r.URL.Path != "/" && r.URL.Path != "/pdf" && r.URL.Path != "/image" {
// handle /, /pdf, and /image, keep the rest for future use
httpError(w, errors.New("path not found: "+r.URL.Path), http.StatusNotFound)
return
}
Expand Down Expand Up @@ -159,8 +167,14 @@ func handler(w http.ResponseWriter, r *http.Request) {
}
}

// determine if this is an image request
isImageRequest := r.URL.Path == "/image"

if docOutput {
w.Header().Add("Content-Type", "text/plain")
} else if isImageRequest {
w.Header().Add("Content-Type", "image/png")
args = append(args, "-")
} else {
w.Header().Add("Content-Type", "application/pdf")
args = append(args, "-")
Expand All @@ -170,7 +184,12 @@ func handler(w http.ResponseWriter, r *http.Request) {

log.Println(redactedArgs, "starting")

cmd := exec.Command(wkhtmltopdfBin(), args...)
var cmd *exec.Cmd
if isImageRequest {
cmd = exec.Command(wkhtmltoimageBin(), args...)
} else {
cmd = exec.Command(wkhtmltopdfBin(), args...)
}
cmdStdout, err := cmd.StdoutPipe()
if err != nil {
httpError(w, err, http.StatusInternalServerError)
Expand Down Expand Up @@ -199,6 +218,9 @@ func handler(w http.ResponseWriter, r *http.Request) {

func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/pdf", handler)
http.HandleFunc("/image", handler)
log.Println("kwkhtmltopdf server listening on port 8080")
log.Println("Available endpoints: / (PDF), /pdf (PDF), /image (Image), /status (Health check)")
log.Fatal(http.ListenAndServe(":8080", nil))
}