「如何实现流动式软件发布」线上课堂开课啦,快来报名参与课堂抽奖吧~ 了解详情
写点什么

nginScript 系列:nginScript 简介

2017 年 4 月 12 日

这篇文章是 nginScript 系列文章的第一篇,介绍了 Nginx 公司为什么要开发自己的 JavaScript 实现,并提供了一个 nginScript 入门的示例。

nginScript 项目自 2015 年 9 月启动以来,一直处于实验性阶段,不过有很多特性和核心语言支持不断被添加进来。随着 NGINX Plus R12 的发布,我们很高兴地宣布,nginScript 现在正式成为 NGINX 和 NGINX Plus 的一个稳定模块。

nginScript 是为了 NGINX 和 NGINX Plus 而开发的 JavaScript 实现,它被设计用于在服务器端处理请求。它通过融入 JavaScript 代码对 NGINX 的配置语法进行扩展,以便实现复杂的配置。

nginScript 同时支持 HTTP 和 TCP/UDP 两种协议,所以它的应用场景很广。

  • 生成自定义的日志格式, 日志里可以包含普通 NGINX 变量无法表示的值。
  • 实现新的负载均衡算法。
  • 通过解析 TCP/UDP 协议,实现应用层的粘性会话。

除此之外,nginScript 还有很多其他特性,不过还有很多特性还没有实现。虽然我们发布了具有一般可用性的 nginScript 版本,并且可被用于生产环境,不过,我们仍然计划了一个演化线路图,包含了更多的应用场景。

  • 检查和修改 HTTP 请求消息和响应消息的 body(已经支持 TCP/UDP)。
  • 从 nginScript 代码里发起 HTTP 子请求。
  • 编写 HTTP 认证处理器(已经支持 TCP/UDP)。
  • 读写文件。

在更详细地讨论 nginScript 之前,我们先来澄清两个概念。

nginScript 不是 Lua

多年来,NGINX 社区创建了很多编程式扩展,Lua 是其中最受欢迎的一个。Lua 被作为 NGINX 的一个模块,也是通过 NGINX Plus 认证的第三方模块。Lua 模块和它的扩展插件与 NGINX 内核深度集成,提供了非常丰富的功能,其中就包括 Redis 的驱动程序。

Lua 是一个强大的脚本语言,不过,在市场采用率方面仍然不是很理想,而且对于一线开发人员和 DevOps 工程师来说,它也不在他们的“必备技能”之列。

nginScript 并不是要取代 Lua,nginScript 的目标是要通过使用一门流行的编程语言为广大的社区提供一种编程式的配置方案。

nginScript 不是 Node.js

nginScript 的目标并不在于要把 NGINX 或者 NGINX Plus 升格成为应用服务器。简单地说,nginScript 的应用场景有点类似于中间件,因为 nginScript 的代码运行在客户端和服务器内容之间。从技术角度来看,nginScript 和 NGINX(或 NGINX Plus)组合在一起之后有点类似 Node.js,不过它们与 Node.js 的相似之处也仅限于两点,即采用了基于事件驱动的架构以及使用JavaScript 作为编程语言。

Node.js 使用的是 Google 的 V8 JavaScript 引擎,而 nginScript 实现了 ECMAScript 标准,是为了 NGINX 和 NGINX Plus 而特别设计的。Node.js 在内存里运行稳定的 JavaScript 虚拟机,并通过垃圾回收来管理内存,而 nginScript 会为每个请求启动一个 JavaScript 虚拟机,并为其分配必要的内存,在请求处理完毕之后清理内存。

JavaScript 作为服务器端的编程语言

上面已经提到过,nginScript 是 JavaScript 的一个定制实现。其他大部分 JavaScript 运行时引擎都是为浏览器而设计的。客户端的代码执行属性在很多方面与服务器端的不一样,这些不同点表现在系统资源的可用性和可创建的并发运行时数量等方面。

