处理post请求

时间:2021-04-28 00:45:29

处理post请求

用​​router.get('/path', async fn)​​处理的是get请求。如果要处理post请求,可以用​​router.post('/path', async fn)​​。

用post请求处理URL时,我们会遇到一个问题:post请求通常会发送一个表单,或者JSON,它作为request的body发送,但无论是Node.js提供的原始request对象,还是koa提供的request对象,都不提供解析request的body的功能!

所以,我们又需要引入另一个middleware来解析原始request请求,然后,把解析后的参数,绑定到​​ctx.request.body​​中。

​koa-bodyparser​​就是用来干这个活的。

我们在​​package.json​​中添加依赖项:

"koa-bodyparser": "3.2.0"

然后使用​​npm install​​安装。

下面,修改​​app.js​​,引入​​koa-bodyparser​​:

const bodyParser = require('koa-bodyparser');

在合适的位置加上:

app.use(bodyParser());

由于middleware的顺序很重要,这个​​koa-bodyparser​​必须在​​router​​之前被注册到​​app​​对象上。

现在我们就可以处理post请求了。写一个简单的登录表单:

router.get('/', async (ctx, next) => {
ctx.response.body = `<h1>Index</h1>
<form action="/signin" method="post">
<p>Name: <input name="name" value="koa"></p>
<p>Password: <input name="password" type="password"></p>
<p><input type="submit" value="Submit"></p>
</form>`;
});

router.post('/signin', async (ctx, next) => {
var
name = ctx.request.body.name || '',
password = ctx.request.body.password || '';
console.log(`signin with name: ${name}, password: ${password}`);
if (name === 'koa' && password === '12345') {
ctx.response.body = `<h1>Welcome, ${name}!</h1>`;
} else {
ctx.response.body = `<h1>Login failed!</h1>
<p><a href="/">Try again</a></p>`;
}
});

注意到我们用​​var name = ctx.request.body.name || ''​​拿到表单的​​name​​字段,如果该字段不存在,默认值设置为​​''​​。

类似的,put、delete、head请求也可以由router处理。

重构

现在,我们已经可以处理不同的URL了,但是看看​​app.js​​,总觉得还是有点不对劲。

处理post请求

所有的URL处理函数都放到​​app.js​​里显得很乱,而且,每加一个URL,就需要修改​​app.js​​。随着URL越来越多,​​app.js​​就会越来越长。

如果能把URL处理函数集中到某个js文件,或者某几个js文件中就好了,然后让​​app.js​​自动导入所有处理URL的函数。这样,代码一分离,逻辑就显得清楚了。最好是这样:

url2-koa/
|
+- .vscode/
| |
| +- launch.json <-- VSCode 配置文件
|
+- controllers/
| |
| +- login.js <-- 处理login相关URL
| |
| +- users.js <-- 处理用户管理相关URL
|
+- app.js <-- 使用koa的js
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包

于是我们把​​url-koa​​复制一份,重命名为​​url2-koa​​,准备重构这个项目。

我们先在​​controllers​​目录下编写​​index.js​​:

var fn_index = async (ctx, next) => {
ctx.response.body = `<h1>Index</h1>
<form action="/signin" method="post">
<p>Name: <input name="name" value="koa"></p>
<p>Password: <input name="password" type="password"></p>
<p><input type="submit" value="Submit"></p>
</form>`;
};

var fn_signin = async (ctx, next) => {
var
name = ctx.request.body.name || '',
password = ctx.request.body.password || '';
console.log(`signin with name: ${name}, password: ${password}`);
if (name === 'koa' && password === '12345') {
ctx.response.body = `<h1>Welcome, ${name}!</h1>`;
} else {
ctx.response.body = `<h1>Login failed!</h1>
<p><a href="/">Try again</a></p>`;
}
};

module.exports = {
'GET /': fn_index,
'POST /signin': fn_signin
};

这个​​index.js​​通过​​module.exports​​把两个URL处理函数暴露出来。

类似的,​​hello.js​​把一个URL处理函数暴露出来:

var fn_hello = async (ctx, next) => {
var name = ctx.params.name;
ctx.response.body = `<h1>Hello, ${name}!</h1>`;
};

module.exports = {
'GET /hello/:name': fn_hello
};

现在,我们修改​​app.js​​,让它自动扫描​​controllers​​目录,找到所有​​js​​文件,导入,然后注册每个URL:

// 先导入fs模块,然后用readdirSync列出文件
// 这里可以用sync是因为启动时只运行一次,不存在性能问题:
var files = fs.readdirSync(__dirname + '/controllers');

// 过滤出.js文件:
var js_files = files.filter((f)=>{
return f.endsWith('.js');
});

// 处理每个js文件:
for (var f of js_files) {
console.log(`process controller: ${f}...`);
// 导入js文件:
let mapping = require(__dirname + '/controllers/' + f);
for (var url in mapping) {
if (url.startsWith('GET ')) {
// 如果url类似"GET xxx":
var path = url.substring(4);
router.get(path, mapping[url]);
console.log(`register URL mapping: GET ${path}`);
} else if (url.startsWith('POST ')) {
// 如果url类似"POST xxx":
var path = url.substring(5);
router.post(path, mapping[url]);
console.log(`register URL mapping: POST ${path}`);
} else {
// 无效的URL:
console.log(`invalid URL: ${url}`);
}
}
}

如果上面的大段代码看起来还是有点费劲,那就把它拆成更小单元的函数:

function addMapping(router, mapping) {
for (var url in mapping) {
if (url.startsWith('GET ')) {
var path = url.substring(4);
router.get(path, mapping[url]);
console.log(`register URL mapping: GET ${path}`);
} else if (url.startsWith('POST ')) {
var path = url.substring(5);
router.post(path, mapping[url]);
console.log(`register URL mapping: POST ${path}`);
} else {
console.log(`invalid URL: ${url}`);
}
}
}

function addControllers(router) {
var files = fs.readdirSync(__dirname + '/controllers');
var js_files = files.filter((f) => {
return f.endsWith('.js');
});

for (var f of js_files) {
console.log(`process controller: ${f}...`);
let mapping = require(__dirname + '/controllers/' + f);
addMapping(router, mapping);
}
}

addControllers(router);

确保每个函数功能非常简单,一眼能看明白,是代码可维护的关键。

Controller Middleware

最后,我们把扫描​​controllers​​目录和创建​​router​​的代码从​​app.js​​中提取出来,作为一个简单的middleware使用,命名为​​controller.js​​:

const fs = require('fs');

function addMapping(router, mapping) {
...
}

function addControllers(router, dir) {
...
}

module.exports = function (dir) {
let
controllers_dir = dir || 'controllers', // 如果不传参数,扫描目录默认为'controllers'
router = require('koa-router')();
addControllers(router, controllers_dir);
return router.routes();
};

这样一来,我们在​​app.js​​的代码又简化了:

...

// 导入controller middleware:
const controller = require('./controller');

...

// 使用middleware:
app.use(controller());

...