feat(config, handler): Remove IMG_PATH config and update IMG_MAP handling, add root path greeting

This commit is contained in:
wood chen 2024-10-22 19:59:27 +08:00
parent c8f3d0bf38
commit d872d71dcb
3 changed files with 59 additions and 171 deletions

View File

@ -31,6 +31,8 @@
* 使用流式JSON编码器
* 内存使用监控
6. 根目录设置打招呼
7. 移除`IMG_PATH`的配置方式, 只保留`IMG_MAP`.
8. 当前缀与`IMG_MAP`不匹配时, 直接返回错误
<p align="center">
<img src="./pics/webp_server.png"/>

View File

@ -137,7 +137,7 @@ func LoadConfig() {
decoder := json.NewDecoder(jsonObject)
_ = decoder.Decode(&Config)
_ = jsonObject.Close()
switchProxyMode()
Config.ImageMap = parseImgMap(Config.ImageMap)
if slices.Contains(Config.ConvertTypes, "webp") {
@ -282,12 +282,12 @@ func parseImgMap(imgMap map[string]string) map[string]string {
var parsedImgMap = map[string]string{}
httpRegexpMatcher := regexp.MustCompile(HttpRegexp)
for uriMap, uriMapTarget := range imgMap {
if httpRegexpMatcher.Match([]byte(uriMap)) || strings.HasPrefix(uriMap, "/") {
if httpRegexpMatcher.Match([]byte(uriMapTarget)) || strings.HasPrefix(uriMap, "/") {
// Valid
parsedImgMap[uriMap] = uriMapTarget
} else {
// Invalid
log.Warnf("IMG_MAP key '%s' does matches '%s' or starts with '/' - skipped", uriMap, HttpRegexp)
log.Warnf("IMG_MAP 值'%s'与'%s'不匹配或键不以“/”开头 -已跳过", uriMapTarget, HttpRegexp)
}
}
return parsedImgMap
@ -299,12 +299,3 @@ type ExtraParams struct {
MaxWidth int // in px
MaxHeight int // in px
}
func switchProxyMode() {
matched, _ := regexp.MatchString(HttpRegexp, Config.ImgPath)
if matched {
// Enable proxy based on ImgPath should be deprecated in future versions
log.Warn("Enable proxy based on ImgPath will be deprecated in future versions. Use IMG_MAP config options instead")
ProxyMode = true
}
}

View File

@ -4,7 +4,6 @@ import (
"net/http"
"net/url"
"path"
"regexp"
"strconv"
"strings"
"webp_server_go/config"
@ -16,43 +15,29 @@ import (
)
func Convert(c *fiber.Ctx) error {
// this function need to do:
// 1. get request path, query string
// 2. generate rawImagePath, could be local path or remote url(possible with query string)
// 3. pass it to encoder, get the result, send it back
// normal http request will start with /
// 检查路径是否以 "/" 开头
if !strings.HasPrefix(c.Path(), "/") {
return c.SendStatus(http.StatusBadRequest)
}
// 处理根路径请求
// 检查是否为根路径
if c.Path() == "/" {
return c.SendString("Welcome to CZL WebP Server")
}
var (
reqHostname = c.Hostname()
reqHost = c.Protocol() + "://" + reqHostname // http://www.example.com:8000
reqHeader = &c.Request().Header
reqURIRaw, _ = url.QueryUnescape(c.Path()) // /mypic/123.jpg
reqURIwithQueryRaw, _ = url.QueryUnescape(c.OriginalURL()) // /mypic/123.jpg?someother=200&somebugs=200
reqURI = path.Clean(reqURIRaw) // delete ../ in reqURI to mitigate directory traversal
reqURIwithQuery = path.Clean(reqURIwithQueryRaw) // Sometimes reqURIwithQuery can be https://example.tld/mypic/123.jpg?someother=200&somebugs=200, we need to extract it
reqURIRaw, _ = url.QueryUnescape(c.Path())
reqURIwithQueryRaw, _ = url.QueryUnescape(c.OriginalURL())
reqURI = path.Clean(reqURIRaw)
reqURIwithQuery = path.Clean(reqURIwithQueryRaw)
filename = path.Base(reqURI)
realRemoteAddr = ""
targetHostName = config.LocalHostAlias
targetHost = config.Config.ImgPath
proxyMode = config.ProxyMode
mapMode = false
targetHostName = ""
targetHost = ""
width, _ = strconv.Atoi(c.Query("width")) // Extra Params
height, _ = strconv.Atoi(c.Query("height")) // Extra Params
maxHeight, _ = strconv.Atoi(c.Query("max_height")) // Extra Params
maxWidth, _ = strconv.Atoi(c.Query("max_width")) // Extra Params
width, _ = strconv.Atoi(c.Query("width"))
height, _ = strconv.Atoi(c.Query("height"))
maxHeight, _ = strconv.Atoi(c.Query("max_height"))
maxWidth, _ = strconv.Atoi(c.Query("max_width"))
extraParams = config.ExtraParams{
Width: width,
Height: height,
@ -63,155 +48,65 @@ func Convert(c *fiber.Ctx) error {
log.Debugf("Incoming connection from %s %s %s", c.IP(), reqHostname, reqURIwithQuery)
var rawImageAbs string
var metadata = config.MetaFile{}
// 非图片清况下302到源文件
if !helper.IsImageFile(filename) {
log.Infof("Non-image file requested: %s", reqURI)
var redirectURL string
// 检查是否存在匹配的 IMG_MAP
for prefix, target := range config.Config.ImageMap {
if strings.HasPrefix(reqURI, prefix) {
// 检查目标是否为远程资源
if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") {
// 远程资源,构造重定向 URL
redirectURL = target + strings.TrimPrefix(reqURI, prefix)
} else {
// 本地资源,按原逻辑处理
return c.SendFile(path.Join(target, strings.TrimPrefix(reqURI, prefix)))
}
break
}
}
// 如果没有找到匹配的 IMG_MAP或者是本地资源使用默认的处理方式
if redirectURL == "" {
if proxyMode {
redirectURL = realRemoteAddr
} else {
// 本地资源,按原逻辑处理
localPath := path.Join(config.Config.ImgPath, reqURI)
if helper.FileExists(localPath) {
return c.SendFile(localPath)
} else {
return c.SendStatus(fiber.StatusNotFound)
}
}
}
// 只有在确定需要重定向时才执行重定向
if redirectURL != "" {
log.Infof("Redirecting to: %s", redirectURL)
return c.Redirect(redirectURL, fiber.StatusFound)
// 检查路径是否匹配 IMG_MAP 中的任何前缀
var matchedPrefix string
var matchedTarget string
for prefix, target := range config.Config.ImageMap {
if strings.HasPrefix(reqURI, prefix) {
matchedPrefix = prefix
matchedTarget = target
break
}
}
// 如果不匹配任何 IMG_MAP 前缀,直接返回 404
if matchedPrefix == "" {
log.Warnf("请求的路径不匹配任何配置的 IMG_MAP: %s", c.Path())
return c.SendStatus(fiber.StatusNotFound)
}
// 设置目标主机信息
targetUrl, _ := url.Parse(matchedTarget)
targetHostName = targetUrl.Host
targetHost = targetUrl.Scheme + "://" + targetUrl.Host
// 调整请求 URI
reqURI = strings.Replace(reqURI, matchedPrefix, targetUrl.Path, 1)
reqURIwithQuery = strings.Replace(reqURIwithQuery, matchedPrefix, targetUrl.Path, 1)
// 构造远程地址
realRemoteAddr = targetHost + reqURIwithQuery
// 处理非图片文件
if !helper.IsImageFile(filename) {
log.Infof("Non-image file requested: %s", reqURI)
log.Infof("Redirecting to: %s", realRemoteAddr)
return c.Redirect(realRemoteAddr, fiber.StatusFound)
}
// 检查允许的文件类型
if !helper.CheckAllowedType(filename) {
msg := "不允许的文件扩展名 " + filename
log.Warn(msg)
c.Status(http.StatusBadRequest)
_ = c.Send([]byte(msg))
return nil
return c.Status(http.StatusBadRequest).SendString(msg)
}
// Rewrite the target backend if a mapping rule matches the hostname
if hostMap, hostMapFound := config.Config.ImageMap[reqHost]; hostMapFound {
log.Debugf("找到host映射 %s -> %s", reqHostname, hostMap)
targetHostUrl, _ := url.Parse(hostMap)
targetHostName = targetHostUrl.Host
targetHost = targetHostUrl.Scheme + "://" + targetHostUrl.Host
proxyMode = true
} else {
// There's not matching host mapping, now check for any URI map that apply
httpRegexpMatcher := regexp.MustCompile(config.HttpRegexp)
for uriMap, uriMapTarget := range config.Config.ImageMap {
if strings.HasPrefix(reqURI, uriMap) {
log.Debugf("找到 URI 映射 %s -> %s", uriMap, uriMapTarget)
mapMode = true
// if uriMapTarget we use the proxy mode to fetch the remote
if httpRegexpMatcher.Match([]byte(uriMapTarget)) {
targetHostUrl, _ := url.Parse(uriMapTarget)
targetHostName = targetHostUrl.Host
targetHost = targetHostUrl.Scheme + "://" + targetHostUrl.Host
reqURI = strings.Replace(reqURI, uriMap, targetHostUrl.Path, 1)
reqURIwithQuery = strings.Replace(reqURIwithQuery, uriMap, targetHostUrl.Path, 1)
proxyMode = true
} else {
reqURI = strings.Replace(reqURI, uriMap, uriMapTarget, 1)
reqURIwithQuery = strings.Replace(reqURIwithQuery, uriMap, uriMapTarget, 1)
}
break
}
}
}
if proxyMode {
if !mapMode {
// Don't deal with the encoding to avoid upstream compatibilities
reqURI = c.Path()
reqURIwithQuery = c.OriginalURL()
}
log.Tracef("reqURIwithQuery is %s", reqURIwithQuery)
// Replace host in the URL
// realRemoteAddr = strings.Replace(reqURIwithQuery, reqHost, targetHost, 1)
realRemoteAddr = targetHost + reqURIwithQuery
log.Debugf("realRemoteAddr is %s", realRemoteAddr)
}
if proxyMode {
// 这是 proxyMode我们必须使用这个 url 来下载并将其保存到本地路径,这也为我们提供了 rawImageAbs
// https://test.webp.sh/mypic/123.jpg?someother=200&somebugs=200
metadata = fetchRemoteImg(realRemoteAddr, targetHostName)
rawImageAbs = path.Join(config.Config.RemoteRawPath, targetHostName, metadata.Id)
} else {
// not proxyMode, we'll use local path
metadata = helper.ReadMetadata(reqURIwithQuery, "", targetHostName)
if !mapMode {
// by default images are hosted in ImgPath
rawImageAbs = path.Join(config.Config.ImgPath, reqURI)
} else {
rawImageAbs = reqURI
}
// detect if source file has changed
if metadata.Checksum != helper.HashFile(rawImageAbs) {
log.Info("源文件已更改,重新编码...")
helper.WriteMetadata(reqURIwithQuery, "", targetHostName)
cleanProxyCache(path.Join(config.Config.ExhaustPath, targetHostName, metadata.Id))
}
}
// 获取远程图像元数据
metadata := fetchRemoteImg(realRemoteAddr, targetHostName)
rawImageAbs := path.Join(config.Config.RemoteRawPath, targetHostName, metadata.Id)
// 后续的图像处理逻辑
supportedFormats := helper.GuessSupportedFormat(reqHeader)
// resize itself and return if only raw(original format) is supported
if supportedFormats["raw"] == true &&
supportedFormats["webp"] == false &&
supportedFormats["avif"] == false &&
supportedFormats["jxl"] == false {
dest := path.Join(config.Config.ExhaustPath, targetHostName, metadata.Id)
if !helper.ImageExists(dest) {
encoder.ResizeItself(rawImageAbs, dest, extraParams)
}
return c.SendFile(dest)
}
// 检查原始图像是否存在,
// 检查原始图像是否存在
if !helper.ImageExists(rawImageAbs) {
helper.DeleteMetadata(reqURIwithQuery, targetHostName)
msg := "Image not found!"
_ = c.Send([]byte(msg))
log.Warn(msg)
_ = c.SendStatus(404)
return nil
return c.Status(404).SendString(msg)
}
// 新增:检查文件大小
// 检查文件大小
isSmall, err := helper.IsFileSizeSmall(rawImageAbs, 100*1024) // 100KB
if err != nil {
log.Errorf("检查文件大小时出错: %v", err)
@ -224,7 +119,7 @@ func Convert(c *fiber.Ctx) error {
}
avifAbs, webpAbs, jxlAbs := helper.GenOptimizedAbsPath(metadata, targetHostName)
// Do the convertion based on supported formats and config
// 根据支持的格式和配置进行转换
encoder.ConvertFilter(rawImageAbs, jxlAbs, avifAbs, webpAbs, extraParams, supportedFormats, nil)
var availableFiles = []string{rawImageAbs}