Koa - Node.js框架學(xué)習(xí)@郝晨光


本文由郝晨光整理總結(jié)并編寫沐寺,未經(jīng)允許禁止轉(zhuǎn)載。

前言

學(xué)習(xí)koa盖奈,我之前學(xué)習(xí)過express混坞,但是在使用express的時候,還是一直使用的回調(diào)函數(shù)的方式來處理異步,現(xiàn)在想想真是恐怖究孕,后來了解到koa這個框架啥酱,它相對于express來說,小巧了很多厨诸,對于異步的處理也變得更加優(yōu)雅了镶殷。用官方的話來說,koa是基于Node.js平臺的下一代web平臺開發(fā)框架微酬。


Image 3.png

正文

  1. 首先肯定要先開始一個新的項目绘趋,新建一個文件夾

  2. 在文件夾目錄中,使用命令提示符執(zhí)行npm init -y颗管,初始化package.json文件陷遮;

  3. 安裝

    • Koa 依賴 node - v7.6.0 或 ES2015及更高版本和 async 方法支持。
    • 先查看node的版本 node -v;
    • 然后執(zhí)行npm install koa 進行安裝垦江;
  4. 使用

    • 新建index.js帽馋;
    • 先上官網(wǎng)上最經(jīng)典的hello wrold案例;
    • const Koa = require('koa');
      const app = new Koa();
      app.use(async ctx => {
          ctx.body = 'hello word!'
      })
      app.listen(8000, () => {
          console.log('Koa server listen in http://localhost:8000/');
      })
      
    • 打開瀏覽器疫粥,輸入localhost:8000/茬斧,就可以看到我們的頁面上顯示了hello world!;
    • 那我們的第一個koa應(yīng)用就已經(jīng)運行起來了~。
  5. 讀取 HTML 頁面

    • 在Koa中梗逮,我們?nèi)绻褂?HTML 應(yīng)該怎么做呢项秉?
    • 在原生nodejs或者express中,我們使用的是fs模塊慷彤,然后使用回調(diào)函數(shù)娄蔼,一層套一層;
    • 在Koa中底哗,我們依舊使用fs模塊岁诉,但是,要對fs模塊的讀取文件跋选,進行進一步的封裝涕癣;
    • 在目錄下,我們新建一個 index.html,并隨便寫一點東西前标;
    • 接著開始我們漸行漸遠(yuǎn)的程序生涯坠韩;
    • const Koa = require('koa');
      const fs = require('fs');
      const app = new Koa();
      function readFile(url) {
        return new Promise((resolve, reject) => {
            fs.readFile(url, (err,file) => {
                if(err) {
                    reject(err);
                }else {
                    resolve(file);
                }
            })
        })
      }
      app.use(async ctx => {
          ctx.response.type = 'html'; // 重點
          ctx.body = await readFile('index.html');
      })
      app.listen(8000, () => {
          console.log('Koa server listen in http://localhost:8000/');
      })
      // 本文由郝晨光整理總結(jié)并編寫,未經(jīng)允許禁止轉(zhuǎn)載炼列。
      
    • 可以看到只搁,我們利用Promise將fs模塊的readFile方法進行了二次封裝,讀取了本地的index.html文件
    • 重點的地方我已經(jīng)標(biāo)出來了俭尖,為什么要標(biāo)重點呢氢惋?
    • 因為在Koa中洞翩,ctx.body默認(rèn)返回的類型是text/plain;而我們要返回html文件焰望,所以應(yīng)該在返回文件之前骚亿,設(shè)置好返回類型,讓客戶端可以正確的接收柿估;
    • 然后我們使用async + await組合循未,在數(shù)據(jù)讀取完成之后,返回讀取的文件秫舌;
    • 最后的妖,打開瀏覽器,可以看到瀏覽器上輸出的就是我們index.html文件中寫的內(nèi)容
  6. 讀取靜態(tài)資源

    • html文件我們已經(jīng)可以正確的讀取并返回了足陨,在我們的頁面上嫂粟,避免不了會使用很多的靜態(tài)資源,例如css墨缘、js星虹、image、font等等镊讼;這些靜態(tài)資源我們應(yīng)該怎么處理呢宽涌?
    • 在這里,我們使用一個Koa的中間件koa-static
    • 在命令提示符中執(zhí)行npm install koa-static蝶棋;
    • 接著書寫我們的程序
    • // ~~ 省略 ~~
      const app = new Koa(); // 新建koa對象
      const KoaStatic = require('koa-static'); // koa靜態(tài)文件讀取
      app.use(KoaStatic(__dirname)); // 使用中間件
      // ~~ 省略 ~~
      
      • 接著卸亮,打開我們的瀏覽器,可以看到靜態(tài)資源已經(jīng)可以正確的請求到了玩裙;
      • 當(dāng)然了兼贸,這個時候,我們可以把之前返回html文件時設(shè)置響應(yīng)類型的一步省略掉了吃溅,因為koa-static已經(jīng)幫我們做了這件事了溶诞。
  7. 定義路由

    • 所謂的路由,就是根據(jù)不同的請求路徑决侈,響應(yīng)不同的內(nèi)容螺垢;
    • 在Koa中,我們可以定義原生路由赖歌,也可以使用封裝好的路由中間件枉圃;
    • 先看一下原生路由的寫法吧!
    • app.use(async ctx => {
        if(ctx.request.method==='GET') { // GET請求
            switch (ctx.request.path) {
                case '/': // 匹配默認(rèn)路由
                    ctx.body = await readFile('index.html'); // 返回index.html文件
                    break;
                case '/about': // 匹配/about路由
                    ctx.body = 'about路由頁面'; // 返回about路由頁面
                    break;
                default:
                    ctx.body = await readFile('index.html'); // 默認(rèn)返回index.html文件
                    break;
            }
        }else if(ctx.request.method==='POST') { // POST請求
            switch (ctx.request.path) {
                default:
                    ctx.body = 'post 請求'; // 響應(yīng)post請求
                    break;
            }
        }
      });
      //本文由郝晨光整理總結(jié)并編寫俏站,未經(jīng)允許禁止轉(zhuǎn)載讯蒲。
      
    • 可以看到我上邊寫的痊土,對請求方式以及路由進行了處理肄扎,在不同的請求方式,不同的請求路徑下,執(zhí)行不同的操作犯祠,響應(yīng)不同的結(jié)果旭等。


      原生路由
  8. 路由中間件

    • 我們使用原生方式定義路由,未免有些太過繁瑣衡载,并且代碼不便于閱覽和維護搔耕;所以,建議使用中間件的方式進行路由配置痰娱。
    • 我這里使用的是 koa-router 這個中間件弃榨,當(dāng)然,koa的路由中間件不是只有這一種梨睁。
    • 在命令提示符中安裝 npm install koa-router;
    • 接著鲸睛,進行我們的程序生涯;
    • // ~~ 省略 ~~
      const KoaRouter = require('koa-router');
      const router = new KoaRouter();
      router.get('/',async ctx => {
          ctx.body = await readFile('index.html');
      });
      
      router.get('/about',async ctx=> {
           ctx.body = 'about路由頁面'
      });
      
      router.post('/form',async ctx => {
          ctx.body = 'post請求'
      });
      
      app.use(router.routes());
      // ~~ 省略 ~~
      
    • 打開頁面坡贺,并進行路由切換官辈,可以看到?jīng)]有任何問題;
    • 但是這樣所有的路由配置都寫在了index.js中遍坟,不利于維護拳亿,所以我們要將路由內(nèi)容和公用方法提取出來。


      路由中間件
  1. 代碼模塊化

    • 首先對路由進行模塊化
    • 在目錄下新建routes文件夾愿伴,并新建home.js肺魁,用來存放關(guān)于首頁的一些路由配置。
    • 將上邊的路由代碼單獨寫到home.js中公般,并拋出万搔,如下:
    • home.js
    • const KoaRouter = require('koa-router');
      const router = new KoaRouter();
      
      router.get('/',async ctx => {
        ctx.body = await readFile('index.html');
      });
      
      router.get('/about',async ctx=> {
        ctx.body = 'about 路由'
      });
      
      router.post('/from',async ctx => {
         ctx.body = 'post請求'
      });
      
      module.exports = router;
      
    • index.js
    • const homeRouter = require('./routes/home');
      
      app.use(KoaStatic(__dirname));
      
      app.use(homeRouter.routes());
      
    • 在index.js中亡哄,直接引入使用即可胆描,和原有代碼沒有任何區(qū)別。
    • 需要注意的是凤价,對于功能中間件刽虹,例如koa-static這種酗捌,我們應(yīng)該放在路由中間件之前。
    • 否則的話可能會出錯涌哲,在路由中不能正確的讀取靜態(tài)文件等胖缤。
  2. 二級路由

    • 已經(jīng)學(xué)會了一級路由的定義,我們趁熱打鐵阀圾,學(xué)習(xí)二級路由
    • 在routes目錄下哪廓,新建user.js,定義用戶路由
    • // user.js
      const KoaRouter = require('koa-router');
      const router = new KoaRouter({
        prefix: '/user'
      });
      
      router.get('/',async ctx => {
        ctx.body = '用戶界面'
      });
      
      router.get('/:id',async ctx => {
        ctx.body = '用戶詳情'+ctx.params.id;
      });
      
      router.post('/login',async ctx => {
        ctx.body = '用戶注冊成功初烘!'
      });
      
      module.exports = router;
      
      // index.js
      // ~~ 省略 ~~
      const userRouter = require('./routes/user');
      // ~~ 省略 ~~
      app.use(userRouter.routes());
      
    • 接著涡真,打開瀏覽器分俯,我們測試一下我們的二級路由,是沒有任何問題的哆料。


      二級路由.gif
  3. post請求處理

    • 我們都知道缸剪,使用post請求一般用來提交表單,或者修改數(shù)據(jù)等东亦。而post請求中的數(shù)據(jù)杏节,存放在請求體中,使用原生Node的方法的話典阵,我們需要監(jiān)聽原生req對象上的data方法以及end方法奋渔,并將數(shù)據(jù)格式轉(zhuǎn)化。而express中壮啊,我們有一個body-parser的插件可以使用卒稳。
    • 那么在Koa中,我們應(yīng)該使用什么呢他巨?
    • 在Koa中充坑,我們使用 koa-bodyparser 這個中間件來處理post請求的數(shù)據(jù)
    • 在命令提示符安裝 npm install koa-bodyparser;
    • 接著我們在index.js中使用這個中間件;
    • 加入下面兩行代碼染突,與原先使用koa-static中間件位置一樣即可捻爷;
    • const KoaBodyParser = require('koa-bodyparser');
      
      app.use(KoaBodyParser());
      
    • 使用了這個中間件,我們就可以在ctx.request.body中拿到post請求的數(shù)據(jù)了份企;
    •  router.post('/login',async ctx => {
          ctx.body = ctx.request.body;
       });
      
    • 接著改造一下我們的html文件也榄,提交一下表單;
    •   <form action="/user/login" method="post">
            姓名:<input type="text" name="name" autocomplete="off"/>
            <br>
            <input type="radio" name="sex" value="男"/>男
            <input type="radio" name="sex" value="女"/>女
            <input type="submit" value="提交form">
        </form>
      
      • 可以看到司志,表單提交正常甜紫,我們也正確的拿到了表單提交的數(shù)據(jù)。


        Koa提交表單
  4. CORS跨域配置

    • 在我們現(xiàn)在的服務(wù)器生涯中骂远,避免不了要使用跨域囚霸,特別是CORS跨域。
    • 那么激才,在Koa中拓型,我們應(yīng)該怎么去設(shè)置跨域呢?
    • 我們可以使用中間件瘸恼,也可以使用原生方法手寫劣挫。
    • 老規(guī)矩,先看原生方法东帅。
    •   app.use(async (ctx, next) => {
            // 允許來自所有域名請求
            ctx.set("Access-Control-Allow-Origin", "*");
            // 這樣就能只允許 http://localhost:8080 這個域名的請求了
            // ctx.set("Access-Control-Allow-Origin", "http://localhost:8080");
      
            // 設(shè)置所允許的HTTP請求方法
            ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE");
      
            // 字段是必需的压固。它也是一個逗號分隔的字符串,表明服務(wù)器支持的所有頭信息字段.
            ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
      
            // 服務(wù)器收到請求以后靠闭,檢查了Origin帐我、Access-Control-Request-Method和Access-Control-Request-Headers字段以后刘莹,確認(rèn)允許跨源請求,就可以做出回應(yīng)焚刚。
      
            // Content-Type表示具體請求中的媒體類型信息
            ctx.set("Content-Type", "application/json;charset=utf-8");
      
            // 該字段可選。它的值是一個布爾值扇调,表示是否允許發(fā)送Cookie矿咕。默認(rèn)情況下,Cookie不包括在CORS請求之中狼钮。
            // 當(dāng)設(shè)置成允許請求攜帶cookie時碳柱,需要保證"Access-Control-Allow-Origin"是服務(wù)器有的域名,而不能是"*";
            ctx.set("Access-Control-Allow-Credentials", true);
      
            // 該字段可選熬芜,用來指定本次預(yù)檢請求的有效期莲镣,單位為秒。
            // 當(dāng)請求方法是PUT或DELETE等特殊方法或者Content-Type字段的類型是application/json時涎拉,服務(wù)器會提前發(fā)送一次請求進行驗證
            // 下面的的設(shè)置只本次驗證的有效時間瑞侮,即在該時間段內(nèi)服務(wù)端可以不用進行驗證
            ctx.set("Access-Control-Max-Age", 300);
      
            /*
            CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:
            Cache-Control鼓拧、Content-Language半火、Content-Type、Expires季俩、Last-Modified钮糖、Pragma。
            */
            // 需要獲取其他字段時酌住,使用Access-Control-Expose-Headers店归,
            // getResponseHeader('myData')可以返回我們所需的值
            ctx.set("Access-Control-Expose-Headers", "myData");
            await next();
        })
      
      • 原生方法來自:node.js 應(yīng)答跨域請求實現(xiàn)(以koa2-cors為例)
      • 接著我們來看中間件吧!
      • 在Koa2中酪我,我們使用koa2-cors這個中間件來設(shè)置CORS跨域請求消痛。
      • 先安裝 npm install koa2-cors;
      • 接著,在index.js中使用都哭。
      • const KoaCors = require('koa2-cors');
        // ~~ 省略 ~~
        // CORS跨域
        app.use(KoaCors({
            origin: ctx => {
                return ctx.request.header.origin;
            },
            exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
            maxAge: 5,
            credentials: true,
            allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
            allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin'],
        }));
        //本文由郝晨光整理總結(jié)并編寫肄满,未經(jīng)允許禁止轉(zhuǎn)載。
        
      • 特別需要注意的是质涛,在app.use()使用koa2-cors的時候稠歉,我們應(yīng)該把它放在別的中間件的前邊,特別是靜態(tài)資源處理和路由處理中間件的前邊汇陆,這樣可以保證我們在跨域請求靜態(tài)資源的時候不會出問題怒炸。



