docker compose
This commit is contained in:
BIN
.output-temp/cat.png
Normal file
BIN
.output-temp/cat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
BIN
.output-temp/cat.webp
Normal file
BIN
.output-temp/cat.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
FROM golang:1.25.3 AS build-stage
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY *.go ./
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /app.bin
|
||||||
|
|
||||||
|
from build-stage AS run-tests-stage
|
||||||
|
RUN go test -v ./
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/base-debian11 AS build-release-stage
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
COPY --from=build-stage /app.bin /app.bin
|
||||||
|
|
||||||
|
EXPOSE ${LISTEN_PORT}/tcp
|
||||||
|
|
||||||
|
USER nonroot:nonroot
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app.bin"]
|
||||||
|
CMD ["-p", "${LISTEN_PORT}"]
|
||||||
28
docker-compose.yaml
Normal file
28
docker-compose.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
services:
|
||||||
|
backend:
|
||||||
|
image: rither:dev
|
||||||
|
environment:
|
||||||
|
- LISTEN_PORT=${BACKEND_PORT}
|
||||||
|
ports:
|
||||||
|
- "13200:13200"
|
||||||
|
networks:
|
||||||
|
- shared
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
environment:
|
||||||
|
- BACKEND_PORT=${BACKEND_PORT}
|
||||||
|
image: nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx/templates/:/etc/nginx/templates/
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
- "13201:8080"
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- shared
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
networks:
|
||||||
|
shared:
|
||||||
|
driver: bridge
|
||||||
8
docker/backend.local.conf
Normal file
8
docker/backend.local.conf
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_pass http://localhost:13200;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
frontend/index.html
Normal file
20
frontend/index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Dither</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="index.js"></script>
|
||||||
|
<form action="http://localhost:8080/dither" method="post" enctype="multipart/form-data">
|
||||||
|
<input name="source" type="file" accept="image/jpeg, image/png" />
|
||||||
|
<input type="submit" />
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
0
frontend/index.js
Normal file
0
frontend/index.js
Normal file
2
go.mod
2
go.mod
@@ -4,7 +4,7 @@ go 1.25.3
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/makeworld-the-better-one/dither v1.0.0
|
github.com/makeworld-the-better-one/dither/v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -34,8 +34,8 @@ github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzh
|
|||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/makeworld-the-better-one/dither v1.0.0 h1:sBZdGV4o6MG6UMMRJhzDhruwlt99yQe0ChwgL29LMWg=
|
github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE=
|
||||||
github.com/makeworld-the-better-one/dither v1.0.0/go.mod h1:iYNC2QRNGWaeJ7G6eiItq30v4ZRPHOb2Od6g7AFYehI=
|
github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
|||||||
123
main.go
123
main.go
@@ -5,17 +5,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/makeworld-the-better-one/dither"
|
"github.com/makeworld-the-better-one/dither/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ditherImage(src image.Image) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func hw(ctx *gin.Context) {
|
func hw(ctx *gin.Context) {
|
||||||
ctx.JSON(http.StatusOK, gin.H{"message": 123})
|
ctx.JSON(http.StatusOK, gin.H{"message": 123})
|
||||||
}
|
}
|
||||||
@@ -24,6 +23,113 @@ func heart(ctx *gin.Context) {
|
|||||||
ctx.JSON(http.StatusOK, gin.H{"emo": "<3"})
|
ctx.JSON(http.StatusOK, gin.H{"emo": "<3"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ditherImageHandler(ctx *gin.Context) {
|
||||||
|
fileHeader, fileErr := ctx.FormFile("source")
|
||||||
|
|
||||||
|
if fileErr != nil {
|
||||||
|
fmt.Printf("Could not fetch form file; %v", fileErr)
|
||||||
|
ctx.Status(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceFile, sourceFileErr := fileHeader.Open()
|
||||||
|
|
||||||
|
if sourceFileErr != nil {
|
||||||
|
|
||||||
|
fmt.Printf("Could not open source file; %v", sourceFileErr)
|
||||||
|
ctx.Status(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer sourceFile.Close()
|
||||||
|
|
||||||
|
img, formatName, decodeErr := image.Decode(sourceFile)
|
||||||
|
|
||||||
|
fmt.Printf("Format: %v", formatName)
|
||||||
|
|
||||||
|
if decodeErr != nil {
|
||||||
|
fmt.Printf("Failed to decode image from stream: %v", decodeErr)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error:": decodeErr.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
palette := []color.Color{color.Black, color.White}
|
||||||
|
|
||||||
|
ditherer := dither.NewDitherer(palette)
|
||||||
|
ditherer.Matrix = dither.FalseFloydSteinberg
|
||||||
|
|
||||||
|
ditheredImage := ditherer.DitherCopy(img)
|
||||||
|
|
||||||
|
var encodeErr error
|
||||||
|
|
||||||
|
switch formatName {
|
||||||
|
case "png":
|
||||||
|
encodeErr = png.Encode(ctx.Writer, ditheredImage)
|
||||||
|
case "jpeg":
|
||||||
|
options := jpeg.Options{Quality: 100}
|
||||||
|
encodeErr = jpeg.Encode(ctx.Writer, ditheredImage, &options)
|
||||||
|
default:
|
||||||
|
fmt.Printf("Failed to encode dithered image: %v", decodeErr)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error:": "format unsupported: " + formatName})
|
||||||
|
}
|
||||||
|
|
||||||
|
if encodeErr != nil {
|
||||||
|
fmt.Printf("Failed to encode dithered image: %v", decodeErr)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error:": encodeErr.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if encodeErr != nil {
|
||||||
|
fmt.Printf("Failed to stream image: %v", encodeErr)
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error:": encodeErr.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Header("Content-Type", "image/png")
|
||||||
|
ctx.Status(http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ditherCat(ctx *gin.Context) {
|
||||||
|
picStream, err := os.Open("./.output-temp/cat.png")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to stream image: %v", err)
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error:": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer picStream.Close()
|
||||||
|
|
||||||
|
img, formatName, decodeErr := image.Decode(picStream)
|
||||||
|
|
||||||
|
fmt.Printf("Format: %v", formatName)
|
||||||
|
|
||||||
|
if decodeErr != nil {
|
||||||
|
fmt.Printf("Failed to decode image from stream: %v", decodeErr)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error:": decodeErr.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
palette := []color.Color{color.Black, color.White}
|
||||||
|
|
||||||
|
ditherer := dither.NewDitherer(palette)
|
||||||
|
ditherer.Matrix = dither.FalseFloydSteinberg
|
||||||
|
|
||||||
|
ditheredImage := ditherer.DitherCopy(img)
|
||||||
|
|
||||||
|
encodeErr := png.Encode(ctx.Writer, ditheredImage)
|
||||||
|
|
||||||
|
if encodeErr != nil {
|
||||||
|
fmt.Printf("Failed to encode dithered image: %v", decodeErr)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{"error:": encodeErr.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Header("Content-Type", "image/png")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
port := flag.Int("p", 8080, "port to listen on")
|
port := flag.Int("p", 8080, "port to listen on")
|
||||||
|
|
||||||
@@ -31,13 +137,10 @@ func main() {
|
|||||||
|
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
palette := []color.Color{color.Black, color.White}
|
|
||||||
|
|
||||||
ditherer := dither.NewDitherer(palette)
|
|
||||||
ditherer.Matrix = dither.FalseFloydSteinberg
|
|
||||||
|
|
||||||
router.GET("/", hw)
|
router.GET("/", hw)
|
||||||
router.GET("/heart", heart)
|
router.GET("/heart", heart)
|
||||||
|
router.GET("/cat", ditherCat)
|
||||||
|
router.POST("/dither", ditherImageHandler)
|
||||||
|
|
||||||
//router.POST("dither",ditherImage())
|
//router.POST("dither",ditherImage())
|
||||||
|
|
||||||
|
|||||||
8
nginx/sites-available/backend.local.conf
Normal file
8
nginx/sites-available/backend.local.conf
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_pass http://localhost:13200;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
nginx/sites-enabled/backend.local.conf
Symbolic link
1
nginx/sites-enabled/backend.local.conf
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/etc/nginx/sites-available/backend.local.conf
|
||||||
13
nginx/templates/default_cfg.conf.template
Normal file
13
nginx/templates/default_cfg.conf.template
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://backend:${BACKEND_PORT};
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
proxy_set_header Testing-Header "$remote_addr $host";
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user