资讯专栏INFORMATION COLUMN

Koa2 入门教程

blankyao / 1192人阅读

摘要:简单入门惯例拿创建应用程序作为一个框架的入门例子。级联应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。参考资料框架教程

完整Demo地址

里面demo都是自己写的,保证能跑,至於环境问题我就不敢保证了。懒得写就去上面搬走看,懒得搬就直接看文章,大部分代码连输出信息都给你们了。
koa-demo

官网介绍

koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。 使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套, 并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件, 它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。

前期准备

我们首先安装一些必要库先,根据个人选择可以使用yarn,cnpm,或者npm都行。

KOA框架

yarn add koa

这还不够,因为 Koa 依赖 node v7.6.0 或 ES2015及更高版本和 async 方法支持.你们可以根据自身需要安装

Babel register

transform-async-to-generator 或 transform-async-to-module-method

因为我用到 Nodejs10.0,所以不需要安装这些东西,就不说了。

简单入门

惯例拿创建应用程序作为一个框架的入门例子。

const Koa = require("koa"),
  app = new Koa();

app
  .use(async ctx => {
    ctx.body = "暗号:Hello World";
  })
  .listen(3000);

console.log("已建立连接,效果请看http://127.0.0.1:3000/");

代码一目了然,不废话了。
(完整代码可以执行koa-demo的 lesson1 查看效果)

Favicon.ico

所谓 favicon,即 Favorites Icon 的缩写,顾名思义,便是其可以让浏览器的收藏夹中除显示相应的标题外,还以图标的方式区别不同的网站。当然,这不是Favicon的全部,根据浏览器的不同,Favicon显示也有所区别:在大多数主流浏览器如FireFox和Internet Explorer (5.5及以上版本)中,favicon不仅在收藏夹中显示,还会同时出现在地址栏上,这时用户可以拖曳favicon到桌面以建立到网站的快捷方式;除此之外,标签式浏览器甚至还有不少扩展的功能,如FireFox甚至支持动画格式的favicon等。
问题在於这裡代码浏览器会自动发起请求网站根目录的这个图标,干扰测试,所以接下来的打印结果大家无视Favicon.ico请求就好。

级联

Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。
当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。(用一种比较相似的比喻就是中间件相当於一次DOM事件,从事件捕捉到事件冒泡的过程)。

const Koa = require("koa"),
  app = new Koa();

// 一层中间件
app.use((ctx, next) => {
  console.log("请求资源:" + ctx.url);
  console.log("一层中间件控制传递下去");
  next();
  console.log("一层中间件控制传递回来");
  ctx.body = "暗号:Day Day Up";
});

// 二层中间件
app.use((ctx, next) => {
  console.log("二层中间件控制传递下去");
  next();
  console.log("二层中间件控制传递回来");
});

// response
app.use(ctx => {
  console.log("输出body");
  ctx.body = "暗号:Good Good Study";
});

app.listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");

// 一层中间件控制传递下去
// 二层中间件控制传递下去
// 输出body
// 二层中间件控制传递回来
// 一层中间件控制传递回来

(完整代码可以执行koa-demo的 lesson2 查看效果)

从上面结果可以看出每请求一次资源都会经过所有中间件,并且在执行到最尾部中间件时候会将控制权反向传递,输出结果是头部的body覆盖尾部的body。
说实话没试过这种方式,有点不习惯。

上下文(Context)

Koa Context 将 Nodejs 的 requestresponse 对象封装到单个对象中,每个请求都将创建一个 Context,并在中间件中作为接收器引用,或者 ctx 标识符,许多上下文的访问器和方法直接委托给它们的 ctx.requestctx.response
我们可以直接打印出来ctx对象看看有什么。

const Koa = require("koa"),
  app = new Koa();

// response
app.use(async ctx => {
  console.log("ctx:", ctx);
});

app.listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");

/*
ctx: { request:
   { method: "GET",
     url: "/",
     header:
      { host: "localhost:3000",
        connection: "keep-alive",
        "cache-control": "max-age=0",
        "upgrade-insecure-requests": "1",
        "user-agent":
         "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
        accept:
         "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*!/!*;q=0.8",
        "accept-encoding": "gzip, deflate, sdch",
        "accept-language": "zh-CN,zh;q=0.8",
        cookie:
         "loginInfo={"username":"abc","password":"MjIyMjIy","rememberMe":1}" } },
  response: { status: 404, message: "Not Found", header: {} },
  app: { subdomainOffset: 2, proxy: false, env: "development" },
  originalUrl: "/",
  req: "",
  res: "",
  socket: "" }*/

