Koa2基础入门教程

一、koa安装与hello world示例:

koa需要node v7.6.0以上(因为需要ES6) ```npm install koa –save``` 习惯性加上save,不加也可以。 koa的hello world示例

1
2
3
4
5
6
const Koa = require('koa');
const app = new Koa();
app.use(async(ctx)=>{
ctx.body = 'hello koa!';
})
app.listen(3000);

这个示例很简单,从node_modules中导入Koa类,然后实例化给app,通过app.use(中间件处理函数)来触发接受请求(每当有请求的时候就会按编写顺序和next堆栈方式触发这些所有的中间件函数),通过koa语法糖创建监听端口. ctx.body即是我们显示在网页上的内容。

二、next函数与中间件调用顺序

官网的表述:

Koa 的中间件通过一种更加传统(您也许会很熟悉)的方式进行级联,摒弃了以往 node 频繁的回调函数造成的复杂代码逻辑。 然而,使用异步函数,我们可以实现”真正” 的中间件。与之不同,当执行到 yield next 语句时,Koa 暂停了该中间件,继续执行下一个符合请求的中间件(‘downstrem’),然后控制权再逐级返回给上层中间件(‘upstream’)。 下面的例子在页面中返回 “Hello World”,然而当请求开始时,请求先经过 x-response-time 和 logging 中间件,并记录中间件执行起始时间。 然后将控制权交给 reponse 中间件。当一个中间件调用next()函数时,函数挂起并控件传递给定义的下一个中间件。在没有更多的中间件执行下游之后,堆栈将退出,并且每个中间件被恢复以执行其上游行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 中间件函数参数 第一个context上下文对象 第二个next函数,用于控制koa的执行顺序
// 接下来我们编写三个中间件使得koa能够反馈响应并打印到控制台上
// 打印请求方式和请求地址
const Koa = require('koa');
const app = new Koa();
// 大致意义就是堆栈类似的调用方式:x-response-time -> logger -> response ->logger ->x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
console.log("x-response-time was used at 1")
await next();
console.log("x-response-time was used at 2")
const ms = Date.now() - start;
// 这句话的意义是设置返回头 response headers
ctx.set('X-Response-Time', `${ms}ms`);
});

// logger
app.use(async (ctx, next) => {
const start = Date.now();
console.log("logger was used at 1")
await next();
console.log("logger was used at 2")
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});

// response

app.use(async ctx => {
console.log("response was used at 1")
ctx.body = 'Hello World';
});

app.listen(3000);

打印结果: x-response-time was used at 1 logger was used at 1 response was used at 1 logger was used at 2 GET / - 12 x-response-time was used at 2 大致意义就是堆栈类似的调用方式:x-response-time,next之前的内容 -> logger,next之前的内容 -> response,无next,全部调用 ->logger,next之后的内容 ->x-response-time,next之后的内容->结束

三、request对象和参数的获取

get请求: 我们可以通过 ```ctx.request```属性获取统一的request对象,获取其中url/query的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Koa = require("koa");
const app = new Koa();
app.use(async (ctx) => {
let url = ctx.url;

let request = ctx.request;
// query返回的是格式化好的参数对象,querystring则是请求字符串,
let req_query = request.query;
let req_query_string = request.querystring;
// 也可以直接通过ctx获取
let ctx_query = ctx.query;
let ctx_querystring = ctx.querystring;
ctx.body = {
url,
req_query,
req_query_string,
ctx_query,
ctx_querystring
};
console.log(url);
});

app.listen(3000);
console.log("server start at http://127.0.0.1:3000");

post请求: 1.使用第三方的koa-bodyparser中间件,在使用后从ctx.request.body中获取post的数据(对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const Koa = require("koa");
const app = new Koa();
const bodyparser = require("koa-bodyparser");
// koa-bodyparser需要先引用
app.use(bodyparser());

app.use(async (ctx) => {
if (ctx.url === "/" && ctx.method === "GET") {
// 显示表单界面
let html = `
<h1>AX Koa2 request post</h1>
<form action="/" method="POST">
<p>userName:</p>
<input type="text" name="userName"/><br />
<p>password:</p>
<input type="password" name="password"/><br />
<p>website:</p>
<input name="website" /><br />
<button type="submit">提交</button>
</form>
`;
ctx.body = html;
} else if (ctx.url === "/" && ctx.method === "POST") {
// 导入bodyparser后,直接从body中拿取post
let postData = ctx.request.body;
ctx.body = postData;
} else {
ctx.body = "<h1>404!</h1>";
}
});

app.listen(3000, () => {
console.log("server started at http:127.0.0.1:3000/");
});

  1. 手写解析(原理剖析,非重点) 注意这里的
1
2
3
4
ctx.req.addListener("data", (data) => {
postData += data;
});

1
2
3
4
ctx.req.on("end", function () {
let parseData = parseQueryStr(postData);
resolve(parseData);
});

这两种监听,都是通过ctx.req对象实现,ctx.req就是原生的nodejs对象里的http/https板块内容 ```js const Koa = require(“koa”); const app = new Koa(); app.use(async (ctx) => { if (ctx.url === “/“ && ctx.method === “GET”) { // 显示表单界面 let html = `

AX Koa2 request post

userName:
password:
website:
提交

`; ctx.body = html; } else if (ctx.url === “/“ && ctx.method === “POST”) { let postData = await parsePostData(ctx); ctx.body = postData; } else { ctx.body = “

404!

“; } });

function parsePostData(ctx) { return new Promise((resolve, reject) => { try { let postData = “”;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    // 原生node设置数据监听
//当node.js后台收到post请求时,
//会以buffer的形式将数据缓存起来。
//Koa2中通过ctx.req.addListener('data', ...)这个方法监听这个buffer。
ctx.req.addListener("data", (data) => {
postData += data;
});
// buffer流结束触发
ctx.req.on("end", function () {
let parseData = parseQueryStr(postData);
resolve(parseData);
});
} catch (error) {
reject(error);
}
});

} function parseQueryStr(queryStr) { let queryData = {}; let queryStrList = queryStr.split(“&”); console.log(queryStrList); console.log(queryStrList.entries()); for (let [index, queryStr] of queryStrList.entries()) { let itemList = queryStr.split(“=”); console.log(itemList); // 原生方法decodeURIComponent queryData[itemList[0]] = decodeURIComponent(itemList[1]); } return queryData; } app.listen(3000, () => { console.log(“server started at http:127.0.0.1:3000/“); }); ``` ## 四、路由的使用 首先我们可以通过原生手段,解析url从而来实现不同页面的分发: 准备三个html文件: 在这里插入图片描述 基本框架类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const Koa = require("koa");
const fs = require("fs");

const app = new Koa();
async function render(page) {
return new Promise((resolve, reject) => {
let pageUrl = `./page/${page}`;
// 使用fs,用utf-8指定读取编码
fs.readFile(pageUrl, "utf-8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}

async function route(url) {
let page = "404.html";
switch (url) {
case "/":
page = "index.html";
break;
case "index":
page = "index.html";
break;
case "/todo":
page = "todo.html";
break;
default:
break;
}
let html = await render(page);
return html;
}
app.use(async (ctx) => {
let url = ctx.request.url;
let html = await route(url);
ctx.body = html;
});

app.listen(3000);

显然这种方式比较缓慢,于是我们使用koa-router来实现路由的分发: 大致上分为以下步骤

  • 导入koa-router
  • 实例化koa-router
  • 通过实例化对象的get/post/put/delete等方法(指定请求方式)来创建路由,第一个参数为路由(字符串),第二个参数为触发的中间件async函数。
  • 挂载到实例化的app上,通过app.use
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const Koa = require('koa')
    const Router = require('koa-router')
    const app = new Koa();
    const router = new Router();

    router
    .get('/', (ctx, next) => {
    ctx.body = 'hello ax';
    })
    .get('/todo', (ctx, next) => {
    ctx.body = 'todo page';
    })
    // 确定路由请求
    app
    .use(router.routes())
    .use(router.allowedMethods());
    app.listen(3000, () => {
    console.log("starting at port 3000")
    })

通过koa-router实现多级路由:

  1. 添加层级:比如将/ax,/todo变为/home/ax,/home/todo 我们可以通过实例化router对象的时候,在参数中加入prefix
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    const Koa = require('koa')
    const Router = require('koa-router')
    const app = new Koa();
    // 程序层级
    const router = new Router(
    {
    // 前缀
    prefix: '/ax'
    }
    );

    router
    .get('/', (ctx, next) => {
    ctx.body = 'hello ax';
    })
    .get('/todo', (ctx, next) => {
    ctx.body = 'todo page';
    })
    // 确定路由请求
    app
    .use(router.routes())
    .use(router.allowedMethods());
    app.listen(3000, () => {
    console.log("starting at port 3000")
    })
  2. 多级路由和多前缀实现 创建父路由并将子路由挂载到其下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    const Koa = require('koa')
    const Router = require('koa-router')
    const app = new Koa();
    // 子路由
    let home = new Router();
    home.get('/ax', async (ctx) => {
    ctx.body = "home ax page";
    })
    .get('/todo', async (ctx) => {
    ctx.body = "home toDo page";
    })
    let page = new Router();
    page.get('/ax', async (ctx) => {
    ctx.body = "page ax page";
    })
    .get('/todo', async (ctx) => {
    ctx.body = "page toDo page";
    })
    // 父级路由
    let router = new Router();
    router.use('/home', home.routes(), home.allowedMethods());
    router.use('/page', page.routes(), page.allowedMethods());

    // 装载中间件
    app
    .use(router.routes())
    .use(router.allowedMethods());

    app.listen(3000)

五、静态资源开辟与允许加载

我们可以通过koa-static第三方中间件实现静态资源的加载 使用步骤:

  • 导入koa-static
  • 作为中间件调用导入的函数并且将其挂载到示例(以要开辟的空间的路径使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const Koa = require('koa')
    const path = require('path')
    // 导入koa-static
    const static = require('koa-static')
    const app = new Koa();
    const staticPath = './static';
    // 路径字符串作为初始化值
    app.use(static(path.join(__dirname, staticPath)))
    app.use(async (ctx) => {
    console.log(path.join(__dirname, staticPath))
    ctx.body = "hello world"
    })
    app.listen(3000)

到此koa的基本内容结束,关于cookies,ejs等其他前后不分离的开发形式请自行参考官方文档