实现一个简单的Koa

作者: 唔六 | 来源:发表于2019-03-16 10:31 被阅读5次

从http.createServer开始

先用最简单的方法来实现一个web服务器,命名为koa_01.js

let app = http.createServer( (req, res) => {
  let body = [];
  res.writeHead(200, {
    'content-type': 'text-html'
  });
  res.write('');
  res.end('First test')
});

module.exports = app;
if (!moudle.parent) app.listen(3000);

在浏览器中进行测试并不是一个好做法。我们可以使用 mocha + supertest来验证我们的服务器是否创建成功。

为了保持跟Koa的原始实现保持一致,包的版本如下:

  • "should": "^13.2.3"
  • "supertest": "^4.0.0"
  • "co":"1.5.1"

将测试文件命名为 first_koa.test.js

let app = require('./koa_01.js')
let request = require('supertest').agent;
require('should');

describe('第一个测试:简单服务器', () => {
    it('返回状态应为200', (done) => {
        let server = app.listen(8900)
        koa_request(server)
            .get('/')
            .expect(200)
            .end((err, res) => {
                done()
            })
    });
});

运行 mocha first_koa.test.js 得到:

测试结果

思考什么是HTTP Server

http协议

目前已经有很多服务器支持 HTTP/2,简单的来说,http协议主要经历了这样三个阶段:

  • 1.0 只允许一个时间内只能接受一个请求。(allowed one request to be outstanding aet a time)
  • 1.1 使用 pipeline的方式来处理请求,但是仍然会出现 排头堵塞(head-of-line blocking)。
  • 2 增加了长连接……

协议更加详细的介绍,建议大家可以看最新的标准。

HTTP/2 RFC

服务器应该具备的功能

无论web服务器现在如何发展,能够实现正确进行http协议交互的服务端,我们都可以称之为web服务器。

其核心要素三点:

  • 监听客户端请求
  • 处理客户端请求
  • 响应客户端请求

其它的诸如对性能、安全、日志等等方面的实现,甚至于对各类语言的支持,虽然也很重要,但并不是web服务器最核心的理念。

koa的监听、处理、响应

监听http请求,可以通过nodejs自带的 API进行实现。koa主要关注如何处理及响应请求。

实际上对请求的处理和响应可以放到一块。在 restful标准中,请求只是定义一个 名词描述 (url) 和 动词方法 (get,post,put,delete)。

  • 从响应的状态上来看:
常用的Http <wbr>Response <wbr>Code状态码一览表

……响应类型大全

  • 从响应的类型上看
    • String
    • Buffer
    • Stream
    • Object

所以,如果不考虑其它情况,我们实现一个对请求处理的函数大概如下:

function respond() {
    var res = this.res;
    var body = this.body;
    var head = 'HEAD' == this.method;
    var ignore = 204 == this.status || 304 == this.status;

    // 404
    if (null == body && 200 == this.status) {
      this.status = 404;
    }

    // body为空
    if (ignore) return res.end();

    // ignore情况
    if (null == body) {
      this.set('Content-Type', 'text/plain');
      body = http.STATUS_CODES[this.status];
    }
    
    // Buffer body
    if (Buffer.isBuffer(body)) {
      var ct = this.responseHeader['content-type'];
      if (!ct) this.set('Content-Type', 'application/octet-stream');
      this.set('Content-Length', body.length);
      if (head) return res.end();
      return res.end(body);
    }

    // string body
    if ('string' == typeof body) {
      var ct = this.responseHeader['content-type'];
      if (!ct) this.set('Content-Type', 'text/plain; charset=utf-8');
      this.set('Content-Length', Buffer.byteLength(body));
      if (head) return res.end();
      return res.end(body);
    }

    // Stream body
    if (body instanceof Stream) {
      body.on('error', this.error.bind(this));
      if (head) return res.end();
      return body.pipe(res);
    }
    
    // body: json
    body = JSON.stringify(body, null, this.app.jsonSpaces);
    this.set('Content-Length', body.length);
    this.set('Content-Type', 'application/json');
    if (head) return res.end();
    res.end(body);
  }
}

那么,将这个函数封装到第一步中的简单服务器请求中,应该是这样的:

koa_02.js

let app = http.createServer( (req, res) => {
  let body = 'test';
  let context = {req, res, body}
  respond.call(context)
});

module.exports = app;
if (!moudle.parent) app.listen(3001);

koa之中间件

上面第二个版本的实现。基本完成一个web服务的框架。那么有以下几个问题:

  • 响应的body应该如何设置
  • 如何更改响应头
  • 如何增加路由、日志等等功能

……

这些问题,实际上有很多的实现方法。koa主要采用 洋葱模型。更加详细的介绍可以参看 koa中间件

  • 中间件函数fn需要push到数组中。

    middlware.push(fn)

  • 调用的时候,需要将状态(request, response)保存到一个对象中.

    let ctx = Context(self, req, res)

  • 所有的函数都需要放在http服务的回调函数中

    let server = http.createServer(this.callback())

  • 请求需要经过所有的中间件。最后一定是respond函数,否则处理到最后,就无法完成响应的过程。

    [respond].concat(middleware)

具体实现

