跟我一起走通 http 函数计算

跟我一起走通 http 函数计算

函数计算是什么

阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传。函数计算会为您准备好计算资源,以弹性、可靠的方式运行您的代码,并提供日志查询、性能监控、报警等功能。借助于函数计算,您可以快速构建任何类型的应用和服务,无需管理和运维。而且,您只需要为代码实际运行所消耗的资源付费,代码未运行则不产生费用。

建立一个http函数

按照官方文档的顺序就可以完成http函数的建立.

使用自定义角色

上面的函数建立完毕后, 去控制台导选中刚建立的函数, 点击导出 -> 导出配置
在这里插入图片描述
你将得到类似这样的配置文件:

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
 your-server-name:
   Type: 'Aliyun::Serverless::Service'
   Properties:
     Role: 'acs:ram::********:role/your_role_name'
     LogConfig:
       Project: aliyun-fc-cn-shanghai-******
       Logstore: function-log
     InternetAccess: true
   your-http-fn-name:
     Type: 'Aliyun::Serverless::Function'
     Properties:
       Initializer: http/tar-images/index.initializer
       InitializationTimeout: 3
       Handler: http/tar-images/index.handler
       Runtime: nodejs12
       Timeout: 90
       MemorySize: 128
       EnvironmentVariables: {}
     Events:
       tar-images:
         Type: HTTP
         Properties:
           AuthType: function
           Methods:
             - POST

通过导出的配置文件可以看到, your-server-name 服务在执行时使用了一个叫做: ***your_role_name 的角色, 如果你的配置中没有这个角色, 按照下面的步骤可以建立自定义角色:

  1. 角色管理控制台, 点击"RAM角色管理"
  2. 点击"创建RAM角色"
  3. 弹出框"当前可信实体类型"选择"阿里云服务", 下一步
  4. 选择"普通服务角色", 填写服务名(例如fn-service), 选择管理服务"函数计算", 完成
  5. 给新建的角色添加权限. 你的函数计算需要使用什么服务就添加什么权限
  6. 回到你的"函数计算", 选中你已经创建的一个"服务", 点击配置
  7. 最下面, 选择"已经存在角色", 从中选择你刚建立的角色名字(例如 fn-service, 记住这个名字, 下面会用到)
  8. 然后点"点击授权", 自己建立的角色可以执行函数计算, 但没有日志权限, 点击授权可以通过给角色添加策略的方式插入日志权限. 如果已经授权过不需要点击.
    通过以上步骤, 你就可以使用自定义角色了, 如果未来你的函数新增了依赖的服务, 只需要给角色 fn-service 添加授权即可

http触发器校验逻辑

到这里你应该已经可以通过在线编辑控制台正确的调用代码了. 下面尝试使用浏览器或者是server程度调用函数计算. 打开刚建立的 http函数, 选择"触发器", 查看认证方式, 如果认证方式是 “anonymous”, 调用过程同普通的 http 请求一致, 没难度.

修改认证方式为: “function”, 和我一起来看一下怎样走通 function 认证. 直接上代码:
参考官方文档, 开发工具类 util.js

const URL = require('url');
const crypto = require('crypto');

// javascript
// prefix = 'x-fc-'
function buildCanonicalHeaders(headers, prefix) {
  let list = [];
  let keys = Object.keys(headers);
  let fcHeaders = {};
  for (let i = 0; i < keys.length; i++) {
    let key = keys[i];
    let lowerKey = key.toLowerCase().trim();
    if (lowerKey.startsWith(prefix)) {
      list.push(lowerKey);
      fcHeaders[lowerKey] = headers[key];
    }
  }
  list.sort();
  let canonical = '';
  for (let _i = 0; _i < list.length; _i++) {
    let _key = list[_i];
    canonical += `${_key}:${fcHeaders[_key]}\n`;
  }
  return canonical;
}

// 构造字符串的过程
exports.composeStringToSign = function (method, path, headers, queries) {
  let contentMD5 = '';
  let contentType = '';
  let date = '';

  let keys = Object.keys(headers);
  for (let k of keys) {
    let lower = k.toLowerCase();
    if (lower === 'content-md5') {
      contentMD5 = headers[k] || '';
    } else if (lower === 'content-type') {
      contentType = headers[k] || '';
    } else {
      date = headers[k] || '';
    }
  }

  let signHeaders = buildCanonicalHeaders(headers, 'x-fc-');
  let u = URL.parse(path);
  let pathUnescaped = decodeURIComponent(u.pathname);
  // **********
  // 注意这里结尾添加了 \n 修复了 get 方式参数加密错误
  // **********
  let str = `${method}\n${contentMD5}\n${contentType}\n${date}\n${signHeaders}${pathUnescaped}\n`; 

  if (queries) {
    let params = [];
    Object.keys(queries).forEach(function (key) {
      let values = queries[key];
      let type = typeof values;
      if (type === 'string') {
        params.push(`${key}=${values}`);
        return;
      }
      if (type === 'object' && values instanceof Array) {
        queries[key].forEach(function (value) {
          params.push(`${key}=${value}`);
        });
      }
    });
    params.sort();
    str += params.join('\n');
  }

  return str;
}

