Nginx实现反向代理私有OSS

3,949 阅读4分钟

一、业务背景

某小程序项目,使用阿里云DCDN全站加速模式进行配置;

图片资源存储到私有OSS桶内,通过OSSFs 工具将OSS挂载到ECS服务器中,当做文件系统进行读取。

二、业务问题

因客户侧缺少设计人员,有些图片运营人员无法自行处理尺寸和体积问题,所以经常会在用小图的位置使用大尺寸的图片。导致本来约定 750像素的移动端海报图,会使用了1920像素的PC端海报图片,因此导致小程序侧图片加载速度缓慢,影响用户体验。

三、提出的解决方案

1、上传时,严格限制图片尺寸和体积大小

此举会对运营侧造成人员成本,且运营团队并非专属团队而是其他运营团队兼顾,因此运营侧和客户拒绝此要求。

2、上传时对图片进行压缩处理

对后台前端技术有一定要求,且压缩处理后的图片无法保证清晰度等问题,如果将处理程序做的很完整带有预览,参数调节等功能的话,开发成本又会增加。

3、借用OSS图片处理规则

相当于第二条的压缩处理,但是因本身就使用的OSS进行图片存储,所以开发成本可以忽略不记。

但是因使用的全站加速,并且挂载OSS到服务器,因此图片处理规则无效,需要重新对图片进行处理。

3.1、将OSS权限设置为公共读,并单独设置CDN域名进行图片资源的访问。
3.2、重写nginx图片反代规则,通过反代实现当前图片链接支持 图片处理规则参数的访问。

最终采用方案

经过沟通讨论后,决定采用 3.2 的解决方案,借用OSS的图片处理功能,同时实现nginx反向代理携带处理参数的功能。

四、详细处理流程

1、安装nginx模块

使用 nginx-njs 模块,进行代码的制作

服务器:Centos 7.9

安装方式:yum

# 安装模块
yum install nginx-module-njs -y
# 修改nginx 配置

vim /etc/nginx/nginx.conf

# 在上方  导入 njs
load_module modules/ngx_http_js_module.so;

# 在 http 标签中配置默认的脚本目录
js_path "/etc/nginx/njs/";

#保存重启nginx

2、编写OSS签名代码

在 /etc/nginx/njs/ 中创建 oss.js 文件

import crypto from 'crypto';

/* oss签名配置数据,换成你自己的PAM账号的AK和bucket */
var ossAccess = {
  /* 示例:https://bucketName.oss-cn-chengdu.aliyuncs.com OSS的访问域名*/
  region: "",
  accessKeyId: "",
  accessKeySecret: "",
  bucket: "",
};


/**
 * 生成加密字符串
 * @param {*} method
 * @param {*} resourcePath
 * @param {*} request
 * @param {*} expires
 * @returns
 */
function buildCanonicalString(method, resourcePath, request, expires) {
  request = request || {};
  const headers = request.headers;
  const OSS_PREFIX = "x-oss-";
  const ossHeaders = [];
  const headersToSign = {};

  let signContent = [
    method.toUpperCase(),
    headers["content-md5"] || "",
    headers["content-type"],
    expires || headers["x-oss-date"],
  ];

  Object.keys(headers).forEach((key) => {
    const lowerKey = key.toLowerCase();
    if (lowerKey.indexOf(OSS_PREFIX) === 0) {
      headersToSign[lowerKey] = String(headers[key]).trim();
    }
  });

  Object.keys(headersToSign)
    .sort()
    .forEach((key) => {
      ossHeaders.push(`${key}:${headersToSign[key]}`);
    });

  signContent = signContent.concat(ossHeaders);

  signContent.push(resourcePath);

  return signContent.join("\n");
}

/* 计算签名字符串 */
function computeSignature(accessKeySecret, canonicalString) {
  var signature = crypto.createHmac("sha1", accessKeySecret);
  return signature
    .update(Buffer.from(canonicalString, "utf-8"))
    .digest("base64");
}

/* 生成完整认证字符串 */
function authorization(accessKeyId, accessKeySecret, canonicalString) {
  return (
    "OSS " +
    accessKeyId +
    ":" +
    computeSignature(accessKeySecret, canonicalString)
  );
}

var GMTdate = "";

/* 返回oss header date字符串 */
function getGMTtime(r) { 
  /* 函数运行在http环境,如果有多个server最好加上判断条件来避免不必要的消耗 */
  /* nginx 环境下 toUTCString 函数和标准有差异, 此处做转换处理,后续njs版本升级后可能需要修改 */
  var currentDate = new Date();
  var currentDateUTCStr = currentDate.toUTCString();
  var dateStrs = currentDateUTCStr.split(" ");
  /* 这里需要改动一下处理方式 */
  var result =
    dateStrs[0] +
    " " +
    dateStrs[1] +
    " " +
    dateStrs[2] +
    " " +
    dateStrs[3] +
    " " +
    dateStrs[4] +
    " GMT";
  GMTdate = result;
  return result;
}

/* 返回oss header authorization字符串 */
function ossSign(r) {
  var method = r.method;
  //替换你的文件路径 
  //参数一:XX.com/upload/XX.jpg
  //参数二: "" 代表 OSS的路径,我们存储路径为OSS根目录所以为空
  var uri = r.uri.replace("upload/", "");
  var headers = new Object();
  var process = getProcess(r);
  var param = "";
  if (process) {
    param = "?x-oss-process=" + process;
    headers["x-oss-process"] = process;
  }
  var time = getGMTtime();
  var canonicalString = buildCanonicalString(
    method,
    "/" + ossAccess.bucket + uri + param,
    {
      headers: headers,
    },
    time
  );

  var auth = authorization(
    ossAccess.accessKeyId,
    ossAccess.accessKeySecret,
    canonicalString
  );
  return auth;
}


function getProcess(r) {
  var p = r.args["x-oss-process"];
  if (!p) {
    p = "";
  }
  return p;
}

export default { getGMTtime, ossSign, getProcess };

3、修改nginx配置文件

修改站点配置文件

在server 标签中修改反代配置

js_import oss.js;
# 定义签名字符串和GMT时间字符串
js_set $ossDate oss.getGMTtime;
js_set $ossAuth oss.ossSign;
js_set $ossProcess oss.getProcess;

location ^~ /upload/ {
  # 设置反向代理时请求header,
  proxy_set_header Date $ossDate;
  proxy_set_header Authorization $ossAuth;
  proxy_set_header x-oss-process $ossProcess;

  proxy_pass_header Date;
  proxy_pass_header x-oss-process;
  proxy_pass_header Authorization;
  proxy_pass_request_headers on;
  # oss.js 中的 region参数,也是 OSS对外
  proxy_pass https://bucketName.oss-cn-chengdu.aliyuncs.com/;

}

重启nginx,测试访问链接

五、参考链接

www.jianshu.com/p/cbd6594d5…

github.com/nginx/njs

github.com/nginx/njs-e…

nginx.org/en/docs/njs…

nginx.org/en/docs/njs…