Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
CSS in JS是一種解決css問題想法的集合,而不是一個指定的庫。從CSS in JS的字面意思可以看出,它是將css樣式寫在JavaScript文件中,而不需要獨立出.css、.less之類的文件。將css放在js中使我們更方便的使用js的變量、模塊化、tree-shaking。還解決了css中的一些問題,譬如:更方便解決基于狀態(tài)的樣式,更容易追溯依賴關(guān)系,生成唯一的選擇器來鎖定作用域。盡管CSS in JS不是一個很新的技術(shù),但國內(nèi)的普及程度并不高。由于Vue和Angular都有屬于他們自己的一套定義樣式的方案,React本身也沒有管用戶怎樣定義組件的樣式[1],所以CSS in JS在React社區(qū)的熱度比較高。
目前為止實現(xiàn)CSS in JS的第三方庫有很多:(http://michelebertoli.github.io/css-in-js/)。像JSS[2]、styled-components[3]等。在這里我們就不展開贅述了(相關(guān)鏈接已放在下方),這篇文章的重點是JS in CSS。
在上面我們提到CSS in JS就是把CSS寫在JavaScript中,那么JS in CSS我們可以推斷出就是可以在CSS中使用JavaScript腳本,如下所示。可以在CSS中編寫Paint API的功能。還可以訪問:ctx,geom。甚至我們還可以編寫自己的css自定義屬性等。這些功能的實現(xiàn)都基于CSS Houdini[4]。
.el {
--color: cyan;
--multiplier: 0.24;
--pad: 30;
--slant: 20;
--background-canvas: (ctx, geom) => {
let multiplier = var(--multiplier);
let c = `var(--color)`;
let pad = var(--pad);
let slant = var(--slant);
ctx.moveTo(0, 0);
ctx.lineTo(pad + (geom.width - slant - pad) * multiplier, 0);
ctx.lineTo(pad + (geom.width - slant - pad) * multiplier + slant, geom.height);
ctx.lineTo(0, geom.height);
ctx.fillStyle = c;
ctx.fill();
};
background: paint(background-canvas);
transition: --multiplier .4s;
}
.el:hover {
--multiplier: 1;
}
在如今的Web開發(fā)中,JavaScript幾乎占據(jù)了項目代碼的大部分。我們可以在項目開發(fā)中使用ES 2020、ES2021、甚至提案中的新特性(如:Decorator[5]),即使瀏覽器尚未支持,也可以編寫Polyfill或使用Babel之類的工具進行轉(zhuǎn)譯,讓我們可以將最新的特性應(yīng)用到生產(chǎn)環(huán)境中(如下圖所示)。
JavaScript標(biāo)準(zhǔn)制定流程.png
而CSS就不同了,除了制定CSS標(biāo)準(zhǔn)規(guī)范所需的時間外,各家瀏覽器的版本、實戰(zhàn)進度差異更是曠日持久(如下圖所示),最多利用PostCSS、Sass等工具來幫我們轉(zhuǎn)譯出瀏覽器能接受的CSS。開發(fā)者們能操作的就是通過JS去控制DOM與CSSOM來影響頁面的變化,但是對于接下來的Layout、Paint與Composite就幾乎沒有控制權(quán)了。為了解決上述問題,為了讓CSS的魔力不在受到瀏覽器的限制,Houdini就此誕生。
CSS 標(biāo)準(zhǔn)制定流程.png
我們上文中提到JavaScript中進入提案中的特性我們可以編寫Polyfill,只需要很短的時間就可以講新特性投入到生產(chǎn)環(huán)境中。這時,腦海中閃現(xiàn)出的第一個想法就是CSS Polyfill,只要CSS的Polyfill 足夠強大,CSS或許也能有JavaScript一樣的發(fā)展速度,令人可悲的是編寫CSS Polyfill異常的困難,并且大多數(shù)情況下無法在不破壞性能的情況下進行。這是因為JavaScript是一門動態(tài)腳本語言[6]。它帶來了極強的擴展性,正是因為這樣,我們可以很輕松使用JavaScript做出JavaScript的Polyfill。但是CSS不是動態(tài)的,在某些場景下,我們可以在編譯時將一種形式的CSS的轉(zhuǎn)換成另一種(如PostCSS[7])。如果你的Polyfill依賴于DOM結(jié)構(gòu)或者某一個元素的布局、定位等,那么我們的Polyfill就無法編譯時執(zhí)行,而需要在瀏覽器中運行了。不幸的是,在瀏覽器中實現(xiàn)這種方案非常不容易。
頁面渲染流程.png
如上圖所示,是從瀏覽器獲取到HTML到渲染在屏幕上的全過程,我們可以看到只有帶顏色(粉色、藍色)的部分是JavaScript可以控制的環(huán)節(jié)。首先我們根本無法控制瀏覽器解析HTML與CSS并將其轉(zhuǎn)化為DOM與CSSOM的過程,以及Cascade,Layout,Paint,Composite我們也無能為力。整個過程中我們唯一完全可控制的就是DOM,另外CSSOM部分可控。
CSS Houdini草案中提到,這種程度的暴露是不確定的、兼容性不穩(wěn)定的以及缺乏對關(guān)鍵特性的支持的。比如,在瀏覽器中的 CSSOM 是不會告訴我們它是如何處理跨域的樣式表,而且對于瀏覽器無法解析的 CSS 語句它的處理方式就是不解析了,也就是說——如果我們要用 CSS polyfill讓瀏覽器去支持它尚且不支持的屬性,那就不能在 CSSOM 這個環(huán)節(jié)做,我們只能遍歷一遍DOM,找到 <style> 或 <link rel="stylesheet"> 標(biāo)簽,獲取其中的 CSS 樣式、解析、重寫,最后再加回 DOM 樹中。令人尷尬的是,這樣DOM樹全部刷新了,會導(dǎo)致頁面的重新渲染(如下如所示)。
即便如此,有的人可能會說:“除了這種方法,我們也別無選擇,更何況對網(wǎng)站的性能也不會造成很大的影響”。那么對于部分網(wǎng)站是這樣的。但如果我們的Polyfill是需要對可交互的頁面呢?例如scroll,resize,mousemove,keyup等等,這些事件隨時會被觸發(fā),那么意味著隨時都會導(dǎo)致頁面的重新渲染,交互不會像原本那樣絲滑,甚至導(dǎo)致頁面崩潰,對用戶的體驗也極其不好。
綜上所述,如果我們想讓瀏覽器解析它不認(rèn)識的樣式(低版本瀏覽器使用grid布局),然而渲染流程我們無法介入,我們也只能通過手動更新DOM的方式,這樣會帶來很多問題,Houdini的出現(xiàn)正是致力于解決他們。
Houdini是一組底層API,它公開了CSS引擎的各個部分,如下圖所示展示了每個環(huán)節(jié)對應(yīng)的新API(灰色部分各大瀏覽器還未實現(xiàn)),從而使開發(fā)人員能夠通過加入瀏覽器渲染引擎的樣式和布局過程來擴展CSS。Houdini是一群來自Mozilla,Apple,Opera,Microsoft,HP,Intel和Google的工程師組成的工作小組設(shè)計而成的。它們使開發(fā)者可以直接訪問CSS對象模型(CSSOM),使開發(fā)人員可以編寫瀏覽器可以解析為CSS的代碼,從而創(chuàng)建新的CSS功能,而無需等待它們在瀏覽器中本地實現(xiàn)。
CSS Houdini-API
盡管當(dāng)前已經(jīng)有了CSS變量,可以讓開發(fā)者控制屬性值,但是無法約束類型或者更嚴(yán)格的定義,CSS Houdini新的API,我們可以擴展css的變量,我們可以定義CSS變量的類型,初始值,繼承。它是css變量更強大靈活。
CSS變量現(xiàn)狀:
.dom {
--my-color: green;
--my-color: url('not-a-color'); // 它并不知道當(dāng)前的變量類型
color: var(--my-color);
}
Houdini提供了兩種自定義屬性的注冊方式,分別是在js和css中。
CSS.registerProperty({
name: '--my-prop', // String 自定義屬性名
syntax: '<color>', // String 如何去解析當(dāng)前的屬性,即屬性類型,默認(rèn) *
inherits: false, // Boolean 如果是true,子節(jié)點將會繼承
initialValue: '#c0ffee', // String 屬性點初始值
});
我們還可以在css中注冊,也可以達到上面的效果
@property --my-prop {
syntax: '<color>';
inherits: false;
initial-value: #c0ffee;
}
這個API中最令人振奮人心的功能是自定義屬性上添加動畫,像這樣:transition: --multiplier 0.4s;,這個功能我們在前面介紹什么是js in css那個demo[8]用使用過。我們還可以使用+使syntax屬性支持一個或多個類型,也可以使用|來分割。更多syntax屬性值:
屬性值描述<length>長度值<number>數(shù)字<percentage>百分比<length-percentage>長度或百分比,calc將長度和百分比組成的表達式<color>顏色<image>圖像<url>網(wǎng)址<integer>整數(shù)<angle>角度<time>時間<resolution>分辨率<transform-list>轉(zhuǎn)換函數(shù)<custom-ident>ident
Worklets是渲染引擎的擴展,從概念上來講它類似于Web Workers[9],但有幾個重要的區(qū)別:
Worklet是一個JavaScript模塊,通過調(diào)用worklet的addModule方法(它是個Promise)來添加。比如registerLayout,registerPaint, registerAnimator 我們都需要放在Worklet中
//加載單個
await demoWorklet.addModule('path/to/script.js');
// 一次性加載多個worklet
Promise.all([
demoWorklet1.addModule('script1.js'),
demoWorklet2.addModule('script2.js'),
]).then(results => {});
registerDemoWorklet('name', class {
// 每個Worklet可以定義要使用的不同函數(shù)
// 他們將由渲染引擎在需要時調(diào)用
process(arg) {
return !arg;
}
});
Worklets的生命周期
Worklets lifecycle
Typed OM是對現(xiàn)有的CSSOM的擴展,并實現(xiàn) Parsing API 和 Properties & Values API相關(guān)的特性。它將css值轉(zhuǎn)化為有意義類型的JavaScript的對象,而不是像現(xiàn)在的字符串。如果我們嘗試將字符串類型的值轉(zhuǎn)化為有意義的類型并返回可能會有很大的性能開銷,因此這個API可以讓我們更高效的使用CSS的值。
現(xiàn)在讀取CSS值增加了新的基類CSSStyleValue,他有許多的子類可以更加精準(zhǔn)的描述css值的類型:
子類描述CSSKeywordValueCSS關(guān)鍵字和其他標(biāo)識符(如inherit或grid)CSSPositionValue位置信息 (x,y)CSSImageValue表示圖像的值屬性的對象CSSUnitValue表示為具有單個單位的單個值(例如50px),也可以表示為沒有單位的單個值或百分比CSSMathValue比較復(fù)雜的數(shù)值,比如有calc,min和max。這包括子類 CSSMathSum, CSSMathProduct, CSSMathMin,CSSMathMax, CSSMathNegate 和 CSSMathInvertCSSTransformValue由CSS transforms組成的CSSTransformComponent列表,其中包括CSSTranslate, CSSRotate, CSSScale, CSSSkew, CSSSkewX, CSSSkewY, CSSPerspective 和 CSSMatrixComponent
使用Typed OM主要有兩種方法:
使用attributeStyleMap設(shè)置并獲取
myElement.attributeStyleMap.set('font-size', CSS.em(2));
myElement.attributeStyleMap.get('font-size'); // CSSUnitValue { value: 2, unit: 'em' }
myElement.attributeStyleMap.set('opacity', CSS.number(.5));
myElement.attributeStyleMap.get('opacity'); // CSSUnitValue { value: 0.5, unit: 'number' };
在線demo[10]
使用computedStyleMap
.foo {
transform: translateX(1em) rotate(50deg) skewX(10deg);
vertical-align: baseline;
width: calc(100% - 3em);
}
const cs = document.querySelector('.foo').computedStyleMap();
cs.get('vertical-align');
// CSSKeywordValue {
// value: 'baseline',
// }
cs.get('width');
// CSSMathSum {
// operator: 'sum',
// length: 2,
// values: CSSNumericArray {
// 0: CSSUnitValue { value: -90, unit: 'px' },
// 1: CSSUnitValue { value: 100, unit: 'percent' },
// },
// }
cs.get('transform');
// CSSTransformValue {
// is2d: true,
// length: 3,
// 0: CSSTranslate {
// is2d: true,
// x: CSSUnitValue { value: 20, unit: 'px' },
// y: CSSUnitValue { value: 0, unit: 'px' },
// z: CSSUnitValue { value: 0, unit: 'px' },
// },
// 1: CSSRotate {...},
// 2: CSSSkewX {...},
// }
開發(fā)者可以通過這個API實現(xiàn)自己的布局算法,我們可以像原生css一樣使用我們自定義的布局(像display:flex, display:table)。在Masonry layout library[11] 上我們可以看到開發(fā)者們是有多想實現(xiàn)各種各樣的復(fù)雜布局,其中一些布局光靠 CSS 是不行的。雖然這些布局會讓人耳目一新印象深刻,但是它們的頁面性能往往都很差,在一些低端設(shè)備上性能問題猶為明顯。
CSS Layout API 暴露了一個registerLayout方法給開發(fā)者,接收一個布局名(layout name)作為后面在 CSS中使用的屬性值,還有一個包含有這個布局邏輯的JavaScript類。
my-div {
display: layout(my-layout);
}
// layout-worklet.js
registerLayout('my-layout', class {
static get inputProperties() { return ['--foo']; }
static get childrenInputProperties() { return ['--bar']; }
async intrinsicSizes(children, edges, styleMap) {}
async layout(children, edges, constraints, styleMap) {}
});
await CSS.layoutWorklet.addModule('layout-worklet.js');
目前瀏覽器大部分還不支持
我們可以在CSS background-image中使用它,我們可以使用Canvas 2d上下文,根據(jù)元素的大小控制圖像,還可以使用自定義屬性。
await CSS.paintWorklet.addModule('paint-worklet.js');
registerPaint('sample-paint', class {
static get inputProperties() { return ['--foo']; }
static get inputArguments() { return ['<color>']; }
static get contextOptions() { return {alpha: true}; }
paint(ctx, size, props, args) { }
});
這個API讓我們可以控制基于用戶輸入的關(guān)鍵幀動畫,并且以非阻塞的方式。還能更改一個 DOM 元素的屬性,不過是不會引起渲染引擎重新計算布局或者樣式的屬性,比如 transform、opacity 或者滾動條位置(scroll offset)。Animation API的使用方式與 Paint API 和Layout API略有不同我們還需要通過new一個WorkletAnimation來注冊worklet。
// animation-worklet.js
registerAnimator('sample-animator', class {
constructor(options) {
}
animate(currentTime, effect) {
effect.localTime = currentTime;
}
});
await CSS.animationWorklet.addModule('animation-worklet.js');
// 需要添加動畫的元素
const elem = document.querySelector('#my-elem');
const scrollSource = document.scrollingElement;
const timeRange = 1000;
const scrollTimeline = new ScrollTimeline({
scrollSource,
timeRange,
});
const effectKeyframes = new KeyframeEffect(
elem,
// 動畫需要綁定的關(guān)鍵幀
[
{transform: 'scale(1)'},
{transform: 'scale(.25)'},
{transform: 'scale(1)'}
],
{
duration: timeRange,
},
);
new WorkletAnimation(
'sample-animator',
effectKeyframes,
scrollTimeline,
{},
).play();
關(guān)于此API的更多內(nèi)容:(https://github.com/w3c/css-houdini-drafts/tree/main/css-animation-worklet-1)
允許開發(fā)者自由擴展 CSS 詞法分析器。
解析規(guī)則:
const background = window.cssParse.rule("background: green");
console.log(background.styleMap.get("background").value) // "green"
const styles = window.cssParse.ruleSet(".foo { background: green; margin: 5px; }");
console.log(styles.length) // 5
console.log(styles[0].styleMap.get("margin-top").value) // 5
console.log(styles[0].styleMap.get("margin-top").type) // "px"
解析CSS:
const style = fetch("style.css")
.then(response => CSS.parseStylesheet(response.body));
style.then(console.log);
它將提供一些方法來測量在屏幕上呈現(xiàn)的文本元素的尺寸,將允許開發(fā)者控制文本元素在屏幕上呈現(xiàn)的方式。使用當(dāng)前功能很難或無法測量這些值,因此該API將使開發(fā)者可以更輕松地創(chuàng)建與文本和字體相關(guān)的CSS特性。例如:
Is Houdini ready yet
(https://ishoudinireadyyet.com/)
了解到這里,部分開發(fā)者可能會說:“我不需要這些花里胡哨的技術(shù),并不能帶收益。我只想簡簡單單的寫幾個頁面,做做普通的Web App,并不想試圖干預(yù)瀏覽器的渲染過程從而實現(xiàn)一些實驗性或炫酷的功能。”如果這樣想的話,我們不妨退一步再去思考。回憶下最近做過的項目,用于實現(xiàn)頁面效果所使用到的技術(shù),grid布局方式在考慮兼容老版本瀏覽器時也不得不放棄。我們想控制瀏覽器渲染頁面的過程并不是僅僅為了炫技,更多的是為了幫助開發(fā)者們解決以下兩個問題:
幾年過后再回眸,當(dāng)主流瀏覽器完全支持Houdini的時候。我們可以在瀏覽器上隨心所欲的使用任何CSS屬性,并且他們都能完美支持。像今天的grid布局在舊版本瀏覽器支持的并不友好的這類問題,那時我們只需要安裝對應(yīng)的Polyfill就能解決類似的問題。
前一陣兒被某網(wǎng)站的 JS 反爬流程難住了,至今也沒明白它的反扒原理和攻破方法。最終找到了一個自動化腳本工具 autoit 3,用一個笨方法將人手動點擊瀏覽器的動作腳本化,達到網(wǎng)頁數(shù)據(jù)獲取目的,拿到網(wǎng)頁文件后,再用代碼解析,曲線完成任務(wù)。
本文將介紹這個自動化的過程,并帶編寫一個完整的 autoit 3 爬蟲腳本,希望對各位讀者朋友有所啟發(fā)。
以國家信息安全漏洞共享平臺為例,它在返回數(shù)據(jù)前發(fā)起了兩次 512 響應(yīng),第三次瀏覽器帶著動態(tài)生成的 Cookie 信息才能得到數(shù)據(jù)。
這次咱們直接從網(wǎng)頁入手,操作鍵盤找到“下一頁” 按鈕,按下 Enter 鍵完全請求。通過鍵盤定位到 “下頁” 按鈕的過程為:
接著就可以編寫自動化腳本了,把剛剛的手動操作翻譯成腳本命令:
這個流程,對其他高反扒的信息發(fā)布網(wǎng)站,也是適用的。
按照上面的流程,編寫 autoit 自動化腳本,創(chuàng)建一個 myspider.au3 文件:
#include <AutoItConstants.au3>
;;切換為英文輸入法,保證瀏覽器輸入正常
$hWnd = WinGetHandle("[ACTIVE]");$hWnd 為目標(biāo)窗口句柄,這里設(shè)置的是當(dāng)前活動窗口
$ret = DllCall("user32.dll", "long", "LoadKeyboardLayout", "str", "08040804", "int", 1 + 0)
DllCall("user32.dll", "ptr", "SendMessage", "hwnd", $hWnd, "int", 0x50, "int", 1, "int", $ret[0])
$url = "https://www.cnvd.org.cn/flaw/list.htm"
spiderData($url)
Func spiderData($url)
;;打開 Chrome 瀏覽器窗口
$chromePath = "C:\Users\admin\AppData\Local\Google\Chrome\Application\chrome.exe"
Run($chromePath)
;;登錄窗口顯示
WinWaitActive("[CLASS:Chrome_WidgetWin_1]")
;; 休息2000毫秒
Sleep(2000)
;; 移動窗口
WinMove("[CLASS:Chrome_WidgetWin_1]", "打開新的標(biāo)簽頁 - Google Chrome", 0, 0,1200,740,2)
;; 休息500毫秒
Sleep(500)
;;地址欄輸入URL 并按下 Enter 鍵
Send($url)
Sleep(500)
Send("{enter}")
Sleep(3000)
;; 循環(huán)爬取需要的頁數(shù),測試只爬3頁
For $i = 1 To 3 Step 1
;;打開右鍵另存為按鈕: Ctrl+S
send("^s")
Sleep(2000)
WinWait("[CLASS:#32770]","",10)
;;將存儲路徑設(shè)置到另存為組件輸入框 Edit1 里
$timeNow = @YEAR & "" & @MON & "" & @MDAY & "" & @HOUR & "" & @MIN
$savePath = "F:\A2021Study\ListData\" &$timeNow & "_page" & $i & ".html"
ControlSetText("另存為","", "Edit1", $savePath)
;;點擊確定
ControlClick("另存為","","Button2")
;;再次確定
WinWait("[CLASS:#32770]","",10)
ControlClick("確認(rèn)另存為","","Button1")
;; 等待保存操作完成
Sleep(3000)
;; 定位到下一頁按鈕,并觸發(fā)點擊下一頁
send("{END}")
Send("+{TAB 15}")
Send("{enter}")
;;點擊確定后,等待網(wǎng)頁加載完成
Sleep(3000)
Next
;; 整個操作完成,則關(guān)閉瀏覽器
Send("^w")
EndFunc
腳本編寫過程中,有幾點需要注意:
因為爬蟲要作為定時任務(wù)運行的,為避免打開太多瀏覽器窗口,因此需要在腳本結(jié)束時關(guān)閉瀏覽器。
數(shù)據(jù)爬取一般分為列表頁和詳情頁,定位點擊每一條詳情的過程比較麻煩,所以爬取詳情頁面的和列表分開,用 Java 代碼解析所有詳情 URL 后,再由另一個 autoit 腳本去獲取詳情頁面,這個流程大家可以自己寫一下,這里就不詳細(xì)介紹了。
最后再匯總下整個爬取的流程:
第一步,執(zhí)行爬取列表的 autoit 腳本,得到列表頁面 html;
第二步,解析列表頁 html ,得到所有詳情頁面的 URL ,寫入到文件中;
第三步,執(zhí)行爬取詳情頁面的 autoit 腳本,它遍歷第二步的目標(biāo) URL ,得到詳情頁 html ;
第四步,解析詳情頁 html 文件,得到詳情數(shù)據(jù)。
總控流程、第二步和第四步的解析都用 Java 代碼完成,用 Runtime.getRuntime().exec("cmd /c E:\A2021Study\Autoit3\myspider.au3") 調(diào)用腳本,文件路徑是反斜杠。
這個方法雖然有點笨,但完全是人工操作瀏覽器,能夠?qū)狗磁老x策略,感興趣的朋友可以執(zhí)行下本文的腳本試試。
autoit 還是蠻有意思的,語法也很簡單,DirCreate 創(chuàng)建文件,iniread 讀取配置項,一行代碼頂 Java 幾十行,不得不承認(rèn) Java 操作文件才是最麻煩的哇!
HTTP車是由蒂姆·伯納斯-李( TimBerners—Lee )于1989年在歐洲核子研究組織( CERN )所發(fā)起
其中最著名的是 1999 年 6 月公布的 RFC 2616 ,定義了 HTTP 協(xié)議中現(xiàn)今廣泛使用的一個版本—— HTTP 1.1
全稱:超文本傳輸協(xié)議( HyperText Transfer Protocol )
概念: HTTP 是一種能夠獲取像 HTML 、圖片等網(wǎng)絡(luò)資源的通訊協(xié)議( protocol )。它是在 web 上進行數(shù)據(jù)交換的基礎(chǔ),是一種 client-server 協(xié)議
HTTP ——因特網(wǎng)的多媒體信使 ——《HTTP權(quán)威指南》。 HTTP 在因特網(wǎng)的角色:充當(dāng)一個信使的角色,干的就是一個跑腿的活,在客戶端和服務(wù)端之間傳遞信息,但我們又不能缺少它。 HTTP 協(xié)議是應(yīng)用層的協(xié)議,是與前端開發(fā)最息息相關(guān)的協(xié)議。平時我們遇到的 HTTP 請求、 HTTP 緩存、 Cookies 、跨域等其實都跟 HTTP 息息相關(guān)
也就是說, HTTP 依賴于面向連接的 TCP 進行消息傳遞,但連接并不是必須的。只需要它是可靠的,或不丟失消息的(至少返回錯誤)。
HTTP/1.0 默認(rèn)為每一對 HTTP 請求/響應(yīng)都打開一個單獨的 TCP 連接。當(dāng)需要連續(xù)發(fā)起多個請求時,這種模式比多個請求共享同一個 TCP 鏈接更低效。為此, HTTP 1.1 持久連接的概念,底層 TCP 連接可以通過 connection 頭部實現(xiàn)。但 HTTP 1.1 在連接上也是不完美的,后面我們會提到。
HTTP 的組件系統(tǒng)包括客戶端、 web 服務(wù)器和代理
瀏覽器,特殊比如是工程師使用的程序,以及 Web 開發(fā)人員調(diào)試應(yīng)用程序
由 Web Server 來服務(wù)并提供客戶端所請求的文檔。每一個發(fā)送到服務(wù)器的請求,都會被服務(wù)器處理并返回一個消息,也就是 response
在瀏覽器和服務(wù)器之間,有很多計算機和其他設(shè)備轉(zhuǎn)發(fā)了 HTTP 消息。它們可能出現(xiàn)在傳輸層、網(wǎng)絡(luò)層和物理層上,對于 HTTP 應(yīng)用層而言就是透明的
有如下的一些作用
HTTP 有兩種類型的消息:
HTTP 消息由采用 ASCII 編碼的多行文本構(gòu)成的。在 HTTP/1.1 以及更早的版本中,這些消息通過連接公開的發(fā)送。在 HTTP2.0 中,消息被分到了多個 HTTP 幀中。通過配置文件(用于代理服務(wù)器或者服務(wù)器), API (用于瀏覽器)或者其他接口提供 HTTP 消息
HTTP 請求和響應(yīng)都包括起始行( start line )、請求頭( HTTP Headers )、空行( empty line )以及 body 部分,如下圖所示:
下面詳細(xì)說下請求 Path ,請求路徑( Path )有以下幾種:
1)一個絕對路徑,末尾跟上一個 ' ? ' 和查詢字符串。這是最常見的形式,稱為 原始形式 ( origin form ),被 GET , POST , HEAD 和 OPTIONS 方法所使用
POST / HTTP/1.1
GET /background.png HTTP/1.0
HEAD /test.html?query=alibaba HTTP/1.1
OPTIONS /anypage.html HTTP/1.0
復(fù)制代碼
2)一個完整的 URL 。主要在使用 GET 方法連接到代理的時候使用
GET http://developer.mozilla.org/en-US/docs/Web/HTTP/Messages HTTP/1.1
復(fù)制代碼
3)由域名和可選端口(以':'為前綴)組成的 URL 的 authority component ,稱為 authority form 。僅在使用 CONNECT 建立 HTTP 隧道時才使用
CONNECT developer.mozilla.org:80 HTTP/1.1
復(fù)制代碼
4)星號形式 ( asterisk form ),一個簡單的星號('*'),配合 OPTIONS 方法使用,代表整個服務(wù)器。
OPTIONS * HTTP/1.1
復(fù)制代碼
請求 Body 部分: 有些請求將數(shù)據(jù)發(fā)送到服務(wù)器以便更新數(shù)據(jù):常見的的情況是 POST 請求(包含 HTML 表單數(shù)據(jù))。請求報文的 Body 一般為兩類。一類是通過 Content-Type 和 Content-Length 定義的單文件 body 。另外一類是由多 Body 組成,通常是和 HTML Form 聯(lián)系在一起的。兩者的不同表現(xiàn)在于 Content-Type 的值。
1) Content-Type —— application/x-www-form-urlencoded 對于 application/x-www-form-urlencoded 格式的表單內(nèi)容,有以下特點:
I.其中的數(shù)據(jù)會被編碼成以&分隔的鍵值對
II.字符以URL編碼方式編碼。
// 轉(zhuǎn)換過程: {a: 1, b: 2} -> a=1&b=2 -> 如下(最終形式)
"a%3D1%26b%3D2"
復(fù)制代碼
2) Content-Type —— multipart/form-data
請求頭中的 Content-Type 字段會包含 boundary ,且 boundary 的值有瀏覽器默認(rèn)指定。例: Content-Type: multipart/form-data;boundary=----WebkitFormBoundaryRRJKeWfHPGrS4LKe 。
數(shù)據(jù)會分為多個部分,每兩個部分之間通過分隔符來分隔,每部分表述均有 HTTP 頭部描述子包體,如 Content-Type ,在最后的分隔符會加上--表示結(jié)束。
Content-Disposition: form-data;name="data1";
Content-Type: text/plain
data1
----WebkitFormBoundaryRRJKeWfHPGrS4LKe
Content-Disposition: form-data;name="data2";
Content-Type: text/plain
data2
----WebkitFormBoundaryRRJKeWfHPGrS4LKe--
復(fù)制代碼
響應(yīng) Body 部分:
1)由已知長度的單個文件組成。該類型 body 有兩個 header 定義: Content-Type 和 Content-Length
2)由未知長度的單個文件組成,通過將 Transfer-Encoding 設(shè)置為 chunked 來使用 chunks 編碼。
關(guān)于 Content-Length 在下面 HTTP 1.0 中會提到,這個是 HTTP 1.0 中新增的非常重要的頭部。
安全方法: HTTP 定義了一組被稱為安全方法的方法。 GET 方法和 HEAD 方法都被認(rèn)為是安全的,這意味著 GET 方法和 HEAD 方法都不會產(chǎn)生什么動作 —— HTTP 請求不會再服務(wù)端產(chǎn)生什么結(jié)果,但這并不意味著什么動作都沒發(fā)生,其實這更多的是 web 開發(fā)者決定的
首先要了解下副作用和冪等的概念,副作用指的是對服務(wù)器端資源做修改。冪等指發(fā)送 M 和 N 次請求(兩者不相同且都大于 1),服務(wù)器上資源的狀態(tài)一致。應(yīng)用場景上,get是無副作用的,冪等的。post 主要是有副作用的,不冪等的情況
技術(shù)上有以下的區(qū)分:
HTTP Headers
1.通用首部( General headers )同時適用于請求和響應(yīng)消息,但與最終消息主體中傳輸?shù)臄?shù)據(jù)無關(guān)的消息頭。如 Date
2.請求首部( Request headers )包含更多有關(guān)要獲取的資源或客戶端本身信息的消息頭。如 User-Agent
3.響應(yīng)首部( Response headers )包含有關(guān)響應(yīng)的補充信息
4.實體首部( Entity headers )含有關(guān)實體主體的更多信息,比如主體長( Content-Length )度或其 MIME 類型。如 Accept-Ranges
詳細(xì)的 Header 見 HTTP Headers 集合
HTTP(HyperText Transfer Protocol) 是萬維網(wǎng)( World Wide Web )的基礎(chǔ)協(xié)議。 Tim Berners-Lee 博士和他的團隊在 1989-1991 年間創(chuàng)造出它。【HTTP、網(wǎng)絡(luò)瀏覽器、服務(wù)器】
在 1991 年發(fā)布了 HTTP 0.9 版,在 1996 年發(fā)布 1.0 版,1997 年是 1.1 版,1.1 版也是到今天為止傳輸最廣泛的版本。2015 年發(fā)布了 2.0 版,其極大的優(yōu)化了 HTTP/1.1 的性能和安全性,而 2018 年發(fā)布的 3.0 版,繼續(xù)優(yōu)化 HTTP/2 ,激進地使用 UDP 取代 TCP 協(xié)議,目前, HTTP/3 在 2019 年 9 月 26 日 被 Chrome , Firefox ,和 Cloudflare 支持
單行協(xié)議,請求由單行指令構(gòu)成。以唯一可用的方法 GET 開頭。后面跟的是目標(biāo)資源的路徑
GET /mypage.html
復(fù)制代碼
響應(yīng):只包括響應(yīng)文檔本身
<HTML>
這是一個非常簡單的HTML頁面
</HTML>
復(fù)制代碼
HTML
RFC 1945 提出了 HTTP1.0 , 構(gòu)建更好可拓展性
媒體類型是一種標(biāo)準(zhǔn)。用來表示文檔、文件或者字節(jié)流的性質(zhì)和格式。瀏覽器通常使用 MIME ( Multipurpose Internet Mail Extensions )類型來確定如何處理 URL ,因此 Web 服務(wù)器在響應(yīng)頭中配置正確的 MIME 類型會非常的重要。如果配置不正確,可能會導(dǎo)致網(wǎng)站無法正常的工作。 MIME 的組成結(jié)構(gòu)非常簡單;由類型與子類型兩個字符串中間用'/'分隔而組成。
HTTP 從 MIME type 取了一部分來標(biāo)記報文 body 部分的數(shù)據(jù)類型,這些類型體現(xiàn)在 Content-Type 這個字段,當(dāng)然這是針對于發(fā)送端而言,接收端想要收到特定類型的數(shù)據(jù),也可以用 Accept 字段。
這兩個字段的取值可以分為下面幾類:
- text: text/html, text/plain, text/css 等
- image: image/gif, image/jpeg, image/png 等
- audio/video: audio/mpeg, video/mp4 等
- application: application/json, application/javascript, application/pdf, application/octet-stream
復(fù)制代碼
同時為了約定請求的數(shù)據(jù)和響應(yīng)數(shù)據(jù)的壓縮方式、支持語言、字符集等,還提出了以下的 Header
1.壓縮方式:發(fā)送端: Content-Encoding (服務(wù)端告知客戶端,服務(wù)器對實體的主體部分的編碼方式) 和 接收端: Accept-Encoding (用戶代理支持的編碼方式),值有 gzip: 當(dāng)今最流行的壓縮格式;deflate: 另外一種著名的壓縮格式;br: 一種專門為 HTTP 發(fā)明的壓縮算法
2.支持語言: Content-Language 和 Accept-Language (用戶代理支持的自然語言集)
3.字符集:發(fā)送端: Content-Type 中,以 charset 屬性指定。接收端: Accept-Charset (用戶代理支持的字符集)。
// 發(fā)送端
Content-Encoding: gzip
Content-Language: zh-CN, zh, en
Content-Type: text/html; charset=utf-8
// 接收端
Accept-Encoding: gzip
Accept-Language: zh-CN, zh, en
Accept-Charset: charset=utf-8
復(fù)制代碼
雖然 HTTP1.0 在 HTTP 0.9 的基礎(chǔ)上改進了很多,但還是存在這不少的缺點
HTTP/1.0 版的主要缺點是,每個 TCP 連接只能發(fā)送一個請求。發(fā)送數(shù)據(jù)完畢,連接就關(guān)閉,如果還要請求其他資源,就必須再新建一個連接。 TCP 連接的新建成本很高,因為需要客戶端和服務(wù)器三次握手,并且開始時發(fā)送速率較慢( slow start )。
HTTP 最早期的模型,也是 HTTP/1.0 的默認(rèn)模型,是短連接。每一個 HTTP 請求都由它自己獨立的連接完成;這意味著發(fā)起每一個 HTTP 請求之前都會有一次 TCP 握手,而且是連續(xù)不斷的。
HTTP/1.1 在1997年1月以 RFC 2068 文件發(fā)布。
HTTP 1.1 消除了大量歧義內(nèi)容并引入了多項技術(shù)
虛擬主機( virtual hosting )即共享主機( shared web hosting ),可以利用虛擬技術(shù)把一臺完整的服務(wù)器分成若干個主機,因此可以在單一主機上運行多個網(wǎng)站或服務(wù)。
舉個例子,有一臺 ip 地址為 61.135.169.125 的服務(wù)器,在這臺服務(wù)器上部署著谷歌、百度、淘寶的網(wǎng)站。為什么我們訪問 https://www.google.com 時,看到的是 Google 的首頁而不是百度或者淘寶的首頁?原因就是 Host 請求頭決定著訪問哪個虛擬主機。
2015年, HTTP2.0 面世。 rfc7540
HTTP 2.0 中的幀將 HTTP/1.x 消息分成幀并嵌入到流 ( stream ) 中。數(shù)據(jù)幀和報頭幀分離,這將允許報頭壓縮。將多個流組合,這是一個被稱為多路復(fù)用 ( multiplexing ) 的過程,它允許更有效的底層 TCP 連接。
也就是說,流用來承載消息,消息又是有一個或多個幀組成。二進制傳輸?shù)姆绞礁犹嵘藗鬏斝阅堋?每個數(shù)據(jù)流都以消息的形式發(fā)送,而消息又由一個或多個幀組成。 幀是流中的數(shù)據(jù)單位。
HTTP 幀現(xiàn)在對 Web 開發(fā)人員是透明的。在 HTTP/2 中,這是一個在 HTTP/1.1 和底層傳輸協(xié)議之間附加的步驟。 Web 開發(fā)人員不需要在其使用的 API 中做任何更改來利用 HTTP 幀;當(dāng)瀏覽器和服務(wù)器都可用時, HTTP/2 將被打開并使用。
之前我們提到,雖然 HTTP 1.1 有了長連接和管道化的技術(shù),但是還是會存在 隊頭阻塞。而 HTTP 2.0 就解決了這個問題 HTTP/2 中新的二進制分幀層突破了這些限制,實現(xiàn)了完整的請求和響應(yīng)復(fù)用:客戶端和服務(wù)器可以將 HTTP 消息分解為互不依賴的幀,然后交錯發(fā)送,最后再在另一端把它們重新組裝起來。
如上圖所示,快照捕捉了同一個連接內(nèi)并行的多個數(shù)據(jù)流。 客戶端正在向服務(wù)器傳輸一個 DATA 幀(數(shù)據(jù)流 5),與此同時,服務(wù)器正向客戶端交錯發(fā)送數(shù)據(jù)流 1 和數(shù)據(jù)流 3 的一系列幀。因此,一個連接上同時有三個并行數(shù)據(jù)流。
將 HTTP 消息分解為獨立的幀,交錯發(fā)送,然后在另一端重新組裝是 HTTP 2 最重要的一項增強。事實上,這個機制會在整個網(wǎng)絡(luò)技術(shù)棧中引發(fā)一系列連鎖反應(yīng),從而帶來巨大的性能提升,讓我們可以: 1.并行交錯地發(fā)送多個請求,請求之間互不影響。 2.并行交錯地發(fā)送多個響應(yīng),響應(yīng)之間互不干擾。 3.使用一個連接并行發(fā)送多個請求和響應(yīng)。 4.消除不必要的延遲和提高現(xiàn)有網(wǎng)絡(luò)容量的利用率,從而減少頁面加載時間。 5.不必再為繞過 HTTP/1.x 限制而做很多工作(比如精靈圖) ...
連接共享,即每一個 request 都是是用作連接共享機制的。一個 request 對應(yīng)一個 id ,這樣一個連接上可以有多個 request ,每個連接的 request 可以隨機的混雜在一起,接收方可以根據(jù) request 的 id 將 request 再歸屬到各自不同的服務(wù)端請求里面。
HTTP 1.1 和 HTTP 2.0 的對比,可以參考這個 網(wǎng)站 demo 演示
HTTP 1.1 演示如下:
HTTP2.0 演示如下:
使用 HTTP/1.1 和 HTTP/2 對于站點和應(yīng)用來說是透明的。擁有一個最新的服務(wù)器和新點的瀏覽器進行交互就足夠了。只有一小部分群體需要做出改變,而且隨著陳舊的瀏覽器和服務(wù)器的更新,而不需 Web 開發(fā)者做什么,用的人自然就增加了
HTTPS 也是通過 HTTP 協(xié)議進行傳輸信息,但是采用了 TLS 協(xié)議進行了加密
對稱加密就是兩邊擁有相同的秘鑰,兩邊都知道如何將密文加密解密。但是因為傳輸數(shù)據(jù)都是走的網(wǎng)絡(luò),如果將秘鑰通過網(wǎng)絡(luò)的方式傳遞的話,一旦秘鑰被截獲就沒有加密的意義的
非對稱加密
公鑰大家都知道,可以用公鑰加密數(shù)據(jù)。但解密數(shù)據(jù)必須使用私鑰,私鑰掌握在頒發(fā)公鑰的一方。首先服務(wù)端將公鑰發(fā)布出去,那么客戶端是知道公鑰的。然后客戶端創(chuàng)建一個秘鑰,并使用公鑰加密,發(fā)送給服務(wù)端。服務(wù)端接收到密文以后通過私鑰解密出正確的秘鑰
TLS 握手的過程采用的是非對稱加密
強緩存主要是由 Cache-control 和 Expires 兩個 Header 決定的
Expires 的值和頭里面的 Date 屬性的值來判斷是否緩存還有效。 Expires 是 Web 服務(wù)器響應(yīng)消息頭字段,在響應(yīng) http 請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數(shù)據(jù),而無需再次請求。 Expires 的一個缺點就是,返回的到期時間是服務(wù)器端的時間,這是一個絕對的時間,這樣存在一個問題,如果客戶端的時間與服務(wù)器的時間相差很大(比如時鐘不同步,或者跨時區(qū)),那么誤差就很大。
Cache-Control 指明當(dāng)前資源的有效期,控制瀏覽器是否直接從瀏覽器緩存取數(shù)據(jù)還是重新發(fā)請求到服務(wù)器取數(shù)據(jù)。但是其設(shè)置的是一個相對時間。
指定過期時間: max-age 是距離請求發(fā)起的時間的秒數(shù),比如下面指的是距離發(fā)起請求 31536000S 內(nèi)都可以命中強緩存
Cache-Control: max-age=31536000
復(fù)制代碼
表示沒有緩存
Cache-Control: no-store
復(fù)制代碼
有緩存但要重新驗證
Cache-Control: no-cache
復(fù)制代碼
私有和公共緩存
public 表示響應(yīng)可以被任何中間人(比如中間代理、 CDN 等緩存) 而 private 則表示該響應(yīng)是專用于某單個用戶的,中間人不能緩存此響應(yīng),該響應(yīng)只能應(yīng)用于瀏覽器私有緩存中。
Cache-Control: private
Cache-Control: public
復(fù)制代碼
驗證方式:以下表示一旦資源過期(比如已經(jīng)超過 max-age ),在成功向原始服務(wù)器驗證之前,緩存不能用該資源響應(yīng)后續(xù)請求
Cache-Control: must-revalidate
復(fù)制代碼
Cache-control 優(yōu)先級比 Expires 優(yōu)先級高
以下是一個 Cache-Control 強緩存的過程:
Last-Modified 表示本地文件最后修改日期,瀏覽器會在 request header 加上 If-Modified-Since (上次返回的 Last-Modified 的值),詢問服務(wù)器在該日期后資源是否有更新,有更新的話就會將新的資源發(fā)送回來
但是如果在本地打開緩存文件,就會造成 Last-Modified 被修改,所以在 HTTP / 1.1 出現(xiàn)了 ETag
Etag 就像一個指紋,資源變化都會導(dǎo)致 ETag 變化,跟最后修改時間沒有關(guān)系, ETag 可以保證每一個資源是唯一的。 If-None-Match 的 header 會將上次返回的 Etag 發(fā)送給服務(wù)器,詢問該資源的 Etag 是否有更新,有變動就會發(fā)送新的資源回來
If-none-match 、 ETags 優(yōu)先級高于 If-Modified-Since、Last-Modified
第一次請求:
第二次請求相同網(wǎng)頁:
協(xié)商緩存,假如沒有改動的話,返回 304 ,改動了返回 200 資源
現(xiàn)在的200 (from cache) 已經(jīng)變成了 disk cache (磁盤緩存)和 memory cache (內(nèi)存緩存)兩種
上面提到 HTTP 緩存相關(guān),但是很多有時候,我們希望上線之后需要更新線上資源。
web 開發(fā)者發(fā)明了一種被 Steve Souders 稱之為 revving 的技術(shù)。不頻繁更新的文件會使用特定的命名方式:在 URL 后面(通常是文件名后面)會加上版本號。
弊端:更新了版本號,所有引用這些的資源的地方的版本號都要改變
web 開發(fā)者們通常會采用自動化構(gòu)建工具在實際工作中完成這些瑣碎的工作。當(dāng)?shù)皖l更新的資源( js/css )變動了,只用在高頻變動的資源文件( html )里做入口的改動。
HTTP Cookie (也叫 Web Cookie 或瀏覽器 Cookie )是服務(wù)器發(fā)送到用戶瀏覽器并保存在本地的一小塊數(shù)據(jù),它會在瀏覽器下次向同一服務(wù)器再發(fā)起請求時被攜帶并發(fā)送到服務(wù)器上。
Set-Cookie 響應(yīng)頭部和 Cookie 請求頭部
Set-Cookie: <cookie名>=<cookie值>
復(fù)制代碼
會話期Cookie是最簡單的 Cookie :瀏覽器關(guān)閉之后它會被自動刪除,也就是說它僅在會話期內(nèi)有效。會話期 Cookie 不需要指定過期時間( Expires )或者有效期( Max-Age )。需要注意的是,有些瀏覽器提供了會話恢復(fù)功能,這種情況下即使關(guān)閉了瀏覽器,會話期 Cookie 也會被保留下來,就好像瀏覽器從來沒有關(guān)閉一樣
和關(guān)閉瀏覽器便失效的會話期 Cookie 不同,持久性 Cookie 可以指定一個特定的過期時間( Expires )或有效期( Max-Age )。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
復(fù)制代碼
標(biāo)記為 Secure 的 Cookie 只應(yīng)通過被 HTTPS 協(xié)議加密過的請求發(fā)送給服務(wù)端。
標(biāo)記為 Secure 的 Cookie 只應(yīng)通過被 HTTPS 協(xié)議加密過的請求發(fā)送給服務(wù)端。但即便設(shè)置了 Secure 標(biāo)記,敏感信息也不應(yīng)該通過 Cookie 傳輸,因為 Cookie 有其固有的不安全性, Secure 標(biāo)記也無法提供確實的安全保障
通過 JavaScript 的 Document.cookie API 是無法訪問帶有 HttpOnly 標(biāo)記的 cookie 。這么做是為了避免跨域腳本攻擊( XSS )
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
復(fù)制代碼
Domain 和 Path 標(biāo)識定義了 Cookie 的作用域:即 Cookie 應(yīng)該發(fā)送給哪些 URL 。
Domain 標(biāo)識指定了哪些主機可以接受 Cookie 。如果不指定,默認(rèn)為當(dāng)前的主機(不包含子域名)。如果指定了 Domain ,則一般包含子域名。
例如,如果設(shè)置 Domain=mozilla.org ,則 Cookie 也包含在子域名中(如 developer.mozilla.org )。
Path 標(biāo)識指定了主機下的哪些路徑可以接受 Cookie (該 URL 路徑必須存在于請求 URL 中)。以字符 %x2F ("/") 作為路徑分隔符,子路徑也會被匹配。
例如,設(shè)置 Path=/docs ,則以下地址都會匹配:
/docs
/docs/Web/
/docs/Web/HTTP
復(fù)制代碼
SameSite Cookie 允許服務(wù)器要求某個 cookie 在跨站請求時不會被發(fā)送,從而可以阻止跨站請求偽造攻擊
Set-Cookie: key=value; SameSite=Strict
復(fù)制代碼
None Strict Lax
在新版本的瀏覽器( Chrome 80 之后)中, SameSite 的默認(rèn)屬性是 SameSite=Lax 。換句話說,當(dāng) Cookie 沒有設(shè)置 SameSite 屬性時,將會視作 SameSite 屬性被設(shè)置為 Lax —— 這意味著 Cookies 將不會在當(dāng)前用戶使用時被自動發(fā)送。如果想要指定 Cookies 在同站、跨站請求都被發(fā)送,那么需要明確指定 SameSite 為 None 。因為這一點,我們需要好好排查舊系統(tǒng)是否明確指定 SameSite ,以及推薦新系統(tǒng)明確指定 SameSite ,以兼容新舊版本 Chrome
更多 cookie 相關(guān),可以查看我之前總結(jié)的一篇關(guān)于 cookie 的文章 前端須知的 Cookie 知識小結(jié)
跨域資源共享( CORS )是一種機制,它使用額外的 HTTP 頭告訴瀏覽器,讓運行在一個 origin ( domain ) 上的 web 應(yīng)用被準(zhǔn)許訪問來自不同源服務(wù)器上的指定的資源
跨域資源共享標(biāo)準(zhǔn)新增了一組 HTTP 首部字段,允許服務(wù)器聲明哪些源站通過瀏覽器有權(quán)限訪問哪些資源。
簡單請求(不會觸發(fā) CORS 的預(yù)檢請求)需要同時滿足以下三點:
以下為一個簡單請求的請求報文以及響應(yīng)報文
簡化以下:
請求首部字段 Origin 表明該請求來源于 http://foo.example
本例中,服務(wù)端返回的 Access-Control-Allow-Origin: * 表明,該資源可以被任意外域訪問。如果服務(wù)端僅允許來自 http://foo.example 的訪問,該首部字段的內(nèi)容如下:
Access-Control-Allow-Origin: http://foo.example
復(fù)制代碼
Access-Control-Allow-Origin 應(yīng)當(dāng)為 * 或者包含由 Origin 首部字段所指明的域名。
規(guī)范要求,對那些可能對服務(wù)器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請求方法。瀏覽器必須首先使用 OPTIONS 方法發(fā)起一個預(yù)檢請求( preflight request ),從而獲知服務(wù)端是否允許該跨域請求。
服務(wù)器確認(rèn)允許之后,才發(fā)起實際的 HTTP 請求。在預(yù)檢請求的返回中,服務(wù)器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認(rèn)證相關(guān)數(shù)據(jù))
預(yù)檢請求中同時攜帶了下面兩個首部字段:
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
復(fù)制代碼
首部字段 Access-Control-Request-Method 告知服務(wù)器,實際請求將使用 POST 方法。首部字段 Access-Control-Request-Headers 告知服務(wù)器,實際請求將攜帶兩個自定義請求首部字段: X-PINGOTHER 與 Content-Type 。服務(wù)器據(jù)此決定,該實際請求是否被允許。
預(yù)檢請求的響應(yīng)中,包括了以下幾個字段
Access-Control-Allow-Origin: http://foo.example
// 表明服務(wù)器允許客戶端使用 POST, GET 和 OPTIONS 方法發(fā)起請求
Access-Control-Allow-Methods: POST, GET, OPTIONS
// 表明服務(wù)器允許請求中攜帶字段 X-PINGOTHER 與 Content-Type
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
// 表明該響應(yīng)的有效時間為 86400 秒,也就是 24 小時。在有效時間內(nèi),瀏覽器無須為同一請求再次發(fā)起預(yù)檢請求。
Access-Control-Max-Age: 86400
復(fù)制代碼
一般而言,對于跨域 XMLHttpRequest 或 Fetch 請求,瀏覽器不會發(fā)送身份憑證信息。如果要發(fā)送憑證信息,需要設(shè)置 XMLHttpRequest 的某個特殊標(biāo)志位。比如說 XMLHttpRequest 的 withCredentials 標(biāo)志設(shè)置為 true ,則可以發(fā)送 cookie 到服務(wù)端。
對于附帶身份憑證的請求,服務(wù)器不得設(shè)置 Access-Control-Allow-Origin 的值為“*”。 這是因為請求的首部中攜帶了 Cookie 信息,如果 Access-Control-Allow-Origin 的值為“*”,請求將會失敗。而將 Access-Control-Allow-Origin 的值設(shè)置為 http://foo.example ,則請求將成功執(zhí)行。
CORS 涉及到的請求和響應(yīng)頭如下: HTTP 響應(yīng)首部字段
HTTP 請求首部字段
Origin
Access-Control-Request-Method
Access-Control-Request-Headers
有感興趣的朋友可以關(guān)注一下我的公眾號:前端維他命,不定時更新優(yōu)秀文章。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。