diff options
author | Mike Crute <mike@crute.us> | 2023-10-30 20:16:57 -0700 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2023-10-30 20:16:57 -0700 |
commit | dc21aa0eb842ee0bf6e730aed48b7299b987f6a7 (patch) | |
tree | 91ae2faf36627b0054247f5d55674604f8631d8d | |
parent | beb31aa8f2a19f50f52bdb55ad8c42f5f3e487db (diff) | |
download | golib-dc21aa0eb842ee0bf6e730aed48b7299b987f6a7.tar.bz2 golib-dc21aa0eb842ee0bf6e730aed48b7299b987f6a7.tar.xz golib-dc21aa0eb842ee0bf6e730aed48b7299b987f6a7.zip |
image: updates to webp clientv0.8.2
- Support a logger
- Support readiness checks for the server
- Support converting raw image bytes instead of tiff
-rw-r--r-- | image/webp/client.go | 82 |
1 files changed, 74 insertions, 8 deletions
diff --git a/image/webp/client.go b/image/webp/client.go index 6b08b72..aabf240 100644 --- a/image/webp/client.go +++ b/image/webp/client.go | |||
@@ -13,7 +13,9 @@ import ( | |||
13 | "path" | 13 | "path" |
14 | "strconv" | 14 | "strconv" |
15 | "syscall" | 15 | "syscall" |
16 | "time" | ||
16 | 17 | ||
18 | "code.crute.us/mcrute/golib/log" | ||
17 | "golang.org/x/image/tiff" | 19 | "golang.org/x/image/tiff" |
18 | ) | 20 | ) |
19 | 21 | ||
@@ -22,8 +24,9 @@ import ( | |||
22 | // bytestream. This struct is only threadsafe after Start has been | 24 | // bytestream. This struct is only threadsafe after Start has been |
23 | // called. | 25 | // called. |
24 | type WebPConvertClient struct { | 26 | type WebPConvertClient struct { |
25 | p *exec.Cmd | 27 | p *exec.Cmd |
26 | c *http.Client | 28 | c *http.Client |
29 | Logger log.LeveledLogger | ||
27 | } | 30 | } |
28 | 31 | ||
29 | // Start starts the server, cancelling the passed context will stop | 32 | // Start starts the server, cancelling the passed context will stop |
@@ -31,12 +34,17 @@ type WebPConvertClient struct { | |||
31 | // process and not just context.Background. This function will return an | 34 | // process and not just context.Background. This function will return an |
32 | // error if setup fails but does not block. | 35 | // error if setup fails but does not block. |
33 | func (c *WebPConvertClient) Start(ctx context.Context) error { | 36 | func (c *WebPConvertClient) Start(ctx context.Context) error { |
37 | if c.Logger == nil { | ||
38 | c.Logger = &log.NoopLeveledLogger{} | ||
39 | } | ||
40 | |||
34 | sockDir, err := os.MkdirTemp("", "webp-*") | 41 | sockDir, err := os.MkdirTemp("", "webp-*") |
35 | if err != nil { | 42 | if err != nil { |
36 | return err | 43 | return err |
37 | } | 44 | } |
38 | 45 | ||
39 | sockPath := path.Join(sockDir, "webp.sock") | 46 | sockPath := path.Join(sockDir, "webp.sock") |
47 | c.Logger.Debugf("Webp conversion server socket: %s", sockPath) | ||
40 | 48 | ||
41 | c.c = &http.Client{ | 49 | c.c = &http.Client{ |
42 | Transport: &http.Transport{ | 50 | Transport: &http.Transport{ |
@@ -53,6 +61,7 @@ func (c *WebPConvertClient) Start(ctx context.Context) error { | |||
53 | } | 61 | } |
54 | 62 | ||
55 | c.p.Cancel = func() error { | 63 | c.p.Cancel = func() error { |
64 | c.Logger.Debugf("Shutting down webp conversion server") | ||
56 | if err := c.p.Process.Signal(syscall.SIGTERM); err != nil { | 65 | if err := c.p.Process.Signal(syscall.SIGTERM); err != nil { |
57 | return err | 66 | return err |
58 | } | 67 | } |
@@ -65,6 +74,11 @@ func (c *WebPConvertClient) Start(ctx context.Context) error { | |||
65 | os.RemoveAll(sockDir) | 74 | os.RemoveAll(sockDir) |
66 | }() | 75 | }() |
67 | 76 | ||
77 | if err := c.awaitServerReadiness(ctx, 10); err != nil { | ||
78 | c.p.Cancel() | ||
79 | return err | ||
80 | } | ||
81 | |||
68 | select { | 82 | select { |
69 | case err = <-ec: | 83 | case err = <-ec: |
70 | return err | 84 | return err |
@@ -73,6 +87,47 @@ func (c *WebPConvertClient) Start(ctx context.Context) error { | |||
73 | } | 87 | } |
74 | } | 88 | } |
75 | 89 | ||
90 | // awaitServerReadiness waits for the server to become ready by pinging | ||
91 | // its endpoint. This should happen rather quickly, but without this | ||
92 | // check it can be a race to see if the server starts before the first | ||
93 | // call is made in some programs. | ||
94 | func (c *WebPConvertClient) awaitServerReadiness(ctx context.Context, maxTries int) error { | ||
95 | for { | ||
96 | c.Logger.Debugf("Waiting for webp conversion server to be ready") | ||
97 | |||
98 | maxTries-- | ||
99 | if maxTries == 0 { | ||
100 | break | ||
101 | } | ||
102 | |||
103 | if err := c.ping(ctx); err == nil { | ||
104 | return nil | ||
105 | } | ||
106 | |||
107 | time.Sleep(time.Second) | ||
108 | } | ||
109 | return fmt.Errorf("Webp server failed to become ready") | ||
110 | } | ||
111 | |||
112 | func (c *WebPConvertClient) ping(ctx context.Context) error { | ||
113 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://unix/ping", nil) | ||
114 | if err != nil { | ||
115 | return err | ||
116 | } | ||
117 | |||
118 | res, err := c.c.Do(req) | ||
119 | if err != nil { | ||
120 | return err | ||
121 | } | ||
122 | defer res.Body.Close() | ||
123 | |||
124 | if res.StatusCode != http.StatusOK { | ||
125 | return fmt.Errorf("server not ready") | ||
126 | } | ||
127 | |||
128 | return nil | ||
129 | } | ||
130 | |||
76 | // Convert encodes an image as TIFF and sends it to the conversion | 131 | // Convert encodes an image as TIFF and sends it to the conversion |
77 | // server. It returns either an error or the webp encoded bytes from the | 132 | // server. It returns either an error or the webp encoded bytes from the |
78 | // input image. | 133 | // input image. |
@@ -86,7 +141,18 @@ func (c *WebPConvertClient) Convert(ctx context.Context, img image.Image, qualit | |||
86 | return nil, err | 141 | return nil, err |
87 | } | 142 | } |
88 | 143 | ||
89 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://unix/", bb) | 144 | return c.ConvertBytes(ctx, bb.Bytes(), quality, lossless, exact) |
145 | } | ||
146 | |||
147 | // ConvertBytes takes an image in any format and sends it to the | ||
148 | // conversion server. It returns either an error or the webp encoded | ||
149 | // bytes from the input image. | ||
150 | func (c *WebPConvertClient) ConvertBytes(ctx context.Context, img []byte, quality int, lossless, exact bool) ([]byte, error) { | ||
151 | if c.c == nil { | ||
152 | return nil, fmt.Errorf("WebPConvertClient has not been started") | ||
153 | } | ||
154 | |||
155 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://unix/", bytes.NewReader(img)) | ||
90 | if err != nil { | 156 | if err != nil { |
91 | return nil, err | 157 | return nil, err |
92 | } | 158 | } |
@@ -100,7 +166,7 @@ func (c *WebPConvertClient) Convert(ctx context.Context, img image.Image, qualit | |||
100 | } | 166 | } |
101 | defer res.Body.Close() | 167 | defer res.Body.Close() |
102 | 168 | ||
103 | if res.StatusCode != 200 { | 169 | if res.StatusCode != http.StatusOK { |
104 | e, err := io.ReadAll(res.Body) | 170 | e, err := io.ReadAll(res.Body) |
105 | if err != nil { | 171 | if err != nil { |
106 | return nil, err | 172 | return nil, err |
@@ -108,10 +174,10 @@ func (c *WebPConvertClient) Convert(ctx context.Context, img image.Image, qualit | |||
108 | return nil, fmt.Errorf("WebPConvertClient server error: %s", e) | 174 | return nil, fmt.Errorf("WebPConvertClient server error: %s", e) |
109 | } | 175 | } |
110 | 176 | ||
111 | bb.Reset() | 177 | webp, err := io.ReadAll(res.Body) |
112 | if _, err := io.Copy(bb, res.Body); err != nil { | 178 | if err != nil { |
113 | return nil, err | 179 | return nil, fmt.Errorf("WebPConvertClient error reading response: %w", err) |
114 | } | 180 | } |
115 | 181 | ||
116 | return bb.Bytes(), nil | 182 | return webp, nil |
117 | } | 183 | } |