Guide 开发指南

高性能 Web 应用程序实践

介绍

fibjs 是一个主要为 Web 后端开发而设计的高性能应用服务器框架。它建立在 Google v8 JavaScript 引擎基础上,并且选择了和传统的 callback 不同的并发解决方案。fibjs 利用 fibers 在框架层隔离了异步调用带来的业务复杂性,极大降低了开发难度,并减少因为用户空间频繁异步处理带来的性能问题。

fibjs 的设计非常注重性能,内置的网络 IO 和 HTTP 模块采用了基于事件驱动的非阻塞 I/O 模型,开发者可以很容易地实现高可靠性的服务器应用程序。而且由于底层是由 C++ 实现的,fibjs 的性能表现非常优越,可以轻松地应对高并发的访问,提供极为稳定和可靠的服务。

同时,fibjs 也支持 WebSocket,这是一种基于 TCP 协议的全双工通信协议,在浏览器和服务器之间建立起一个不断开的连接,可以实现实时双向数据传输,并且可以支持任意格式的数据传输。利用 WebSocket 可以轻松地实现通信效果更加优秀的实时通信应用。

总之,fibjs 既强调了高性能和高可靠性,又提供了 WebSocket 这样的实时通信特性,是一款非常适合开发高速 Web 应用程序的框架。

开发环境搭建

在开始进行 fibjs 的开发之前,我们需要先准备好开发环境。本章节将介绍如何安装 fibjs,如何使用 fibjs 工具初始化项目以及如何使用 IDE 集成开发环境。

安装 fibjs

针对不同的操作系统,安装 fibjs 的方式也略有不同。

对于 Linux 和 macOS 用户,可以使用以下命令来安装 fibjs:

1
curl -s https://fibjs.org/download/installer.sh | sh

如果使用 macOS 并且使用了 Homebrew 包管理器,也可以使用以下命令安装:

1
brew install fibjs

对于 Windows 用户,需要从 fibjs 官网 下载安装程序,然后按照指示进行安装。

使用 fibjs –init 创建新项目

在安装好 fibjs 后,可以使用 fibjs 工具来快速创建新项目。使用以下命令可以创建一个基础的项目模板:

1
fibjs --init

该命令将在当前目录创建一个新的项目结构,其中包括 package.json,用来存储项目的基本信息,以及依赖信息。

编写 Web 应用程序

Web 应用程序开发是目前 fibjs 最常用的一种应用场景,fibjs 提供了一系列工具和模块帮助我们更快速地构建 Web 应用程序。

编写 HTTP 服务器

  • 先导入 http 模块;
  • 实例化 http.Server,监听请求。
  • 服务器通过 start 函数启动。
1 2 3 4 5 6
const http = require('http'); const server = new http.Server(8080, (req) => { req.response.write('Hello World!'); }); server.start();

解析 URL 参数和请求体

解析 url 参数和请求体非常重要,应用于各种服务端应用程序。在 fibjs 中可以直接通过 req.query 解析传入的 url 参数,而请求体是通过 req.body 读取。

1 2 3 4 5 6 7 8
const http = require('http'); const server = new http.Server(8080, (req) => { var name = req.query.get('name'); var msg = name ? `Hello ${name}!` : 'Hello world!'; req.response.write(msg); }); server.start();

实现接口访问控制

通过接口限制用户访问是非常常见的一种场景。下面是一个简单的例子。

1 2 3 4 5 6 7 8 9 10
const http = require('http'); const server = new http.Server(8080, (req) => { if (req.headers.get('auth') === 'ok') { req.response.write('Hello World!'); } else { req.response.write('Access Denied!'); } }); server.start();

增加路由处理

路由是一个Web应用程序中最重要的概念之一,路由就是指将接收到的请求按照一定规则分发到处理器中去。在 fibjs,你可以自己编写路由模块,并绑定到 http server 中,然后通过自定义的路由解析进行URL匹配以及对应的处理。

1 2 3 4 5 6 7 8 9
const http = require('http'); const { Router } = require('mq'); var router = new Router(); router.get('/hello/:name', function (req, name) { req.response.write('hello, ' + name); }); var svr = new http.Server(8080, router); svr.start();

以上例子也可以用更简单的语法实现:

1 2 3 4 5 6 7 8
const http = require('http'); var svr = new http.Server(8080, { '/hello/:name': function (req, name) { req.response.write('hello, ' + name); } }); svr.start();

