1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
package main
// webp is a small HTTP server that listens on a Unix socket and
// receives an 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 originally used exclusively 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. This was changed because it spends a lot of memory
// on the client side.
import (
"context"
"fmt"
"image"
"log"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
_ "image/jpeg"
_ "image/png"
_ "golang.org/x/image/tiff"
"github.com/chai2010/webp"
)
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 := image.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 <socket path>", 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)
}
|