EdgeJS规则配置方法
更新时间:2023-06-09
概述
EdgeJS为您提供可编程的自定义边缘配置服务,百度率先支持nginx扩展js对象,支持在边缘节点执行您自己编写的JavaScript代码。性能高,时延低,部署配置简单,帮助您快速定制化业务,极大降低业务实现成本。
特性
- 与请求生命周期绑定的VM。
 - 非堵塞的执行过程。
 - 基于ECMAScript标准实现。注意:目前和ECMAScript 5.1(严格模式 strict mode)是兼容的,仅支持部分ECMAScript 6特性
 - 与请求处理阶段紧密集成。
 
语法
JavaScript全局特性
- Object/String/JSON/Date/RegExp/Function/Promise/Array/ArrayBuffer/Number/Math等等
 - 同ECMAScript标准,参考 JavaScript 标准内置对象。
 
HTTP请求对象r
| 请求对象r子成员 | 描述 | 
|---|---|
| r.uri | 请求uri,可写 | 
| r.args{} | 请求参数对象,可写。 | 
| r.headersIn{} | 请求头对象,可写。Foo请求头可以使用r.headersIn.foo或者r.headersIn['Foo']来访问。"Host", "Connection", "If-Modified-Since", "If-Unmodified-Since", "If-Match", "If-None-Match", "User-Agent", "Referer", "Content-Length", "Content-Range", "Content-Type", "Range", "If-Range", "Transfer-Encoding", "TE", "Expect", "Upgrade", "Accept-Encoding", "Via", "Authorization", "Keep-Alive", "X-Real-IP", "Accept", "Accept-Language", "Depth", "Destination", "Overwrite", "Date",这些请求头只能有一个,重复的会被忽略。重复的“Cookie”请求头,会返回所有的重复部分,并以分号(;)分隔。重复的其他请求头,会返回所有的重复部分,并以逗号(,)分隔。 r.headersIn.foo = 'foo',赋值会覆盖所有的重复部分。r.headersIn['Foo'] = ['a', 'b'],赋值数组,会产生两个重复的请求头:Foo: a和Foo: b。 | 
| r.rawHeadersIn{} | 请求头KV Array,只读。 请求头 Host: localhost Foo: bar foo: bar2输出类似于['Host', 'localhost'], ['Foo', 'bar'], ['foo', 'bar2'] 获取所有的请求头 foo可使用r.rawHeadersIn.filter(v=>v[0].toLowerCase() == 'foo').map(v=>v[1]),输出['bar', 'bar2']。 | 
| r.headersOut{} | 响应头对象,可写。Foo响应头可以使用r.headersOut.foo或者r.headersOut['Foo']来访问。"Server", "Date", "Content-Length", "Content-Encoding", "Location", "Refresh", "Last-Modified", "Content-Range", "Accept-Ranges", "WWW-Authenticate", "Expires", "E-Tag", "ETag", "Content-Type", "X-Override-Charset", "Cache-Control", "Link", "Age", "Retry-After",这些响应头只能有一个,重复的会被忽略。重复的 "Set-Cookie"响应头,会返回一个数组,例如r.headersOut['Set-Cookie'].forEach(element => console.log(element))。重复的其他响应头,会返回所有的重复部分,并以逗号(,)分隔 r.headersOut.foo = 'foo',赋值会覆盖所有的重复部分。r.headersOut['Foo'] = ['a', 'b'],赋值数组,会产生两个重复的响应头:Foo: a和Foo: b。 | 
| r.rawHeadersOut{} | 响应头KV Array,只读。 用法类似于 r.rawHeadersIn{}。 | 
| r.respHeader(callback) | 响应头处理callback注册方法。在callback中可以对后端传递的r.headersOut、r.status进行修改。r.return/r.send/r.sendHeader/r.finish/r.respHeader无法工作在callback里面。 | 
| r.httpVersion | http协议版本,0.9/1.0/1.1,只读。 | 
| r.method | http方法,GET/HEAD/PUT/POST等,只读。 | 
| r.remoteAddress | 客户端地址,只读。 | 
| r.variables{} | nginx变量对象,部分可写。 设置限速可使用 r.variables['limit_rate']。 | 
| r.rawVariables{} | 类似r.variables{},只读,返回Buffer。 | 
| 响应处理方法 | 描述 | 
|---|---|
| r.status | 设置响应码,可写。 | 
| r.sendHeader() | 发送http响应头给客户端。 先通过 r.status设置状态码,r.headersOut{}设置响应头,然后使用r.sendHeader()发送响应头,如果有响应体,再通过r.send(string)发送响应体。如果没有响应体,则调用 r.finish()结束。 | 
| r.send(string) | 发送一部分响应string给客户端,发送完成时调用r.finish()结束。 | 
| r.finish() | 结束请求处理,响应完成。 | 
| r.return(status[, string]) | 指定响应码status,并同时发送整个响应string给客户端,后续不能再调用r.send(string)发送响应。可以用来重定向,status指定301、302、303、307或者308,重定向URL作为第二个参数。 注意:这个方法并不能结束js代码的运行,如果想结束脚本,必须在 r.return之后使用return全局关键字退出js代码。 | 
| 调试打印 | 描述 | 
|---|---|
| console.log(string/obj) | 打印string或者obj到调试窗口里面。 | 
| console.dump(string/obj) | 同console.log,格式化输出。 | 
| Fetch | 描述 | 
|---|---|
| ngx.fetch(url, [options]) | 类似于JavaScript原生的Fetch,请求URL,并返回解析Response对象的Promise。 重定向需要调用者处理,options支持body / headers / method/verify。 选项verify表示,是否开启ssl证书校验,默认开启。 sni和Host头保持一致,不设置默认为参数url中的域名部分  | 
| Response | 同JavaScript原生的Response。支持arrayBuffer() / bodyUsed / headers(get(name)/getAll(name)/has(name)) / json() / ok / redirected / status / statusText / text() / type / url。 | 
Crypto
提供密码类函数支持,通过require('crypto')返回来使用。
| Hash | 描述 | 
|---|---|
| crypto.createHash(algorithm) | 创建并返回hash对象,使用给定的algorithm生成hash摘要。 algorithm支持md5、sha1和sha256。  | 
| hash.update(data) | 使用给定的data更新hash计算结果。 | 
| hash.digest([encoding]) | 通过hash.update()传递的所有data,计算其hash摘要encoding支持hex、base64和base64url,不指定默认为byte string。  | 
| HMAC | 描述 | 
|---|---|
| crypto.createHmac(algorithm, secret key) | 创建并返回HMAC对象,使用给定的algorithm和secret key。 algorithm支持md5、sha1和sha256。  | 
| hmac.update(data) | 使用给定的data更新HMAC计算结果。 | 
| hmac.digest([encoding]) | 通过hmac.update()传递的所有data,计算其hmac摘要encoding支持hex、base64和base64url,不指定默认为byte string。  | 
Async/Await
async/await可以以更舒适的方式处理 Promise。同JavaScript规范
WebCrypto
同JavaScript规范SubtleCrypto接口
                Plain Text
                
            
            1crypto = require('crypto').webcrypto
