歡迎光臨我的博客拓跋的前端客棧,這個(gè)是原文地址。如果您發(fā)現(xiàn)我文章中存在錯(cuò)誤,請(qǐng)盡情向我吐槽,大家一起學(xué)習(xí)一起進(jìn)步φ(>ω<*)
引子
前段時(shí)間做項(xiàng)目時(shí),由于某個(gè)請(qǐng)求在后臺(tái)要進(jìn)行長(zhǎng)時(shí)間的腳本處理,頻頻超出系統(tǒng)默認(rèn)的超時(shí)時(shí)長(zhǎng),于是我就對(duì)超時(shí)時(shí)長(zhǎng)進(jìn)行了檢查。由于超時(shí)的時(shí)候請(qǐng)求報(bào)錯(cuò)的提示信息并不明確,為了修改這玩意兒也查了一堆資料,忙活了半天,終于把超時(shí)問(wèn)題解決了,在這里做個(gè)記錄。
我的項(xiàng)目用的是web前臺(tái)(這個(gè)無(wú)所謂用的什么框架,反正歸根結(jié)底是js)+nginx代理+Node.js后臺(tái)搭建的,我發(fā)現(xiàn)每一節(jié)都設(shè)了一道timeout超時(shí)的關(guān)卡,也就是說(shuō)有客戶端超時(shí),代理超時(shí),服務(wù)端超時(shí)三種情況。下面針對(duì)每種超時(shí),我將進(jìn)行分別講解。
客戶端超時(shí)
XMLHttpRequest的超時(shí)設(shè)置
講客戶端超時(shí),就要從XMLHttpRequest來(lái)談起。
XMLHttpRequest是一個(gè)API,它為客戶端提供了在客戶端和服務(wù)器之間傳輸數(shù)據(jù)的功能。它提供了一個(gè)通過(guò)URL來(lái)獲取數(shù)據(jù)的簡(jiǎn)單方式,并且不會(huì)使整個(gè)頁(yè)面刷新。這使得網(wǎng)頁(yè)只更新一部分頁(yè)面而不會(huì)打擾到用戶。XMLHttpRequest在AJAX中被大量使用。
XMLHttpRequest一開(kāi)始只是微軟瀏覽器提供的一個(gè)接口,后來(lái)各大瀏覽器紛紛效仿也提供了這個(gè)接口,再后來(lái)W3C對(duì)它進(jìn)行了標(biāo)準(zhǔn)化,提出了XMLHttpRequest標(biāo)準(zhǔn)。XMLHttpRequest標(biāo)準(zhǔn)又分為L(zhǎng)evel 1和Level 2。
XMLHttpRequest Level 1主要存在以下缺點(diǎn):
- 受同源策略的限制,不能發(fā)送跨域請(qǐng)求;
- 不能發(fā)送二進(jìn)制文件(如圖片、視頻、音頻等),只能發(fā)送純文本數(shù)據(jù);
- 在發(fā)送和獲取數(shù)據(jù)的過(guò)程中,無(wú)法實(shí)時(shí)獲取進(jìn)度信息,只能判斷是否完成;
那么Level 2對(duì)Level 1 進(jìn)行了改進(jìn),XMLHttpRequest Level 2中新增了以下功能:
- 可以發(fā)送跨域請(qǐng)求,在服務(wù)端允許的情況下;
- 支持發(fā)送和接收二進(jìn)制數(shù)據(jù);
- 新增formData對(duì)象,支持發(fā)送表單數(shù)據(jù);
- 發(fā)送和獲取數(shù)據(jù)時(shí),可以獲取進(jìn)度信息;
- 可以設(shè)置請(qǐng)求的超時(shí)時(shí)間;
在Level 2版本的XMLHttpRequest對(duì)象,增加了timeout屬性,可以設(shè)置HTTP請(qǐng)求的時(shí)限。
xhr.timeout = 60*1000;
xhr.ontimeout = function(event){
alert('timeout!');
}
在上述代碼中,請(qǐng)求超時(shí)時(shí)間被設(shè)為1分鐘(xhr.timeout的單位是毫秒),如果超過(guò)1分鐘的時(shí)限,則會(huì)自動(dòng)停止http請(qǐng)求;同時(shí),觸發(fā)ontimeout事件,彈出“timeout!”的提示框。
同時(shí)值得注意的一點(diǎn)是,超時(shí)時(shí)間的計(jì)算,是從調(diào)用xhr.send()開(kāi)始,至xhr.loadend觸發(fā)為止的這段時(shí)間。即時(shí)xhr.timeout的設(shè)置是在xhr.send()之后,timeout的計(jì)時(shí)起點(diǎn)仍為調(diào)用xhr.send()的時(shí)刻。
其他超時(shí)設(shè)置
其實(shí)會(huì)了XMLHttpRequest的超時(shí)設(shè)置,其他前端的框架啊、工具啊的超時(shí)設(shè)置都不再是問(wèn)題,這就有點(diǎn)萬(wàn)法歸宗的意思。因?yàn)槲覀兂S玫膉Query.ajax()方法實(shí)際上就是對(duì)瀏覽器提供的XMLHttpRequest對(duì)象的封裝。而又有很多其他框架或者工具的請(qǐng)求模塊是對(duì)jQuery.ajax()的封裝。說(shuō)到底,都是依賴(lài)的XMLHttpRequest對(duì)象。因此掌握了XMLHttpRequest,其他都很好學(xué)會(huì)。
依jQuery.ajax()的超時(shí)設(shè)置為例:
$.ajax({
url: "test.html",
error: function(){
// will fire when timeout is reached
},
success: function(){
// do something
},
timeout: 60*1000 // sets timeout to 1 minute
});
實(shí)在是簡(jiǎn)單,對(duì)不對(duì)?
代理超時(shí)
nginx的超時(shí)設(shè)置主要分3種:
- proxy_connect_timeout
- proxy_read_timeout
- proxy_send_timeout
proxy_connect_timeout
語(yǔ)法: proxy_connect_timeout timeout_in_seconds
上下文: http, server, location
默認(rèn)值: 60s
proxy_connect_timeout是和后端建立連接的超時(shí)時(shí)間。需要記住的是,這個(gè)時(shí)間不能超過(guò)75秒。
這個(gè)不是等待后端返回頁(yè)面的時(shí)間,那是由proxy_read_timeout聲明的。如果你的upstream服務(wù)器起來(lái)了,但是掛起了(例如,沒(méi)有足夠的線程處理請(qǐng)求,所以把你的請(qǐng)求放到請(qǐng)求池里稍后處理),那么這個(gè)聲明是沒(méi)有用的,由于與upstream服務(wù)器的連接已經(jīng)建立了。
proxy_read_timeout
語(yǔ)法: proxy_read_timeout the_time
上下文: http, server, location
默認(rèn)值: 60s
proxy_read_timeout是從后端讀取數(shù)據(jù)的超時(shí)時(shí)間,兩次讀取操作的時(shí)間間隔如果大于這個(gè)值,和后端的連接會(huì)被關(guān)閉。如果一個(gè)請(qǐng)求時(shí)間時(shí)間非常大,要把這個(gè)值設(shè)大點(diǎn)。
proxy_send_timeout
語(yǔ)法: proxy_send_timeout time
上下文: http, server, location
默認(rèn)值: 60s
proxy_send_timeout是向后端寫(xiě)數(shù)據(jù)的超時(shí)時(shí)間,兩次寫(xiě)操作的時(shí)間間隔大于這個(gè)值,也就是過(guò)了這么長(zhǎng)時(shí)間后端還是沒(méi)有收到數(shù)據(jù),連接會(huì)被關(guān)閉。
服務(wù)端超時(shí)
Node.js做服務(wù)器時(shí),默認(rèn)的超時(shí)時(shí)長(zhǎng)為2分鐘。假設(shè)請(qǐng)求發(fā)送到服務(wù)端,在2分鐘內(nèi)沒(méi)有響應(yīng)返回給客戶端,客戶端的鏈接就會(huì)被重置。這個(gè)時(shí)長(zhǎng)的設(shè)置是很必要的,因?yàn)檫^(guò)長(zhǎng)的請(qǐng)求響應(yīng)會(huì)阻塞IO,造成極差的用戶體驗(yàn)。特別對(duì)于Node.js這種單線程應(yīng)用來(lái)說(shuō),影響可以說(shuō)是災(zāi)難性的,因此如何設(shè)置服務(wù)端的超時(shí)時(shí)間也是非常關(guān)鍵的。
服務(wù)端超時(shí)設(shè)置也不是一刀切的,總體來(lái)說(shuō),有一個(gè)總開(kāi)關(guān),還可以對(duì)每個(gè)服務(wù)端的請(qǐng)求或者響應(yīng)單獨(dú)設(shè)置超時(shí)時(shí)長(zhǎng)。
我們后面的代碼均以Node.js+Express搭建的后臺(tái)為例。
服務(wù)端超時(shí)總開(kāi)關(guān)
使用Node.js的http模塊啟動(dòng)服務(wù)器,并設(shè)置超時(shí)時(shí)長(zhǎng):
const express = require('express');
const config = require(./config);
process.env.PORT = config.webport || 8888;
let app = express();
let server = app.listen(process.env.PORT);
server.setTimeout(60*1000);
server.on('timeout', function(){
console.log('timeout!')
})
server.setTimeout()是設(shè)置server的超時(shí)時(shí)間設(shè)置方法,單位是毫秒,一旦超過(guò)設(shè)置的超時(shí)時(shí)長(zhǎng),則觸發(fā)server對(duì)象的'timeout'事件,并傳入socket作為一個(gè)參數(shù)。
設(shè)置Express中間件超時(shí)時(shí)間
Express是一個(gè)路由和中間件Web框架,其自身只具有最低程度的功能:Express應(yīng)用程序基本上是一系列中間件函數(shù)調(diào)用。
中間件函數(shù)能夠訪問(wèn)請(qǐng)求對(duì)象 (req)、響應(yīng)對(duì)象 (res) 以及應(yīng)用程序的請(qǐng)求/響應(yīng)循環(huán)中的下一個(gè)中間件函數(shù)。下一個(gè)中間件函數(shù)通常由名為 next 的變量來(lái)表示。
中間件函數(shù)可以執(zhí)行以下任務(wù):
- 執(zhí)行任何代碼。
- 對(duì)請(qǐng)求和響應(yīng)對(duì)象進(jìn)行更改。
- 結(jié)束請(qǐng)求/響應(yīng)循環(huán)。
- 調(diào)用堆棧中的下一個(gè)中間件函數(shù)。
如果當(dāng)前中間件函數(shù)沒(méi)有結(jié)束請(qǐng)求/響應(yīng)循環(huán),那么它必須調(diào)用next(),以將控制權(quán)傳遞給下一個(gè)中間件函數(shù)。否則,請(qǐng)求將保持掛起狀態(tài)。
對(duì)于Express的中間件,我們可以在app.js里為每個(gè)路由及其處理程序函數(shù)的中間件設(shè)置請(qǐng)求的響應(yīng)和超時(shí)時(shí)間,
app.get('/user/:id', function (req, res, next) {
// 設(shè)置該中間件下所有HTTP請(qǐng)求的超時(shí)時(shí)間
req.setTimeout(60*1000);
// 設(shè)置該中間件下所有HTTP請(qǐng)求的服務(wù)器響應(yīng)超時(shí)時(shí)間
res.setTimeout(60*1000);
res.send('USER');
});
Express不僅可以針對(duì)中間件設(shè)置超時(shí)時(shí)間,還可以針對(duì)某個(gè)具體的接口設(shè)置請(qǐng)求和響應(yīng)的超時(shí)時(shí)長(zhǎng):
router.get('/user/:id', function (req, res, next) {
// 設(shè)置該接口下所有HTTP請(qǐng)求的超時(shí)時(shí)間
req.setTimeout(60*1000);
// 設(shè)置該接口下所有HTTP請(qǐng)求的服務(wù)器響應(yīng)超時(shí)時(shí)間
res.setTimeout(60*1000);
res.send('USER');
});
小結(jié)
全寫(xiě)完了以后,仔細(xì)想了想,發(fā)現(xiàn)還沒(méi)有寫(xiě)全,比如說(shuō)服務(wù)端也可以發(fā)請(qǐng)求,不管是用http.method還是用request模塊,都可以發(fā)請(qǐng)求。由于Node.js在很多項(xiàng)目中都是作為中間層,不管是向JAVA后臺(tái)請(qǐng)求數(shù)據(jù),還是用作解決客戶端跨域問(wèn)題的請(qǐng)求中轉(zhuǎn)層,發(fā)請(qǐng)求都是很常用的用法。但是只要讀者能耐心讀到這里,相信隨手網(wǎng)上搜一下就知道怎么寫(xiě)了,因此我這里就不再贅述了。
寫(xiě)這篇文章主要想分享一下我的感受,超時(shí)限制就像一道關(guān)卡,一個(gè)請(qǐng)求從客戶端到服務(wù)端,再?gòu)姆?wù)端返回客戶端的路上,要“過(guò)五關(guān)斬六將”。有的時(shí)候想要修改超時(shí)設(shè)置,可能不是簡(jiǎn)單修改某一處就能解決問(wèn)題的,這時(shí)候一定要仔細(xì)檢查,把這個(gè)系統(tǒng)理一遍,每一個(gè)地方的超時(shí)設(shè)置都看一看,才能解決問(wèn)題~