# Oak 框架介紹

### 什麼是 Oak？

Oak 是一款用來開發 http server 的中間件框架，其包含了 Router 路由中間件。

此外，如果讀者有使用 Node.js 的經驗，可能有聽過 Koa 、 Express 等後端框架。 Oak 其實就是致敬 Koa 開發出來的，因此在使用上，兩者有許多重疊的地方，除了第三方的中間件不可共用以外，大部分的觀念都能套用到 Oak 身上。

> 筆者第一次看到 Oak 時，真的直接笑出來。想說這群開發者到底是奪懶，也不花點時間想名字就把 Koa 的名字重組 XDD

### 中間件的概念

![](https://cdn.deno.js.cn/uploads/default/original/1X/4e00d3ec9067bb10a190ffb71894c9a4a8dfc7d8.jpeg)

如圖，每個 Oak 的中間層都像是洋蔥的其中一圈，當後端程式收到 Http Request 會經過每層中間件的處理，最後變為 Response 傳送回去。

> 本圖取自[該網站](https://www.google.com/url?sa=i\&url=https%3A%2F%2Fdeno.js.cn%2Ft%2Ftopic%2F35\&psig=AOvVaw3etFLrSmVMeiAQopLzvsvo\&ust=1602767758699000\&source=images\&cd=vfe\&ved=0CAIQjRxqFwoTCNDFiK6WtOwCFQAAAAAdAAAAABAD)。

讓我們進一步透過範例觀察：

```
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) 為例，都會有多個虛擬路由，每個路由都代表了不同的頁面，像是： `Home` 、 `About` 、 `Contact` 等等。

因此，我們在實作 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)](https://ithelp.ithome.com.tw/articles/10251678)，在本篇筆者有提到 Chrome V8 是如何做任務排程的。

### 延伸閱讀

> 同樣的事情在不同人眼中可能會有不同的見解、看法。
>
> 在讀完本篇以後，筆者也強烈建議大家去看看以下文章，或許會對型別、變數宣告...等觀念有更深層的看法唷！

* &#x20;[使用 Promise](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Using_promises)
* &#x20;[鐵人賽：JavaScript Await 與 Async](https://wcc723.github.io/javascript/2017/12/30/javascript-async-await/)
* &#x20;[《Koa2进阶学习笔记》](https://chenshenhai.github.io/koa2-note/note/start/info.html)
* &#x20;[Oak Repo](https://github.com/oakserver/oak)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ianchen0119.gitbook.io/deno/shi-yong-deno-da-zao-web-api/oak-kuang-jia-jie-shao.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