考虑到上述要求,定义一个对象Application。

Koa_03.js

function Application() {
    if (!(this instanceof Application)) return new Application;
    this.env = process.env.NODE_ENV || 'development';
    this.outputErrors = 'development' == this.env;
    this.middleware = [];
}
  • 对象应该能够实现http服务
app = Applicatin.prototype
app.listen = function () {
    let server = http.createServer(this.callback());
    return server.listen.apply(server, arguments);
}
  • 对象应该允许push中间件函数
app.use = function(fn) {
    // debug('use %s ', fn.name || 'unnamed');
    this.middleware.push(fn);
    return this;
}
  • 封装的callback可以按照规则执行中间件
app.callback = function () {
    // 首先push进respond函数
    let mw = [respond].concat(this.middleware);
    let fn = compose(mw)(downstream);
    let self = this;
    return function (req, res) {
        // let ctx = new Context(self, req, res);
        let ctx = new Context(self, req, res);

        function done (err) {
            // if (err) ctx.error(err);
            // console.log(err)
            if(err) throw new Error('sdf')
        }
        
        co.call(ctx, function *() {
            yield fn;
        }, done);
    }
};
  • 保证respond函数能够最后执行
function respond(next) {
    return function *() {
        yield next;
        st = this.status
        let res = this.res;
        let body = this.body;
        let head = 'HEAD' == this.method;
        let ignore = 204 == this.status || 304 == this.status;

        this.status = 200;
        if (null == body && 200 == this.status) {
            this.status = 404;
        }

        if (ignore) return res.end();
        if (null == body) {
            this.set('Content-Type', 'text/plain');
            body = http.STATUS_CODES[this.status];
        }

        if (Buffer.isBuffer(body)) {
            
        }
        res.write('')
        res.end('');

    }
}
  • Context应该保存所有的状态
function Context(app, req, res) {
    this.app = app;
    this.req = req;
    this.res = res;
}
……

测试

koa_03.test.js

let request = require('supertest').agent;
const koa = require('../koa_03.js');
const app = new koa()
const http = require('http');
require('should');


describe('koa 03正常启动web服务', () => {
    it('响应为200', (done) => {
        let server = app.listen(8900)
        request(server)
            .get('/')
            .expect(200)
            .end((err, res) => {
                done()
            })
    });
});


describe('koa可以执行一个中间件函数', () => {
    it('reponse with 200', (done) => {
        let calls = [];
        app.use(function(next) {
            return function * () {
                calls.push(1);
                yield next;
            }
        });
        let server = app.listen(8901)
        request(server)
            .get('/')
            .expect(200)
            .end((err, res) => {
                calls.should.eql([1])
                done()
            })
    });
})


describe('运行中间件函数流程正确', () => {
    it('执行流程应为 1,2,3,4,5,6,响应请求', (done) => {
        let app = new koa();
        let calls = [];
        app.use(function(next) {
            return function * () {
                calls.push(1);
                yield next;
                calls.push(6);
            }
        });

        app.use(function(next) {
            return function * () {
                calls.push(2);
                yield next;
                calls.push(5);
            }
        });

        app.use(function(next) {
            return function * () {
                calls.push(3);
                yield next;
                calls.push(4);
            }
        }); 

        server = app.listen(9000);
        request(server)
            .get('/')
            .end(function(err) {
                calls.should.eql([1,2,3,4,5,6])
                if (err) return done(err);
                done()
            });
    });
});

参考 & 引用

从零实现一个http服务器

相关文章

  • 《Koa诞生记》——如何写好README

    回顾前三章内容,讨论了koa的核心模块 koa-compose的实现,自己实现一个简单的koa,以及koa实现 s...

  • 实现一个简单的Koa

    从http.createServer开始 先用最简单的方法来实现一个web服务器,命名为koa_01.js 在浏览...

  • koa2 自学笔记(二)

    此系列文章会持续更新。。。 这篇主要讲 koa2 的路由 首先,我们先用 koa2 原生实现一个简单的路由然后再使...

  • 半小时掌握koa

    半小时掌握koa koa分析与实现从一个例子开始koa之流程app对象分析callback实现其他function...

  • Koa源码简单实现

    github地址https://github.com/sommouns/my_koa 然后附上一些常用的中间件的实...

  • 路由

    一、Koa 路由 1.安装koa-router cnpm install koa-router -S 2.一个简单...

  • 基于koa实现mvc

    之前使用了基于koa2的MVC结构的 egg 框架,大概理解是egg的使用规范。所以这里我们也简单的实现基于koa...

  • 详解如何使用koa实现socket.io官网的例子

    socket.io官网中使用express实现了一个最简单的IM即时聊天,今天我们使用koa来实现一下 框架准备 ...

  • koa2+koa-router+mongDB+mongoose学

    快速搭建一个简单的koa2开发环境 node版本需要7.6+采用koa2 + koa-router + mongo...

  • Koa 洋葱模型简单实现

    直接上代码 测试 运行,在浏览器中访问http://localhost:19191/[http://localho...

网友评论

    本文标题:实现一个简单的Koa

    本文链接:https://www.haomeiwen.com/subject/cmpjmqtx.html