学习完egg入门篇后,掌握日常开发其实就没啥问题了,接下来学习下调试、部署、日志、异常处理和安全等内容。
调试
调试永远扮演者重要的角色,在 egg 中我觉得有两种适用的调试技巧
- 使用 Chrome DevTools 进行调试
- 执行
npm run debug
- 访问 chrome://inspect,配置对应的端口
- 选择Node应用,导入代码,设置断点即可
- 执行
- 使用 VSCode 调试
- 安装 vscode-eggjs 扩展
- 选择 debug 模式,选的 Egg Debug
- F5开启调试
在实践 VSCode 调试,碰到报错问题,查阅资料后修改 launch.json 中 --inpect-brk 为 --inspect,依旧会有报错弹窗,但是应用启动成功且可以调试,这个模式就是不能启动时断点调试了
单元测试
egg 对单元测试做了很好的集成,对于单元测试可能碰到的问题都有进行考虑,具体文档很清楚了,说说单元测试的感受吧。
个人觉得单元测试从项目长期而言,是一个十分值得投资的东西,对于项目日常编码的约束,日后的重构迭代都起着至关重要的作用。
- 代码质量持续有保障
- 重构正确性保障
- 增强自信心
- 自动化运行
Web 应用中的单元测试更加重要,在 Web 产品快速迭代的时期,每个测试用例都给应用的稳定性提供了一层保障。 API 升级,测试用例可以很好地检查代码是否向下兼容。 对于各种可能的输入,一旦测试覆盖,都能明确它的输出。 代码改动后,可以通过测试结果判断代码的改动是否影响已确定的结果。
在 egg 中具体测试工具选型
- Mocha 测试框架
- power-assert 断言库
- egg-mock 模拟对象
应用部署
在线上环境不能使用egg-bin dev
启动服务,因为 egg-bin dev 会针对本地开发做很多处理,而生产运行需要一个更加简单稳定的方式。
一般从源码代码到真正运行,我们会拆分成构建和部署两步,可以做到一次构建多次部署。
一般安装依赖会指定 NODE_ENV=production 或 npm install --production 只安装 dependencies 的依赖。因为 devDependencies 中的模块过大而且在生产环境不会使用,安装后也可能遇到未知问题。构建完成后打包成 tgz 文件,部署的时候解压启动就可以了。
npm install --production
tar -zcvf ../release.tgz .
框架内置了 egg-cluster 来启动 Master 进程,Master 有足够的稳定性,不再需要使用 pm2 等进程守护模块。同时,框架也提供了 egg-scripts 来支持线上环境的运行和停止。
日志
日志对于一个 Web 应用的重要性就不多说了,egg 内置了强大的企业级日志支持
egg 同时还提供高级自定义日志功能,日志默认是打印到日志文件中,当本地开发时同时会打印到终端。 但是,有时候我们会有需求把日志打印到其他媒介上,这时候我们就需要自定义日志的 transport。
Transport 是一种传输通道,一个 logger 可包含多个传输通道。比如默认的 logger 就有 fileTransport 和 consoleTransport 两个通道, 分别负责打印到文件和终端。
如果我们需要一个错误日志上报功能,就可以自定义 transport。
日志切割:如果日志不进行很好的分类和切割,时间一长,我们就很难从日志中快速找到我们需要的信息了。egg 提供了各种策略进行日志切割
- 按天切割(默认)
- 按照文件大小切割
- 按照小时切割
通常 Web 访问是高频访问,每次打印日志都写磁盘会造成频繁磁盘 IO,为了提高性能,我们采用的文件日志写入策略是:
日志同步写入内存,异步每隔一段时间(默认 1 秒)刷盘
多进程
Cluster 模块早所有了解,并应用在自己的博客上,但是由于自身是屌丝单核服务器,所以也就是玩玩了。具体作用
- 在服务器同时启动多个进程
- 这些进程可以同时监听一个端口
具体工作
- 负责启动其他进程的叫做 Master 进程,他好比是个『包工头』,不做具体的工作,只负责启动其他进程。
- 其他被启动的叫 Worker 进程,顾名思义就是干活的『工人』。它们接收请求,对外提供服务。
- Worker 进程的数量一般根据服务器的 CPU 核数来定,这样就可以完美利用多核资源。
Agent机制
Agent 专门处理一些公共事务
Master VS Agent VS Worker
- Master 进程承担了进程管理的工作,不运行任何业务代码,我们只需要运行起一个 Master 进程它就会帮我们搞定所有的 Worker、Agent 进程的初始化以及重启等工作了。
- Agent 只有一个,而且会负责许多维持连接的脏活累活,因此它不能轻易挂掉和重启,所以 Agent 进程在监听到未捕获异常时不会退出,但是会打印出错误日志,我们需要对日志中的未捕获异常提高警惕。
- Worker 进程负责处理真正的用户请求和定时任务的处理。而 Egg 的定时任务也提供了只让一个 Worker 进程运行的能力,所以能够通过定时任务解决的问题就不要放到 Agent 上执行。
- Worker 运行的是业务代码,相对会比 Agent 和 Master 进程上运行的代码复杂度更高,稳定性也低一点,当 Worker 进程异常退出时,Master 进程会重启一个 Worker 进程。
IPC 进程间通信,高阶使用会用到吧...
异常处理
得益于框架支持的异步编程模型,错误完全可以用 try catch 来捕获。在编写应用代码时,所有地方都可以直接用 try catch 来捕获异常。
但有一种特殊情况就是如果我们自行创建异步函数,比如setImmediate,会导致跳出了异步调用链,异常就捕获不到了。
框架也考虑到了这类场景,提供了 ctx.runInBackground(scope) 辅助方法,通过它又包装了一个异步链,所有在这个 scope 里面的错误都会统一捕获。
为了保证异常可追踪,必须保证所有抛出的异常都是 Error 类型,因为只有 Error 类型才会带上堆栈信息,定位到问题。
egg 对异常处理进行了同意处理,自动根据请求想要获取的类型返回不同类型的错误。但同时支持各种配置项给用户自定义
安全
egg 为常见的安全问题都做了考虑,为了方式开发者不注意安全问题,默认安全设置是打开的,且提供了很多常见方法供开发者使用。
XSS
具体函数有
- helper.escape()
- helper.sjs()
- helper.sjson()
- helper.shtml()
由于是一个非常复杂的安全处理过程,对服务器处理性能一定影响,如果不是输出 HTML,请勿使用。
了解CSP,从根本上防御XSS
CSRF
CSRF常见的防御方案
- Synchronizer Tokens
- Double Cookie Defense
XST
XST 的全称是 Cross-Site Tracing,客户端发 TRACE 请求至服务器,如果服务器按照标准实现了 TRACE 响应,则在 response body 里会返回此次请求的完整头信息。通过这种方式,客户端可以获取某些敏感的头字段,例如 httpOnly 的 Cookie。
框架已经禁止了 trace,track,options 三种危险类型请求。
钓鱼攻击
钓鱼有多种形式, 比如 url 钓鱼、图片钓鱼和 iframe 钓鱼。
url 钓鱼
服务端未对传入的跳转 url 变量进行检查和控制,可能导致可恶意构造任意一个恶意地址,诱导用户跳转到恶意网站。
同时可能引发的 XSS 漏洞(主要是跳转常常使用 302 跳转,即设置 HTTP 响应头,Locatioin: url,如果 url 包含了 CRLF,则可能隔断了 HTTP 响应头,使得后面部分落到了 HTTP body,从而导致 XSS 漏洞)。
图片钓鱼
如果可以允许用户向网页里插入未经验证的外链图片,这有可能出现钓鱼风险。
防范方式:框架提供了 .surl() 宏做 url 过滤。
iframe 钓鱼
iframe 钓鱼,通过内嵌 iframe 到被攻击的网页中,攻击者可以引导用户去点击 iframe 指向的危险网站,甚至遮盖,影响网站的正常功能,劫持用户的点击操作。
框架提供了 X-Frame-Options 这个安全头来防止 iframe 钓鱼。默认值为 SAMEORIGIN,只允许同域把本页面当作 iframe 嵌入。
HPP
Http Parameter Pollution(HPP),即 HTTP 参数污染攻击。在HTTP协议中是允许同样名称的参数出现多次,而由于应用的实现不规范,攻击者通过传播参数的时候传输 key 相同而 value 不同的参数,从而达到绕过某些防护的后果。
HPP 可能导致的安全威胁有:
- 绕过防护和参数校验。
- 产生逻辑漏洞和报错,影响应用代码执行。
中间人攻击与 HTTP / HTTPS
框架提供了 hsts Strict-Transport-Security 这个头的默认开启。让 HTTPS 站点不跳转到 HTTP,如果站点支持 HTTPS,请一定要开启。
SSRF
通过 Server-Side Request Forgery(SSRF) 攻击,攻击者可以发起网络请求访问或者操作内部网络的资源。
一般来说,SSRF 安全漏洞常见于开发者在服务端直接请求客户端传递进来的 URL 资源,一旦攻击者传入一些内部的 URL 即可发起 SSRF 攻击。
通常我们会基于内网 IP 黑名单的形式来防范 SSRF 攻击,通过对解析域名后得到的 IP 做过滤,禁止访问内部 IP 地址来达到防范 SSRF 攻击的目的。
框架在 ctx, app 和 agent 上都提供了 safeCurl 方法,在发起网络请求的同时会对指定的内网 IP 地址过滤,除此之外,该方法和框架提供的 curl 方法一致。
国际化
作为一个好大上的应用,怎么可以不支持国际化呢?具体返回什么语言,优先级如下
- query: /?locale=en-US
- cookie: locale=zh-TW
- header: Accept-Language: zh-CN,zh;q=0.5
具体使用看文档吧,在记录的话,废话越来越多了
其他
Cookie & Session 部分可以说是 Web 开发的基础知识了
HttpClient在我们需要对接其他服务时会很常用,具体使用查看文档即可