普通中间件式HTTP服务实现

前言

用过Express.jsKoa.js的人会发现使用方式很类似,也是基于中间件的理念去实现Web服务。

直接以Express.js回调式的中间件服务比较容易理解。再基于回调式的中间件服务接入Koa.js的中间件引擎去处理回调嵌套的处理。

这一章主要以原生的Node.js实现纯回调的中间件HTTP服务。

必要条件

  • 内置中间件队列
  • 中间件遍历机制
  • 异常处理机制

最简实现

  • demo源码

https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-01-06

  • 服务类封装
  1. const http = require('http');
  2. const Emitter = require('events');
  3. class WebServer extends Emitter {
  4. constructor() {
  5. super();
  6. this.middleware = [];
  7. this.context = Object.create({});
  8. }
  9. /**
  10. * 服务事件监听
  11. * @param {*} args
  12. */
  13. listen(...args) {
  14. const server = http.createServer(this.callback());
  15. return server.listen(...args);
  16. }
  17. /**
  18. * 注册使用中间件
  19. * @param {Function} fn
  20. */
  21. use(fn) {
  22. if (typeof fn === 'function') {
  23. this.middleware.push(fn);
  24. }
  25. }
  26. /**
  27. * 中间件总回调方法
  28. */
  29. callback() {
  30. let that = this;
  31. if (this.listeners('error').length === 0) {
  32. this.on('error', this.onerror);
  33. }
  34. const handleRequest = (req, res) => {
  35. let context = that.createContext(req, res);
  36. this.middleware.forEach((cb, idx) => {
  37. try {
  38. cb(context);
  39. } catch (err) {
  40. that.onerror(err);
  41. }
  42. if (idx + 1 >= this.middleware.length) {
  43. if (res && typeof res.end === 'function') {
  44. res.end();
  45. }
  46. }
  47. });
  48. };
  49. return handleRequest;
  50. }
  51. /**
  52. * 异常处理监听
  53. * @param {EndOfStreamError} err
  54. */
  55. onerror(err) {
  56. console.log(err);
  57. }
  58. /**
  59. * 创建通用上下文
  60. * @param {Object} req
  61. * @param {Object} res
  62. */
  63. createContext(req, res) {
  64. let context = Object.create(this.context);
  65. context.req = req;
  66. context.res = res;
  67. return context;
  68. }
  69. }
  70. module.exports = WebServer;
  • 服务使用
  1. const WebServer = require('./index');
  2. const app = new WebServer();
  3. const PORT = 3001;
  4. app.use(ctx => {
  5. ctx.res.write('<p>line 1</p>');
  6. });
  7. app.use(ctx => {
  8. ctx.res.write('<p>line 2</p>');
  9. });
  10. app.use(ctx => {
  11. ctx.res.write('<p>line 3</p>');
  12. });
  13. app.listen(PORT, () => {
  14. console.log(`the web server is starting at port ${PORT}`);
  15. });