最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Koa 使用小技巧(小結(jié))

 更新時間:2018年10月22日 08:28:37   作者:vicanso  
這篇文章主要介紹了Koa 使用小技巧(小結(jié)),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

cookie的安全保護(hù)

基于cookie來驗證用戶狀態(tài)的系統(tǒng)中,如何提高cookie的安全級別是首要因素,最簡單直接的方式就生成的cookie值隨機(jī)而且復(fù)雜。一般使用uuid來生成cookie,生成的隨機(jī)串在復(fù)雜度上已滿足需求,但是如果真被攻擊者嘗試到一個可用的值,那怎么防范呢?使用signed的cookie設(shè)置,如下所示:

app.keys = ["token"];

...

ctx.cookies.set("jt", "abcd", {
 signed: true,
});

在設(shè)置 jt 這個cookie的時候,koa會以 jt 的值 abcd 加上設(shè)置的密鑰,生成校驗值,并寫入至 jt.sig 這個cookie中,所以能看到響應(yīng)的HTTP頭中如下所示:

Set-Cookie: jt=abcd; path=/; httponly
Set-Cookie: jt.sig=gpDbdxr25sarDhE_1yMSAnIn_bU; path=/; httponly

在后續(xù)的請求中,獲取 jt 這個cookie時,則會根據(jù) jt.sig 的值判斷是否合法,安全性上又明顯提升。

那么 app.keys 為什么是設(shè)計為數(shù)組呢?先來考慮以下的一種場景,當(dāng)希望更換密鑰的時候,原有的的cookie都將因為密鑰更新而導(dǎo)致校驗失敗,則用戶的登錄狀態(tài)失效。一次還好,如果需要經(jīng)常需要更新密鑰(我一般一個月更換一次),那怎么處理好?這就是 app.keys 為配置為數(shù)組的使用邏輯了。

當(dāng)生成cookie時,使用keys中的第一個元素來生成,而校驗的時候,是從第一個至最后一個,一個個的校驗,直到通過為止,所以在更新密鑰的時候,只需要把新的密鑰加到數(shù)組第一位則可以。我一般再保留兩組密鑰,因為更新是一個月一次,因此如果客戶的cookie是三個月前生成的,那就會失效了。

cookie的校驗是基于 keygrip 來處理的,大家也可以使用它來做自己的一些數(shù)據(jù)校驗,如驗證碼之類。

異常處理

在使用koa時,一般出錯都是使用 ctx.throw 來拋出一個error,中斷處理流程,接口響應(yīng)出錯,處理邏輯如下所未:

app.on('error', (err, ctx) => {
 // 記錄異常日志
 console.error(err);
});

app.use((ctx) => {
 ctx.throw(400, '參數(shù)錯誤');
});

此處只利用了koa自帶的異常出錯,過于簡單,我們希望能針對主動拋出的異常與程序異常能加以區(qū)分,因此需要自定義異常處理的中間件,如下:

app.on('error', (err, ctx) => {
 // 記錄異常日志
 console.error(err);
});

app.use(async(ctx, next) => {
 try {
  await next()
 } catch (err) {
  let status = 500;
  const message = err.message;
  // koa的throw使用http-errors來生成error
  // 此處只判斷是否有status,有則認(rèn)為是http-errors
  if (err.status) {
   status = err.status
  } else {
   // 非主動拋出異常,則觸發(fā)error事件,記錄異常日志
   ctx.app.emit("error", err, ctx);
  }
  ctx.status = status;
  ctx.body = {
   message,
  };
 }
})

app.use((ctx) => {
 // 代碼異常
 // ctx.i.j = 0;
 // 主動拋出異常
 ctx.throw(400, '參數(shù)錯誤');
});

通過此調(diào)整后,將邏輯主動拋出異常與程序異常區(qū)分開,定時去查看異常日志,減少程序異常。此例子只是簡單的使用了http-errors來創(chuàng)建主動拋出的異常,在實際使用中,可以根據(jù)自己的場景創(chuàng)建自定義的Error類,定制相應(yīng)的異常信息。

當(dāng)前正在處理請求數(shù)

