PipyJS - 函数式网络编程语言
这篇文章主要介绍 Pipy[1] 以及强大的 PipyJS - 没有垃圾回收开销、轻量级的函数式 JavaScript 解释引擎。
Pipy 概览
Pipy 是一个开源的[2]、轻量级、高性能、模块化、可编程的云原生网络流处理器,适用于边缘路由、负载均衡和代理解决方案、API 网关、静态 HTTP 服务器、服务网格边车、策略即代码等应用场景。
技术术语有点多,我们继续深入了解一下。
轻量级
Pipy 的可执行文件只有 10MB 左右,没有任何外部依赖,仅需很少的内存就可以运行。
高性能
Pipy 是使用 C++ 并基于异步 I/O 库 Asio 开发的。
模块化
Pipy 的核心使用了模块化设计,提供了大量可复用的小模块(过滤器)。这些过滤器可以组装成管道,网络数据流经这些管道并被处理。
流处理器
Pipy 将网络流数据字节抽象成事件,以事件驱动的方式来操作网络流。提供了事件驱动管道的抽象,管道使用输入流、执行用户定义的转换并输出流。
可编程
Pipy 对外屏蔽了底层的细节,提供了类似拼图的编程方式来实现业务目标。Pipy 是通过强大的 PipyJS 实现的,也是接下来我们要详细介绍的。
为什么使用 JavaScript
就像标题介绍的,PipyJS 是基于 JavaScript 的。Pipy 决定使用类似 JavaScript 脚本语言是有原因的:
•JavaScript 可能是世界上最广泛使用的语言[3]•它的语法有 C 和 Java 的影子,因此对大多数程序员不陌生•Pipy 作为流处理器,意味着有大量的数据转换和重组的工作。通过 JavaScript 提供的很多强大的语法糖可以轻松搞定,比如展开语法[4]、解构赋值[5]、以及数组的 map()[6]、reduce()[7] 等操作•JSON 作为网络上最广泛使用的数据格式,JavaScript 提供了原生支持可以轻松操作•最后也是最最重要的,JavaScript 包含了“函数式编程”范式
Pipy 就像是一些彼此相连的管道一样。每个管道由一系列的过滤器组成,每个过滤器就像接收输入并返回特定结果的函数。对于这种设计模式,假如你的目标是保持连贯和简单,函数式编程语言是最适合 Pipy 的。
PipyJS 介绍
Pipy 通过自研的 PipyJS 组件来解析 JavaScript,该组件是 Pipy 代码库的一部分,但不依赖它。
PipyJS 是一个小且可嵌入的 JavaScript 引擎,专为高性能而设计,没有垃圾回收开销。它支持 ECMAScript 标准的子集,并且在某些方面进行了大胆的扩展。Pipy 拥抱纯函数[8]式 JavaScript,在 PipyJS 中,万物皆表达式。
数据类型
与标准 JavaScript 一样, PipyJS 支持 4 种基本类型和结构数据的 对象 类型。
•基本类型•Undefined:变量未初始化时的唯一值 undefinde
。•布尔类型 Boolean:true
和 false
。•数字类型 Number:64 位双精度浮点数,如 123
、0xabc
、3.1415926
、1e6
。•字符串类型 String:Unicode 字符序列。•结构化数据类型•Null 对象:唯一值 null
。•用户定义的 POD(plain old data) ,如 { x: 1.2, y: 300 }
•内置对象:比如 Array、RegExp。•函数:比如 (a, b) => a * a + b * b
。
操作符
PipyJS 支持所有的标准 JavaScript 操作符,包括一些在 ES2020 才引入的,比如 可选链操作符[9] 和 空值合并运算符[10]。
全局变量
全局变量在 PipyJS 中也叫做 上下文变量。这里的上下文概念等同于高并发网络编程中的连接,每个连接都有彼此独立的状态。在 PipyJS 中,为方便起见这些隔离的状态保存在全局变量中,这也是为什么有时我们称其上下文变量。这也很好理解为什么 PipyJS 全局变量与标准 JavaScript 不同,简单说就是在不同的连接中值不相同。这点上,有点像线程本地存储[11]
全局变量通过内置的函数 pipy()
来定义,它通常是脚本中第一个调用的函数。
pipy({
_myGlobalVariable: undefined
})
为了方便起见,全局变量使用下划线作为变量名的首字符,尽管不是语言强制的。
全局变量的作用范围是单个文件或者模块,多个模块间可通过 export()
和 import()
来共享。
// file A
pipy().export('namespace-1', {
__myGlobalVariable: undefined
})
// file B
pipy().import({
__myGlobalVariable: 'namespace-1'
})
为了方便起见,导出的全局变量名使用双下划线开头,但不强制。
本地变量
在 PipyJS 中,我们使用嵌套在局部变量函数范围内的函数参数作为本地变量。
void ((
x, y, z, // declare local variables as function arguments
) => (
x = 0,
y = 0,
z = 0 // initialize and use the variables in the function body
))() // Don't miss the () to invoke the function right away!
假如想要上面的表达式执行后返回结果,则需要删除开头的
void
分支
在 PipyJS 中,万物皆表达式。不存在代码块或者流程控制,不能写 if 或者 for 语句。但这并不是说就不能写分支和循环了,而只是换了种形式:函数式。
简单分支可以使用逻辑运算符 &&
。
res.status === 200 && (_result = 'OK', console.log('Success.'))
// That's equivalent to:
if (res.status === 200) {
_result = 'OK';
console.log('Success.');
}
注意:上面是 PipyJS 分支语法,下面是标准 JavaScript 语法(下同)。
多选择分支可以将逻辑运算符 &&
和 ||
组合使用。
(res.status === 200) && (
_result = 'OK'
) ||
(res.status === 404) && (
_result = 'Not found'
) || (
_result = ''
)
// That's equivalent to:
if (res.status === 200) {
_result = 'OK';
} else if (res.status === 404) {
_result = 'Not found';
} else {
_result = '';
}
循环
可以使用 Array.forEach()[12] 来实现简单有界的循环。
new Array(100).fill(0).forEach(
(_, i) => (
console.log(i)
)
)
// That's equivalent to:
for (let i = 0; i < 100; i++) {
console.log(i);
}
或者,对于通用条件循环,可以使用内置的函数 repeat()
。
void ((
n, i
) => (
n = i = 1,
repeat(
() => (
n *= i,
i += 1,
i <= 10
)
)
))()
// That's equivalent to:
let n = 1, i = 1;
while (i <= 10) {
n *= i;
i += 1;
}
派生 ECMAScript
PipyJS 专为速度设计,其结构对于编写高性能网络流处理逻辑至关重要。下面重点介绍 PipyJS 与标准 ECMAScript 的不同或者未实现的:
•面向对象编程(OOP)结构 - 没有用户定义类型或者会构造器,没有原型系统•控制流•关键字•break
, case
, catch
, continue
, debugger
, default
, do
, else
, finally
, function
, for
, if
, return
, switch
, throw
, try
, while
, with
, yield
, class
, import
, export
, extends
, static
, super
•类型系统•BigInt and Symbol•Strings 内部存储为 UTF-8,在脚本中可以作为 UTF-32 来访问。比如 "😀".length
在标准 JavaScript 中是 2,但在 PipyJS 中是 1•变量 - 没有用来声明变量的关键词 var
或者 let
尽管 PipyJS 派生自标准 ECMAScript,但是通过函数式机制来填补空白。
如何使用
现在你应该对 PipyJS 有了一定的了解,接下来我们看下如何编写代码并运行。
首先下载[13]对应平台的二进制文件,将下面的代码保存为 ”hello.js“。
pipy()
.listen(8080)
.serveHTTP(
new Message('Hi, there!\n')
)
然后使用刚刚下载的二进制文件执行该脚本。
pipy hello.js
2022-02-23 14:14:33.315 [INF] [config]
2022-02-23 14:14:33.316 [INF] [config] Module /hello.js
2022-02-23 14:14:33.316 [INF] [config] ================
2022-02-23 14:14:33.316 [INF] [config]
2022-02-23 14:14:33.316 [INF] [config] [Listen on 8080 at 0.0.0.0]
2022-02-23 14:14:33.316 [INF] [config] ----->|
2022-02-23 14:14:33.316 [INF] [config] |
2022-02-23 14:14:33.316 [INF] [config] serveHTTP
2022-02-23 14:14:33.316 [INF] [config] |
2022-02-23 14:14:33.316 [INF] [config] <-----|
2022-02-23 14:14:33.316 [INF] [config]
2022-02-23 14:14:33.316 [INF] [listener] Listening on port 8080 at 0.0.0.0
这段脚本会监听 8080
端口,并返回信息 Hi, there!
。
curl http://localhost:8080
Hi, there!
总结
Pipy[14] 是有 Flomesh[15] 开源的、极其快速且轻量级的网络流量处理器,适用于多种场景。Pipy 正由全职的提交者和贡献者积极地开发和维护中,尽管还还是早期的版本,但已经过多个商业客户生产环境的实战测试。它的创建者和维护者 Flomesh.cn[16] 提供了以 Pipy 为核心的商业级解决方案。
这篇文章对 Pipy 进行了简短的概述和高阶的介绍。在 GitHub 页面[17]上可以找到教程和文档,也可以通过 Pipy 的管理控制台界面来访问。Pipy 社区非常欢迎贡献开发、尝试特定场景、或者提供简介和反馈。
引用链接
[1]
Pipy: https://github.com/flomesh-io/pipy[2]
开源的: https://github.com/flomesh-io/pipy[3]
世界上最广泛使用的语言: https://insights.stackoverflow.com/survey/2020#technology-programming-scripting-and-markup-languages-professional-developers[4]
展开语法: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax[5]
解构赋值: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment[6]
map(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map[7]
reduce(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce[8]
纯函数: https://en.wikipedia.org/wiki/Functional_programming[9]
可选链操作符: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Optional_chaining[10]
空值合并运算符: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator[11]
线程本地存储: https://en.wikipedia.org/wiki/Thread-local_storage[12]
Array.forEach(): https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach[13]
下载: https://github.com/flomesh-io/pipy/releases[14]
Pipy: https://github.com/flomesh-io/pipy[15]
Flomesh: https://flomesh.cn/[16]
Flomesh.cn: https://flomesh.cn/[17]
GitHub 页面: https://github.com/flomesh-io/pipy