docker compose

This commit is contained in:
2025-10-27 14:40:01 +05:00
parent e34f1baf50
commit 51a3308f8c
15 changed files with 221 additions and 13 deletions

BIN
.output-temp/cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
.output-temp/cat.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

27
Dockerfile Normal file
View 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}"]

BIN
cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

28
docker-compose.yaml Normal file
View 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

View 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
View 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
View File

2
go.mod
View File

@@ -4,7 +4,7 @@ go 1.25.3
require (
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 (

4
go.sum
View File

@@ -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/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/makeworld-the-better-one/dither v1.0.0 h1:sBZdGV4o6MG6UMMRJhzDhruwlt99yQe0ChwgL29LMWg=
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 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE=
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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=

123
main.go
View File

@@ -5,17 +5,16 @@ import (
"fmt"
"image"
"image/color"
"image/jpeg"
"image/png"
"net/http"
"os"
"strconv"
"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) {
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"})
}
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() {
port := flag.Int("p", 8080, "port to listen on")
@@ -31,13 +137,10 @@ func main() {
router := gin.Default()
palette := []color.Color{color.Black, color.White}
ditherer := dither.NewDitherer(palette)
ditherer.Matrix = dither.FalseFloydSteinberg
router.GET("/", hw)
router.GET("/heart", heart)
router.GET("/cat", ditherCat)
router.POST("/dither", ditherImageHandler)
//router.POST("dither",ditherImage())

View File

@@ -0,0 +1,8 @@
server {
listen 8080;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://localhost:13200;
}
}

View File

@@ -0,0 +1 @@
/etc/nginx/sites-available/backend.local.conf

View 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";
}
}