得益于nodejs的IO處理,koa在高并發(fā)的場景下的CPU、內(nèi)存都占用并不高,但是也因為這樣,如果只通過CPU、內(nèi)存來監(jiān)控程序運行狀態(tài)并不全面,因此需要增加當(dāng)前處理請求數(shù)的監(jiān)控,代碼如下:

let processingCount = 0;
const maxProcessingCount = 1000;
app.use(async (ctx, next) => {
 processingCount++;
 if (processingCount > maxProcessingCount) {
  // 如果需要也可以直接在處理請求超時時,直接出錯
  console.error("processing request over limit");
 }
 try {
  await next();
 } catch (err) {
  throw err; 
 } finally {
  processingCount--;
 }
});

app.use(async (ctx) => {
 // 延時一秒
 await new Promise(resolve => setTimeout(resolve, 1000));
 ctx.body = {
  account: 'vicanso',
 };
});

此中間件在接收到請求時,將處理請求數(shù)加一,在處理完成后減一。最大的處理請求數(shù)根據(jù)系統(tǒng)的性能與用戶數(shù)量選擇合理的值。如果接口處理慢或者突然并發(fā)請求暴漲的時,可以盡早得知異常情況,盡早排查。

延時響應(yīng)

接口的處理一般而言都是希望越快越好,但有些場景我們不希望接口響應(yīng)的太快(如注冊),避免惡意者迅速嘗試功能,因此需要一個延時響應(yīng)的中間件,代碼如下:

function delayResponse(delayMs) {
 const delay = (t) => {
  const d = delayMs - (Date.now() - t);
  // 如果處理時長已超過delayMs,無需等待
  if (d <= 0) {
   return Promise.resolve();
  }
  return new Promise(resolve => setTimeout(resolve, d));
 }
 return async(ctx, next) => {
  const startedAt = Date.now();
  try {
   await next();
   // 成功處理時等待
   await delay(startedAt);
  } catch (err) {
   // 失敗時也等待
   await delay(startedAt);
   throw err;
  }
 }
}

router.post('/users/v1/register', delayResponse(1000), (ctx) => {
 ctx.body = {
  account: 'vicanso',
 };
});

通過此中間件,可以限制某些功能的響應(yīng)時長(保證每次處理時間都大于期望值),需要注意的是,延時響應(yīng)的不要超過全局的超時配置。

接口性能統(tǒng)計

系統(tǒng)是否穩(wěn)定,性能是否需要優(yōu)化等都依賴于統(tǒng)計,為了能及時反應(yīng)出系統(tǒng)狀態(tài),并方便添加告警指標(biāo),我將相關(guān)的統(tǒng)計數(shù)據(jù)寫入influxdb,主要指標(biāo)如下:

tags:

  • method,請求類型
  • type,根據(jù)響應(yīng)狀態(tài)碼分組,1xx -> 1, 2xx -> 2
  • spdy,根據(jù)自定義的響應(yīng)時間劃分區(qū)間,方便將接口響應(yīng)時間分組
  • route,接口路由

fields:

  • connecting,處理請求數(shù)
  • use,處理時長
  • bytes,響應(yīng)數(shù)字長度
  • code,響應(yīng)狀態(tài)碼
  • url,請求地址
  • ip,用戶IP

在influxdb中,tags可用于對數(shù)據(jù)分組,根據(jù) type 將接口請求分組,將 4 與 5 的單獨監(jiān)控,可以簡單快速的把當(dāng)前接口出錯匯總。統(tǒng)計中間件代碼如下:

function stats() {
 let connecting = 0;
 const spdyList = [
  100,
  300,
  1000,
  3000,
 ];
 return async (ctx, next) => {
  const start = Date.now();
  const tags = {
   method: ctx.method,
  };
  connecting++;
  const fields = {
   connecting,
   url: ctx.url,
  }
  let status = 0;
  try {
   await next();
  } catch (err) {
   // 出錯時狀態(tài)碼從error中獲取
   status = err.status;
   throw err;
  } finally {
   // 如果非出錯,則從ctx中取狀態(tài)碼
   if (!status) {
    status = ctx.status;
   }
   const use = Date.now() - start;
   connecting--;
   tags.route = ctx._matchedRoute;
   tags.type = `${status / 100 | 0}`
   let spdy = 0;
   // 確認(rèn)處理時長所在區(qū)間
   spdyList.forEach((v, i) => {
    if (use > v) {
     spdy = i + 1;
    }
   });
   tags.spdy = `${spdy}`;

   fields.use = use;
   fields.bytes = ctx.length || 0;
   fields.code = status;
   fields.ip = ctx.ip;
   // 統(tǒng)計數(shù)據(jù)寫入統(tǒng)計系統(tǒng)(如influxdb)
   console.info(tags);
   console.info(fields);
  }
 };
}

app.use(stats());

router.post('/users/v1/:type', async (ctx) => {
 await new Promise(resolve => setTimeout(resolve, 100))
 ctx.body = {
  account: 'vicanso',
 };
});

接口全日志記錄

為了方便排查問題,需要將接口的相關(guān)信息輸出至日志中,中間件的實現(xiàn)如下:

function tracker() {
 const stringify = (data) => JSON.stringify(data, (key, value) => {
  // 對于隱私數(shù)據(jù)做***處理
  if (/password/.test(key)) {
   return '***';
  }
  return value;
 });
 return async (ctx, next) => {
  const trackerInfo = {
   url: ctx.url,
   form: ctx.request.body,
  };
  try {
   await next();
  } catch (err) {
   trackerInfo.error = err.message;
   throw err;
  } finally {
   trackerInfo.params = ctx.params;
   if (!trackerInfo.error) {
    trackerInfo.body = ctx.body;
   }
   console.info(stringify(trackerInfo))
  }
 };
}

app.use(bodyParser());
app.use(tracker());

router.post('/users/v1/:type', async (ctx) => {
 // ctx.throw(400, '密碼出錯');
 await new Promise(resolve => setTimeout(resolve, 100))
 ctx.body = {
  account: 'vicanso',
 };
});

使用此中間件之后,可以將所有接口的參數(shù)、正常響應(yīng)數(shù)據(jù)或出錯信息都全部輸出至日志中,可根據(jù)需要調(diào)整 stringify 的實現(xiàn),將一些隱私數(shù)據(jù)做***處理。需要注意的是,由于部分接口的body響應(yīng)體部分較大,是否需要將所有數(shù)據(jù)都輸出至日志最好根據(jù)實際情況衡量。如可根據(jù)HTTP Method過濾,或者根據(jù)url規(guī)則等。

參數(shù)校驗

由于javascript的弱類型,接口參數(shù)校驗一直是要求最嚴(yán)格的一點,而在了解過 joi 之后,我就一直使用它來做參數(shù)校驗,如注冊功能,賬號、密碼為必選參數(shù),而郵箱為可選,接口校驗的代碼如下:

function validate(data, schema) {
 const result = Joi.validate(data, schema);
 if (result.error) {
  // 出錯可創(chuàng)建自定義的校驗出錯類型
  throw result.error;
 }
 return result.value;
}

router.post('/users/v1/register', async (ctx) => {
 const data = validate(ctx.request.body, Joi.object({
  // 賬號限制長度為3-20個字符串
  account: Joi.string().min(3).max(20).required(),
  // 密碼限制長度為6-30,而且只允許字母與數(shù)字
  password: Joi.string().regex(/^[a-zA-Z0-9]{6,30}$/).required(),
  email: Joi.string().email().optional(),
 }));
 ctx.body = {
  account: data.account,
 };
});

通過joi簡單快捷實現(xiàn)了參數(shù)的校驗,不過在實際使用中,有部分的參數(shù)校驗規(guī)則是通用的,如賬號、密碼這些的校驗規(guī)則在注冊和登錄中都通過,但是有些接口是可選,有一些是必須,怎么才能更通用一些呢?代碼調(diào)整如下:

const userSchema = {
 // 賬號限制長度為3-20個字符串
 account: () => Joi.string().min(3).max(20),
 // 密碼限制長度為6-30,而且只允許字母與數(shù)字
 password: () => Joi.string().regex(/^[a-zA-Z0-9]{6,30}$/),
 email: () => Joi.string().email(),
}