我们之所以要实现自己的 JavaScript 运行时,是为了满足服务器端代码的执行需求,并与 NGINX 的请求处理架构保持兼容。nginScript 的设计遵循了如下原则。

  • 根据请求来创建和销毁运行时环境

    nginScript 使用单线程执行字节码,可以实现快速的初始化和销毁。系统为每个请求创建一个新的运行时环境,因为不需要初始化复杂的状态和辅助组件,所以启动速度非常快。在处理请求期间分配一个内存池,请求处理完毕之后销毁内存池。这种内存使用模式避免了对象的跟踪和释放工作,也不需要使用垃圾回收器。

  • 非阻塞的代码执行

    nginScript 的运行时环境是通过 NGINX 和 NGINX Plus 的事件驱动模型来执行的。当某个 nginScript 规则在执行阻塞操作时(比如读取网络数据或向外部发起子请求),NGINX 和 NGINX Plus 会将该虚拟机挂起,直到阻塞操作事件结束。也就是说,规则的编写会变得很简单,NGINX 和 NGINX Plus 会在内部把它们变成非阻塞操作。

  • 只实现必要的语言特性

    ECMAScript 定义了 JavaScript 的规范。nginScript 实现了 ECMAScript 5.1 ,以及 ECMAScript 6 中与数学运算相关的规范。实现自己的 JavaScript 运行时给了我们充分的自由,我们可以优先实现服务器端需要的特性,忽略掉我们不需要的部分。我们给出了一个列表,包括已经支持的语言特性和尚未支持的部分。

  • 与请求处理阶段的深度集成

    NGINX 和 NGINX Plus 把请求的处理分为不同的阶段。配置指令一般会在一个特定的阶段执行,本地 NGINX 模块经常利用这个特性检查和修改请求消息的内容。nginScript 通过配置指令把某些处理阶段暴露出来,从而能够控制 JavaScript 代码的执行。这种方式保证了本地 NGINX 模块的强大和灵活,同时保持 JavaScript 代码的简单性。

下面的表格列出了可以通过 nginScript 来访问的处理阶段,以及相应的配置指令。

nginScript 入门——一个真实的例子

我们可以将 nginScript 作为模块编译到 NGINX 里,也可以动态地将其加载到 NGINX 或 NGINX Plus 里。这篇文章的末尾将介绍如何在 NGINX 和 NGINX Plus 里启用 nginScript。

下面的例子使用 NGINX 或 NGINX Plus 作为简单的反向代理,并用 nginScript 构造具有特定格式的访问日志,每个日志里包含了如下几项内容。

  • 由客户端发送的请求消息头。
  • 由服务器端返回给客户端的响应消息头。
  • 使用键值对的格式,方便日志处理工具(ELK、Graylog、Splunk)处理这些日志。

NGINX 的配置非常简单:

复制代码
js_include conf.d/header_logging.js; # Load JavaScript code from here
js_set $access_log_with_headers kvAccessLog; # Fill variable from JS function
log_format kvpairs $access_log_with_headers; # Define special log format
server {
listen 80;
access_log /var/log/nginx/access_headers.log kvpairs;
location / {
proxy_pass http://www.example.com;
}
}

从上面可以看出,配置里并没有直接包含 nginScript 代码。我们使用 js_include 指令指定包含了 JavaScript 代码的文件。js_set 指令定义了一个 NGINX 变量 $access_log_with_headers,以及处理这个变量的 JavaScript 函数。log_format 指令定义了新的格式kvpairs,日志里的每一行将包含 $access_log_with_headers 的值。server 代码块定义了一个简单的 HTTP 反向代理,它将所有的请求转向到 ** http://www.example.com 。access_log 指令表示所有的请求消息将会以kvpairs** 的格式被记录下来。

现在来看一下格式化日志的 JavaScript 代码。我们有两个函数:

  • kvHeaders——这个函数把 headers 对象转成一系列键值对。函数必须在被调用前声明。
  • kvAccessLog——这个函数是在 js_set 指令里定义的,它接收两个参数,一个是客户端的请求(req),一个是服务器端的响应(res)。这类内建对象可以被传给任何一个 nginScript 函数。
复制代码
function kvHeaders(headers, parent) {
var kvpairs = "";
for (var h in headers) {
kvpairs += " " + parent + "." + h + "=";
if ( headers[h].indexOf(" ") == -1 ) {
kvpairs += headers[h];
} else {
kvpairs += "'" + headers[h] + "'";
}
}
return kvpairs;
}
function kvAccessLog(req, res) {
var log = req.variables.time_iso8601; // nginScript 可以访问所有变量
log += " client=" + req.remoteAddress; // request 对象的属性
log += " method=" + req.method; // "
log += " uri=" + req.uri; // "
log += " status=" + res.status; // response 对象的属性
log += kvHeaders(req.headers, "req"); // 把 request header 对象传给函数
log += kvHeaders(res.headers, "res"); // 把 response header 对象传给函数
return log;
}

kvAccessLog 函数的返回值被传给了 js_set 配置指令。NGINX 变量是按需进行计算的,也就是说,只有当变量需要被用到的时候,js_set 定义的函数才会被执行。在这个例子里,log_format 指令使用了变量 $access_log_with_headers,所以 kvAccessLog() 函数会在记录日志时执行。