(完整代码可以执行koa-demo的 lesson3 查看效果)

请求(Request)

Koa Request 对象是在 Nodejs 的 vanilla 请求对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。Koa的 Request 对象包括由 acceptsnegotiator 提供的有用的内容协商实体。

request.accepts(types)

request.acceptsEncodings(types)

request.acceptsCharsets(charsets)

request.acceptsLanguages(langs)

如果没有提供类型,则返回所有可接受的类型;

如果提供多种类型,将返回最佳匹配;

如果没有找到匹配项,则返回一个false;

因为用法都一个样,挑选一个来讲解。

request.accepts(types)

检查给定的类型是否可以接受,type 值可能是一个或多个 mime 类型的字符串或数组,如果可以就返回最佳匹配类型字符串,否则返回false。

const Koa = require("koa"),
  app = new Koa();

app
  .use(async ctx => {
    switch (ctx.accepts("json", "html", "text")) {
      case "json":
        ctx.type = "json";
        ctx.body = "

匹配类型json

"; break; case "html": ctx.type = "html"; ctx.body = "

匹配类型html

"; break; case "text": ctx.type = "text"; ctx.body = "

匹配类型text

"; break; default: ctx.throw(406, "json, html, or text only"); } }) .listen(3000); console.log("已建立连接,效果请看http://127.0.0.1:3000/");

(完整代码可以执行koa-demo的 lesson4 查看效果)

实际开发中需要更多不同的处理细节,所以我们可以把内容设置成模板template1.html引用。




  
    
    
  

  
    

匹配类型html

(完整代码可以执行koa-demo的 template1 查看效果)

const Koa = require("koa"),
  fs = require("fs"),
  app = new Koa();

app
  .use(async ctx => {
    switch (ctx.accepts("json", "html", "text")) {
      case "html":
        ctx.type = "html";
        ctx.body = fs.createReadStream("./template1.html");
        break;
      default:
        ctx.throw(406, "json, html, or text only");
    }
  })
  .listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");

(完整代码可以执行koa-demo的 lesson5 查看效果)

路由

其实我们上面的代码已经是原始路由的用法了,我们增加请求资源的判断就可以了,另外新增一个template2.html模板切换看效果




  
    
    
  

  
    

template2

(完整代码可以执行koa-demo的 template2 查看效果)
然后我们去掉类型判断等代码看效果,直接写死html即可,不然type默认为空,打开页面会触发下载的,不信你们去掉设置类型那行代码看看。

const Koa = require("koa"),
  fs = require("fs"),
  app = new Koa();

app
  .use(async ctx => {
    console.log("type: " + ctx.type);
    switch (ctx.url) {
      case "/":
        ctx.type = "html";
        ctx.body = fs.createReadStream("./template1.html");
        break;
      case "/template2":
        ctx.type = "html";
        ctx.body = fs.createReadStream("./template2.html");
        break;
      default:
        ctx.throw(406, "json, html, or text only");
    }
  })
  .listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");

(完整代码可以执行koa-demo的 lesson6 查看效果)
执行脚本之后会默认看到template2.html模板内容,手动换成http://127.0.0.1:3000/template2如果没设置type在Chrome会看到下载弹窗,其他浏览器没试过。

实际开发我们不会这么繁琐的去区分路由,上面说过 koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,所以我们需要安装一个路由中间件。
koa-route3.2.0 ,上次推送已经是两年前了,如果不是放弃维护就是已经很稳定了。

yarn add koa-route

如果你需要使用完整特性的路由库可以看 koa-router
这里简单展示 koa-route 用法。

const Koa = require("koa"),
  _ = require("koa-route"),
  fs = require("fs"),
  app = new Koa();

const route = {
  index: ctx => {
    //doSomethings
    ctx.type = "html";
    ctx.body = fs.createReadStream("./template1.html");
  },
  template2: ctx => {
    //doSomethings
    ctx.type = "html";
    ctx.body = fs.createReadStream("./template2.html");
  },
};

app
  .use(_.get("/", route.index))
  .use(_.get("/template2", route.template2))
  .listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");

(完整代码可以执行koa-demo的 lesson7 查看效果)

响应状态

如果代码运行过程中发生错误,我们需要把错误信息返回给用户。

ctx.throw([status], [msg], [properties])

等价于

const err = new Error(msg);
err.status = status;
err.expose = true;
throw err;

注意:这些是用户级错误,并用 err.expose 标记,这意味着消息适用于客户端响应,这通常不是错误消息的内容,因为您不想泄漏故障详细信息。

100 "continue"
101 "switching protocols"
102 "processing"
200 "ok"
201 "created"
202 "accepted"
203 "non-authoritative information"
204 "no content"
205 "reset content"
206 "partial content"
207 "multi-status"
208 "already reported"
226 "im used"
300 "multiple choices"
301 "moved permanently"
302 "found"
303 "see other"
304 "not modified"
305 "use proxy"
307 "temporary redirect"
308 "permanent redirect"
400 "bad request"
401 "unauthorized"
402 "payment required"
403 "forbidden"
404 "not found"
405 "method not allowed"
406 "not acceptable"
407 "proxy authentication required"
408 "request timeout"
409 "conflict"
410 "gone"
411 "length required"
412 "precondition failed"
413 "payload too large"
414 "uri too long"
415 "unsupported media type"
416 "range not satisfiable"
417 "expectation failed"
418 "I"m a teapot"
422 "unprocessable entity"
423 "locked"
424 "failed dependency"
426 "upgrade required"
428 "precondition required"
429 "too many requests"
431 "request header fields too large"
500 "internal server error"
501 "not implemented"
502 "bad gateway"
503 "service unavailable"
504 "gateway timeout"
505 "http version not supported"
506 "variant also negotiates"
507 "insufficient storage"
508 "loop detected"
510 "not extended"
511 "network authentication required"
状态码错误

有两种写法,ctx.throw(状态码) 或者 ctx.status = 状态码 ,它们都会自动返回默认文字信息,区别在于两者设置返回信息的方式。
注意:默认情况下,response.status 设置为 404 而不是像 node 的 res.statusCode 那样默认为 200。

const Koa = require("koa"),
  _ = require("koa-route"),
  app = new Koa();

const router = {
  "403": ctx => {
    //doSomethings
    ctx.throw(403, "403啦!");
  },
  "404": ctx => {
    //doSomethings
    ctx.status = 404;
    ctx.body = `

404啦!

`; }, }; app .use(_.get("/403", router[403])) .use(_.get("/404", router[404])) .listen(3000); console.log("已建立连接,效果请看http://127.0.0.1:3000/");

(完整代码可以执行koa-demo的 lesson8 查看效果)
你们可以分别打开 http://localhost:3000/403http://localhost:3000/404 看输出结果。

错误监听
const Koa = require("koa"),
  _ = require("koa-route"),
  app = new Koa();

const router = {
  index: ctx => {
    //doSomethings
    ctx.throw(500, "我是故意的!");
  },
};

app
  .use(_.get("/", router.index))
  .on("error", (err, ctx) => {
    console.error("error", err);
  })
  .listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");
/*
error { InternalServerError: 我是故意的!
    at Object.throw (C:project	estkoa-demo
ode_moduleskoalibcontext.js:93:11)
    at Object.index (C:project	estkoa-demolesson9.js:8:18)
    at C:project	estkoa-demo
ode_moduleskoa-routeindex.js:39:44
    at dispatch (C:project	estkoa-demo
ode_moduleskoa-composeindex.js:42:32)
    at C:project	estkoa-demo
ode_moduleskoa-composeindex.js:34:12
    at Application.handleRequest (C:project	estkoa-demo
ode_moduleskoalibapplication.js:150:12)
    at Server.handleRequest (C:project	estkoa-demo
ode_moduleskoalibapplication.js:132:19)
    at Server.emit (events.js:182:13)
    at parserOnIncoming (_http_server.js:654:12)
    at HTTPParser.parserOnHeadersComplete (_http_common.js:109:17) message: "我是故意的!" }*/

(完整代码可以执行koa-demo的 lesson9 查看效果)

错误捕捉

你也能直接使用try...catch()直接处理,但是“error”监听事件就不会再接收该错误信息。

const Koa = require("koa"),
  app = new Koa();

const err = async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      ctx.status = 404;
      ctx.body = `

你看看终端有没打印错误!

`; } }, index = ctx => { ctx.throw(500); }; app .use(err) .use(index) .on("error", function(err) { console.error("error", err); }) .listen(3000); console.log("已建立连接,效果请看http://127.0.0.1:3000/");

(完整代码可以执行koa-demo的 lesson10 查看效果)
注意,这裡的错误处理如果使用ctx.throw()方法的话能被“error”事件监听到,不是因為该方法会再拋出新的错误。

const Koa = require("koa"),
  app = new Koa();

const err = async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      ctx.throw(404, "你看看终端有没打印错误!");
    }
  },
  index = ctx => {
    ctx.throw(500);
  };

