aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2023-10-16 19:53:08 -0700
committerMike Crute <mike@crute.us>2023-10-16 19:53:08 -0700
commit6c1eed0ff4872d6d5a478294e57ce0e8b90dcab3 (patch)
tree4b63801a272e2f637becb2481a06101c46809abc
parentdec964fdc95aa63b2aa74561001b73617d2f1fb7 (diff)
downloadgolib-6c1eed0ff4872d6d5a478294e57ce0e8b90dcab3.tar.bz2
golib-6c1eed0ff4872d6d5a478294e57ce0e8b90dcab3.tar.xz
golib-6c1eed0ff4872d6d5a478294e57ce0e8b90dcab3.zip
bin/webp: add webp server binarybin/webp/v0.1.0
-rw-r--r--bin/webp/go.mod8
-rw-r--r--bin/webp/go.sum6
-rw-r--r--bin/webp/main.go106
3 files changed, 120 insertions, 0 deletions
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 @@
1module code.crute.us/mcrute/golib/bin/webp
2
3go 1.20
4
5require (
6 github.com/chai2010/webp v1.1.1
7 golang.org/x/image v0.0.0-20211028202545-6944b10bf410
8)
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 @@
1github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
2github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
3golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
4golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
5golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
6golang.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 @@
1package main
2
3// webp is a small HTTP server that listens on a Unix socket and
4// receives an uncompressed TIFF image as a POST value; it returns a
5// webp encoded image. The `quality` header is an integer ranging from
6// 0-100 indicating the webp quality, it is required. The `lossless` and
7// `exact` headers specify that the image is lossless and to preserve
8// RGB values in transparent areas, respectively. These headers may
9// only be set to "true" and their absence implies that they are false.
10//
11// This program exists because there is no pure-Go implementation of
12// a webp encoder and using CGO is not desirable for most binaries.
13// Furthermore, forking a cwebp binary per image conversion may not
14// scale well for a given application, especially if this forking occurs
15// on a per-user-request basis. Additionally cwebp requires that the
16// image be loaded from disk which may not be desirable while this
17// program works purely with byte streams.
18//
19// This program should be shipped with, started, managed, and killed
20// by the parent process that needs webp support. It supports clean
21// shutdown in response to SIGTERM. Errors and requests are logged to
22// stderr.
23//
24// Uncompressed TIFF was used as the transit format because it supports
25// all image color spaces as well as transparency. Compression is
26// unneeded because it wastes CPU cycles on the encode and decode side
27// for an IPC call, which has no appreciable bandwidth limit.
28
29import (
30 "context"
31 "fmt"
32 "log"
33 "net"
34 "net/http"
35 "os"
36 "os/signal"
37 "strconv"
38 "syscall"
39
40 "github.com/chai2010/webp"
41 "golang.org/x/image/tiff"
42)
43
44func writeError(w http.ResponseWriter, code int, message string, args ...any) {
45 w.WriteHeader(code)
46 w.Write([]byte(fmt.Sprintf(message+"\n", args...)))
47 log.Printf("error: %d: %s", code, fmt.Sprintf(message, args...))
48}
49
50type webpConverterHandler struct{}
51
52func (h *webpConverterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
53 img, err := tiff.Decode(r.Body)
54 if err != nil {
55 writeError(w, http.StatusBadRequest, "decode failed: %s", err)
56 return
57 }
58
59 quality, err := strconv.Atoi(r.Header.Get("quality"))
60 if err != nil {
61 writeError(w, http.StatusBadRequest, "unable to parse quality header: %s", err)
62 return
63 }
64
65 w.WriteHeader(http.StatusOK)
66 err = webp.Encode(w, img, &webp.Options{
67 Lossless: r.Header.Get("lossless") == "true",
68 Exact: r.Header.Get("exact") == "true",
69 Quality: float32(quality),
70 })
71 if err != nil {
72 log.Printf("error encoding web: %s", err)
73 }
74
75 log.Printf(`"%s %s" %s`, r.Method, r.URL.String(), r.Header.Get("Content-Length"))
76}
77
78func main() {
79 if len(os.Args) != 2 {
80 log.Fatalf("usage: %s <socket path>", os.Args[0])
81 }
82
83 sig := make(chan os.Signal)
84 signal.Notify(sig, syscall.SIGTERM)
85
86 spath := os.Args[1]
87 os.Remove(spath)
88
89 server := http.Server{
90 Handler: &webpConverterHandler{},
91 ErrorLog: log.Default(),
92 }
93
94 listener, err := net.Listen("unix", spath)
95 if err != nil {
96 log.Fatalf("error: %s", err)
97 }
98
99 log.Printf("listening on %s", spath)
100 go server.Serve(listener)
101
102 <-sig
103 log.Printf("process received SIG_TERM, quitting")
104 server.Shutdown(context.Background())
105 os.Remove(spath)
106}