前言
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折淡出。在实践中,蚂蚁的 Chair 与淘系的 Midway 给了我不少启发,也借鉴了不少 bad case。思考过身边团队、自己团队、国外团队的各种案例之后发现,搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项。
在接下来的一段时间里,我会以如何 “从零搭建 Node.js 企业级 Web 服务器” 为主题,将自己的所见所闻、所思所想详尽地记录下来,每个章节最后会附上实现本章内容的源码,希望可以帮助正在学习和了解 Node.js 的朋友对 Web 服务器领域获得更清晰的理解和洞见。
阅读提示:
- 本文着重表述 Web 后端技术相关内容,Web 前端内容采用 JavaScript Modules 进行演示。
- 本文需要读者具备基础的编程能力以及对计算机网络的基本了解,一些常用术语限于篇幅不再展开。
准备环境
安装 Node.js
Node.js 发布版本分为 Current 和 LTS 两类,前者每 6 个月迭代一个大版本,快速提供新增功能和问题修复,后者以 30 个月为一个大周期,为生产环境提供稳定依赖。当前 Node.js 最新 LTS 版本为 12.18.2,本文以此版本作为运行环境,可以在官网下载并安装。
安装完成后,在命令行输入 node --version
查看输出是否为 v12.8.2
,如果一致,那么安装成功。
另外,有兴趣的读者可以尝试通过 nvm / nvm-windows 管理多个 Node.js 版本,此处不是本文重点不再展开。
安装 Yarn
Node.js 提供了自己的包管理器 npm,npm 默认从海外官方的 registry 拉取包信息速度比较慢,需要执行以下命令设置使用国内镜像地址:
$ npm config set registry http://r.cnpmjs.org/
通过 npm 可以全局或本地安装依赖包,当本地安装时 npm 会根据 package.json
安装最新的依赖包,同时自动生成并更新 package-lock.json
文件,这就引发了一个问题:如果一个依赖包在 package.json
标记版本范围内发布了有问题的新版本,那么我们自己的项目也会跟着出问题。
为了解决这个问题,就引入了第三方包管理器 yarn,通过以下命令安装:
$ npm i -g yarn
相比 npm,yarn 会严格按照自动生成的 yarn.lock
本地安装依赖包,只有增删依赖或者 package.json
标记版本发生不可兼容的变化时才会更新 yarn.lock
,这就彻底杜绝了上述 npm 的问题。考虑到企业级 Web 服务器对稳定性的要求,yarn 是必要的。
安装 Docker
一般来讲,做一个 Web 服务器会有两种部署选择,要么是传统的包部署,要么是容器镜像部署,后者较前者在编排上更方便,结合 Kubernetes 可以做到很好的伸缩,是当前发展的趋势。Docker 作为主流容器技术必须熟练掌握,可以在官网下载并安装。
写一个静态资源服务器
初始化工程
准备好了环境就可以开始编码了,先新建工程根目录,然后进入并初始化 package.json
与目录结构:
$ mkdir 00-static # 新建工程根目录
$ cd 00-static # 进入工程根目录
$ yarn init -y # 初始化 package.json
yarn init v1.22.4
success Saved package.json
$ mkdir src # 新建 src 目录存放核心逻辑
$ mkdir public # 新建 public 目录存放静态资源
$ tree -L 1 # 展示当前目录内容结构
.
├── package.json
├── public
└── src
Express 还是 Koa?
Express 与 Koa 均是 Node.js 服务端基础框架。Express 发布于 2010 年,凭借出色的中间件机制在开源社区积累了大量的成熟模块,现在是 OpenJS 基金会 At-Large 级别项目。Koa 发布于 2013 年,相比 Express 具备了更加完善的中间件机制以及编程体验,但在开源社区模块积累的质与量上还有一定差距。在此比较几个常用模块:
模块名称 | 功能简介 | Express / Koa | Star | Contributers | Used by | 最近提交时间 |
---|---|---|---|---|---|---|
passport | 认证登录 | Express | 17.7k | 33 | 385k | 2020-06-10 |
koa-passport | 认证登录 | Koa | 737 | 21 | 4.7k | 2019-07-13 |
connect-redis | 会话存储 | Express | 2.3k | 51 | 26.3k | 2020-07-10 |
koa-redis | 会话存储 | Koa | 310 | 13 | 2.7k | 2020-01-16 |
helmet | 网络安全 | Express | 7.2k | 25 | 136.4k | 2020-07-11 |
koa-helmet | 网络安全 | Koa | 546 | 24 | 4.1k | 2020-06-03 |
上表整理自 Github 截止 2020 年 7 月 20 日的数据。
相比 Koa 模块,Express 模块普遍在星数(Star)、贡献者数(Contributers)、使用数(Used by)上高出一个层次,同时 Express 模块的贡献者更热心于维护与更新,Koa 尽管在国内受到过一些追捧,但在更全面的考量下 Express 才是更稳健的选择。在工程根目录执行以下命令安装:
$ yarn add express # 本地安装 Express
# ...
info Direct dependencies
└─ [email protected]
# ...
$ tree -L 1 # 展示当前目录内容结构
.
├── node_modules
├── package.json
├── public
├── src
└── yarn.lock
静态服务
现在可以开始写应用逻辑了,本章先做一个静态资源服务器,以 public
目录为静态资源目录:
// src/server.js
const express = require('express');
const { resolve } = require('path');
const { promisify } = require('util');
const server = express();
const port = parseInt(process.env.PORT || '9000');
const publicDir = resolve('public');
async function bootstrap() {
server.use(express.static(publicDir));
await promisify(server.listen.bind(server, port))();
console.log(`> Started on port ${port}`);
}
bootstrap();
It works!
$ tree -L 2 -I node_modules # 展示除了 node_modules 之外的目录内容结构
.
├── package.json
├── public
│ └── index.html
├── src
│ └── server.js
└── yarn.lock
逻辑写好之后在 package.json
中设置启动脚本:
{
"name": "00-static",
"version": "1.0.0",
- "main": "index.js",
+ "scripts": {
+ "start": "node src/server.js"
+ },
"license": "MIT",
"dependencies": {
"express": "^4.17.1"
}
}
然后就可以启动应用了:
$ yarn start
> Started on port 9000
访问 http://localhost:9000/ 即可看到 index.html
内容:
使用容器
接下来通过 Docker 对做好的静态资源服务器进行容器化,新建以下配置文件:
# Dockerfile
FROM node:12.18.2-slim
WORKDIR /usr/app/00-static
COPY . .
RUN yarn
EXPOSE 9000
CMD yarn start
# .dockerignore
node_modules
$ tree -L 1 -a # 展示包括 . 开头的全部目录内容结构
.
├── .dockerignore
├── Dockerfile
├── node_modules
├── package.json
├── public
├── src
└── yarn.lock
然后构建镜像并启动容器:
$ # 构建容器镜像,命名为 00-static,标签为 1.0.0
$ docker build -t 00-static:1.0.0 .
# ...
Successfully tagged 00-static:1.0.0
$ # 以镜像 00-static:1.0.0 运行容器,命名为 00-static
$ docker run -p 9090:9000 -d --name 00-static 00-static:1.0.0
$ docker logs 00-static # 查看 00-static 容器的日志
> Started on port 9000
$ docker stats 00-static # 查看 00-static 容器的状态
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
43c451232fa5 00-static 0.03% 37.41MiB / 1.945GiB 1.88% 8.52kB / 3.35kB 0B / 0B 24
访问 http://localhost:9090/ 即可与之前一样看到 index.html
内容:
本章源码
licg9999/nodejs-server-examples - 00-static
更多阅读
从零搭建 Node.js 企业级 Web 服务器(零):静态服务
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
从零搭建 Node.js 企业级 Web 服务器(二):校验
从零搭建 Node.js 企业级 Web 服务器(三):中间件
从零搭建 Node.js 企业级 Web 服务器(四):异常处理
从零搭建 Node.js 企业级 Web 服务器(五):数据库访问
从零搭建 Node.js 企业级 Web 服务器(六):会话
从零搭建 Node.js 企业级 Web 服务器(七):认证登录
从零搭建 Node.js 企业级 Web 服务器(八):网络安全
从零搭建 Node.js 企业级 Web 服务器(九):配置项
从零搭建 Node.js 企业级 Web 服务器(十):日志
从零搭建 Node.js 企业级 Web 服务器(十一):定时任务
从零搭建 Node.js 企业级 Web 服务器(十二):远程调用
从零搭建 Node.js 企业级 Web 服务器(十三):断点调试与性能分析
从零搭建 Node.js 企业级 Web 服务器(十四):自动化测试
从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望