上文我们已经完成了一个基本的 HTTP 反向代理程序,也通过简单的方法验证了它是能正常工作的。但是,我们并没有足够的测试,比如只验证了 GET 请求,并没有验证 POST 请求或者其他的请求方法。而且通过手工去做更多的测试也比较麻烦,很容易遗漏。所以,接下来我们要给它加上自动化的单元测试。 在本文中我们选用在 Node.js 界应用广泛的 mocha 作为单元测试框架,搭配使用 supertest 来进行 HTTP 接口请求的测试。由于 supertest 已经自带了一些基本的断言方法,我们暂时不需要chai 或者 should 这样的第三方断言库。 首先执行 npm init 初始化一个 package.json 文件,并执行以下命令安装 mocha 和 supertest : npm install mocha supertest --save-dev 然后新建文件 test.js : const http = require("http"); const log = require("./log"); const reverseProxy = require("./proxy"); const { expect } = require("chai"); const request = require("supertest"); // 创建反向代理服务器 function startProxyServer() { return new Promise((resolve, reject) => { const server = http.createServer( reverseProxy({ servers: ["127.0.0.1:3001", "127.0.0.1:3002", "127.0.0.1:3003"] }) ); log("反向代理服务器已启动"); resolve(server); }); } // 创建演示服务器 function startExampleServer(port) { return new Promise((resolve, reject) => { const server = http.createServer(function(req, res) { const chunks = []; req.on("data", chunk => chunks.push(chunk)); req.on("end", () => { const buf = Buffer.concat(chunks); res.end(`${port}: ${req.method} ${req.url} ${buf.toString()}`.trim()); }); }); server.listen(port, () => { log("服务器已启动: %s", port); resolve(server); }); server.on("error", reject); }); } describe("测试反向代理", function() { let server; let exampleServers = []; // 测试开始前先启动服务器 before(async function() { exampleServers.push(await startExampleServer(3001)); exampleServers.push(await startExampleServer(3002)); exampleServers.push(await startExampleServer(3003)); server = await startProxyServer(); }); // 测试结束后关闭服务器 after(async function() { for (const server of exampleServers) { server.close(); } }); it("顺序循环返回目标地址", async function() { await request(server) .get("/hello") .expect(200) .expect(`3001: GET /hello`); await request(server) .get("/hello") .expect(200) .expect(`3002: GET /hello`); await request(server) .get("/hello") .expect(200) .expect(`3003: GET /hello`); await request(server) .get("/hello") .expect(200) .expect(`3001: GET /hello`); }); it("支持 POST 请求", async function() { await request(server) .post("/xyz") .send({ a: 123, b: 456 }) .expect(200) .expect(`3002: POST /xyz {"a":123,"b":456}`); }); }); 说明: 在单元测试开始前,需要通过 before() 来注册回调函数,以便在开始执行测试用例时先把服务器启动起来 同理,通过 after() 注册回调函数,以便在执行完所有测试用例后把服务器关闭以释放资源(否则 mocha 进程不会退出) 使用 supertest 发送请求时,代理服务器不需要监听端口,只需要将 server 实例作为调用参数即可 接着修改 package.json 文件的 scripts 部分: { "scripts": { "test": "mocha test.js" } } 执行以下命令开始测试: npm test 如果一切正常,我们应该会看到这样的输出结果,其中 passing 这样的提示表示我们的测试完全通过了: 测试反向代理 2017-12-12 18:28:15 服务器已启动: 3001 2017-12-12 18:28:15 服务器已启动: 3002 2017-12-12 18:28:15 服务器已启动: 3003 2017-12-12 18:28:15 反向代理服务器已启动 2017-12-12 18:28:15 [GET /hello => 127.0.0.1:3001] 代理请求 2017-12-12 18:28:15 [GET /hello => 127.0.0.1:3001] 响应: 200 2017-12-12 18:28:15 [GET /hello => 127.0.0.1:3002] 代理请求 2017-12-12 18:28:15 [GET /hello => 127.0.0.1:3002] 响应: 200 2017-12-12 18:28:15 [GET /hello => 127.0.0.1:3003] 代理请求 2017-12-12 18:28:15 [GET /hello => 127.0.0.1:3003] 响应: 200 2017-12-12 18:28:15 [GET /hello => 127.0.0.1:3001] 代理请求 2017-12-12 18:28:15 [GET /hello => 127.0.0.1:3001] 响应: 200 ✓ 顺序循环返回目标地址 2017-12-12 18:28:15 [POST /xyz => 127.0.0.1:3002] 代理请求 2017-12-12 18:28:15 [POST /xyz => 127.0.0.1:3002] 响应: 200 ✓ 支持 POST 请求 2 passing (45ms) 当然以上的测试代码还远远不够,剩下的就交给读者们来实现了。 接口改进 如果要设计成一个比较通用的反向代理中间件,我们还可以通过提供一个生成 http.ClientRequest 的函数来实现在代理时动态修改请求: reverseProxy({ servers: ["127.0.0.1:3001", "127.0.0.1:3002", "127.0.0.1:3003"], request: function(req, info) { // info 是默认生成的 request options 对象 // 我们可以动态增加请求头,比如当前请求时间戳 info.headers["X-Request-Timestamp"] = Date.now(); // 返回 http.ClientRequest 对象 return http.request(info); } }); 然后在原来的 http.request(info, (res2) => {}) 部分可以改为监听 response 事件: const req2 = http.request(options.request(info)); req2.on("response", res2 => {}); 同理,我们也可以通过提供一个函数来修改部分的响应内容: reverseProxy({ servers: ["127.0.0.1:3001", "127.0.0.1:3002", "127.0.0.1:3003"], response: function(res, info) { // info 是发送代理请求时所用的 request options 对象 // 我们可以动态设置一些响应头,比如实际代理的模板服务器地址 res.setHeader("X-Backend-Server", `${info.hostname}:${info.port}`); } }); 此处只发散一下思路,具体实现方法和代码就不再赘述了。 总结 (责任编辑:admin) |