router.post('/users/v1/register', async (ctx) => {
 const data = validate(ctx.request.body, Joi.object({
  account: userSchema.account().required(),
  password: userSchema.password().required(),
  email: userSchema.email().optional(),
 }));
 ctx.body = {
  account: data.account,
 };
});

經(jīng)此調(diào)整后,將用戶參數(shù)校驗的基本規(guī)則都定義在 userSchema 中,每個接口在各自的場景下選擇不同的參數(shù)以及增加規(guī)則,提高代碼復(fù)用率以及校驗準(zhǔn)確性。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • nvm報錯Now?using?node?v版本號(64-bit)圖文解決方法

    nvm報錯Now?using?node?v版本號(64-bit)圖文解決方法

    這篇文章主要給大家介紹了關(guān)于nvm報錯Now?using?node?v版本號(64-bit)的解決方法,文中將解決的辦法介紹的非常詳細(xì),對遇到這個問題的朋友具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-11-11
  • 手把手教你如何使用nodejs編寫cli命令行

    手把手教你如何使用nodejs編寫cli命令行

    這篇文章主要介紹了手把手教你如何使用nodejs編寫cli命令行,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-11-11
  • nodejs發(fā)布靜態(tài)https服務(wù)器的方法

    nodejs發(fā)布靜態(tài)https服務(wù)器的方法

    這篇文章主要介紹了nodejs發(fā)布靜態(tài)https服務(wù)器的方法,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-09-09
  • 利用npm 安裝刪除模塊的方法

    利用npm 安裝刪除模塊的方法

    今天小編就為大家分享一篇利用npm 安裝刪除模塊的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • node.js通過axios實現(xiàn)網(wǎng)絡(luò)請求的方法

    node.js通過axios實現(xiàn)網(wǎng)絡(luò)請求的方法

    下面小編就為大家分享一篇node.js通過axios實現(xiàn)網(wǎng)絡(luò)請求的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • 深入理解Node.js中通用基礎(chǔ)設(shè)計模式

    深入理解Node.js中通用基礎(chǔ)設(shè)計模式

    大家在談到設(shè)計模式時最先想到的就是 singletons, observers(觀察者) 或 factories(工廠方法)。本文重點給大家介紹Node.JS一些基礎(chǔ)模式的實現(xiàn)方法,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧
    2017-09-09
  • Node.js中的WebSocket底層實現(xiàn)

    Node.js中的WebSocket底層實現(xiàn)

    WebSockets是基于HTTP的雙向通信協(xié)議,允許客戶端和服務(wù)器之間實現(xiàn)實時、持久的數(shù)據(jù)交換,本文詳細(xì)介紹了使用JavaScript和Node.js創(chuàng)建WebSockets服務(wù)器和客戶端的過程,感興趣的可以了解一下
    2024-10-10
  • node如何實現(xiàn)cmd彈窗交互之inquirer

    node如何實現(xiàn)cmd彈窗交互之inquirer

    這篇文章主要介紹了node如何實現(xiàn)cmd彈窗交互之inquirer問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • 監(jiān)控Nodejs的性能實例代碼

    監(jiān)控Nodejs的性能實例代碼

    這篇文章主要介紹了監(jiān)控Nodejs的性能實例代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-07-07
  • node.js正則表達(dá)式獲取網(wǎng)頁中所有鏈接的代碼實例

    node.js正則表達(dá)式獲取網(wǎng)頁中所有鏈接的代碼實例

    這篇文章主要介紹了node.js正則表達(dá)式獲取網(wǎng)頁中所有鏈接的代碼實例,使用正則表達(dá)式實現(xiàn),需要的朋友可以參考下
    2014-06-06

最新評論

武穴市| 韩城市| 阿合奇县| 安新县| 伊宁县| 手游| 嘉定区| 江都市| 海阳市| 湟中县| 琼结县| 唐河县| 遵义市| 罗源县| 平遥县| 绵阳市| 建宁县| 泾源县| 从化市| 宁河县| 京山县| 保德县| 梅州市| 右玉县| 临武县| 奉化市| 东光县| 赤壁市| 银川市| 新乐市| 偃师市| 商南县| 秦皇岛市| 平远县| 亳州市| 手机| 宁德市| 天门市| 布拖县| 绥阳县| 麻江县|