app
  .use(err)
  .use(index)
  .on("error", function(err) {
    console.error("error", err);
  })
  .listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");

(完整代码可以执行koa-demo的 lesson1 查看效果)1
如果想同时触发错误监听,KOA也提供了 emit 方法可以实现。

const Koa = require("koa"),
  app = new Koa();

const err = async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      ctx.status = 404;
      ctx.body = `

你看看终端有没打印错误!

`; ctx.app.emit("error", err, ctx); } }, index = ctx => { ctx.throw(500); }; app .use(err) .use(index) .on("error", function(err) { console.error("error", err); }) .listen(3000); console.log("已建立连接,效果请看http://127.0.0.1:3000/"); /* error { InternalServerError: Internal Server Error at Object.throw (C:project estkoa-demo ode_moduleskoalibcontext.js:93:11) at index (C:project estkoa-demolesson12.js:14:18) at dispatch (C:project estkoa-demo ode_moduleskoa-composeindex.js:42:32) at err (C:project estkoa-demolesson12.js:6:19) at dispatch (C:project estkoa-demo ode_moduleskoa-composeindex.js:42:32) at C:project estkoa-demo ode_moduleskoa-composeindex.js:34:12 at Application.handleRequest (C:project estkoa-demo ode_moduleskoalibapplication.js:150:12) at Server.handleRequest (C:project estkoa-demo ode_moduleskoalibapplication.js:132:19) at Server.emit (events.js:182:13) at parserOnIncoming (_http_server.js:654:12) message: "Internal Server Error" } */

