commit 6b5f1e6e92f4004b79f985e5bf67bd0cbd17ce7c Author: n0vad3v Date: Sun Feb 9 12:21:51 2020 +0800 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90816b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +exhaust/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c39b4c --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +

+ +

+ +**THIS PROJECT IS UNDER DEVELOPMENT, DON'T USE IT ON PRODUCTION ENVIRONMENT.** + +After the [n0vad3v/webp_server](https://github.com/n0vad3v/webp_server), I decide to rewrite the whole program with Go, as there will be no more `npm install`s or `docker-compose`s. + +This is a Server based on Go and a precompiled cwebp(libwebp-1.1.0-rc2-linux-x86-64), which allows you to serve WebP images on the fly, it will convert `jpg,jpeg,png` files by default, this can be customized by editing the `config.js`.. + +> e.g When you visit `https://a.com/1.jpg`,it will serve as `image/webp` without changing the URL. +> +> For Safari and Opera users, the original image will be used. + +## Compare to [n0vad3v/webp_server](https://github.com/n0vad3v/webp_server) + +### Size + +* `webp_server` with `node_modules`: 43M +* `webp_server_go` single binary: 9.5M + +### Performance + +It's basically between `ExpressJS` and `Fiber`, much faster than the `http` package of course. + +### Convenience + +* `webp_server`: Clone -> `npm install` -> run with `pm2` +* `webp_server_go`: Download -> Run + +## Usage + +Regarding the `IMG_PATH` section in `config.json`, if you are serving images at `https://example.com/pics/tsuki.jpg` and your files are at `/var/www/image/pics/tsuki.jpg`, then `IMG_PATH` shall be `/var/www/image`. + +1. Edit the `config.json` to face your need, default convert quality is 80%. +2. Run the binary like this: `./webp_server`, use `screen` or `tmux` to hold it currently. +3. Let Nginx to `proxy_pass http://localhost:3333/;` + +## TODO + +* This version doesn't support header-based-output, which means Safari users will not see the converted `webp` images, this should be fixed in later releases. +* Multi platform support. \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..f42f283 --- /dev/null +++ b/config.json @@ -0,0 +1,7 @@ +{ + "HOST": "127.0.0.1", + "PORT": "3333", + "QUALITY": "80", + "IMG_PATH": "/path/to/pics", + "ALLOWED_TYPES": ["jpg","png","jpeg"] +} diff --git a/pics/webp_server.png b/pics/webp_server.png new file mode 100755 index 0000000..865a4f4 Binary files /dev/null and b/pics/webp_server.png differ diff --git a/webp/cwebp b/webp/cwebp new file mode 100755 index 0000000..d068991 Binary files /dev/null and b/webp/cwebp differ diff --git a/webp_server.go b/webp_server.go new file mode 100644 index 0000000..3be1612 --- /dev/null +++ b/webp_server.go @@ -0,0 +1,149 @@ +package main + +import ( + "bytes" + "github.com/gofiber/fiber" + "encoding/json" + "fmt" + "strings" + "path" + "os" + "os/exec" +) + +type Config struct { + HOST string + PORT string + IMG_PATH string + QUALITY string + ALLOWED_TYPES []string + +} + +func main() { + app := fiber.New() + app.Banner = false + app.Server = "WebP Server Go" + + // Config Here + config := load_config("config.json") + + HOST := config.HOST + PORT := config.PORT + IMG_PATH := config.IMG_PATH + QUALITY := config.QUALITY + ALLOWED_TYPES := config.ALLOWED_TYPES + + LISTEN_ADDRESS := HOST + ":" + PORT + + // Server Info + SERVER_INFO := "WebP Server is running at " + LISTEN_ADDRESS + fmt.Println(SERVER_INFO) + + app.Get("/*", func(c *fiber.Ctx) { + + // /var/www/IMG_PATH/path/to/tsuki.jpg + IMG_ABSOLUTE_PATH := IMG_PATH + c.Path() + + // /path/to/tsuki.jpg + IMG_PATH := c.Path() + + // jpg + IMG_EXT := strings.Split(path.Ext(IMG_PATH),".")[1] + + // tsuki.jpg + IMG_NAME := path.Base(IMG_PATH) + + // /path/to + DIR_PATH := path.Dir(IMG_PATH) + + // /path/to/tsuki.jpg.webp + WEBP_IMG_PATH := DIR_PATH + "/" + IMG_NAME + ".webp" + + // /home/webp_server + CURRENT_PATH, err := os.Getwd() + if err != nil { + fmt.Println(err.Error()) + } + + // /home/webp_server/exhaust/path/to/tsuki.webp + WEBP_ABSOLUTE_PATH := CURRENT_PATH + "/exhaust" + WEBP_IMG_PATH + + // /home/webp_server/exhaust/path/to + DIR_ABSOLUTE_PATH := CURRENT_PATH + "/exhaust" + DIR_PATH + + // Check file extension + _, found := Find(ALLOWED_TYPES, IMG_EXT) + if !found { + c.Send("File extension not allowed!") + c.SendStatus(403) + return + } + + // Check the original image for existence + if !imageExists(IMG_ABSOLUTE_PATH) { + // The original image doesn't exist, check the webp image, delete if processed. + if imageExists(WEBP_ABSOLUTE_PATH) { + os.Remove(WEBP_ABSOLUTE_PATH) + } + c.Send("File not found!") + c.SendStatus(404) + return + } + + if imageExists(WEBP_ABSOLUTE_PATH) { + c.SendFile(WEBP_ABSOLUTE_PATH) + } else{ + // Mkdir + os.MkdirAll(DIR_ABSOLUTE_PATH , os.ModePerm) + + // cwebp -q 60 Cute-Baby-Girl.png -o Cute-Baby-Girl.webp + OS_CMD := exec.Command("./webp/cwebp","-q",QUALITY,IMG_ABSOLUTE_PATH,"-o",WEBP_ABSOLUTE_PATH) + var out bytes.Buffer + var stderr bytes.Buffer + OS_CMD.Stdout = &out + OS_CMD.Stderr = &stderr + err := OS_CMD.Run() + if err != nil { + fmt.Println(stderr.String()) + fmt.Println(err.Error()) + } + + if err != nil { + fmt.Println(err) + } + c.SendFile(WEBP_ABSOLUTE_PATH) + } + }) + + app.Listen(LISTEN_ADDRESS) +} + +func load_config(path string) Config { + var config Config + json_object,err := os.Open(path) + if err != nil { + fmt.Println(err.Error()) + } + defer json_object.Close() + decoder := json.NewDecoder(json_object) + decoder.Decode(&config) + return config +} + +func imageExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func Find(slice []string, val string) (int, bool) { + for i, item := range slice { + if item == val { + return i, true + } + } + return -1, false +} \ No newline at end of file