[React] 06 - Route: koa makes your life easier
听说koa比express更傻瓜化,真的?
本身代码只有1000多行,所有功能都通过插件实现,很符合 Unix 哲学。
搭建简单服务器
- Koa, 架设一个简单的服务器
// demos/01.js const Koa = require('koa'); const app = new Koa(); app.listen(3000);
访问 http://127.0.0.1:3000,显示:
- Express呢?
From: nodejs搭建web服务器就是这么简单!
//引入http模块 var http = require("http");
//设置主机名 var hostName = '127.0.0.1'; //设置端口 var port = 8080; //创建服务 var server = http.createServer(function(req,res){ res.setHeader('Content-Type','text/plain'); res.end("hello nodejs"); // ----> (1) }); server.listen(port,hostName,function(){ console.log(`服务器运行在http://${hostName}:${port}`); });
However, koa省略的步骤,如果使用时,还是要补回来的。
返回的内容
// demos/02.js const Koa = require('koa'); const app = new Koa(); const main = ctx => { ctx.response.body = 'Hello World'; // ----> (1) }; app.use(main); app.listen(3000);
返回的类型
根据用户想要的类型,返回对应类型的内容。
// demos/03.js const main = ctx => { if (ctx.request.accepts('xml')) { ctx.response.type = 'xml'; // the type that client wants to get. ctx.response.body = '<data>Hello World</data>'; // we will respond what they would like.
} else if (ctx.request.accepts('json')) { ctx.response.type = 'json'; ctx.response.body = { data: 'Hello World' };
} else if (ctx.request.accepts('html')) { ctx.response.type = 'html'; ctx.response.body = '<p>Hello World</p>';
} else { ctx.response.type = 'text'; ctx.response.body = 'Hello World'; } };
网页模板
让 Koa 先读取模板文件,然后将这个模板返回给用户。
const fs = require('fs'); const Koa = require('koa'); const app = new Koa(); const main = ctx => { ctx.response.type = 'html'; ctx.response.body = fs.createReadStream('./demos/template.html'); // 先加载模板 }; app.use(main); app.listen(3000);
路由设置
- 原生路由
// demos/05.js const main = ctx => { if (ctx.request.path !== '/') { ctx.response.type = 'html'; ctx.response.body = '<a href="/">Index Page</a>'; } else { ctx.response.body = 'Hello World'; } };
- koa-route 模块
一个路径 ----> 函数调用
// demos/06.js const route = require('koa-route'); ---------------------------------------------------- const about = ctx => { ctx.response.type = 'html'; ctx.response.body = '<a href="/">Index Page</a>'; }; const main = ctx => { ctx.response.body = 'Hello World'; }; ---------------------------------------------------- app.use(route.get('/', main)); app.use(route.get('/about', about));
- 静态资源
指定某文件夹,通过路径直接获得文件夹内的静态文件。
const Koa = require('koa'); const app = new Koa(); const path = require('path'); const serve = require('koa-static'); const main = serve(path.join(__dirname)); app.use(main); app.listen(3000);
访问 http://127.0.0.1:3000/12.js,在浏览器里就可以看到这个脚本的内容。
- 重定向
重定向(redirect)访问请求。比如,用户登陆以后,将他重定向到登陆前的页面。
如下,访问 http://127.0.0.1:3000/redirect ,浏览器会将用户导向根路由。
const Koa = require('koa'); const route = require('koa-route'); const app = new Koa(); const redirect = ctx => { ctx.response.redirect('/'); // <---- (1) }; const main = ctx => { ctx.response.body = 'Hello World'; }; app.use(route.get('/', main)); app.use(route.get('/redirect', redirect)); // ----> (1) 如上 app.use(main); app.listen(3000);
中间件 - middleware
处在 HTTP Request 和 HTTP Response 中间,用来实现某种中间功能。
基本上,Koa 所有的功能都是通过中间件实现的。
// demos/08.js const logger = (ctx, next) => { console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`); next(); } app.use(logger);
每个中间件默认接受两个参数:
第一个参数,是 Context 对象;
第二个参数,是next
函数。(只要调用next
函数,就可以把执行权转交给下一个中间件)
next函数示例:访问 http://127.0.0.1:3000 ,命令行窗口会有如下输出。
const Koa = require('koa'); const app = new Koa(); const one = (ctx, next) => { console.log('>> one'); // step 1 next(); console.log('<< one'); // step 6 } const two = (ctx, next) => { console.log('>> two'); // step 2 next(); console.log('<< two'); // step 5 } const three = (ctx, next) => { console.log('>> three'); // step 3 next(); console.log('<< three'); // step 4 }
-------------------------------------------- app.use(one); // <----导入中间件 app.use(two); app.use(three);
异步中间件
const fs = require('fs.promised'); const Koa = require('koa'); const app = new Koa(); const main = async function (ctx, next) { ctx.response.type = 'html'; ctx.response.body = await fs.readFile('./demos/template.html', 'utf8'); }; app.use(main); app.listen(3000);
Goto: [JS] ECMAScript 6 - Async : compare with c#
中间件的合并
// demos/11.js const compose = require('koa-compose'); const logger = (ctx, next) => { console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`); next(); } const main = ctx => { ctx.response.body = 'Hello World'; }; const middlewares = compose([logger, main]); app.use(middlewares);
暂时用不到,什么时候才能发挥其优势,不清楚。
错误处理
- 两个错误编号
500 错误
// demos/14.js const main = ctx => { ctx.throw(500); };
访问 http://127.0.0.1:3000,你会看到一个500错误页"Internal Server Error"。
404错误
// demos/15.js const main = ctx => { ctx.response.status = 404; ctx.response.body = 'Page Not Found'; };
访问 http://127.0.0.1:3000 ,你就看到一个404页面"Page Not Found"。
- 处理错误的中间件
让最外层的中间件,负责所有中间件的错误处理。
const Koa = require('koa'); const app = new Koa(); const handler = async (ctx, next) => { try { await next(); } catch (err) { ctx.response.status = err.statusCode || err.status || 500; ctx.response.body = { message: err.message }; } }; const main = ctx => { ctx.throw(500); }; app.use(handler); // 先放后出 app.use(main); // 处于最外层 app.listen(3000);
- error 事件的监听
// demos/17.js const main = ctx => { ctx.throw(500); }; app.on('error', (err, ctx) => console.error('server error', err); // 触发监听事件 );
如果错误被try...catch
捕获,就不会触发error
事件
// demos/18.js` const handler = async (ctx, next) => { try { await next(); } catch (err) { // step 2, 截获错误 ctx.response.status = err.statusCode || err.status || 500; ctx.response.type = 'html'; ctx.response.body = '<p>Something wrong, please contact administrator.</p>'; ctx.app.emit('error', err, ctx); // step 3, 补发一个新错误信号 } }; const main = ctx => { ctx.throw(500); // step 1, 抛出错误 }; app.on('error', function(err) { // step 4, 监听到错误,执行“错误处理” console.log('logging error ', err.message); console.log(err); });
Web App 的功能
- Cookies
// demos/19.js const main = function(ctx) { const n = Number(ctx.cookies.get('view') || 0) + 1; ctx.cookies.set('view', n); ctx.response.body = n + ' views'; }
访问 http://127.0.0.1:3000 ,你会看到1 views
。刷新一次页面,就变成了2 views
。再刷新,每次都会计数增加1。
-
表单
表单就是 POST 方法发送到服务器的键值对。
const Koa = require('koa');
const koaBody = require('koa-body'); const app = new Koa(); const main = async function(ctx) { const body = ctx.request.body; if (!body.name) ctx.throw(400, '.name required'); ctx.body = { name: body.name }; }; app.use(koaBody()); app.use(main); app.listen(3000);
打开另一个命令行窗口,运行下面的命令。
$ curl -X POST --data "name=Jack" 127.0.0.1:3000 {"name":"Jack"} $ curl -X POST --data "name" 127.0.0.1:3000 name required
上面代码使用 POST 方法向服务器发送一个键值对,会被正确解析。如果发送的数据不正确,就会收到错误提示。
- 文件上传
const os = require('os'); const path = require('path'); const Koa = require('koa'); const fs = require('fs'); const koaBody = require('koa-body'); const app = new Koa(); const main = async function(ctx) { const tmpdir = os.tmpdir(); const filePaths = []; const files = ctx.request.body.files || {}; for (let key in files) { const file = files[key]; const filePath = path.join(tmpdir, file.name); const reader = fs.createReadStream(file.path); const writer = fs.createWriteStream(filePath); reader.pipe(writer); filePaths.push(filePath); } ctx.body = filePaths; }; app.use(koaBody({ multipart: true })); app.use(main); app.listen(3000);
打开另一个命令行窗口,运行下面的命令,上传一个文件。注意,/path/to/file
要更换为真实的文件路径。
$ curl --form upload=@/path/to/file http://127.0.0.1:3000 ["/tmp/file"]