从0到1 koa-node-vue全栈开发(第二章:了解 Koa2 的基本 api)

397 阅读3分钟

使用 koa 启动一个服务

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

app.listen(3000, () => {
    console.log(`http://127.0.0.1:3000`);
});

kao 的 ctx 对象, 里面的 req, res, request, response 的区别

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

// ctx 就是koa的上下文, 它里面封装了node的response和request对象
app.use((ctx, next) => {
    // 表示node的request对象
    const request = ctx.req;
    // 表示node的response对象
    const response = ctx.res;
    // koa 的 request对象
    const koaRequest = ctx.request;
    // koa 的 response对象
    const koaResponse = ctx.response;
    // 设置响应 Content-Type
    koaResponse.type = 'text/plain;charset=utf-8';
    // 将内容之返回给客户端
    koaResponse.body = 'hello world!';
});

app.listen(3000, () => {
    console.log(`http://127.0.0.1:3000`);
});

koa 的 ctx 对象里面常用的属性, ctx.request.url, ctx.request.query

请求接口
query

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

// ctx 就是koa的上下文, 它里面封装了node的response和request对象
app.use((ctx, next) => {
    // koa 的 request对象
    const koaRequest = ctx.request;
    // koa 的 response对象
    const koaResponse = ctx.response;
    // 设置响应头里的 Content-Type 类型
    koaResponse.type = 'application/json;charset=utf-8';
    // 将内容之返回给客户端
    koaResponse.body = {
        url: koaRequest.url, // 请求的url
        query: koaRequest.query //请求url所带的参数
    };
});

app.listen(3000, () => {
    console.log(`http://127.0.0.1:3000`);
});

使用 request 来处理路由

username
admin?username=123

通过下面的几个api, 我们就可以知道请求的方法, url的内容, 和所带的参数, 这样我们便可以通过不同的路由和方法, 来处理不同的请求

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

// ctx 就是koa的上下文, 它里面封装了node的response和request对象
app.use((ctx, next) => {
    const { request, response } = ctx;
    // 获取到请求的方法
    const method = request.method;
    // 获取到请求的url
    const url = request.url;
    // 获取到url上面的参数
    const query = request.query;

    if (method === 'GET') {
        if (url !== '/admin') {
            response.type = 'application/json';
            response.body = {
                method,
                url,
                query,
                message: '请访问/admin'
            };
        } else {
            response.type = 'text/plain';
            response.body = '欢迎登陆';
        }
    }
});

app.listen(3000, () => {
    console.log(`http://127.0.0.1:3000`);
});

koa 的中间件机制

在 koa 里, 有个很重要的概念, 就是中间件, 每个中间件都是一个函数, 从官方的说法来看, 就像是一个洋葱模型, 从最外面往里走, 然后在回到最外层, 图形如下:

middleware-img
换句话讲, 就是你想要执行你后面的函数, 那么必须让上一个函数同意, 如果上一个函数不同意的话, 那么下一个中间件将不会执行,而在被app.use(fun)后, fun的参数里面将会有两个值, 一个是ctx, 一个 是next, ctx是koa的上下文, next就可以理解为让下一个函数是否执行的开关, 下面我将用代码来演示一下koa的中间件.

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

// 第一个中间件
app.use(async (ctx, next) => {
    // 不解析 /favicon.ico 路由
    if (ctx.url === '/favicon.ico') return;
    console.log(`middleware one start => user:${ctx.user}`);
    // 因为next是异步函数, 所以我们需要使用await 来等待他执行完成
    await next();
    console.log(`middleware one end => user:${ctx.user}`);
});

// 第二个中间件
app.use(async (ctx, next) => {
    console.log(`middleware two start => user:${ctx.user}`);
    await next();
    console.log(`middleware tow end => user:${ctx.user}`);
});

// 第三个中间件
app.use(async (ctx, next) => {
    // 我们在第三个中间件上面给ctx对象添加一个user属性
    ctx.user = 'admin';
    console.log(`middleware three start => user:${ctx.user}`);
    await next();
    console.log(`middleware three end => user:${ctx.user}`);
})
    // 第四个中间件
    .use(async (ctx, next) => {
        console.log(`middleware four start => user:${ctx.user}`);
        await next();
        // 我们在第四个中间件去改变这个user属性的值
        ctx.user = '小芳';
        ctx.response.body = 'hello world';
        console.log(`middleware four end => user:${ctx.user}`);
    });

app.listen(3000, () => {
    console.log(`http://127.0.0.1:3000`);
});

下图为上面中间件的执行顺序

koa中间件的执行顺序

自己写一个中间件, 来记录请求的方法, 请求的 url, 以及请求的 host

上面我们已经看到了koa中间件的流程, 那么我们接下来就写一个中间件, 用来记录每次请求的方法, url, 以及host, 和请求的时间

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

app.use(async (ctx, next) => {
    // 不解析 /favicon.ico 路由
    if (ctx.url === '/favicon.ico') return;
    const startTime = Date.now();
    const {
        request: { method, url, host, path }
    } = ctx;
    await next();
    const endTime = Date.now();
    ctx.response.body = `method: ${method}; url: ${url}; host: ${host}; path: ${path}; 本次请求的响应时间为: ${endTime -
        startTime};`;
});
// 通过延迟请求时间, 来观察koa的中间件
app.use(async (ctx, next) => {
    await new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, 3000);
    });
    await next();
});
app.listen(3000, () => {
    console.log(`http://127.0.0.1:3000`);
});

