需求背景:需要在访问临时链中添加缩略图参数支持,让含有缩略图参数的请求返回指定图片宽高大小的文件,满足编辑器、文件列表中图片资源快速加载的需求。为了每个业务能够快速接入使用、复用,我们选择不在业务服务中进行处理,而是在对象存储服务中进行通用处理。
处理流程:
让缩略图参数不参与签名计算 -> 识别图片资源请求 -> 处理文件流,按照指定宽高裁剪 -> 返回文件流
安装缩略图处理插件
go get -u github.com/disintegration/imaging
让缩略图参数不参与签名计算
signature-v4.go doesPresignedSignatureMatch()
notSignParams := set.CreateStringSet(
"filename",
"imageWidth",
"imageHeight",
)
// BillYu 去除URL参数中filename参与校验,query参数中不添加filename。
// Add missing query parameters if any provided in the request URL
for k, v := range req.Form {
if !defaultSigParams.Contains(k) && !notSignParams.Contains(k) {
query[k] = v
}
}
处理图片资源
object-handlers.go getObjectHandler()
对比源码原方法是直接把资源的文件流写入到请求响应中,我们需要处理的是缩略图请求,将处理后的图片资源写入请求响应。如果文件使用缩略图处理报错则返回正常的文件资源,不影响正常业务功能。
//是图片文件
objectLowerName := strings.ToLower(object)
// if filename == "" && (strings.HasSuffix(objectLowerName, "jpg") || strings.HasSuffix(objectLowerName, "png") || strings.HasSuffix(objectLowerName, "jpeg") || strings.HasSuffix(objectLowerName, "bmp")) {
if strings.HasSuffix(objectLowerName, "jpg") || strings.HasSuffix(objectLowerName, "png") || strings.HasSuffix(objectLowerName, "jpeg") || strings.HasSuffix(objectLowerName, "bmp") {
imageWidth := GetUrlArg(r, "imageWidth")
imageHeight := GetUrlArg(r, "imageHeight")
//存在图片缩略参数
if imageWidth != "" || imageHeight != "" {
var updateImage image.Image
img, format, decodeErr := image.Decode(bufio.NewReader(gr.Reader))
if decodeErr != nil {
logger.Error(time.Now().Format("2006-01-02 15:04:05") + " format:" + format + " error:" + decodeErr.Error())
} else {
if imageWidth != "" {
//设置宽高
width, _ := strconv.Atoi(imageWidth)
var height int = 0
if imageHeight != "" {
height, _ = strconv.Atoi(imageHeight)
}
updateImage = imaging.Resize(img, width, height, imaging.Lanczos)
} else if imageHeight != "" {
//设置高度
var height int = 0
height, _ = strconv.Atoi(imageHeight)
updateImage = imaging.Resize(img, 0, height, imaging.Lanczos)
}
w.Header().Set(xhttp.ContentType, "image/jpeg")
w.Header().Del(xhttp.ContentLength)
if encodeError := jpeg.Encode(httpWriter, updateImage, nil); encodeError != nil {
if !httpWriter.HasWritten() && !statusCodeWritten {
// write error response only if no data or headers has been written to client yet
writeErrorResponse(ctx, w, toAPIError(ctx, encodeError), r.URL)
return
}
if !xnet.IsNetworkOrHostDown(encodeError, true) { // do not need to log disconnected clients
logger.LogIf(ctx, fmt.Errorf("Unable to write all the data to client %w", encodeError))
}
return
}
processImage = true
}
}
}
if !processImage {
// Write object content to response body
if _, err = xioutil.Copy(httpWriter, gr); err != nil {
if !httpWriter.HasWritten() && !statusCodeWritten {
// write error response only if no data or headers has been written to client yet
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
logger.LogIf(ctx, fmt.Errorf("Unable to write all the data to client %w", err))
}
return
}
}
responseHeader统一处理
其中需要特别注意的问题是对responseHeader的处理,源码中通过文件属性对responseHeader进行统一设置,这样每次返回的content-length都是固定的,但是我们对图片进行了缩略处理,导致文件大小发生了变化。这样即使返回的是缩略图资源,但是responseHeader中的content-length与实际资源大小不一致,这样会使浏览器认为资源文件下载有问题发生错误。
所以我们在返回缩略图资源前将content-length值移除,使浏览器能够正常响应。
关于源码中的请求头通用设置:
其中包括:content-type content-length version等存储对象本身的信息。
实现效果:
使用方法
在访问临时链追加参数:宽度&imageWidth= 和 高度&imageHeight=
只设置一个参数时(或一个参数为0)另一个参数按照宽高等比自动填充。
原图:
缩略图: