Koa 入门 -->搭建项目 --> 源码解析 --> 手写源码(一)

140 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天,点击查看活动详情

Koa 入门

什么是 Koa

  • koa是基于nodejs平台的下一代web开发框架
  • express原班人马打造,更精简
  • Async + await 处理异步
  • 洋葱圈型的中间件机制

创建一个最简单的 koa

初始化一个 npm 项目

npm init -y

安装 koa 依赖 npm install koa --save

根目录下 exampleApp.js

const Koa = require('koa')
const app = new Koa()

app.use(async (ctx, next) => {
    // ctx 是封装了 request 和 response 的上下文
    ctx.body = 'hello koa'
    // next 是下一个中间件
})

app.listen(3000)

根目录下运行 node exampleApp.js,浏览器输入 localhost:3000,输出 hello koa,服务已经启动成功。

const Koa = require('koa')
const app = new Koa()

// 135642
app.use(async (ctx, next) => {
    ctx.body = '1'
    next()
    ctx.body = ctx.body + '2'
})
app.use(async (ctx, next) => {
    ctx.body += '3'
    next()
    ctx.body +=  '4'
})
app.use(async (ctx, next) => {
    ctx.body += '5'
    next()
    ctx.body += '6'
})

// app.use(async (ctx) => {
//     ctx.body = 'hello koa'
// })

app.listen(3000)

浏览器输出 135642 在这里插入图片描述若将 第一个的 next() 注释掉,则只会输出 12

next() 的作用就是执行下面的中间件

解决异步的情况

const Koa = require('koa')
const app = new Koa()

app.use(async (ctx, next) => {
    ctx.body = '1'
    setTimeout(() => {
        next()
    }, 2000)
    ctx.body = ctx.body + '2'
})
app.use(async (ctx, next) => {
    ctx.body += '3'
    next()
    ctx.body +=  '4'
})
app.use(async (ctx, next) => {
    ctx.body += '5'
    next()
    ctx.body += '6'
})

// app.use(async (ctx) => {
//     ctx.body = 'hello koa'
// })

app.listen(3000)

此时浏览器就是输出 12

const Koa = require('koa')
const app = new Koa()

// 135642

function delay () {
    return new Promise((reslove, reject) => {
        setTimeout(() => {
            reslove()
        }, 2000)
    })
}

app.use(async (ctx, next) => {
    ctx.body = '1'
    await next()
    ctx.body += '2'
})
app.use(async (ctx, next) => {
    ctx.body += '3'
    await delay()
    await next()
    ctx.body +=  '4'
})
app.use(async (ctx, next) => {
    ctx.body += '5'
    await next()
    ctx.body += '6'
})

// app.use(async (ctx) => {
//     ctx.body = 'hello koa'
// })

app.listen(3000)

此时浏览器输出 135642

对于请求的处理和响应

koa-router(路由处理)npm install koa-router --save

const Koa = require('koa')
const Router = require('koa-router')

//  实例化
const app = new Koa()
const router = new Router()

router.get('/', async (ctx) => {
  ctx.body = 'home'
})

router.get('/news', async (ctx) => {
  ctx.body = 'news'
})

//启动路由
app.use(router.routes()).use(router.allowedMethods())
// 设置响应头

// 监听在3000端口
app.listen(3000)

Get传值

// 假设前端的请求路径  http://localhost:3000/newsContent?aid=123&name=zs
router.get('/newsContent', async (ctx) => {

  // 推荐方式
  // 获取路由传递的参数
  console.log(ctx.query)
  // { aid: '123', name: 'zs' }

  console.log(ctx.querystring)
  // aid=123&name=zs

  // console.log(ctx.request)
  /**
   * {
          method: 'GET',
          url: '/newsContent?aid=123&name=zs',
          header: {
          host: 'localhost:3000',
              connection: 'keep-alive',
              'cache-control': 'max-age=0',
              'upgrade-insecure-requests': '1',
              'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
          (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36',
              'sec-fetch-user': '?1',
              accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,im
          age/apng,*;q=0.8,application/signed-exchange;v=b3;q=0.9',
              'sec-fetch-site': 'none',
              'sec-fetch-mode': 'navigate',
              'accept-encoding': 'gzip, deflate, br',
              'accept-language': 'en,zh;q=0.9,zh-CN;q=0.8'
          }
      }
   */

  console.log(ctx.request.url)
  // /newsContent?aid=123&name=zs

  console.log(ctx.request.query)
  //[Object: null prototype] { aid: '123', name: 'zs' }

  console.log(ctx.request.querystring)
  //aid=123&name=zs

  ctx.body = '新闻详情'
})

动态匹配

// http://localhost:3000/page/112
router.get('/page/:id', async (ctx) => {
  console.log(ctx.params)
  // {id: 112}
  ctx.body = '新闻详情'
})
// http://localhost:3000/pages/112/qwe
router.get('/pages/:id/:bid', async (ctx) => {
  console.log(ctx.params)
  // { id: '112', bid: 'qwe' }
  ctx.body = '新闻详情'
})

koa 中间件应用

var Koa = require('koa')
var Router = require('koa-router')

var app = new Koa()
var router = new Router()

// 应用中间件
/**
 * 会匹配任何的路由,无论访问任何的路由,都会打印出 middleware
 */
// 无 next(),匹配终止
app.use(async (ctx) => {
    ctx.body = 'middleware'
})

// 有 next() 就会接着向下执行
app.use(async (ctx, next) => {
    
    console.log(new Date())
    // 2020-04-06T01:53:02.529Z
    //2020-04-06T01:53:10.619Z
    await next()
})

router.get('/', async (ctx) => {
    console.log('home 01')
    ctx.body = 'home'
})

router.get('/news', async (ctx) => {
    console.log('news')
    ctx.body = 'news'
})

router.get('/login', async (ctx) => {
    ctx.body = 'login'
})

// 应用中间件




// 路由中间件
router.get('/pages', async (ctx, next) => {
    console.log('这是一个页面1')

    // 没有 await next (), ctx.body 没有值 界面会显示 Not Found
    // 程序没有办法继续执行
    await next()

    // next() 会继续向下匹配,之后在界面打印出 page,控制台也会输出 这是一个页面1
})

router.get('/pages', async (ctx) => {
    ctx.body = 'page'
})


// 中间件的执行与放置的位置无关
router.get('/', async (ctx) => {
    console.log('home 01')
    ctx.body = 'home'
})

router.get('/news', async (ctx) => {
    console.log('news')
    ctx.body = 'news'
})

router.get('/login', async (ctx) => {
    ctx.body = 'login'
})

app.use(async (ctx, next) => {
    console.log('middleware 01')
    next()

    /**
     * middleware 01
        home 01
     */

     /**
      * 无论放置在哪里,都会先执行中间件,之后继续匹配路由
      */
})

// 中间件的执行与放置的位置无关





// 寻找 404 界面
router.get('/', async (ctx) => {
    console.log('home 01')
    ctx.body = 'home'
})

router.get('/news', async (ctx) => {
    console.log('news')
    ctx.body = 'news'
})

router.get('/login', async (ctx) => {
    ctx.body = 'login'
})

app.use(async (ctx, next) => {
    console.log('middleware 01')
    next()

    /**
     * middleware 01
        home 01
     */

     /**
      * 无论放置在哪里,都会先执行中间件,之后继续匹配路由
      */

    if(ctx.status === 404) {
        ctx.status = 404
        ctx.body = '404 page'
    } else {
        console.log(ctx.url)
    }
    /**
     * 访问 /news 路由
     * 控制台输出
     * middleware 01
        news
        /news

        1.先执行中间件 输出 middleware 01
        2.存在 next (), 继续向下匹配路由
        3.存在 /news 界面,控制台输出 news
        4.ctx.body 有值,控制台输出 /news

        访问 /*** 路由

        1.先执行中间件 输出 middleware 01
        2.存在 next (), 继续向下匹配路由
        3.匹配不到,ctx.body = 404
        4.返回 404 page
     */
})



// 中间件的执行顺序
app.use(async (ctx, next) => {
    console.log('middleware 01')

    await next()

    console.log('return middleware 01')
})

app.use(async (ctx, next) => {
    console.log('middleware 02')

    await next()

    console.log('return middleware 02')
})

router.get('/news', async (ctx) => {
    console.log('匹配到 news page')
    ctx.body = 'news'
})

/**
 *  middleware 01
    middleware 02
    匹配到 news page
    return middleware 02
    return middleware 01

    1.先执行中间件 输出 middleware 01
    2.遇到 next() ,继续执行
    3.碰到中间件 输出 middleware 02
    4.无中间件 匹配路由,匹配到 /news
    5.输出 匹配到 news page
    6. ctx.body 有值,页面输出 news
    7.返回最后执行的中间件 ,输出 return middleware 02
    8.接着返回 输出 return middleware 01
 */

// 中间件的执行顺序





//启动路由
app.use(router.routes()).use(router.allowedMethods())
// 设置响应头

// 监听在3000端口
app.listen(3000)

ejs 模板

npm install koa-views --save npm install ejs --save

var Koa = require('koa')
var Router = require('koa-router')
var views = require('koa-views')

var app = new Koa()
var router = new Router()

// 配置模板引擎
app.use(views(__dirname + '/'), {
    map: {
        html: 'ejs'
    }
})

router.get('/', async (ctx) => {
    await ctx.render('./views/index.ejs')
})

router.get('/news', async (ctx) => {
    ctx.body = 'news'
})

//启动路由
app.use(router.routes()).use(router.allowedMethods())
// 设置响应头

// 监听在3000端口
app.listen(3000)

post 请求

npm install koa-bodyparser --save
var Koa = require('koa')
var Router = require('koa-router')
var views = require('koa-views')
var bodyParser = require('koa-bodyparser')

var app = new Koa()
var router = new Router()

// 配置模板引擎
app.use(views(__dirname + '/'), {
    map: {
        html: 'ejs'
    }
})

app.use(bodyParser())

router.get('/', async (ctx) => {
    await ctx.render('./views/index.ejs')
})

router.post('/doAdd', async (ctx, next) => {
    // 获取表单提交数据
    ctx.body = ctx.request.body
    console.log(ctx.body)
    // { username: 'zasss', password: '123456' }
})

//启动路由
app.use(router.routes()).use(router.allowedMethods())
// 设置响应头

// 监听在3000端口
app.listen(3000)

静态资源

npm install koa-static --save

var Koa = require('koa')
var Router = require('koa-router')
var views = require('koa-views')
var bodyParser = require('koa-bodyparser')
// 引入
const static = require('koa-static')

var app = new Koa()
var router = new Router()

app.use(views(__dirname + '/'), {
    map: {
        html: 'ejs'
    }
})

app.use(bodyParser())

// 静态资源
app.use(static('static'))

// app.use(static('public'))
// 静态资源 可以配置多个

router.get('/', async (ctx) => {
    await ctx.render('./views/index.ejs')
})

router.post('/doAdd', async (ctx, next) => {
    ctx.body = ctx.request.body
    console.log(ctx.body)
})

//启动路由
app.use(router.routes()).use(router.allowedMethods())
// 设置响应头

// 监听在3000端口
app.listen(3000)

koa-cookie

/**
 * 1 保存用户信息
 * 2 浏览器历史记录
 * 3 猜你喜欢的功能
 * 4 10天免登陆
 * 5 多个页面之间的数据传递
 * 6 cookie 实现购物车功能
 */