错误处理与日志记录

在 fibjs 中你可以通过 try-catch 块捕获逻辑异常,通过输出到日志文件进行调式记录;若为致命性异常,可以直接抛出给上层框架进行处理。

1 2 3 4 5 6 7 8 9 10
const console = require('console'); const http = require('http'); const server = new http.Server(8080, (req) => { try { // ... } catch (e) { console.log(e.message, e.stack); } });

跨域请求

在 fibjs 中,我们可以使用 enableCrossOrigin 这个方法来允许跨域请求。以下是如何通过创建 http 服务器并允许跨域请求的示例代码:

1 2 3 4 5 6 7 8
const http = require('http'); const server = new http.Server(8080, (req) => { req.response.write('Hello World!'); }); server.enableCrossOrigin(); // enable cross domain request server.start();

在上面的例子中,我们以 8080 端口创建了一个 http 服务器。enableCrossOrigin() 方法允许跨域请求。

在使用 enableCrossOrigin 允许跨域请求时,可以通过传递一个参数 allowHeaders 来指定允许接收的跨域 Header。默认情况下,allowHeaders 值为 Content-Type。

示例代码如下:

1 2
// enable "Content-Type" and "Authorization" headers in cross domain request server.enableCrossOrigin("Content-Type, Authorization");

在上述代码中,allowHeaders 的值为 "Content-Type, Authorization",表示服务器允许接收 "Content-Type" 和 "Authorization" 这两个跨域 Header。如果请求中包含其他 header,则会被服务器拒绝。

需要注意的是,当我们使用 enableCrossOrigin 设置允许接收跨域 Header 时,也需要在发送跨域请求时设置相应的 request header,否则同样会被服务器拒绝。

WebSocket

WebSocket 协议是一种基于 TCP 协议的全双工通信协议,在浏览器和服务器之间建立起一个不断开的连接,可以实现实时双向数据传输,并且可以支持任意格式的数据传输。在 fibjs 中,WebSocket 支持模块提供了相应的 API 接口,可以实现 WebSocket 服务器端和客户端的开发。

使用 fibjs 原生 WebSocket 模块实现 WebSocket 服务器端

在服务器端,可以通过 upgrade 函数将 HTTP 请求转换为 WebSocket 连接。在创建 http server 对象的时候,可以使用 ws.upgrade(callback) 并将其传入 http.start() 方法,实现将 http 请求转换为 WebSocket 的操作。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
var ws = require('ws'); var http = require('http'); var server = new http.Server(8080, { '/ws': ws.upgrade(function(conn, req) { console.log('a client connected.'); // listening for message events conn.onmessage = function(evt) { console.log('received message: ', evt.data); // echo the message back to client conn.send('Server: ' + evt.data); }; // listening for close events conn.onclose = function(code, reason) { console.log('closed.'); }; // listening for error events conn.onerror = function(err) { console.log(err); }; }) }); server.start();

在上述示例中,我们可以监听到客户端发来的消息事件及服务器与客户端连接关闭事件。当服务器接收到客户端消息后,会将相同的消息发送回客户端。此时实现了简单的 WebSocket 点对点通信。

实现与数据存储的交互

当使用 WebSocket 进行通信的时候,除了单纯消息的收发之外,还需要考虑数据的持久化存储和查询等操作。这时候就需要使用到数据库,可以使用 fibjs 内置的 db 模块与数据库进行交互。

示例代码如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
var ws = require("ws"); var http = require("http"); var db = require("db"); // open a mysql connection var mysql = db.openMySQL("mysql://root:password@localhost/dbname"); var server = new http.Server(8080, { "/ws": ws.upgrade(function(conn, req) { console.log("a client connected."); // listening for message events conn.onmessage = function(evt) { console.log("received message: ", evt.data); // use execute to query the data var rs = mysql.execute("SELECT * FROM user WHERE name=?", evt.data.toString()); conn.send(JSON.stringify(rs)); }; // listening for close events conn.onclose = function(code, reason) { console.log("closed."); }; // listening for error events conn.onerror = function(err) { console.log(err); }; }) }); server.start();

在上面的示例中,我们首先使用了 db 模块的 openMySQL 方法创建了一个 MySQL 数据库连接对象 mysql,然后在监听到来自客户端的消息后,使用 execute 方法直接执行 SQL 查询,获取满足条件的记录。最后将查询结果通过 WebSocket 协议发送回客户端。

需要注意,在实际的开发中,需要做好异常处理和数据的安全性。

综上所述,通过 db 模块,我们可以方便轻松地进行与数据库的交互,结合 WebSocket 协议,实现面向实时、高性能的 Web 应用程序。

实现 WebSocket 客户端与服务器端通信

在客户端,可以通过创建 WebSocket 实例并指定 URL 来连接 WebSocket 服务器,然后将消息发送给服务器。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
var ws = require('ws'); // create a WebSocket object and connect to ws://localhost:8080/ws var conn = new ws.Socket('ws://localhost:8080/ws'); // listening for open events conn.onopen = function() { conn.send('hello'); } // listening for message events conn.onmessage = function(evt) { console.log('received message:', evt.data); } // listening for close events conn.onclose = function(code, reason) { console.log('closed.'); } // listening for error events conn.onerror = function(err) { console.log(err); }

在上述客户端代码中,我们创建了一个 WebSocket 实例并指定其 URL,在连接建立成功后就可以向服务端发送消息了。当服务器接收到客户端消息后,会将相同的消息发送回客户端。此时实现了简单的 WebSocket 点对点通讯。

WebSocket 的优势和使用场景

WebSocket 协议具有典型的双向通信模型,允许服务器主动向客户端推送数据,常常被用于实现聊天、在线游戏等需要实时而即时性高的场合。相比其他传输协议,WebSocket 协议具有以下优势:

• 实时性高,支持双向通信 • 协议规范简单,使用方便 • 能够处理大量并发连接 • 支持长连接,减少网络传输的时间消耗

WebSocket 最常见的使用场景包括网页聊天、游戏对战、在线播放和即时通讯等。

综上所述,通过 WebSocket 支持模块,实现起来十分简单,开发者可以快速构建自己的 Web 应用程序。

单元测试

测试框架和测试方法

在软件开发过程中,测试是一个非常重要的环节,单元测试是其中的重要一环,通过单元测试可以有效地验证代码是否满足设计和需求,以及避免代码修改时引入的错误。一般地,单元测试的原则是对每个函数和方法进行测试,保证每个函数和方法的输入和输出都是正确的。

测试框架是用来编写、运行和验证测试用例的代码库,它提供了测试用例的管理、运行和报告等功能。在 JavaScript 和 Node.js 中,流行的单元测试框架有 Mocha、Jest 和 Jasmine 等。在 fibjs 中,我们也有自己的测试框架,即 test 模块。

在单元测试过程中,一般采用的测试方法包括黑盒测试和白盒测试两种方法。

黑盒测试是一种测试方法,它只考虑函数的输入和输出,而不考虑函数内部的实现细节。黑盒测试基于需求分析和设计规格书,通过测试用例分析和执行,判断程序是否出现逻辑错误、边界错误以及安全性问题等。它的优点是测试过程简单,测试结果可靠,缺点是测试无法覆盖所有的程序路径。

白盒测试是一种测试方法,它考虑函数的内部实现细节,包括条件语句、循环语句、递归以及代码的覆盖率等。通过这些测试,可以发现在共享数据和代码之间的交互中可能存在的问题。白盒测试的优点是可以覆盖所有的程序路径,缺点是测试过程较为复杂,测试结果受到环境和实现方法的影响。

使用 test 模块编写测试用例

在 fibjs 中,我们可以使用 test 模块编写 web server 的测试用例。下面是一个简单的例子:

1 2 3 4 5 6 7 8 9 10 11 12 13 14
var test = require('test'); test.setup(); var http = require('http'); describe('Web server test', () => { it('should return hello world', () => { var r = http.get('http://localhost:8080/hello'); assert.equal(r.statusCode, 200); assert.equal(r.data.toString(), 'Hello World'); }); }); test.run();

在这个例子中,我们使用 describe 和 it 函数分别定义测试模块和测试用例,并使用 assert 函数进行断言验证。

在 describe 函数中,我们可以定义多个 it 函数来分别测试不同的场景。在每个 it 函数中,我们可以使用 http.get 函数来模拟 HTTP GET 请求,获取请求响应并进行 assertTrue 和 assertEqual 等断言验证。

通过编写测试用例,可以有效地测试函数和模块的正确性,保证产品质量,同时也可加强代码的可维护性。

热更新

热更新指的是在不停止服务的情况下,实现服务端代码的更新。在程序开发过程中,为了快速迭代,经常需要进行代码调整,以及添加新功能。使用热更新可以在不停止服务的前提下,使用新的代码,更加高效的完成迭代工作。

在 fibjs 中,我们可以使用 SandBox 模块实现平滑的热更新。SandBox 模块可以提供一个安全的执行环境,模拟全局变量等功能。具体实现可以参考以下步骤:

  • 加载需要更新的代码文件(比如 web.js)。
  • 通过 SandBox,创建一个新的安全模块,在该模块中加载 web.js,生成安全模块。通过生成的安全模块重新挂载正在运行的服务的 handler。
  • 服务器继续处理之前的请求,新的请求将被挂载在新的 handler 上。

下面是一个使用 SandBox 模块实现平滑热更新的示例代码:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
const fs = require('fs'); const http = require('http'); const { SandBox } = require('vm'); let FILE_PATH = './web.js'; let handler = new SandBox().require(FILE_PATH).handler; const server = new http.Server(8080, handler); server.start(); fs.watch(FILE_PATH, (event, filename) => { handler = new SandBox().require(FILE_PATH).handler; server.handler = handler; console.log(`[${new Date().toLocaleString()}] server reloaded.`); });

这份代码中,我们在程序启动时首先加载一次 web.js 中的代码,随后创建 SandBox 实例,并在实例中加载代码。之后我们新建了一个 HTTP Server,使用 handler 中的方法来处理请求。

在代码中,我们使用 fs.watch 监听 web.js 文件的变化,一旦文件发生变化,我们重新加载代码,更新 handler 中的实现。

性能优化

在开发的过程中,我们经常需要面对性能问题。优化代码,提高性能,是开发者必备技能之一。在 fibjs 中,我们可以利用 CPU Profiler 来帮助我们分析程序的运行状态,优化代码。

在 fibjs 中,只需要使用命令行参数 --prof,启动 fibjs,即可以启动 CPU Profiler(默认间隔是 1000ms)。如果需要更高精度的分析日志,可以使用--prof-interval参数,设置日志间隔。例如:

1 2
$ fibjs --prof test.js # 启动 CPU Profiler,默认以 1000ms 为间隔 $ fibjs --prof --prof-interval=10ms test.js # 启动 CPU Profiler,以 10000us(即 10ms)为间隔

当 fibjs 运行完成之后,会在当前目录下生成一个源文件名目录,这个目录里包含了一个日志文件和一些辅助文件。日志文件的默认名字为 fibjs-xxxx.log,其中 xxxx 是一个时间戳。可以使用 --log 选项来指定日志文件名。此时,你可以使用 --prof-process 处理生成的日志:

1
fibjs --prof-process fibjs-xxxx.log prof.svg

运行结束,使用浏览器打开 prof.svg,即可查看此次日志的火焰图: prof 你可以点击查看全尺寸的图片,在全尺寸图片中,你可以通过鼠标操作,查阅更详细的信息:prof.svg

生成的火焰图中,每一个色块,代表一个记录点,色块越长,表示被记录的次数越多;每一行代表一层调用堆栈,层数越多表示调用的层数越多;调用堆栈的摆放,是倒置的,越靠下的色块,越是最初的函数。

色块的颜色有两类,一类是红色,一类是蓝色。在 fibjs 的 profiler 里,红色代表 JavaScript 运算,蓝色代表 io 操作或者 Native 运算。根据你需要解决的问题不同,所需要关注的区域也会不同。比如你需要解决 cpu 占用过高的问题,此时需要关注红色的色块;而如果你的应用,cpu 占用不高,但是响应却比较慢,就就需要关注蓝色的色块。靠近顶部越大的色块,越是需要关注和优化的重点。

我们可以尝试针对占用 CPU 资源较多的函数做调整,通过异步方式实现IO等,或者在编写时优化代码。

部署上线

为了使我们的项目能够运行在生产环境中,我们需要对其进行编译和部署。这里我们介绍如何使用 package.json 文件配置编译和部署。

在项目中,我们可以使用 package.json 来管理项目依赖、配置编译和部署。以一个简单的示例 package.json 为例:

1 2 3 4 5 6 7
{ "name": "my-project", "version": "1.0.0", "dependencies": { "fib-pool": "^1.0.0" } }

当我们需要编译并部署项目时,只需要在终端进入项目目录,然后执行以下命令:

1
fibjs --install

该命令会自动安装项目所依赖的模块。之后,我们可以使用以下命令启动项目:

1
fibjs app.js

👉 【域名路由