// 使用 hmac-sha256 和 base64 计算签名的过程,其中 source 参数为构造出的字符串
exports.signString = function (source, secret) {
  const buff = crypto.createHmac('sha256', secret)
    .update(source, 'utf8')
    .digest();
  return buff.toString('base64');
}

exports.md5 = function (source) {
  const buff = crypto.createHash('md5')
    .update(source)
    .digest();
  return buff.toString('base64');
}

基于以上的工具类, 开发出调用抽象

const request = require('request');
const {composeStringToSign, signString, md5} = require('./util.js');

const accessKeyId = 'your_role_id';
const accessKeySecret = 'your_role_secret';

/**
 * 产生函数计算提交请求
 * @param url
 * @constructor
 */
function FnRequest(url) {
  this.url = url;
}
/**
 * 发送这个方法的结果到浏览器即可在浏览器端发起 http 函数调用
 * @param body
 */
FnRequest.prototype.preparePost = function (body) {
  let stringify = JSON.stringify(body);
  let headers = {
    'Content-Md5': md5(stringify),
    'Content-Type': 'application/json',
    'Date': new Date().toUTCString(),
  }

  let str = composeStringToSign('POST', this.url, headers);
  let signed = signString(str, accessKeySecret);
  headers.Authorization = `FC ${accessKeyId}:${signed}`;

  return {
    url: this.url,
    headers,
    body: stringify,
  };
};
/**
 * 直接发送请求
 * 参考 https://stackoverflow.com/questions/6158933/how-is-an-http-post-request-made-in-node-js
 * @param {*} body
 * @return {Promise<*>}
 */
FnRequest.prototype.post = function (body) {
  let stringify = JSON.stringify(body, null, 2);
  let headers = {
    'Content-Md5': md5(stringify),
    'Content-Type': 'application/json',
    'Content-Length': stringify.length,
    'Date': new Date().toUTCString(),
  }

  let str = composeStringToSign('POST', this.url, headers);
  let signed = signString(str, accessKeySecret);
  headers.Authorization = `FC ${accessKeyId}:${signed}`;

  return new Promise((resolve, reject) => {
    request.post({
      url: this.url,
      headers: headers,
      body: stringify
    }, function (err, res, body) {
      if (err) {
        return reject(err);
      }

      let execError = false; // 出现执行错误
      let hNames = Object.keys(res.headers);

      for (let name of hNames) {
        let value = res.headers[name];
        if (name === 'content-type') {
          if (value.indexOf('application/json') === 0) {
            body = JSON.parse(body);
          }
          continue;
        }
        if (name.indexOf('x-fc-') === 0) {
          name = name.slice(5);
          if (name === 'error-type') {
            execError = true;
          }
        }
      }

      if (execError) {
        return reject(body);
      }
      resolve(body);
    });
  });
};

/**
 * 你可以在这里抽象出更多的函数调用
 * @type {FnRequest}
 */
exports.TarImages = new FnRequest('https://****.cn-shanghai.fc.aliyuncs.com/****/your_http_function_url');

如果你想在浏览器中发起调用, 只需要调用以上代码中的 preparePost, 结果发送到浏览器, 再次发起 request 即可.

建立 function授权角色

注意到以上代码中出现了

const accessKeyId = 'your_role_id';
const accessKeySecret = 'your_role_secret';

这个需要提供一个角色的key&secret, 以便进行加密. 由于 headers.Authorization 中含有 accessKeyId, 所以为了安全性, 你可以单独为函数计算建立一个角色, 它需要包含以下两个授权

  1. AliyunFCInvocationAccess
  2. AliyunSTSAssumeRoleAccess,

其中 AliyunFCInvocationAccess 是发起调用必须的权限, 如果没有, 会提示没有调用权限. AliyunSTSAssumeRoleAccess 是为了让当前用户可以扮演函数/服务执行时的角色.

例如你配置了fn-service这个角色, 添加了 oss 权限. 那么http函数计算时, 使用的就是 fn-service 这个角色. 而http调用者幷不需要定义oss权限, 它只要在执行时可以扮演 fn-server 的角色即可.

素质三连

如果你恰好要采购阿里云产品来我的推广链接看看吧:折扣码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wlpsjgs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值