173 lines
4.4 KiB
Go

package handler
import (
"bytes"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"webp_server_go/config"
"webp_server_go/helper"
"github.com/gofiber/fiber/v2"
"github.com/h2non/filetype"
"github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
)
// Given /path/to/node.png
// Delete /path/to/node.png*
func cleanProxyCache(cacheImagePath string) {
// Delete /node.png*
files, err := filepath.Glob(cacheImagePath + "*")
if err != nil {
log.Infoln(err)
}
for _, f := range files {
if err := os.Remove(f); err != nil {
log.Info(err)
}
}
}
func downloadFile(filepath string, url string) {
resp, err := http.Get(url)
if err != nil {
log.Errorln("Connection to remote error when downloadFile!")
return
}
defer resp.Body.Close()
if resp.StatusCode != fiber.StatusOK {
log.Errorf("remote returned %s when fetching remote image", resp.Status)
return
}
// Copy bytes here
bodyBytes := new(bytes.Buffer)
_, err = bodyBytes.ReadFrom(resp.Body)
if err != nil {
return
}
// Check if remote content-type is image using check by filetype instead of content-type returned by origin
kind, _ := filetype.Match(bodyBytes.Bytes())
mime := kind.MIME.Value
if !strings.Contains(mime, "image") {
log.Errorf("remote file %s is not image, remote content has MIME type of %s", url, mime)
return
}
_ = os.MkdirAll(path.Dir(filepath), 0755)
// Create Cache here as a lock, so we can prevent incomplete file from being read
// Key: filepath, Value: true
config.WriteLock.Set(filepath, true, -1)
err = os.WriteFile(filepath, bodyBytes.Bytes(), 0600)
if err != nil {
// not likely to happen
return
}
// Delete lock here
config.WriteLock.Delete(filepath)
}
func fetchRemoteImg(url string, subdir string) config.MetaFile {
cacheKey := subdir + ":" + helper.HashString(url)
var metadata config.MetaFile
var etag string
var size int64
var lastModified time.Time
if cachedETag, found := config.RemoteCache.Get(cacheKey); found {
etag = cachedETag.(string)
log.Infof("Using cached ETag for remote addr: %s", url)
} else {
log.Infof("Remote Addr is %s, pinging for info...", url)
etag, size, lastModified = pingURL(url)
if etag != "" {
config.RemoteCache.Set(cacheKey, etag, cache.DefaultExpiration)
}
}
metadata = helper.ReadMetadata(url, etag, subdir)
localRawImagePath := path.Join(config.Config.RemoteRawPath, subdir, metadata.Id)
localExhaustImagePath := path.Join(config.Config.ExhaustPath, subdir, metadata.Id)
needUpdate := false
if !helper.ImageExists(localRawImagePath) {
log.Info("Remote file not found in remote-raw, fetching...")
needUpdate = true
} else {
localFileInfo, err := os.Stat(localRawImagePath)
if err == nil {
if size > 0 && size != localFileInfo.Size() {
log.Info("File size changed, updating...")
needUpdate = true
} else if !lastModified.IsZero() && lastModified.After(localFileInfo.ModTime()) {
log.Info("Remote file is newer, updating...")
needUpdate = true
} else if metadata.Checksum != helper.HashString(etag) {
log.Info("ETag changed, updating...")
needUpdate = true
}
} else {
log.Warnf("Error checking local file: %v", err)
needUpdate = true
}
}
if needUpdate {
cleanProxyCache(localExhaustImagePath)
helper.DeleteMetadata(url, subdir)
helper.WriteMetadata(url, etag, subdir)
downloadFile(localRawImagePath, url)
// 重新读取更新后的元数据
metadata = helper.ReadMetadata(url, etag, subdir)
}
return metadata
}
func pingURL(url string) (string, int64, time.Time) {
var etag string
var size int64
var lastModified time.Time
resp, err := http.Head(url)
if err != nil {
log.Errorf("Connection to remote error when pingUrl: %v", err)
return "", 0, time.Time{}
}
defer resp.Body.Close()
if resp.StatusCode == fiber.StatusOK {
etag = resp.Header.Get("ETag")
sizeStr := resp.Header.Get("Content-Length")
size, _ = strconv.ParseInt(sizeStr, 10, 64)
lastModifiedStr := resp.Header.Get("Last-Modified")
lastModified, _ = time.Parse(time.RFC1123, lastModifiedStr)
if etag == "" {
log.Warn("Remote didn't return ETag in header, using Last-Modified if available")
etag = lastModifiedStr
}
if etag == "" && lastModified.IsZero() {
log.Warn("Neither ETag nor Last-Modified available, using Content-Length as fallback")
etag = sizeStr
}
} else {
log.Warnf("Unexpected status code: %d when pinging URL: %s", resp.StatusCode, url)
}
return etag, size, lastModified
}