所有文档

          内容分发网络 CDN

          EdgeJS规则配置方法

          概述

          EdgeJS为您提供可编程的自定义边缘配置服务,百度率先支持nginx扩展js对象,支持在边缘节点执行您自己编写的JavaScript代码。性能高,时延低,部署配置简单,帮助您快速定制化业务,极大降低业务实现成本。

          特性

          • 与请求生命周期绑定的VM。
          • 非堵塞的执行过程。
          • 基于ECMAScript标准实现。
          • 与请求处理阶段紧密集成。

          语法

          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']来访问。
          对于可能出现多次的头,比如“Cookie”或者“X-Forwarded-For”,headersIn只返回第一个。
          如果要获取全部的cookie头,需要使用r.variables.http_cookie。
          使用r.variables["cookie_name"]来获取该name的cookie。
          r.headersOut{} 响应头对象,可写。
          Foo响应头可以使用r.headersOut.foo或者r.headersOut['Foo']来访问。
          r.respHeader(callback) 响应头处理callback注册方法。在callback中可以对后端传递的r.headersOut、r.status进行修改。
          r.httpVersion http协议版本,0.9/1.0/1.1,只读。
          r.method http方法,GET/HEAD/PUT/POST等,只读。
          r.remoteAddress 客户端地址,只读。
          r.variables{} nginx变量对象,部分可写。
          获取完整请求头可使用r.variables.http_cookie(只读)。
          设置限速可使用r.variables['limit_rate']。

          响应处理方法 描述
          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代码。

          子请求处理 描述
          r.parent 父请求对象,只读。
          r.responseBody 子请求响应体,只读。
          r.subrequest(uri[, options[, callback]]) 创建一个子请求,指定uri、options和callback。
          子请求共享父请求的请求头。options:如果是string,则表示子请求的参数;否则其应该是一个对象,有以下key:
          args:string,请求参数,默认为空。
          body:string,请求体,默认和父请求body一致。
          method:string,请求方法,默认为GET
          callback:子请求完成回调,形式为function done(res) {},res为子请求响应对象,其方法和属性与父请求相同。

          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。

          baidu_utils库

          function ipInCidr(ipv4, cidrs)

          参数:
              ipv4为点分十进制的ipv4地址,比如'192.168.2.100'
              cidrs为CIDR地址列表,比如['192.168.1.1/32','192.168.2.1/24']
           
          使用示例:
              if (baidu_utils.ipInCidr('192.168.2.100', ['192.168.1.1/32','192.168.2.1/24'])) {
                  r.return(403);
              }

          场景示例

          文件名改写

          使用请求参数filename,命名下载文件

          r.headersOut['Content-Disposition']='attachment;filename=' + '\"' + r.args['filename'] + '\"';

          跨域访问

          使用请求头Origin,赋值给跨域响应头Access-Control-Allow-Origin

          r.headersOut['Access-Control-Allow-Origin']=r.headersIn['Origin'];

          重定向

          r.return(302, '/a');

          访问控制

          IP黑白名单

          if (baidu_utils.ipInCidr(r.remoteAddress, ['192.168.1.1/32','192.168.2.1/24'])) { r.return(403); }

          Referer黑白名单

          var refers = ['http://*.baidu.com.cn/*','http://*.baidu.com/*'];
          var i = 0;
          for (; i < refers.length; i += 1) {
              if (baidu_utils.matchWildcard(r.headersIn['referer'], refers[i])) {
                  r.return(403);
                  return;
              }
          }

          UA黑白名单

          var uas = ['curl','AppleWebKit'];
          var i = 0;
          var ua = r.headersIn['User-Agent'];
          for (; i < uas.length; i += 1) {
              if (ua.includes(uas[i])) {
                  r.return(403);
                  return;
              }
          }

          限速

          r.variables['limit_rate'] = '1k';

          鉴权

          B类防盗链为例

          var part = r.uri.substr(1).split('/');
          if (part.length < 3) {
              r.return(403);
              return;
          }
           
          var date_in_uri = part[0];
          var d = new Date('');
          d.setFullYear(date_in_uri.substring(0, 4));
          d.setMonth(date_in_uri.substring(4, 6) - 1); //the month (0-11) in the specified date
          d.setDate(date_in_uri.substring(6, 8));
          d.setHours(date_in_uri.substring(8, 10));
          d.setMinutes(date_in_uri.substring(10));
          var time = d.getTime();
          var timeout = 1000000000000; // not overdue
          if (time + timeout < Date.now()) {
              r.return(403);
              return;
          }
           
          var md5_in_uri = part.splice(1,1)[0];
          var md5_str = 'bdcloud666' + part.join('/');
          var md5 = require('crypto').createHash('md5').update(md5_str).digest('hex');
          if (md5_in_uri != md5) {
              r.return(403);
              return;
          }

          子请求

          js子请求在回调done函数返回,对于在回调done函数之外定义的变量,其在done函数中也是可见的,比如变量test,r.subrequest之后的代码先于done函数执行,最终响应头x-test的值为OK。

          var test = 'test';
           
          function done(res) {
              for (var key in res.headersOut) {
                  r.headersOut[key] = res.headersOut[key];
              }
           
              r.headersOut['x-test'] = test;
          }
           
          r.subrequest('/foo', r.variables.args, done);
          r.subrequest('/bar', r.variables.args, done);
           
          test = 'OK';

          修改请求uri

          r.uri = '/test';

          修改请求args

          r.args['test1'] = 'test';
          if (r.args['test1'] == 'test') { /* use first one if duplicate */
              delete r.args['test2'];
              delete r.args['test'];
              delete r.args['test']; /* remove duplicate arg, it needs to delete twice */
          }
           
          r.args['gy'] = 'test';
          r.variables['args'] = 'test=test'; /* set entire args directly */

          修改请求header

          对于请求头来说,如果有多个相同的头,r.headersIn['name']只取第一个;

          如果想获取或者修改所有重复的头,有两种办法:

          • 如下面代码所示,先逐一获取和删除(不删除的话,获取的永远是第一个),拼接完成后,再重新设置给r.headersIn['name']
          • 使用r.variables['http_name'](只读的,不能修改),对于X-Forwarded-For来说,即r.variables['http_x_forwarded_for'](注意http_namename需要大写转小写,连接符-变为下划线_,比如X-Forwarded-For变为http_x_forwarded_for)。
          var xff = '';
          while (r.headersIn['X-Forwarded-For']) { /* remove duplicate header in loop */
              xff += r.headersIn['X-Forwarded-For'] + ', ';
              delete r.headersIn['X-Forwarded-For'];
          }
           
          r.headersIn['X-Forwarded-For'] = xff.substring(0, xff.length - 2);

          修改响应header

          通过r.respHeader(callback)设置回调来处理后端upstream传递回来的响应头,与请求头类似,需要delete多次,然后再set,代码示例如下:

          注意:r.return/r.send/r.sendHeader/r.finish/r.internalRedirect/r.subrequest/r.respHeader(本身)无法工作在callback里面。

          function done() {
              delete r.headersOut['dup'];
              delete r.headersOut['dup'];
              r.headersOut['dup'] = 'dup';
          }
          r.respHeader(done);

          A/B Test

          if (r.args['a']) {
              r.args['version'] = 'new';
          }
          else if (r.args['b']) {
              r.args['version'] = 'old';
          }

          自定义错误页面

          function done() {
              if (r.status == 404) {
                  r.status = 302;
                  r.headersOut['Location'] = 'www.baidu.com/test';
              }
          }
           
          r.respHeader(done);
          上一篇
          EdgeJS配置步骤
          下一篇
          统计分析