(完整代码可以执行koa-demo的 lesson11 查看效果)

静态资源

聪明的人在上面代码就能看出一些问题,还记得我们说过忽略 Favicon.ico 的请求麼。我们不仅仅有页面的请求,还有其他资源的请求。
我们现在还是通过url判断返回页面,如果是其他静态资源如图片那些又怎么办?这裡介绍一下依赖库koa-static5.0.0,

yarn add koa-static
--------------------------
require("koa-static")(root, opts)

通过设置根目录和可选项会配置静态资源查找路径,我们先创建一个img目录存放一张图片,然后在 template3.html 引用,再设置路径 require("koa-static")(__dirname + "/img/"),它会自动到指定目录下查找资源。




  
    
    
  

  
    

没错,我就是首页

(完整代码可以执行koa-demo的 template3 查看效果)

const Koa = require("koa"),
  _ = require("koa-route"),
  serve = require("koa-static")(__dirname + "/img/"),
  fs = require("fs"),
  app = new Koa();

const router = {
  index: ctx => {
    //doSomethings
    ctx.type = "html";
    ctx.body = fs.createReadStream("./template3.html");
  },
};

app
  .use(serve)
  .use(_.get("/", router.index))
  .listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");

(完整代码可以执行koa-demo的 lesson13 查看效果)
如果你还是有些不懂的话修改下路径,require("koa-static")(__dirname),然后图片地址换成 "./img/1.gif" 。你就看到还是能找到对应资源。

中间件管理

随着项目开发你可能会安装越来越多的中间件,所有可以使用koa-compose做中间件管理。这个很多库都有类似的中间件,用於简化中间件的使用。
上面我们用来讲解 koa 级联的那个例子可以直接拿来修改使用。

const Koa = require("koa"),
  compose = require("koa-compose"),
  app = new Koa();

// 一层中间
const mid1 = (ctx, next) => {
  console.log("请求资源:" + ctx.url);
  console.log("一层中间件控制传递下去");
  next();
  console.log("一层中间件控制传递回来");
};

// 二层中间
const mid2 = (ctx, next) => {
  console.log("二层中间件控制传递下去");
  next();
  console.log("二层中间件控制传递回来");
};

// response
const mid3 = ctx => {
  console.log("输出body");
  ctx.body = "暗号:Hello World";
};

app.use(compose([mid1, mid2, mid3])).listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");
// 请求资源:/
// 一层中间件控制传递下去
// 二层中间件控制传递下去
// 输出body
// 二层中间件控制传递回来
// 一层中间件控制传递回来

(完整代码可以执行koa-demo的 lesson14 查看效果)
可以看出大概原理就是把引用多个中间件的使用方式改成将多个中间件组装成一个使用。

请求处理

我们处理请求的时候可以用 koa-body 解析请求体。

A full-featured koa body parser middleware. Support multipart, urlencoded and json request bodies. Provides same functionality as Express"s bodyParser - multer. And all that is wrapped only around co-body and formidable.

一个功能丰富的body解析中间件,支持多部分,urlencoded,json请求体,提供Express里bodyParse一样的函数方法

直接安装依赖

