diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 74cf87d..c6292f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,10 @@ on: jobs: docker: runs-on: ubuntu-latest + strategy: + matrix: + architecture: [amd64, arm64] + steps: - name: Checkout uses: actions/checkout@v3 @@ -32,8 +36,9 @@ jobs: - name: Build and push uses: docker/build-push-action@v3 with: - build-args: OYAKI_VERSION=${{ steps.meta.outputs.version }} + build-args: + TARGETARCH=${{ matrix.architecture }} + OYAKI_VERSION=${{ steps.meta.outputs.version }} context: . - platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} diff --git a/Dockerfile b/Dockerfile index 15184b1..4ab8f67 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,11 +5,21 @@ ARG OYAKI_VERSION WORKDIR /go/src/oyaki COPY . /go/src/oyaki -RUN CGO_ENABLED=0 go build -ldflags "-s -w -X main.version=${OYAKI_VERSION}" -o /go/bin/oyaki +ARG TARGETARCH="arm64" +RUN apt-get update && apt-get install -y curl tar RUN apt update && apt install -y curl \ - && curl https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.3.1-linux-x86-64.tar.gz --output libwebp.tar.gz \ + && if [ "${TARGETARCH}" = "amd64" ]; then \ + ARCH='amd64'; \ + elif [ "${TARGETARCH}" = "arm64" ]; then \ + ARCH='aarch64'; \ + else \ + echo "Unsupported arch: ${TARGETARCH}"; exit 1; \ + fi && \ + curl https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.3.1-linux-${ARCH}.tar.gz --output libwebp.tar.gz \ && tar vzxf libwebp.tar.gz \ - && mv libwebp-1.3.1-linux-x86-64/bin/cwebp /go/bin/ + && mv libwebp-1.3.1-linux-${ARCH}/bin/cwebp /go/bin/ + +RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -ldflags "-s -w -X main.version=${OYAKI_VERSION}" -o /go/bin/oyaki FROM gcr.io/distroless/static-debian11 diff --git a/convert.go b/convert.go index 72fe611..a532f97 100644 --- a/convert.go +++ b/convert.go @@ -16,7 +16,7 @@ func convert(src io.Reader, q int) (*bytes.Buffer, error) { buf := new(bytes.Buffer) - if err := jpeg.Encode(buf, img, &jpeg.Options{Quality: quality}); err != nil { + if err := jpeg.Encode(buf, img, &jpeg.Options{Quality: q}); err != nil { return nil, err } diff --git a/go.mod b/go.mod index f0e0360..8bb2c22 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,14 @@ module github.com/pepabo/oyaki go 1.19 -require github.com/disintegration/imaging v1.6.2 +require ( + github.com/disintegration/imaging v1.6.2 + github.com/fujiwara/ridge v0.9.0 +) -require golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect +require ( + github.com/aws/aws-lambda-go v1.46.0 // indirect + github.com/pires/go-proxyproto v0.7.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect +) diff --git a/go.sum b/go.sum index 6e27fcd..fa8802d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,26 @@ +github.com/aws/aws-lambda-go v1.46.0 h1:UWVnvh2h2gecOlFhHQfIPQcD8pL/f7pVCutmFl+oXU8= +github.com/aws/aws-lambda-go v1.46.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/fujiwara/ridge v0.9.0 h1:q5uQENInEYjpk49T2C73BhAMbYhfbkR+PGJ9SXN4mpk= +github.com/fujiwara/ridge v0.9.0/go.mod h1:/xRoaA5T5rrUMNsXDNOc8OjvQ6W7LXc/9jQI0L8y6ck= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= +github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index ec35900..f90f2bb 100644 --- a/main.go +++ b/main.go @@ -2,27 +2,50 @@ package main import ( "bytes" + "context" "errors" "flag" "fmt" "io" - "log" "net/http" "net/url" "os" "path/filepath" "runtime/debug" "strconv" + "strings" "syscall" "time" + + "log/slog" + + "github.com/fujiwara/ridge" ) +type OriginConfig struct { + ServerURL string +} + var client http.Client -var orgSrvURL string -var quality = 90 var version = "" func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logLevel := slog.LevelInfo + if v := os.Getenv("OYAKI_LOGLEVEL"); v != "" { + level := strings.ToLower(v) + switch level { + case "debug": + logLevel = slog.LevelDebug + case "warn": + logLevel = slog.LevelWarn + case "error": + logLevel = slog.LevelError + } + } + logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel})) var ver bool flag.BoolVar(&ver, "version", false, "show version") @@ -33,40 +56,58 @@ func main() { return } + ph := &ProxyHandler{ + logger: logger, + } orgScheme := os.Getenv("OYAKI_ORIGIN_SCHEME") orgHost := os.Getenv("OYAKI_ORIGIN_HOST") if orgScheme == "" { orgScheme = "https" } - orgSrvURL = orgScheme + "://" + orgHost + ph.originConfig = OriginConfig{ + ServerURL: orgScheme + "://" + orgHost, + } + // defaulting + ph.Quality = 90 if q := os.Getenv("OYAKI_QUALITY"); q != "" { - quality, _ = strconv.Atoi(q) + quality, err := strconv.Atoi(q) + if err == nil { + ph.Quality = quality + } } - log.Printf("starting oyaki %s\n", getVersion()) - http.HandleFunc("/", proxy) - http.ListenAndServe(":8080", nil) + logger.InfoContext(ctx, "starting oyaki", "version", getVersion()) + mux := http.NewServeMux() + mux.Handle("/", ph) + ridge.RunWithContext(ctx, ":8080", "/", mux) +} + +type ProxyHandler struct { + logger *slog.Logger + // originConfigは環境変数で一度読み込まれたあとは書き換わらない + originConfig OriginConfig + Quality int } -func proxy(w http.ResponseWriter, r *http.Request) { +func (ph *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.RequestURI() if path == "/" { fmt.Fprintln(w, "Oyaki lives!") return } - orgURL, err := url.Parse(orgSrvURL + path) + orgURL, err := url.Parse(ph.originConfig.ServerURL + path) if err != nil { http.Error(w, "Invalid origin URL", http.StatusBadRequest) - log.Printf("Invalid origin URL. %v\n", err) + ph.logger.ErrorContext(r.Context(), "Invalid origin URL", "error", err) return } req, err := http.NewRequest("GET", orgURL.String(), nil) if err != nil { http.Error(w, "Request Failed", http.StatusInternalServerError) - log.Printf("Request Failed. %v\n", err) + ph.logger.ErrorContext(r.Context(), "Request Failed", "error", err) return } req.Header.Set("User-Agent", "oyaki") @@ -82,20 +123,20 @@ func proxy(w http.ResponseWriter, r *http.Request) { var orgRes *http.Response pathExt := filepath.Ext(req.URL.Path) if pathExt == ".webp" { - orgRes, err = doWebp(req) + orgRes, err = doWebp(ph.logger, req) } else { orgRes, err = client.Do(req) } if err != nil { http.Error(w, "Get origin failed", http.StatusForbidden) - log.Printf("Get origin failed. %v\n", err) + ph.logger.ErrorContext(r.Context(), "Get origin failed", "error", err) return } if orgRes.StatusCode == http.StatusNotFound || orgRes.StatusCode == http.StatusForbidden { http.Error(w, "Get origin failed", orgRes.StatusCode) - log.Printf("Get origin failed. %v\n", err) + ph.logger.ErrorContext(r.Context(), "Get origin failed", "error", err) return } @@ -113,7 +154,7 @@ func proxy(w http.ResponseWriter, r *http.Request) { if orgRes.StatusCode != http.StatusOK { http.Error(w, "Get origin failed", http.StatusBadGateway) - log.Printf("Get origin failed. %v\n", orgRes.Status) + ph.logger.ErrorContext(r.Context(), "Get origin failed", "status", orgRes.Status) return } @@ -131,7 +172,7 @@ func proxy(w http.ResponseWriter, r *http.Request) { // ignore already close client. if !errors.Is(err, syscall.EPIPE) { http.Error(w, "Read origin body failed", http.StatusInternalServerError) - log.Printf("Read origin body failed. %v\n", err) + ph.logger.ErrorContext(r.Context(), "Read origin body failed", "error", err) } } return @@ -141,13 +182,13 @@ func proxy(w http.ResponseWriter, r *http.Request) { resBytes, err := io.ReadAll(orgRes.Body) if err != nil { http.Error(w, "Read origin body failed", http.StatusInternalServerError) - log.Printf("Read origin body failed. %v\n", err) + ph.logger.ErrorContext(r.Context(), "Read origin body failed", "error", err) return } body := io.NopCloser(bytes.NewBuffer(resBytes)) defer body.Close() - buf, err = convWebp(body, []string{}) + buf, err = convWebp(r.Context(), ph.logger, body, []string{}) if err == nil { defer buf.Reset() w.Header().Set("Content-Type", "image/webp") @@ -155,20 +196,20 @@ func proxy(w http.ResponseWriter, r *http.Request) { // if err, normally convertion will be proceeded body = io.NopCloser(bytes.NewBuffer(resBytes)) defer body.Close() - buf, err = convert(body, quality) + buf, err = convert(body, ph.Quality) if err != nil { http.Error(w, "Image convert failed", http.StatusInternalServerError) - log.Printf("Image convert failed. %v\n", err) + ph.logger.ErrorContext(r.Context(), "Image convert failed", "error", err) return } defer buf.Reset() w.Header().Set("Content-Type", "image/jpeg") } } else { - buf, err = convert(orgRes.Body, quality) + buf, err = convert(orgRes.Body, ph.Quality) if err != nil { http.Error(w, "Image convert failed", http.StatusInternalServerError) - log.Printf("Image convert failed. %v\n", err) + ph.logger.ErrorContext(r.Context(), "Image convert failed", "error", err) return } defer buf.Reset() @@ -180,7 +221,7 @@ func proxy(w http.ResponseWriter, r *http.Request) { // ignore already close client. if !errors.Is(err, syscall.EPIPE) { http.Error(w, "Write responce failed", http.StatusInternalServerError) - log.Printf("Write responce failed. %v\n", err) + ph.logger.ErrorContext(r.Context(), "write response failed", "error", err) } } } diff --git a/main_test.go b/main_test.go index addc1fd..d6ee8fa 100644 --- a/main_test.go +++ b/main_test.go @@ -2,6 +2,7 @@ package main import ( "io" + "log/slog" "net/http" "net/http/httptest" "testing" @@ -10,7 +11,8 @@ import ( func TestRoot(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() - proxy(rec, req) + ph := &ProxyHandler{} + ph.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("got http %d, want %d", rec.Code, http.StatusOK) @@ -18,20 +20,18 @@ func TestRoot(t *testing.T) { } func TestRequestHeader(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - defer ts.Close() - cxff := "127.0.0.1" - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, "./testdata/oyaki.jpg") - - sxff := r.Header.Get("X-Forwarded-For") - if sxff != cxff { - t.Errorf("X-Forwarded-For header is %s, want %s", sxff, cxff) - } - })) - - orgSrvURL = origin.URL + origin, ts := setupOriginAndOyaki( + func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "./testdata/oyaki.jpg") + + sxff := r.Header.Get("X-Forwarded-For") + if sxff != cxff { + t.Errorf("X-Forwarded-For header is %s, want %s", sxff, cxff) + } + }) + defer ts.Close() + defer origin.Close() req, err := http.NewRequest("GET", ts.URL+"/oyaki.jpg", nil) if err != nil { @@ -47,13 +47,11 @@ func TestRequestHeader(t *testing.T) { } func TestProxyJPEG(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./testdata/oyaki.jpg") - })) - - orgSrvURL = origin.URL + }) + defer ts.Close() + defer origin.Close() url := ts.URL + "/oyaki.jpg" @@ -62,7 +60,7 @@ func TestProxyJPEG(t *testing.T) { t.Fatal(err) } - orgRes, err := http.Get(orgSrvURL) + orgRes, err := http.Get(origin.URL) if err != nil { t.Fatal(err) } @@ -81,15 +79,13 @@ func TestProxyJPEG(t *testing.T) { } func TestOriginNotModifiedJPEG(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Last-Modified", "2023-01-01T00:00:00") w.WriteHeader(http.StatusNotModified) http.ServeFile(w, r, "./testdata/oyaki.jpg") - })) - - orgSrvURL = origin.URL + }) + defer ts.Close() + defer origin.Close() url := ts.URL + "/oyaki.jpg" @@ -98,7 +94,7 @@ func TestOriginNotModifiedJPEG(t *testing.T) { t.Fatal(err) } - _, err = http.Get(orgSrvURL) + _, err = http.Get(origin.URL) if err != nil { t.Fatal(err) } @@ -113,13 +109,12 @@ func TestOriginNotModifiedJPEG(t *testing.T) { } func TestProxyPNG(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./testdata/corn.png") - })) + }) + defer ts.Close() + defer origin.Close() - orgSrvURL = origin.URL url := ts.URL + "/corn.png" res, err := http.Get(url) @@ -127,7 +122,7 @@ func TestProxyPNG(t *testing.T) { t.Fatal(err) } - orgRes, err := http.Get(orgSrvURL) + orgRes, err := http.Get(origin.URL) if err != nil { t.Fatal(err) } @@ -146,13 +141,11 @@ func TestProxyPNG(t *testing.T) { } func TestOriginNotExist(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "404 Not Found", http.StatusNotFound) - })) - - orgSrvURL = origin.URL + }) + defer ts.Close() + defer origin.Close() url := ts.URL + "/none.jpg" @@ -167,13 +160,11 @@ func TestOriginNotExist(t *testing.T) { } func TestOriginForbidden(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "403 Forbidden", http.StatusForbidden) - })) - - orgSrvURL = origin.URL + }) + defer ts.Close() + defer origin.Close() url := ts.URL + "/forbidden.jpg" @@ -188,13 +179,11 @@ func TestOriginForbidden(t *testing.T) { } func TestOriginBadGateWay(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "502 Bad Gateway", http.StatusBadGateway) - })) - - orgSrvURL = origin.URL + }) + defer ts.Close() + defer origin.Close() url := ts.URL + "/bad.jpg" @@ -209,15 +198,14 @@ func TestOriginBadGateWay(t *testing.T) { } func TestOriginNotModifiedPNG(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Last-Modified", "2023-01-01T00:00:00") w.WriteHeader(http.StatusNotModified) http.ServeFile(w, r, "./testdata/corn.png") - })) + }) + defer ts.Close() + defer origin.Close() - orgSrvURL = origin.URL url := ts.URL + "/corn.png" res, err := http.Get(url) @@ -225,7 +213,7 @@ func TestOriginNotModifiedPNG(t *testing.T) { t.Fatal(err) } - _, err = http.Get(orgSrvURL) + _, err = http.Get(origin.URL) if err != nil { t.Fatal(err) } @@ -243,15 +231,29 @@ func TestOriginNotModifiedPNG(t *testing.T) { } } +func setupOriginAndOyaki( + originHandler func(http.ResponseWriter, *http.Request), +) (*httptest.Server, *httptest.Server) { + origin := httptest.NewServer(http.HandlerFunc(originHandler)) + originServerURL := origin.URL + oyakiHandler := &ProxyHandler{ + originConfig: OriginConfig{ + ServerURL: originServerURL, + }, + logger: slog.New(slog.NewJSONHandler(io.Discard, nil)), + } + oyaki := httptest.NewServer(oyakiHandler) + return origin, oyaki +} + func BenchmarkProxyJpeg(b *testing.B) { b.ResetTimer() - ts := httptest.NewServer(http.HandlerFunc(proxy)) - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./testdata/oyaki.jpg") - })) - - orgSrvURL = origin.URL + }) + defer ts.Close() + defer origin.Close() url := ts.URL + "/oyaki.jpg" @@ -270,13 +272,13 @@ func BenchmarkProxyJpeg(b *testing.B) { func BenchmarkProxyPNG(b *testing.B) { b.ResetTimer() - ts := httptest.NewServer(http.HandlerFunc(proxy)) - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./testdata/corn.png") - })) + }) + defer ts.Close() + defer origin.Close() - orgSrvURL = origin.URL url := ts.URL + "/corn.png" for i := 0; i < b.N; i++ { diff --git a/webp.go b/webp.go index 2dbc7d2..94dce61 100644 --- a/webp.go +++ b/webp.go @@ -2,9 +2,10 @@ package main import ( "bytes" + "context" "fmt" "io" - "log" + "log/slog" "net/http" "net/url" "os" @@ -13,28 +14,29 @@ import ( "github.com/disintegration/imaging" ) -func doWebp(req *http.Request) (*http.Response, error) { +func doWebp(logger *slog.Logger, req *http.Request) (*http.Response, error) { var orgRes *http.Response orgURL := req.URL newPath := orgURL.Path[:len(orgURL.Path)-len(".webp")] newOrgURL, err := url.Parse(fmt.Sprintf("%s://%s%s?%s", orgURL.Scheme, orgURL.Host, newPath, orgURL.RawQuery)) if err != nil { - log.Println(err) + logger.ErrorContext(req.Context(), "failed to parse URL", "error", err) return nil, err } newReq, err := http.NewRequest("GET", newOrgURL.String(), nil) - newReq.Header = req.Header if err != nil { - log.Println(err) + logger.ErrorContext(req.Context(), "failed to create new request", "error", err) return nil, err } + newReq.Header = req.Header + orgRes, err = client.Do(newReq) if err != nil { - log.Println(err) + logger.ErrorContext(req.Context(), "failed to send request to origin", "error", err) return nil, err } if orgRes.StatusCode != 200 && orgRes.StatusCode != 304 { - log.Println(orgRes.Status) + logger.ErrorContext(req.Context(), "origin response status code must be 200 or 304", "status", orgRes.Status) return nil, fmt.Errorf("origin response is not 200 or 304") } @@ -44,7 +46,12 @@ func doWebp(req *http.Request) (*http.Response, error) { return orgRes, nil } -func convWebp(src io.Reader, params []string) (*bytes.Buffer, error) { +func convWebp( + ctx context.Context, + logger *slog.Logger, + src io.Reader, + params []string, +) (*bytes.Buffer, error) { f, err := os.CreateTemp("/tmp", "") if err != nil { return nil, err @@ -64,7 +71,7 @@ func convWebp(src io.Reader, params []string) (*bytes.Buffer, error) { params = append(params, "-quiet", "-mt", "-jpeg_like", f.Name(), "-o", "-") out, err := exec.Command("cwebp", params...).Output() if err != nil { - log.Println(err) + logger.ErrorContext(ctx, "failed to convert to webp with cwebp", "error", err) return nil, err } return bytes.NewBuffer(out), nil diff --git a/webp_test.go b/webp_test.go index c67dd62..798941f 100644 --- a/webp_test.go +++ b/webp_test.go @@ -1,24 +1,24 @@ package main import ( + "context" "io" + "log/slog" "net/http" - "net/http/httptest" "testing" ) func TestProxyWebP(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./testdata/oyaki.jpg") - })) + }) + defer ts.Close() + defer origin.Close() - orgSrvURL = origin.URL url := ts.URL + "/oyaki.jpg.webp" req, _ := http.NewRequest("GET", url, nil) - resp, err := doWebp(req) + resp, err := doWebp(slog.New(slog.NewTextHandler(io.Discard, nil)), req) if err != nil { t.Fatal(err) } else { @@ -33,22 +33,21 @@ func TestProxyWebP(t *testing.T) { } func TestConvJPG2WebP(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(proxy)) - - origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin, ts := setupOriginAndOyaki(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./testdata/oyaki.jpg") - })) + }) + defer ts.Close() + defer origin.Close() - orgSrvURL = origin.URL url := ts.URL + "/oyaki.jpg.webp" - + nopLogger := slog.New(slog.NewTextHandler(io.Discard, nil)) req, _ := http.NewRequest("GET", url, nil) - resp, err := doWebp(req) + resp, err := doWebp(nopLogger, req) if err != nil { t.Fatal(err) } defer resp.Body.Close() - _, err = convWebp(resp.Body, []string{}) + _, err = convWebp(context.Background(), nopLogger, resp.Body, []string{}) if err != nil { t.Fatal(err) }