起因
最近在看《第一行代码Android》,看到网络请求那章的时候需要服务器相应android应用发出的请求,书中使用的Apache服务器,但是呢,作为nodejs的粉丝,为什么不用nodejs呢,正好久闻KOA的大名,就准备拿来玩玩
本文使用的第三方模块版本
第一个应用!
首先,使用npm安装koa
npm install --save koa
const Koa = require('koa');
const app = new Koa();
const runningPort = 3000;
app.use(ctx => {
ctx.body = 'Hello world!';
});
app.listen(runningPort, function () {
console.log(`Server running on ${runningPort}`);
});
这里的ctx
封装了node中的request
和response
,也包含KOA自己的request
与response
对象,使用 ctx.req
和 ctx.res
调用前两者,使用 koa.request
和 koa.response
调用后两者
注意,不支持 ctx.res.statusCode
,ctx.res.writeHead()
,ctx.res.write()
,ctx.res.end()
更多详细的api见KOA官网
使用 koa-router 进行路由
注意观察的话可以看到,koa使用use方法来处理,但是并没有指定特定的路由,那么对于上面的第一个应用,不管域名后跟什么路径,浏览器上显示的都是 ‘hello world’
当然可以使用 ctx.request.url
来进行判断,但是还有是个选择,使用插件 koa-router
来管理路由
npm install --save koa koa-router
const Koa = require('koa');
const app = new Koa();
const router = new require('koa-router')();
const runningPort = 3000;
// 使用 koa-router,并添加处理404以及服务器内部错误的逻辑
app
.use(router.routes())
.use(router.allowedMethods())
.use(async (ctx, next) => {
try {
await next();
// 处理404
if (ctx.status === 404) {
ctx.body = "Hello 404 error!";
}
} catch (err) {
// 处理500
ctx.response.status = 500;
console.error(err);
}
});
// ctx.body 相当于是 ctx.response.body 的简写
router.get('/', ctx => {
ctx.body = 'Hello world!';
});
router.get('/index', ctx => {
ctx.body = 'Hello index!';
});
// 可以读取url中的数据
router.get('/name/:username', ctx => {
ctx.body = `Hello ${ctx.params.username}`;
});
app.listen(runningPort, function () {
console.log(`Server running on ${runningPort}`);
});
从这里可以发现,koa-router的功能还是比较强大的,当然,这也只是一部分,它可以处理各种http请求,更全的api使用指南请见alexmingoia/koa-router
使用koa-router 配合 koa-body 处理POST数据
服务器总归是要接受数据的,那么给如何处理呢,使用上面的 koa-router
是一个选择,配合着插件 koa-body
可以很好地处理表单提交的数据
npm install -save koa koa-body koa-router
const Koa = require('koa');
const app = new Koa();
const router = new require('koa-router')();
const koaBody = require('koa-body')();
const runningPort = 3000;
app
.use(router.routes())
.use(router.allowedMethods())
.use(async (ctx, next) => {
try {
await next();
if (ctx.status === 404) {
ctx.body = "Hello 404 error!";
}
} catch (err) {
ctx.response.status = 500;
console.error(err);
}
});
// 加入 koa-body 插件
router.post('/api/userinfo', koaBody, (ctx) => {
console.log("get post!");
// 显示POST提交数据
console.log(ctx.request.body);
// 返回给数据发送方
ctx.body = `data return from server: ${JSON.stringify(ctx.request.body)}`;
});
app.listen(runningPort, function () {
console.log(`Server running on ${runningPort}`);
});
由于是为Android的http学习准备的代码,这里就不用Android应用做测试了,所有就用bash中的 curl
来模拟POST请求
curl -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' http://localhost:3000/api/userinfo | more
可以看到服务端显示出提交的数据,并返回数据给数据发送方
是用KOA发送本地JSON/XML文件
数据是不会一开始就存在内存中的,我们需要从本地进行文件读取并发送,KOA可以很好地处理数据文件,需要使用到 async/await 来处理异步操作,使用 Promise 来包装异步操作
在应用文件所在目录中创建 data 文件夹,准备一份 data.json 与一份 data.xml 文件用于读取测试
// data/data.json
[
{
"id": "1",
"name": "aaa",
"version": "2.1"
},
{
"id": "2",
"name": "bbb",
"version": "1.7"
},
{
"id": "3",
"name": "ccc",
"version": "5.3"
}
]
<!-- data/data.xml -->
<apps>
<app>
<id>1</id>
<name>Google Maps</name>
<version>1.0</version>
</app>
<app>
<id>2</id>
<name>Chrome</name>
<version>2.1</version>
</app>
<app>
<id>3</id>
<name>Google Play</name>
<version>2.3</version>
</app>
</apps>
// 引入node核心模块
const fs = require('fs');
const path = require('path');
const Koa = require('koa');
const app = new Koa();
const router = new require('koa-router')();
const koaBody = require('koa-body')();
const runningPort = 3000;
app
.use(router.routes())
.use(router.allowedMethods())
.use(async (ctx, next) => {
try {
await next();
if (ctx.status === 404) {
ctx.body = "Hello 404 error!";
}
} catch (err) {
ctx.response.status = 500;
console.error(err);
}
});
// 根据变量 type 类型返回不同的文件
// KOA的一大特色是 async/await 的集成用于处理异步操作
router.get("/api/get_data/:type", async ctx => {
await new Promise((resolve, reject) => {
let filetype = "";
// 判断type的数值
switch (ctx.params.type) {
case "xml":
filetype = "xml";
break;
case "json":
filetype = "json";
break;
default:
ctx.status = 404;
ctx.body = 'No such file type!';
resolve();
}
// 获取指定文件类型
fs.readFile(path.join(__dirname, `data/data.${filetype}`), 'utf8', (err, data) => {
if (err) {
ctx.status = 500;
reject(err);
}
// 设置返回数据的类型,相当于 ctx.response.set()
ctx.set('Content-Type', `application/${filetype}`);
ctx.body = data;
resolve();
})
});
});
app.listen(runningPort, function () {
console.log(`Server running on ${runningPort}`);
});
使用KOA的进行大文件下载
在《第一行》中第十章的最佳实践为下载文件,那用KOA如何提供api给android进行下载呢?
可以用node中的stream
使用node核心模块 fs
的 createReadStream
读取文件,并将其赋给 ctx.response.body
,因为参考api文档,其值可以为一下几个中的一种:
- string written
- Buffer written
- Stream piped
- Object || Array json-stringified
- null no content response
准备一个视频文件(越大越好),命名为 test.mp4
,放置于 data
目录中
const fs = require('fs');
const path = require('path');
const Koa = require('koa');
const app = new Koa();
const router = new require('koa-router')();
const koaBody = require('koa-body')();
const runningPort = 3000;
app
.use(router.routes())
.use(router.allowedMethods())
.use(async (ctx, next) => {
try {
await next();
if (ctx.status === 404) {
ctx.body = "Hello 404 error!";
}
} catch (err) {
ctx.response.status = 500;
console.error(err);
}
});
// 设置下载文件的路径
router.get('/api/download/test.mp4', async ctx => {
await new Promise((resolve, reject) => {
// 设置文件的名字以及路径
const FILE_NAME = 'test.mp4';
const FILE_PATH = path.join(__dirname, `data/${FILE_NAME}`);
// 新建读取流
// highWaterMark 参数用于设置buffer的大小,默认为 64*1024 byte
// 详见 [这里](https://stackoverflow.com/questions/27641571/changing-readstream-chunksize)
const readStream = fs.createReadStream(FILE_PATH, {highWaterMark: 128 * 1024});
let size = 0;
// 进行赋值
ctx.response.body = readStream;
// 设置body的总长度,便于前端下载时显示总数量
ctx.response.length = fs.statSync(FILE_PATH).size;
// 强制浏览器下载,而不是播放
ctx.response.set('Content-disposition', `attachment; filename= ${FILE_NAME}`);
resolve();
// 每次有数据读入时显示进度
ctx.response.body.on('data', (chunk) => {
size += chunk.length / 1024.0 / 1024.0;
console.log(`${size} MB`)
});
// 读取结束后输出 'end'
ctx.response.body.on('end', () => {
console.log('end');
});
})
});
app.listen(runningPort, function () {
console.log(`Server running on ${runningPort}`);
});
由于在本地(localhost)下载速度极快,看不出进度,所有选择在远程服务器上进行部署,修改监听端口为80,进行演示
总结
其实写上述代码的目的是为了满足对《第一行代码Android》第九,十章代码测试的需要,顺便也学习了KOA框架,觉得这个框架很新颖,和Express的用法有很大的不同,有空再好好把玩把玩