Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ on:
jobs:
docker:
runs-on: ubuntu-latest
strategy:
matrix:
architecture: [amd64, arm64]

steps:
- name: Checkout
uses: actions/checkout@v3
Expand All @@ -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 }}
16 changes: 13 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
21 changes: 21 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
89 changes: 65 additions & 24 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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
}

Expand All @@ -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
}

Expand All @@ -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
Expand All @@ -141,34 +182,34 @@ 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")
} else {
// 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()
Expand All @@ -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)
}
}
}
Expand Down
Loading