diff --git a/README.md b/README.md index 4a631a8..200bc75 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ * 使用流式JSON编码器 * 内存使用监控 6. 根目录设置打招呼 +7. 移除`IMG_PATH`的配置方式, 只保留`IMG_MAP`. +8. 当前缀与`IMG_MAP`不匹配时, 直接返回错误

diff --git a/config/config.go b/config/config.go index d0c8975..114fb03 100644 --- a/config/config.go +++ b/config/config.go @@ -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 - } -} diff --git a/handler/router.go b/handler/router.go index b2cff92..086acdc 100644 --- a/handler/router.go +++ b/handler/router.go @@ -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}