走通 阿里云 函数计算 - 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 的角色, 如果你的配置中没有这个角色, 按照下面的步骤可以建立自定义角色:
- 去角色管理控制台, 点击"RAM角色管理"
- 点击"创建RAM角色"
- 弹出框"当前可信实体类型"选择"阿里云服务", 下一步
- 选择"普通服务角色", 填写服务名(例如fn-service), 选择管理服务"函数计算", 完成
- 给新建的角色添加权限. 你的函数计算需要使用什么服务就添加什么权限
- 回到你的"函数计算", 选中你已经创建的一个"服务", 点击配置
- 最下面, 选择"已经存在角色", 从中选择你刚建立的角色名字(例如 fn-service, 记住这个名字, 下面会用到)
- 然后点"点击授权", 自己建立的角色可以执行函数计算, 但没有日志权限, 点击授权可以通过给角色添加策略的方式插入日志权限. 如果已经授权过不需要点击.
通过以上步骤, 你就可以使用自定义角色了, 如果未来你的函数新增了依赖的服务, 只需要给角色 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, 所以为了安全性, 你可以单独为函数计算建立一个角色, 它需要包含以下两个授权
- AliyunFCInvocationAccess
- AliyunSTSAssumeRoleAccess,
其中 AliyunFCInvocationAccess 是发起调用必须的权限, 如果没有, 会提示没有调用权限. AliyunSTSAssumeRoleAccess 是为了让当前用户可以扮演函数/服务执行时的角色.
例如你配置了fn-service这个角色, 添加了 oss 权限. 那么http函数计算时, 使用的就是 fn-service 这个角色. 而http调用者幷不需要定义oss权限, 它只要在执行时可以扮演 fn-server 的角色即可.
素质三连
如果你恰好要采购阿里云产品来我的推广链接看看吧: