Better

Ethan的博客,欢迎访问交流

egg深入篇

学习完egg入门篇后,掌握日常开发其实就没啥问题了,接下来学习下调试、部署、日志、异常处理和安全等内容。

调试

调试永远扮演者重要的角色,在 egg 中我觉得有两种适用的调试技巧

  • 使用 Chrome DevTools 进行调试
    1. 执行npm run debug
    2. 访问 chrome://inspect,配置对应的端口
    3. 选择Node应用,导入代码,设置断点即可
  • 使用 VSCode 调试
    1. 安装 vscode-eggjs 扩展
    2. 选择 debug 模式,选的 Egg Debug
    3. 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在我们需要对接其他服务时会很常用,具体使用查看文档即可



留言