如果本文對您有幫助,可以看看本人的其他文章:
前端常見面試題(十三)@郝晨光
前端常見面試題(十二)@郝晨光
前端常見面試題(十一)@郝晨光

結(jié)言
感謝您的查閱毡代,本文由郝晨光整理并總結(jié)阅羹,代碼冗余或者有錯誤的地方望不吝賜教勺疼;菜鳥一枚,請多關(guān)照
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捏鱼,一起剝皮案震驚了整個濱河市执庐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌导梆,老刑警劉巖轨淌,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異看尼,居然都是意外死亡递鹉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門藏斩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躏结,“玉大人,你說我怎么就攤上這事狰域∠彼” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵兆览,是天一觀的道長禀挫。 經(jīng)常有香客問我,道長拓颓,這世上最難降的妖魔是什么语婴? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮驶睦,結(jié)果婚禮上砰左,老公的妹妹穿的比我還像新娘。我一直安慰自己场航,他們只是感情好缠导,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溉痢,像睡著了一般僻造。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孩饼,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天髓削,我揣著相機與錄音,去河邊找鬼镀娶。 笑死立膛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宝泵,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼好啰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了儿奶?” 一聲冷哼從身側(cè)響起框往,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闯捎,沒想到半個月后椰弊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡隙券,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了闹司。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娱仔。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖游桩,靈堂內(nèi)的尸體忽然破棺而出牲迫,到底是詐尸還是另有隱情,我是刑警寧澤借卧,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布盹憎,位于F島的核電站,受9級特大地震影響铐刘,放射性物質(zhì)發(fā)生泄漏陪每。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一镰吵、第九天 我趴在偏房一處隱蔽的房頂上張望檩禾。 院中可真熱鬧,春花似錦疤祭、人聲如沸盼产。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽戏售。三九已至,卻和暖如春草穆,著一層夾襖步出監(jiān)牢的瞬間灌灾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工悲柱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留紧卒,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓诗祸,卻偏偏與公主長得像跑芳,于是被迫代替她去往敵國和親轴总。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容