yarn add koa-body

新建一个提交页面




  
    
    
  

  
    

(完整代码可以执行koa-demo的 template4 查看效果)

可以输出格式看看效果

const Koa = require("koa"),
  koaBody = require("koa-body"),
  _ = require("koa-route"),
  fs = require("fs"),
  app = new Koa();

const router = {
    index: ctx => {
      //doSomethings
      ctx.type = "html";
      ctx.body = fs.createReadStream("./template4.html");
    },
  },
  upload = ctx => {
    console.log(ctx.request.body);
    ctx.body = `Request Body: ${JSON.stringify(ctx.request.body)}`;
  };

app
  .use(koaBody())
  .use(_.get("/", router.index))
  .use(_.post("/upload", upload))
  .listen(3000);
console.log("已建立连接,效果请看http://127.0.0.1:3000/");

(完整代码可以执行koa-demo的 lesson15 查看效果)
提交内容之后在页面和终端都能看到body内容。

参考资料

Koa (koajs)
Koa examples
Koa 框架教程

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106737.html

相关文章

  • koa2系列教程koa2应用初见

    摘要:系列教程,持续更新系列教程应用初见系列教程处理静态文件系列教程使用模板引擎系列教程路由控制中间件系列教程综合搭建登录注册页面系列教程实现登录注册功能这篇教程主要介绍构建服务器,简单引用本教程的版本要格外注意版本号案例简单利用搭建服务器文件夹 koa2系列教程,持续更新 koa2系列教程:koa2应用初见 koa2系列教程:koa2处理静态文件 koa2系列教程:koa2使用模板引擎 ...

    import. 评论0 收藏0
  • koa2系列教程koa2处理静态文件

    摘要:系列教程,持续更新系列教程应用初见系列教程处理静态文件系列教程使用模板引擎系列教程路由控制中间件系列教程综合搭建登录注册页面系列教程实现登录注册功能这篇主要介绍处理静态文件的中间件用到的版本项目的结构大家可以往文件夹里面添加点东西编辑启动 koa2系列教程,持续更新 koa2系列教程:koa2应用初见 koa2系列教程:koa2处理静态文件 koa2系列教程:koa2使用模板引擎 k...

    madthumb 评论0 收藏0
  • koa2系列教程koa2使用模板引擎

    摘要:系列教程,持续更新系列教程应用初见系列教程处理静态文件系列教程使用模板引擎系列教程路由控制中间件系列教程综合搭建登录注册页面系列教程实现登录注册功能这篇教大家如何使用模板引擎这里我们使用模板引擎做个例子,你们自己选择自己熟练的模板引擎使用的 koa2系列教程,持续更新 koa2系列教程:koa2应用初见 koa2系列教程:koa2处理静态文件 koa2系列教程:koa2使用模板引擎 ...

    ZoomQuiet 评论0 收藏0
  • koa2系列教程:综合koa2搭建登录注册页面

    摘要:系列教程,持续更新系列教程应用初见系列教程处理静态文件系列教程使用模板引擎系列教程路由控制中间件系列教程综合搭建登录注册页面系列教程实现登录注册功能本文源码地址这篇是将前几天的内容做个综合,运用静态文件处理,路由,模板引擎我的版本项目结构是 koa2系列教程,持续更新 koa2系列教程:koa2应用初见 koa2系列教程:koa2处理静态文件 koa2系列教程:koa2使用模板引擎 ...

    li21 评论0 收藏0
  • koa2系列教程koa2路由控制中间件

    摘要:系列教程,持续更新系列教程应用初见系列教程处理静态文件系列教程使用模板引擎系列教程路由控制中间件系列教程综合搭建登录注册页面这篇我们来使用一个控制一下路由本篇的版本注意版本哦目录结构编辑子路由子路由装载所有子路由加载路由中间件启动服务, koa2系列教程,持续更新 koa2系列教程:koa2应用初见 koa2系列教程:koa2处理静态文件 koa2系列教程:koa2使用模板引擎 ko...

    lily_wang 评论0 收藏0
  • koa2系列教程koa2实现登录注册功能

    摘要:系列教程,持续更新系列教程应用初见系列教程处理静态文件系列教程使用模板引擎系列教程路由控制中间件系列教程综合搭建登录注册页面系列教程实现登录注册功能这个主要结合前几天的内容,做个实际案例的效果版本项目结构前几天,我们把注册和登录的页面实现了 koa2系列教程,持续更新 koa2系列教程:koa2应用初见 koa2系列教程:koa2处理静态文件 koa2系列教程:koa2使用模板引擎 ...

    baiy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<