2var subtlecrypto = crypto.subtle;
            baidu_utils库
function ipInCidr(ipv4, cidrs)
                Plain Text
                
            
            1参数:
2    ipv4为点分十进制的ipv4地址,比如'192.168.2.100'
3    cidrs为CIDR地址列表,比如['192.168.1.1/32','192.168.2.1/24']
4 
5使用示例:
6    if (baidu_utils.ipInCidr('192.168.2.100', ['192.168.1.1/32','192.168.2.1/24'])) {
7        r.return(403);
8    }
            Base64
编码
                Plain Text
                
            
            1'1234'.toBytes().toString('base64');
2'1234'.toBytes().toString('base64url');
3require('crypto').createHash('sha1').update('A').update('B').digest('base64');
4require('crypto').createHmac('sha1', 'key').update('A').update('B').digest('base64url');
            解码
                Plain Text
                
            
            1String.bytesFrom('MTIzNA==', 'base64');
2String.bytesFrom('MTIzNA', 'base64url');
            场景示例
文件名改写
                Plain Text
                
            
            1r.headersOut['Content-Disposition']='attachment;filename=' + '\"' + r.args['filename'] + '\"';
            跨域访问
                Plain Text
                
            
            1r.headersOut['Access-Control-Allow-Origin']=r.headersIn['Origin'];
            重定向
                Plain Text
                
            
            1r.return(302, '/a');
            访问控制
IP黑白名单
                Plain Text
                
            
            1if (baidu_utils.ipInCidr(r.remoteAddress, ['192.168.1.1/32','192.168.2.1/24'])) { r.return(403); }
            Referer黑白名单
                Plain Text
                
            
            1var refers = ['http://*.baidu.com.cn/*','http://*.baidu.com/*'];
