aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Dockerfile11
-rw-r--r--cmd/server/main.go119
-rw-r--r--go.mod1
-rw-r--r--go.sum2
4 files changed, 110 insertions, 23 deletions
diff --git a/Dockerfile b/Dockerfile
index 069309c..d5efbfd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
FROM marketplace.gcr.io/google/debian11 AS build
-ENV BUILD_DEPS 'curl git gcc patch libc6-dev ca-certificates build-essential pkg-config libmagickwand-dev libmagickwand-6-headers'
+ENV BUILD_DEPS 'curl git gcc patch libc6-dev ca-certificates build-essential pkg-config'
RUN apt-get update && apt-get install -y ${BUILD_DEPS} --no-install-recommends
ENV GOPATH=/go
@@ -19,11 +19,16 @@ COPY go.sum /workdir
RUN $GOROOT_BOOTSTRAP/bin/go mod download
COPY . /workdir
-RUN $GOROOT_BOOTSTRAP/bin/go build ./cmd/server
+RUN $GOROOT_BOOTSTRAP/bin/go build -o /workdir/server ./cmd/server
+
+FROM marketplace.gcr.io/google/debian11 AS run
+
+RUN apt-get update && apt-get install -y --no-install-recommends 'exiftool'
ENV PORT=8080
+COPY --from=build /workdir/server /app/server
RUN mkdir -p /app
-RUN mv /workdir/server /app
+#RUN mv /workdir/server /app
ENTRYPOINT /app/server
diff --git a/cmd/server/main.go b/cmd/server/main.go
index 5998fb7..58dc87d 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -1,6 +1,7 @@
package main
import (
+ "bytes"
"context"
"crypto/hmac"
"crypto/rand"
@@ -19,6 +20,7 @@ import (
"net/http"
"net/url"
"os"
+ "os/exec"
"path"
"strings"
"sync"
@@ -29,7 +31,6 @@ import (
"cloud.google.com/go/storage"
"git.toothrot.net/i.dis.band/internal"
secretmanager2 "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
- "gopkg.in/gographics/imagick.v2/imagick"
)
func main() {
@@ -130,6 +131,16 @@ func upload(b *storage.BucketHandle) http.Handler {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
+ tmp, err := ioutil.TempFile("", "disband")
+ if err != nil {
+ log.Printf("ioutil.TempFile(%q, %q) = _, %v", "", "disband", err)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ defer func() {
+ tmp.Close()
+ log.Printf("os.Remove(%q) = %v", tmp.Name(), os.Remove(tmp.Name()))
+ }()
file, head, err := r.FormFile("file")
if err != nil {
log.Printf("r.FormFile(%q) = _, %v", "file", err)
@@ -141,36 +152,52 @@ func upload(b *storage.BucketHandle) http.Handler {
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
return
}
- img, err := ioutil.ReadAll(file)
+ _, err = io.Copy(tmp, file)
if err != nil {
- log.Printf("ioutil.ReadAll(file) = _, %v", err)
+ log.Printf("io.Copy(%v, %v) = _, %v", tmp, file, err)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ tmp.Close()
+ file.Close()
+ r.MultipartForm.RemoveAll()
+ cmd := exec.Command("exiftool", "-all=", "--icc_profile:all", "-overwrite_original_in_place", tmp.Name())
+ var stdOutErr bytes.Buffer
+ cmd.Stdout = &stdOutErr
+ cmd.Stderr = &stdOutErr
+ if err := cmd.Start(); err != nil {
+ log.Printf("cmd.Start() = %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
- imagick.Initialize()
- defer imagick.Terminate()
- wand := imagick.NewMagickWand()
- defer wand.Destroy()
- if err := wand.ReadImageBlob(img); err != nil {
- log.Printf("wand.ReadImageBlob(img) = %v", err)
+ if err := WaitOrStop(r.Context(), cmd, os.Kill, 100*time.Millisecond); err != nil {
+ log.Printf("WaitOrStop(_, %v, %s) = %v", cmd, 100*time.Millisecond, err)
+ log.Printf("Output: %s", stdOutErr.String())
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
- if err := wand.StripImage(); err != nil {
- log.Printf("wand.StripImage() = %v", err)
+ if code := cmd.ProcessState.ExitCode(); code != 0 {
+ log.Printf("cmd.ProcessState.ExitCode() = %d", code)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
+
ct := head.Header.Get("content-type")
ext, err := mime.ExtensionsByType(ct)
if err != nil {
ext = []string{path.Ext(head.Filename)}
}
- outname := fmt.Sprintf("%s%s", filename(), ext[len(ext)-1])
- log.Printf("trying to write %q", outname)
- fw := b.Object(outname).NewWriter(r.Context())
+ outName := fmt.Sprintf("%s%s", filename(), ext[len(ext)-1])
+ log.Printf("trying to write %q", outName)
+ fw := b.Object(outName).NewWriter(r.Context())
fw.ContentType = ct
- n, err := fw.Write(wand.GetImageBlob())
+ file, err = os.Open(tmp.Name())
+ if err != nil {
+ log.Printf("os.Open(%q) = _, %v", tmp.Name(), err)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ n, err := io.Copy(fw, file)
if err != nil || n == 0{
log.Printf("fw.Write(wand.GetImageBlob()) = %d, %v", n, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -181,7 +208,7 @@ func upload(b *storage.BucketHandle) http.Handler {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
- u := url.URL{Scheme: "https", Host: "i.dis.band", Path: outname}
+ u := url.URL{Scheme: "https", Host: "i.dis.band", Path: outName}
fmt.Fprintln(w, u.String())
})
}
@@ -223,7 +250,7 @@ func image(b *storage.BucketHandle, next http.Handler) http.Handler {
})
}
-func home(w http.ResponseWriter, r *http.Request) {
+func home(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("hello"))
}
@@ -242,3 +269,61 @@ func fileServerHandler(fs fs.FS, next http.Handler) http.Handler {
s.ServeHTTP(w, r)
})
}
+
+// WaitOrStop waits for the already-started command cmd by calling its Wait method.
+//
+// If cmd does not return before ctx is done, WaitOrStop sends it the given interrupt signal.
+// If killDelay is positive, WaitOrStop waits that additional period for Wait to return before sending os.Kill.
+func WaitOrStop(ctx context.Context, cmd *exec.Cmd, interrupt os.Signal, killDelay time.Duration) error {
+ if cmd.Process == nil {
+ panic("WaitOrStop called with a nil cmd.Process — missing Start call?")
+ }
+ if interrupt == nil {
+ panic("WaitOrStop requires a non-nil interrupt signal")
+ }
+
+ errc := make(chan error)
+ go func() {
+ select {
+ case errc <- nil:
+ return
+ case <-ctx.Done():
+ }
+
+ err := cmd.Process.Signal(interrupt)
+ if err == nil {
+ err = ctx.Err() // Report ctx.Err() as the reason we interrupted.
+ } else if err.Error() == "os: process already finished" {
+ errc <- nil
+ return
+ }
+
+ if killDelay > 0 {
+ timer := time.NewTimer(killDelay)
+ select {
+ // Report ctx.Err() as the reason we interrupted the process...
+ case errc <- ctx.Err():
+ timer.Stop()
+ return
+ // ...but after killDelay has elapsed, fall back to a stronger signal.
+ case <-timer.C:
+ }
+
+ // Wait still hasn't returned.
+ // Kill the process harder to make sure that it exits.
+ //
+ // Ignore any error: if cmd.Process has already terminated, we still
+ // want to send ctx.Err() (or the error from the Interrupt call)
+ // to properly attribute the signal that may have terminated it.
+ _ = cmd.Process.Kill()
+ }
+
+ errc <- err
+ }()
+
+ waitErr := cmd.Wait()
+ if interruptErr := <-errc; interruptErr != nil {
+ return interruptErr
+ }
+ return waitErr
+}
diff --git a/go.mod b/go.mod
index 240dd23..9229d00 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,6 @@ require (
cloud.google.com/go/secretmanager v1.0.0
cloud.google.com/go/storage v1.18.2
google.golang.org/genproto v0.0.0-20211016002631-37fc39342514
- gopkg.in/gographics/imagick.v2 v2.6.0
)
require (
diff --git a/go.sum b/go.sum
index 82c5d75..8aaff36 100644
--- a/go.sum
+++ b/go.sum
@@ -547,8 +547,6 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/gographics/imagick.v2 v2.6.0 h1:ewRsUQk3QkjGumERlndbFn/kTYRjyMaPY5gxwpuAhik=
-gopkg.in/gographics/imagick.v2 v2.6.0/go.mod h1:/QVPLV/iKdNttRKthmDkeeGg+vdHurVEPc8zkU0XgBk=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=