var Koa = require('koa')
var Router = require('koa-router')
var views = require('koa-views')
var bodyParser = require('koa-bodyparser')
// 引入
const static = require('koa-static')

var app = new Koa()
var router = new Router()

app.use(views(__dirname + '/'), {
    map: {
        html: 'ejs'
    }
})

app.use(bodyParser())

// 静态资源
app.use(static('static'))

// app.use(static('public'))
// 静态资源 可以配置多个


// 设置 cookie
router.get('/', async (ctx) => {

    // 访问 / 路由时设置 cookies
    // maxAge: 设置过期时间
    // path 只有访问特定的路由才可以访问
    // domin 域名
    // httpOnly 只有服务器端才可以访问
    ctx.cookies.set('userInfo', 'zhangsan', {
        maxAge:  60 * 1000 * 60,
        // expires: '2020-04-07',
        // path: '/news',
        // domin: '',
        // httpOnly: true
    })
    await ctx.render('./views/index.ejs')
})

router.get('/news', async (ctx) => {

    // 访问 /news 路由的时候,可以取到相对应的 cookies
    console.log(ctx.cookies.get('userInfo'))
    await ctx.render('./views/index.ejs')
})

router.get('/login', async (ctx) => {

    // 访问 /login 路由的时候,可以取到相对应的 cookies
    console.log(ctx.cookies.get('userInfo'))
    ctx.body = ctx.cookies.get('userInfo')
    // await ctx.render('./views/index.ejs')
})
// 设置 cookie






// 设置中文 cookie
router.get('/', async (ctx) => {

    // value 设置为中文 argument value is invalid
    // 无法访问

    // koa 中 设置中文 Cookies

    var name = new Buffer('张三').toString('base64')

    ctx.cookies.set('userInfo', name, {
        maxAge:  60 * 1000 * 60,
    })
    await ctx.render('./views/index.ejs')
})

router.get('/news', async (ctx) => {

    // 访问 /news 路由的时候,可以取到相对应的 cookies

    var data = ctx.cookies.get('userInfo')

    var userinfo = new Buffer(data, 'base64').toString()

    console.log(userinfo)

    // console.log(ctx.cookies.get('userInfo'))
    await ctx.render('./views/index.ejs')
})
// 设置中文 cookie


//启动路由
app.use(router.routes()).use(router.allowedMethods())
// 设置响应头

// 监听在3000端口
app.listen(3000)

koa-session

/**
 * session 是一种记录客户状态的机制
 * 存储在服务端的
 */

var Koa = require('koa')
var Router = require('koa-router')
var views = require('koa-views')
var bodyParser = require('koa-bodyparser')
// 引入
const static = require('koa-static')
const session = require('koa-session')

var app = new Koa()
var router = new Router()

app.use(views(__dirname + '/'), {
    map: {
        html: 'ejs'
    }
})

app.use(bodyParser())

// 静态资源
app.use(static('static'))

// app.use(static('public'))
// 静态资源 可以配置多个

app.keys = ['some secret hurr']

const CONFIG = {
  key: 'koa:sess',
  maxAge: 9000, // 过期时间
  autoCommit: true,
  overwrite: true,
  httpOnly: true,
  signed: true,
  rolling: false,
  renew: true,
  sameSite: null,
}

app.use(session(CONFIG, app))

router.get('/', async (ctx) => {

    console.log(ctx.session.userInfo)

    await ctx.render('./views/index.ejs')
})

router.get('/login', async (ctx) => {

    ctx.session.userInfo = '张三'

    await ctx.render('./views/index.ejs')
})

router.get('/page', async (ctx, next) => {
    console.log(ctx.session.userInfo)
})

//启动路由
app.use(router.routes()).use(router.allowedMethods())
// 设置响应头

// 监听在3000端口
app.listen(3000)

项目地址:gitee.com/suiboyu/koa…