From 6c1eed0ff4872d6d5a478294e57ce0e8b90dcab3 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Mon, 16 Oct 2023 19:53:08 -0700 Subject: bin/webp: add webp server binary --- bin/webp/go.mod | 8 +++++ bin/webp/go.sum | 6 ++++ bin/webp/main.go | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 bin/webp/go.mod create mode 100644 bin/webp/go.sum create mode 100644 bin/webp/main.go diff --git a/bin/webp/go.mod b/bin/webp/go.mod new file mode 100644 index 0000000..ad9ea20 --- /dev/null +++ b/bin/webp/go.mod @@ -0,0 +1,8 @@ +module code.crute.us/mcrute/golib/bin/webp + +go 1.20 + +require ( + github.com/chai2010/webp v1.1.1 + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 +) diff --git a/bin/webp/go.sum b/bin/webp/go.sum new file mode 100644 index 0000000..27fb1cf --- /dev/null +++ b/bin/webp/go.sum @@ -0,0 +1,6 @@ +github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk= +github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/bin/webp/main.go b/bin/webp/main.go new file mode 100644 index 0000000..c00a6d8 --- /dev/null +++ b/bin/webp/main.go @@ -0,0 +1,106 @@ +package main + +// webp is a small HTTP server that listens on a Unix socket and +// receives an uncompressed TIFF image as a POST value; it returns a +// webp encoded image. The `quality` header is an integer ranging from +// 0-100 indicating the webp quality, it is required. The `lossless` and +// `exact` headers specify that the image is lossless and to preserve +// RGB values in transparent areas, respectively. These headers may +// only be set to "true" and their absence implies that they are false. +// +// This program exists because there is no pure-Go implementation of +// a webp encoder and using CGO is not desirable for most binaries. +// Furthermore, forking a cwebp binary per image conversion may not +// scale well for a given application, especially if this forking occurs +// on a per-user-request basis. Additionally cwebp requires that the +// image be loaded from disk which may not be desirable while this +// program works purely with byte streams. +// +// This program should be shipped with, started, managed, and killed +// by the parent process that needs webp support. It supports clean +// shutdown in response to SIGTERM. Errors and requests are logged to +// stderr. +// +// Uncompressed TIFF was used as the transit format because it supports +// all image color spaces as well as transparency. Compression is +// unneeded because it wastes CPU cycles on the encode and decode side +// for an IPC call, which has no appreciable bandwidth limit. + +import ( + "context" + "fmt" + "log" + "net" + "net/http" + "os" + "os/signal" + "strconv" + "syscall" + + "github.com/chai2010/webp" + "golang.org/x/image/tiff" +) + +func writeError(w http.ResponseWriter, code int, message string, args ...any) { + w.WriteHeader(code) + w.Write([]byte(fmt.Sprintf(message+"\n", args...))) + log.Printf("error: %d: %s", code, fmt.Sprintf(message, args...)) +} + +type webpConverterHandler struct{} + +func (h *webpConverterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + img, err := tiff.Decode(r.Body) + if err != nil { + writeError(w, http.StatusBadRequest, "decode failed: %s", err) + return + } + + quality, err := strconv.Atoi(r.Header.Get("quality")) + if err != nil { + writeError(w, http.StatusBadRequest, "unable to parse quality header: %s", err) + return + } + + w.WriteHeader(http.StatusOK) + err = webp.Encode(w, img, &webp.Options{ + Lossless: r.Header.Get("lossless") == "true", + Exact: r.Header.Get("exact") == "true", + Quality: float32(quality), + }) + if err != nil { + log.Printf("error encoding web: %s", err) + } + + log.Printf(`"%s %s" %s`, r.Method, r.URL.String(), r.Header.Get("Content-Length")) +} + +func main() { + if len(os.Args) != 2 { + log.Fatalf("usage: %s ", os.Args[0]) + } + + sig := make(chan os.Signal) + signal.Notify(sig, syscall.SIGTERM) + + spath := os.Args[1] + os.Remove(spath) + + server := http.Server{ + Handler: &webpConverterHandler{}, + ErrorLog: log.Default(), + } + + listener, err := net.Listen("unix", spath) + if err != nil { + log.Fatalf("error: %s", err) + } + + log.Printf("listening on %s", spath) + go server.Serve(listener) + + <-sig + log.Printf("process received SIG_TERM, quitting") + server.Shutdown(context.Background()) + os.Remove(spath) +} -- cgit v1.2.3