request
response

使用 koa-bodyparser 来解决 koa 的 其他请求参数

由于在koa里面, 没有封装其他请求的参数获取方法, 所以我们先使用node原生的方法, 来获取post请求所带的参数, 如下:

const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
    const { request, response, req } = ctx;
    const { method, query } = request;
    if (method === 'GET') {
        response.body = `请求的方法为: ${method}; 请求的参数为: ${JSON.stringify(query)}`;
    } else if (method === 'POST') {
        let params = await new Promise(resolve => {
            let params = '';
            // post请求的参数不是一次性就发送的, 因为post请求可以发送大量的数据, 所以在node里面采用流的形式来传递,
            // 分多次传递, 然后我们将它拼接起来.
            req.on('data', chunk => {
                // chunk 是一个buffer对象, 通过 toString() 方法将他进行序列化
                params += chunk.toString();
            });
            // 当post的数据传递完成之后, end事件将会被调用一次, 这个时候, 我们就能拿到所有的post请求的参数了
            req.on('end', () => {
                resolve(params);
            });
        });
        response.body = `请求的方法为: ${method}; 请求的参数为: ${params}`;
    }
});

app.listen(3000, () => {
    console.log(`http://127.0.0.1:3000`);
});

从上面可以看出来, 使用node的原生方法来处理post请求, 会十分的复杂, 所以有高手为我们提供了一个koa-bodyparser的中间件, 用来处理get以外的其他请求方式, 下面我将使用koa-bodyparser中间件来写一个小例子:

koa-bodyparser
koa-bodyparser-login

const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const app = new Koa();

// 注册中间件
app.use(bodyparser());

app.use(async (ctx, next) => {
    const { request, response } = ctx;
    const { method, path, body } = request;
    if (method === 'GET') {
        if (path === '/') {
            response.type = 'text/html';
            response.body = `
            <h1>home页</h1>
            <form action="/login" method="post">
                用户名: <input type="text" name="username"><br>
                密码: <input type="password" name="password"><br>
                <button type="submit">提交</button>
            </form>
            `;
        }
    } else if (method === 'POST') {
        if (path === '/login') {
            // 通过koa-bodyparser中间件解析之后, 参数会被存放到request.body里面
            const { username, password } = body;
            response.type = 'text/plain';
            response.body = `你的用户名是: ${username}; 你的密码是: ${password}`;
        }
    }
});

app.listen(3000, () => {
    console.log(`http://127.0.0.1:3000`);
});

从上面可以看出, 使用koa-bodyparser来处理请求的参数, 会大大的减少我们代码量, 但是我们又发现, 我们在处理路由的时候, 也使用了很多的判断, 那么有没有一个中间件来帮我们解决路由的问题呢? 让我们一起接着往下看

使用 koa-router 来处理路由

const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const app = new Koa();
// 引入路由中间件
const Router = require('koa-router');
const router = new Router();
// 注册中间件
app.use(bodyparser());

router.get('/', async (ctx, next) => {
    const { response } = ctx;
    response.body = `
            <h1>home页</h1>
            <form action="/login" method="post">
                用户名: <input type="text" name="username"><br>
                密码: <input type="password" name="password"><br>
                <button type="submit">提交</button>
            </form>
        `;
});

router.post('/login', async (ctx, next) => {
    const { request, response } = ctx;
    const { body } = request;
    const { username, password } = body;
    response.body = `
    <h1>欢迎登录</h1>
    <div>你的账号是: ${username}</div>
    <div>你的密码是: ${password}</div>
    `;
});
app.use(router.routes());
app.use(router.allowedMethods()); // 路由的异常处理
app.listen(3000, () => {
    console.log(`http://127.0.0.1:3000`);
});

从上面的例子, 我们可以轻松的写出和我们没有使用koa-router的时候, 一样的效果! 另外, 在koa-router里面,他会给我们提供了丰富的api, 让我们去使用, 下面我们就来尝试一下其他的api. 在一个接口内写多个中间件,我们来写一个参数验证的中间件, 如果参数正确, 就返回正确的页面, 否则就重定向到登录页面,将上面的代码做一点小的修改即可, 如下:

router.get('/', async (ctx, next) => {
    const { response } = ctx;
    response.body = `
            <h1>请使用 admin 用户登录</h1>
            <form action="/login" method="post">
                用户名: <input type="text" name="username"><br>
                密码: <input type="password" name="password"><br>
                <button type="submit">提交</button>
            </form>
        `;
});

const rule = async (ctx, next) => {
    const { request } = ctx;
    // 我们用来验证, 用户名是不是 admin, 如果不是,
    // 我们就让他回到登录页, 重新登录
    const {
        body: { username }
    } = request;
    if (username === 'admin') {
        await next();
    } else {
        // 重定向到登录页
        ctx.redirect('/');
    }
};

router.post('/login', rule, async (ctx, next) => {
    const { request, response } = ctx;
    const { body } = request;
    const { username, password } = body;
    response.body = `
    <h1>欢迎登录</h1>
    <div>你的账号是: ${username}</div>
    <div>你的密码是: ${password}</div>
    `;
});

通过上面的两个小例子, 我们大致可以知道了koa-router的基本用法, 当然他还有其他的一些属性和方法, 大家可以通过官方文档, 也欢迎大家在评论区留言讨论,第二章的内容, 就到此结束了, 我们下一章见