【路飞】node服务器和koa

267 阅读3分钟

原生node服务器和进行了封装的koa

原生

node里面有很多个模块,有net,http,fs等等。我们使用的web服务器用的是 http 模块。

    const http = require('http')

http里面有一个Server的构造函数。也有一个等价于 new Server()createServer。最后通过 listen ,进入循环之中,监听请求。

还有状态码,request,serverResponse等,这里先不讨论。

创建一个服务器最简洁的代码就是:

    const http = require('http')
    
    // const server = new http.Server(cb?)
    // or
    const server = new http.createServer() // cb?
    
    // 可以这样给请求设置回调,也可以在上面创建server时作为参数传进去。
    server.on('request', (req, res) => {
        res.end('hello')
    })
    server.listen(3000, () => {
        console.log('连接成功')
    }) // 可以传3个参数 端口, ip?, 回调?

这样启动服务器,然后访问 localhost:3000 时,页面就会有一个 hello。

如果要处理不同的地址,可以根据 req.url 进行条件分支的判断。

    // localhost:3000/login
    req.url = '/login'
    
    // localhost:3000/home?a=1
    req.url = '/home?a=1'

服务器分为静态资源和动态资源。静态资源就是只要是请求这个地址,永远返回固定的东西。动态资源就是会根据一些条件返回不一样的东西。

平时前端打包后的代码部署到服务器,就是把文件放到启动服务器的电脑里的某个地方,然后请求的时候根据地址去返回文件。

所以简单来看基本上服务器是做两件事,第一分析地址,第二走对应的逻辑返回对应的内容。

    // url = localhost:3000/foo.html
    const fs = require('fs')
    server.on('request', (req, res) => {
        const content = fs.readFileSync(`/dist/foo.html`).toString()
        
        // 设置请求头的类型,用于帮助浏览器识别返回的东西是什么类型的文件
        // 详细在 mdn 搜 MIME 类型
        res.setHeader('Content-Type', 'text/html')
        res.send(content)
    })

逻辑分支可以使用 map 或者对象进行存储,简化条件分支, 这时路由的简单模型,koa-router 的使用方式和这个很像

    const route = {
        '/': (req, res) => {},
        '/login': (req, res) => {}
    }
    
    server.on('request', (req, res) => {
        route[req.url](req, res)
    })

koa

koa是一个轻量的框架,从使用的方法上看,和原生非常相似。

    const Koa = require('koa')
    
    const app = new Koa()
    
    app.use((ctx) => {
        ctx.body = 'hello'
    })
    
    app.listen(3000, () => {
        console.log('连接成功')
    })

koa用的是插件的设计。通过use来注册处理的逻辑。通过封装一些对象和简写放在 ctx 里面,简化了一些写法。但是依然可以在 ctx.req,ctx.res 里面拿到原生的对象。

其中listen是一个语法糖,等价于,这个官网有写。

    const server = http.createServer()
    
    server.listen(3000, () => {
        console.log('连接成功')
    })

可以看出koa确实是比较简洁的。然后中间件采用了洋葱模型。设置了多个中间件后,实行的顺序类似于 1 2 3 3 2 1。前提是调用了 next

把注册的中间件组合起来串成一个函数是用了一个 compose 的函数。类似于 函数柯里化的感觉。这个函数本身也比较简短,并不复杂。

koa 还将中间件包装成 Promise,能够更好的处理异步的函数。下面实现一个简单的compose。可以结合下面的使用方法一起看。

    // 外面的用法是
    // const fn = compose(middleware)
    // fn().then(handleResponse)
    // 然后开始执行中间件

    function compose(middleware = []) {
        if (middleware.length > 0) {
            return (ctx) => {
                // fn 返回这个
                return dispatch(0)
                function dispatch(i) {
                    const fn = middleware[i]
                    if (fn) {
                        // bind很重要,包装成一个函数传进去当 next 调用。
                        // 相当于流程控制。
                        return Promise.resolve(fn(ctx, dispatch.bind(null,i+1)))
                    } else {
                        // 保证统一性,最后一个中间件也会有一个 next
                        return Promise.resolve()
                    }
                }
            }
        } else {
            return Promise.resolve
        }
    }

如果觉得比较绕就多看几遍,或者打断点调试也行。

使用方法是

    // 结果 1,start  2,start  2,over  1,over
    app.use(async (ctx, next) => {
        console.log(1, 'start')
        // 因为封装成了promise, next是dispatch.bind(ctx, 1)
        // 即第二个中间件,然后会走完 next 才会继续走。
        // 实现了一层层进去再一层层出来的洋葱模型
        await next()
        console.log(1, 'over')
    })
    
    app.use(async (ctx, next) => {
        console.log(2, 'start')
        // next是dispatch.bind(ctx, 2) 最后一个
        // 即 Promise.resolve()
        await next()
        console.log(2, 'over')
    })