Pasing request

這一個章節我們來聊聊如何讀取一個請求的內容。

撰寫網站的時候,我們會希望前端使用者能傳遞一些請求,後端伺服器讀取了使用者的請求後做出相對應的服務,達到互動的效果。例如:使用者輸入帳號密碼登入網站,或者讓前端使用者可以有管道可以查詢後端的資料庫等等。

在前端我們可以透過表單來達到這個目的,或者也可以使用AJAX的方式來達成。而請求送出後,伺服器能夠解析請求,再來渲染對應的模板回前端。我們這個章節分成兩個部分來進行,第一部分是GET方法的QueryString,第二部分是POST方法的表單解析。

Get - QueryString parsing

在Koa中,爬取QueryString是再簡單不過的事情了,Koa會自動parsing query string的內容,儲存成ctx.query物件。

我們依舊延續上次YouTube循環播放的那個範例,不過我們這次不使用URL parameters,而改使用GET表單的方式來讀取使用者想要重複播放哪個影片。

在Controller中爬取QueryString

    
// GET-parsing.ts
import Koa = require('koa');
import Router = require('koa-router');
import Path = require('path');
import Views = require('koa-views');

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

app.use(Views(Path.join(__dirname, 'views'), {
    extension: 'pug',
    map: {
        pug: 'pug',
    },
}));

router.get('/loop/', async (ctx) => {
    const VideoID = ctx.query.VideoID;          // 讀取QueryString中的VideoID
    await ctx.render('looper', {
        VideoID: VideoID ? VideoID : undefined, // 如果存在VideoID變數則傳給前端,否則就傳undefined
    });
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000);

這邊標題下Controller是因為在Koa框架中,Routing部分其實就是類似于MVC架構的Controller

looper模板

    
<!DOCTYPE html>
<!-- views/looper.pug -->
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        meta(http-equiv="X-UA-Compatible", content="ie=edge")
        title My awesome youtube looper
    body
        h1 Awesome Youtube Looper
        form(action="/loop" method="GET")
            span VideoID:
            input(type="text" name="VideoID")
            br
            input(type="submit" value="submit")
        br
        if VideoID
            iframe(src=`https://www.youtube.com/v/${VideoID}?version=3&loop=1&playlist=${VideoID}&autoplay=1`, frameborder="0" allowfullscreen)
        else
            p Hi~ Please enter your favorite video, I can loop it~

我們這次增加了一個表單讀取參數,並且讓他使用GET方法request/loop頁面,在15~18行的部分,先判斷變數是否存在,而有這不同的行為。

因此這次的成果如下圖所示

Image

請特別注意表單送出後網址的變化,GET方法會將參數緊接在?後,參數是一個key-value的組合,用&符號連接多組參數
因此我們的參數可以長這樣/loop?VideoID=pgGM7lBEvBs&AutoPlay=1代表傳遞了兩個參數VideoID與AutoPlay

POST - Body Parsing

你可能已經發現了一件事,GET方法會將所有傳遞的變數擺放在URL的後方,如此一來傳遞這個請求時,其實很容易被觀察到表單的內容,因此我們會希望使用POST方法來傳遞一些隱私性較高的參數,例如使用者的帳號密碼等等。

要爬取POST請求我們必須使用koa-bodyparser中間件來達成。

安裝koa-bodyparser

    
yarn add koa-bodyparser @types/koa-bodyparser

使用koa-bodyparser

這邊我們做個簡單的會員註冊機制,而會員資料放在記憶體當中(其實就是放在變數中),當使用者瀏覽/users時會看到所有的使用者明細,當使用者瀏覽/regist時會到一個註冊頁面,註冊的表單則是POST到/regist,因此/regist在不同方法時會有不同行為。

    
// POST-parsing.ts
import Koa = require('koa');
import Router = require('koa-router');
import Path = require('path');
import Views = require('koa-views');
import bodyParser = require('koa-bodyparser');

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

interface IUser {                           // 建立一個IUser的interface,如果是寫Javascript可以不用寫這個
    username: string;
    password: string;
}

const users: IUser[] = [];                  // 建立一個空的users陣列,裡頭即將存放多個users

app.use(Views(Path.join(__dirname, 'views'), {
    extension: 'pug',
    map: {
        pug: 'pug',
    },
}));

app.use(bodyParser());                      // 插入bodyParser中間件,必須要由這個中間件才可以爬取POST資料

router.get('/users', async (ctx) => {       // 瀏覽/users時渲染全部會員的資料
    await ctx.render('users', {
        users,
    });
});

router
.get('/regist', async (ctx) => {            // 瀏覽/regist時渲染註冊頁面
    await ctx.render('regist');
})
.post('/regist', async (ctx) => {           // 處理/regist表單
    const user: IUser = {
        username: ctx.request.body.username,    // bodyParser爬取到的資訊會存放在ctx.request.body物件中
        password: ctx.request.body.password,
    };
    users.push(user);
    console.log(users);
    await ctx.redirect('/users');
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000);

Tracecode

  1. 第25行: 由於Koa並沒有內建爬取POST表單的功能,因此我們引入koa-bodyparser中間件來幫我們解析
  2. 第39-40行: bodyParser爬取到的資訊會存放在ctx.request.body

References:
1. koajs/bodyparser
所有範例中的程式碼皆放置在koa-tutorial-sample專案中