强型闯入DenoLand[28] - Oak 概念篇

Node.js之父新专案Deno 1.0正式亮相| iThome

强型闯入DenoLand[28] - Oak 概念篇

什麽是 Oak?

Oak 是一款用来开发 http server 的中间件框架,其包含了 Router 路由中间件。

此外,如果读者有使用 Node.js 的经验,可能有听过 Koa 、 Express 等後端框架。 Oak 其实就是致敬 Koa 开发出来的,因此在使用上,两者有许多重叠的地方,除了第三方的中间件不可共用以外,大部分的观念都能套用到 Oak 身上。

笔者第一次看到 Oak 时,真的直接笑出来。想说这群开发者到底是夺懒,也不花点时间想名字就把 Koa 的名字重组 XDD

中间件的概念

如图,每个 Oak 的中间层都像是洋葱的其中一圈,当後端程序收到 Http Request 会经过每层中间件的处理,最後变为 Response 传送回去。

本图取自该网站

让我们进一步透过范例观察:

import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

// Logger
app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.headers.get("X-Response-Time");
  console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});

// Timing
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});

// Hello World!
app.use((ctx) => {
  ctx.response.body = "Hello World!";
});

await app.listen({ port: 8000 });

我们可以在范例中看到 use() 以及 next() 方法, use() 用於注册中间件, next() 则用来告诉 Oak 可以先跳到下一个中间件,假设该范例程序正在执行并收到一个请求,中间件的执行顺序如下:

Logger -> Timing -> Hello World! -> Timing -> Logger

这样的顺序就如同洋葱图所表示的一样,我们更可以利用这个特性去纪录从 Request 到 Response 一共花了多少时间:

// Timing
app.use(async (ctx, next) => {
  const start = Date.now();// 纪录收到时间
  await next();// 跳到下一个中间件
  const ms = Date.now() - start;// 回到该中间件时再次纪录时间
  ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});

以上程序码需要注意的事项有两点:

  1. 开始使用前必须记得将 Oak 引入:

    import { Application } from "https://deno.land/x/oak/mod.ts";
    
  2. 启动时需加入 --allow-net 标签。

路由概念

在实作前後端分离的系统时,以 SPA (Single Page Application) 为例,都会有多个虚拟路由,每个路由都代表了不同的页面,像是: HomeAboutContact 等等。

因此,我们在实作 API 时也可以将它拆分成对应的路由, Oak 内建了高度致敬 Koa-Router 的路由中间件: Oak-Router

import { Application, Router } from 'https://deno.land/x/oak/mod.ts';

const app = new Application();
const router = new Router();

router
    .get('/home', (context) => { context.response.body = "This is Home!"; })
		.get('/about', (context) => { context.response.body = "This is About Page."; })
app.use(async (context, next) => {
    await next();
    console.log(`${context.request.method} ${context.request.url}`);
});
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });

上面的范例中,一共实作了两个路由: home 以及 about

实际上,最常见的请求方法有分为 get 以及 post 方法, get 为上面范例所使用到的:

// ...
router
    .get('/home', (context) => { context.response.body = "This is Home!"; })
// ...

post 的用法并不难,收先要做的就是将 get 关键字换成 post 关键字:

// ...
.post('/api/movies', async (context) => {
        const data = await context.request.body();
        // ...
        context.response.body = //... ;
    });
// ...

get 与 post 的差异

get 常用来做查询资料,因为 get 的查询参数可以直接在网址上看到,像是:

https://www.findprice.com.tw/g/Ipad

此为比价网站 findprice 的网址,而网址中的 Ipad 表示查询的关键字,在後端程序中,程序将它作为参数作为数入并进行相关操作。

Post 的话则比较常用来做写入操作,因为相关参数会被放到 http 请求当中,所以我们常常看到大家建议使用 post 而不是使用 get ,因为 get 方法会将请求参数暴露给使用者知道,会更容易出现 SQL Injection 以及 XSS 等安全漏洞。

只要开发者喜欢,要用 get 或是 post 做查询、写入都可以。只是使用 get 做查询相对直观一些,因为使用者可以透过网址知道现在在浏览网站的第几页、自己搜寻了什麽关键字...等等。

使用 Oak 前必备的 JS 观念

我们都知道, JavaScript 是使用非同步处理机制的,至於要如何让 JS 做到同步呢?

我们可以善用 Promise 以及 async/await ,会特别提到同步机制的原因如下:

从一开始的洋葱图可以知道,一个中间件是有可能被通过两次的。若在请求通过中间件第二次时,第一次的操作根本还没做完便有可能造成严重的影响,像是:

// ...
app.use((ctx, next) => {
  const start = Date.now();// 纪录收到时间
  Insert(start);// 将时间存放至资料库
  await next();// 跳到下一个中间件
  const ms = Date.now() - start;// 回到该中间件时再次纪录时间
  ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
// ...

若没有确保在 Insert() 完成前就跳到下一个中间件,便有可能在实际应用中出现巨大的问题,这时候我们可以使用 Promise 以及 async/await 改善:

// ...
app.use(async (ctx, next) => {
  const start = Date.now();// 纪录收到时间
  await Insert(start);// 将时间存放至资料库
  await next();// 跳到下一个中间件
  const ms = Date.now() - start;// 回到该中间件时再次纪录时间
  ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
// ...

这边要注意的是: 我们必须确保使用到 await 的函式在实作中是以 Promise 方法达成的,笔者会将相关文章放到本日的延伸阅读。

如果有读者对 同步 一词有疑问, 同步在这边代表做完一件事才能做下一件事,如果对非同步的概念很模糊,可以回去看强型闯入DenoLand[24] - 使用 Deno 打造多线程应用(1),在本篇笔者有提到 Chrome V8 是如何做任务排程的。

总结

本篇先将 Oak 的重要观念先提一遍,之後会针对每个部分逐一讲解,如果有空,笔者会再花时间把 Promise() 以及 async/await 的教学补上,还请见谅 ORZ

延伸阅读

同样的事情在不同人眼中可能会有不同的见解、看法。

在读完本篇以後,笔者也强烈建议大家去看看以下文章,或许会对型别、变数宣告...等观念有更深层的看法唷!


<<:  [Day 29]老师我学逻辑推论做什麽(4)

>>:  结束了!!

Day-2 既然要在新电视上玩游戏机、当然要从认识 HDMI 开始

就如标题所说的、 HDMI 端子可以说是目前最通用的端子、现在的电视几乎都只有 HDMI 端子、顶多...

Ruby幼幼班--Best Time to Buy and Sell Stock

Best Time to Buy and Sell Stock 题目连结:https://leet...

Day6-Java反编译工具:javap

javap介绍 javap是jdk工具中自带的反编译工具,它是根据class位元组码档案,反解析出当...

电子书阅读器上的浏览器 [Day03] 站在巨人的肩膀上,找到对的起跑点

如何做选择 选择一个好的开源专案来当作起点,可以省下很多重新发明轮子的时间,把精力专注在想要改善和提...

Day4 Are you my destiny?

Multiple criteria filter 继众里寻它後,我们想继续看是否有各个栏位都符合关...