From dc21aa0eb842ee0bf6e730aed48b7299b987f6a7 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Mon, 30 Oct 2023 20:16:57 -0700 Subject: image: updates to webp client - Support a logger - Support readiness checks for the server - Support converting raw image bytes instead of tiff --- image/webp/client.go | 82 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file 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 ( "path" "strconv" "syscall" + "time" + "code.crute.us/mcrute/golib/log" "golang.org/x/image/tiff" ) @@ -22,8 +24,9 @@ import ( // bytestream. This struct is only threadsafe after Start has been // called. type WebPConvertClient struct { - p *exec.Cmd - c *http.Client + p *exec.Cmd + c *http.Client + Logger log.LeveledLogger } // Start starts the server, cancelling the passed context will stop @@ -31,12 +34,17 @@ type WebPConvertClient struct { // process and not just context.Background. This function will return an // error if setup fails but does not block. func (c *WebPConvertClient) Start(ctx context.Context) error { + if c.Logger == nil { + c.Logger = &log.NoopLeveledLogger{} + } + sockDir, err := os.MkdirTemp("", "webp-*") if err != nil { return err } sockPath := path.Join(sockDir, "webp.sock") + c.Logger.Debugf("Webp conversion server socket: %s", sockPath) c.c = &http.Client{ Transport: &http.Transport{ @@ -53,6 +61,7 @@ func (c *WebPConvertClient) Start(ctx context.Context) error { } c.p.Cancel = func() error { + c.Logger.Debugf("Shutting down webp conversion server") if err := c.p.Process.Signal(syscall.SIGTERM); err != nil { return err } @@ -65,6 +74,11 @@ func (c *WebPConvertClient) Start(ctx context.Context) error { os.RemoveAll(sockDir) }() + if err := c.awaitServerReadiness(ctx, 10); err != nil { + c.p.Cancel() + return err + } + select { case err = <-ec: return err @@ -73,6 +87,47 @@ func (c *WebPConvertClient) Start(ctx context.Context) error { } } +// awaitServerReadiness waits for the server to become ready by pinging +// its endpoint. This should happen rather quickly, but without this +// check it can be a race to see if the server starts before the first +// call is made in some programs. +func (c *WebPConvertClient) awaitServerReadiness(ctx context.Context, maxTries int) error { + for { + c.Logger.Debugf("Waiting for webp conversion server to be ready") + + maxTries-- + if maxTries == 0 { + break + } + + if err := c.ping(ctx); err == nil { + return nil + } + + time.Sleep(time.Second) + } + return fmt.Errorf("Webp server failed to become ready") +} + +func (c *WebPConvertClient) ping(ctx context.Context) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://unix/ping", nil) + if err != nil { + return err + } + + res, err := c.c.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("server not ready") + } + + return nil +} + // Convert encodes an image as TIFF and sends it to the conversion // server. It returns either an error or the webp encoded bytes from the // input image. @@ -86,7 +141,18 @@ func (c *WebPConvertClient) Convert(ctx context.Context, img image.Image, qualit return nil, err } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://unix/", bb) + return c.ConvertBytes(ctx, bb.Bytes(), quality, lossless, exact) +} + +// ConvertBytes takes an image in any format and sends it to the +// conversion server. It returns either an error or the webp encoded +// bytes from the input image. +func (c *WebPConvertClient) ConvertBytes(ctx context.Context, img []byte, quality int, lossless, exact bool) ([]byte, error) { + if c.c == nil { + return nil, fmt.Errorf("WebPConvertClient has not been started") + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://unix/", bytes.NewReader(img)) if err != nil { return nil, err } @@ -100,7 +166,7 @@ func (c *WebPConvertClient) Convert(ctx context.Context, img image.Image, qualit } defer res.Body.Close() - if res.StatusCode != 200 { + if res.StatusCode != http.StatusOK { e, err := io.ReadAll(res.Body) if err != nil { return nil, err @@ -108,10 +174,10 @@ func (c *WebPConvertClient) Convert(ctx context.Context, img image.Image, qualit return nil, fmt.Errorf("WebPConvertClient server error: %s", e) } - bb.Reset() - if _, err := io.Copy(bb, res.Body); err != nil { - return nil, err + webp, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("WebPConvertClient error reading response: %w", err) } - return bb.Bytes(), nil + return webp, nil } -- cgit v1.2.3