2var i = 0;
3for (; i < refers.length; i += 1) {
4    if (baidu_utils.matchWildcard(r.headersIn['referer'], refers[i])) {
5        r.return(403);
6        return;
7    }
8}
            UA黑白名单
                Plain Text
                
            
            1var uas = ['curl','AppleWebKit'];
2var i = 0;
3var ua = r.headersIn['User-Agent'];
4for (; i < uas.length; i += 1) {
5    if (ua.includes(uas[i])) {
6        r.return(403);
7        return;
8    }
9}
            限速
                Plain Text
                
            
            1r.variables['limit_rate'] = '1k';
            B类鉴权
                Plain Text
                
            
            1var part = r.uri.substr(1).split('/');
2if (part.length < 3) {
3    r.return(403);
4    return;
5}
6 
7var date_in_uri = part[0];
8var d = new Date('');
9d.setFullYear(date_in_uri.substring(0, 4));
10d.setMonth(date_in_uri.substring(4, 6) - 1); //the month (0-11) in the specified date
11d.setDate(date_in_uri.substring(6, 8));
12d.setHours(date_in_uri.substring(8, 10));
13d.setMinutes(date_in_uri.substring(10));
14var time = d.getTime();
15var timeout = 1000000000000; // not overdue
16if (time + timeout < Date.now()) {
17    r.return(403);
18    return;
19}
20 
21var md5_in_uri = part.splice(1,1)[0];
22var md5_str = 'bdcloud666' + part.join('/');
23var md5 = require('crypto').createHash('md5').update(md5_str).digest('hex');
24if (md5_in_uri != md5) {
25    r.return(403);
26    return;
27}
            修改请求uri
                Plain Text
                
            
            1r.uri = '/test';
            修改请求args
                Plain Text
                
            
            1r.args['test1'] = 'test';
2if (r.args['test1'] == 'test') { /* use first one if duplicate */
3    delete r.args['test2'];
4    delete r.args['test']; /* remove all duplicate arg */
5}
6 
7r.args['gy'] = 'test';  /* remove all duplicate arg, add a new one */
8r.variables['args'] = 'test=test'; /* set entire args directly */
            修改请求header
                Plain Text
                
            
            1var xff = r.headersIn['X-Forwarded-For'] + ', ';
2r.headersIn['X-Forwarded-For'] = xff.substring(0, xff.length - 2);
            修改响应header
                Plain Text
                
            
            1function done() {
2    r.headersOut['dup'] = 'dup';
3}
4r.respHeader(done);
            A/B Test
                Plain Text
                
            
            1if (r.args['a']) {
2    r.args['version'] = 'new';
3}
4else if (r.args['b']) {
5    r.args['version'] = 'old';
6}
            自定义错误页面
                Plain Text
                
            
            1function done() {
2    if (r.status == 404) {
3        r.status = 302;
4        r.headersOut['Location'] = 'http://www.baidu.com/test';
5    }
6}
7 
8r.respHeader(done);
            遍历所有请求头
                Plain Text
                
            
            1function myFunc(item, index) {
2    console.log(item);
3    // action
4}
5r.rawHeadersIn.forEach(myFunc);
            远程鉴权
                Plain Text
                
            
            1ngx.fetch('https://www.baidu.com/auth')
2.then(function(response) {
3    if (!response.ok) {
4        return r.return(403, 'forbidden');
5    }
6})
7.catch(e => console.log(e.message));
            Async/Await
                Plain Text
                
            
            1function pr(x) {
2    return new Promise(resolve => {resolve(x)});
3}
4 
5async function add(x) {
6    const a = await pr(x);
7    throw a + 1;
8    const b = await pr(x + 10);
9    return a + b;
10}
11 
12add(50).then(v => {console.log(v - 1)}).catch(v => console.log(v));
            SubtleCrypto
                Plain Text
                
            
            1if (typeof crypto == 'undefined') {
2    crypto = require('crypto').webcrypto;
3}
4
5let dkey = await crypto.subtle.importKey('raw', params.key,
6                                   {name: params.name},
7                                   false, ['decrypt']);
8 
9let ekey = await crypto.subtle.importKey('raw', params.key,
10                                   {name: params.name},
11                                   false, ['encrypt']);
12 
13let enc = await crypto.subtle.encrypt(params, ekey, params.data);
14let plaintext = await crypto.subtle.decrypt(params, dkey, enc);
15plaintext = Buffer.from(plaintext);
            