map 和 rewrite 指令所使用的变量会在早期处理阶段触发执行相应的 JavaScript 代码。

我们可以向反向代理发送一个请求,并观察日志文件,以便验证这个方案。

复制代码
$ curl http://127.0.0.1/
$ tail --lines=1 /var/log/nginx/access_headers.log
2017-03-14T14:36:53+00:00 client=127.0.0.1 method=GET uri=/
status=200 req.Host=127.0.0.1 req.User-Agent=curl/7.47.0 req.Accept=
*/* res.Cache-Control=max-age=604800 res.Etag=\x22359670651+
ident\x22 res.Expires='
Tue, 21 Mar 2017 14:36:53 GMT'
res.Last-Modified='Fri, 09 Aug 2013 23:54:35 GMT'
res.Vary=Accept-Encoding res.X-Cache=HIT
{1}

大多数情况下,我们使用 nginScript 来访问 NGINX 的内部结构。上面的例子还利用了 request 和 response 对象的一些属性,TCP/UDP 的 Stream nginScript 模块则利用了 session 对象的一些属性。

查看英文原文: Introduction to nginScript


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017 年 4 月 12 日 17:113232
用户头像

发布了 321 篇内容, 共 113.4 次阅读, 收获喜欢 114 次。

关注

评论

发布
暂无评论
发现更多内容

全球台式机CPU市场二八分定型,英特尔仍旧占据全球最大份额

新闻科技资讯

实物资产卡片多数量拆分流程整理2021.01.06

Flychen

ml-agents项目实践(一)

行者AI

模型训练

性能测试思考分析

andy

同城快递系统设计文档

业哥

别无分号只此一家,Python3接入支付宝身份认证接口( alipay.user.certify)体系(2021年最新攻略)

刘悦的技术博客

Python 支付宝 身份认证 刷脸 实名认证

区块链:能源行业出现破坏性创新的基础?

CECBC区块链专委会

区块链 能源

如何使用ClickHouse实现时序数据管理和挖掘?

京东科技开发者

数据库 数据采集 数据库管理工具

使用Github Actions部署静态网站

玉龙BB

GitHub Pages Hugo Github Actions

获奖公布|构建基于容器技术,承载数据类有状态工作负载的数据云平台,技术挑战有哪些?

花斯基

话题讨论 活动专区

通过 Oracle 变量更新公司名称

Flychen

有奖话题 | 2021 新年Flag,牛转乾坤!

InfoQ写作平台官方

话题讨论 活动专区

量化机器人管理系统开发|市值管理机器人模式系统开发

W13902449729

市值管理机器人开发 市值管理系统开发

MySQL蜜罐获取攻击者微信ID

Java架构师迁哥

腾讯大佬直言:只要掌握了这份“Redis实战笔记”就掌握了云计算的未来!

比伯

Java 编程 架构 面试 程序人生

波场链智能合约DAPP系统开发技术

薇電13242772558

智能合约 dapp

干货速递,百度BML自动超参搜索技术原理揭秘与实战攻略!

百度大脑

视频混剪怎么准备素材?会声会影视频消音操作步骤详解!

奈奈的杂社

视频剪辑 视频后期 混剪 视频消音

更新完IOS14,网络工程师又开始背锅了

面向行业智能,华为数据通信推动的2020之变

脑极体

阿里云技术专家解读:2021 年六大容器技术发展趋势

阿里巴巴云原生

云计算 阿里云 容器 云原生 k8s

接口测试--获取动态参数进阶

测试人生路

接口测试

区块链推动电力能源管理新一轮技术变革

CECBC区块链专委会

区块链

阿里巴巴2020首发136道Java高级岗面试题(含答案)

Crud的程序员

Java 程序员 程序员面试

简述CAP原理

andy

全球台式机CPU市场份额AMD超越Intel?别再混淆视听,误导消费者了!

新闻科技资讯

OpenYurt 入门 - 在树莓派上玩转 OpenYurt

阿里巴巴云原生

阿里云 开源 容器 云原生 中间件

揭秘人民日报创作大脑:百度大脑智能创作平台助力打造“十八般武艺”

百度大脑

区块链技术如何真实有效的赋能智慧交通?

CECBC区块链专委会

区块链

1499飞天茅台脚本使用过程中遇到的Python问题汇总索引目录【淘宝-天猫超市、京东】

谙忆

飞天茅台 茅台

LeetCode题解:104. 二叉树的最大深度,BFS,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

nginScript系列:nginScript简介-InfoQ