做爲一名專業的切圖工程師,我歷來不care網頁的header,最多關心Status Code
是否是200
。可是HEADER真的很重要啊,客戶端從服務器端獲取內容,首先就是經過HEADER進行各類溝通!HEADER能夠幫助咱們完成許多騷操做,提升網站的性能,用戶的體驗。好了讓咱們來feel一下。html
Accept-Language
)Referer
、Referered
)Accept-Encoding
,Content-Encoding
)多語言就是一個網站能夠實現多種語言的切換,這裏不討論建N個網站,一個網站也個語言。這裏討論如何智能返回用戶所需的語言。json
server | client |
---|---|
向server扔過去了Accept-Language |
|
接收對方的Accept-Language |
|
字段大概這樣子zh,en-US;q=0.9,en;q=0.8 |
|
開始處理,將字段變成帶權重q 的數組 |
|
排序好大概長這樣[{"name":"zh","q":1},{"name":"en-US","q":0.9},{"name":"en","q":0.8}] |
|
根據權重返回擁有的語言,有zh 返回zh ,沒有zh 就返回en-US |
|
萬一我沒有對方須要的語言包,怎麼辦?急,在線等! | |
沒辦法了,只能給對方咱們的官方(默認)語言 | |
發送,請接收 | |
您的ACCEPT語言已匹配 | 這個網站挺上道的,雖然是國外網站,但知道我是中文 |
咱們沒有你所在地區的語言包 | emmmm,這是火星文嗎? |
附贈多語言的簡易實現版:數組
let languages = { zh:{ title:"你好", content:"同窗" }, en:{ title:"Hey", content:"guy" }, } //設置默認語言,萬一用戶的語言咱們不支持呢? let defaultLanguage="zh" let http = require('http'); function getLanguage(client_langs){ let finalLanguage=defaultLanguage try{ if(client_langs){ //排序獲取語言順序 client_langs=client_langs.split(',').map(l=>{ let [name,q] = l.split(';'); q = q?Number(q.split('=')[1]):1 return {name,q} }).sort((a,b)=>b.q-a.q); //匹配服務器有的語言,並返回 for(let i = 0 ;i <languages.length;i++){ let name= languages[i].name; if(languages[name]){ finalLanguage=name; break; } } } }catch(e){} return languages[finalLanguage] } http.createServer(function (req,res) { //獲取客戶端的語言 let client_langs = req.headers['Accept-Language']; let lan=getLanguage(client_langs) //將語言打印到客戶端 res.end(`<p>${lan.title}</p><p>${lan.content}</p>`) }).listen(3000);
這個技術用的最多的應該就是對於圖片的限制,只有本域名能夠獲取到,其餘域名想都不要想。瀏覽器
server | client |
---|---|
在某網站上請求了一張圖片 | |
經過Referer ,Referered 發現此網站域名不在我方白名單內 |
|
此圖片不提供給某網站 | |
此時po上了一張萬用土 | |
支持正版請上咱們網站 |
實現原理,此處我用iframe來作例子,其實原理很簡單就是對比來源,要麼和請求資源一致要麼在白名單內,否則就拒絕。固然若是沒有來源的狀況下就直接放行,萬一人家是單獨打開的呢,不是盜鏈:服務器
let http = require('http'); let fs = require('fs'); let url = require('url'); let path = require('path'); // 設置白名單 let whiteList = ['localhost:3000']; http.createServer(function (req,res) { //獲取請求地址 let { pathname } = url.parse(req.url); // 獲取物理地址 let realPath = path.join(__dirname,pathname); // 獲取文件狀態 fs.stat(realPath,function(err,statObj) { if(err){ res.statusCode = 404; res.end(); }else{ // 重點來了 let Referer = req.headers['Referer'] || req.headers['referred']; //若是有來源 if(Referer){ //獲取雙方域名 let current = req.headers['host'] Referer = url.parse(Referer).host console.log(current,Referer) //若是域名相同活在白名單中,放行! if (current === Referer || whiteList.includes(Referer)){ fs.createReadStream(realPath).pipe(res); }else{ //不放行,此乃盜鏈!給你個眼神自行體會 fs.createReadStream(path.join(__dirname,'files/2.html')).pipe(res); } }else{ //沒有來源,也放行。萬一是單獨打開的呢~ fs.createReadStream(realPath).pipe(res); } } }) }).listen(3000);
現代瀏覽器很高級,已經能夠接受壓縮包了。佩服佩服。那麼該如何傳輸壓縮的網頁呢?app
server | client |
---|---|
向server扔過去了Accept-Encoding |
|
大概結構是這樣的gzip, deflate, br |
|
get到了對方的用意,開始配置壓縮 | |
若是支持壓縮,先設置個頭部Content-Encoding |
|
有不少種壓縮方式,按照server優先支持的匹配 | |
在線壓縮網頁,成功後返回client | |
歡歡喜喜省了流量,並且不影響體驗 |
附贈建議代碼,你們測試的時候,別忘了建立測試的html文件性能
let http = require('http'); //用於壓縮文件所需的庫 let fs = require('fs'); let path = require('path'); //壓縮的庫 let zlib = require('zlib'); http.createServer(function (req,res) { //獲取客戶端接受的壓縮方式 let rule = req.headers['Accept-Encoding']; // 建立原文件可讀流 let originStream=fs.createReadStream(path.join(__dirname, '1.html')); if(rule){ // 啊啊啊!正則是個坎,我怕我是跨不過去了。 if(rule.match(/\bgzip\b/)){ //若是支持壓縮!必定要設置頭部! res.setHeader('Content-Encoding','gzip'); originStream=originStream.pipe(zlib.createGzip()) } else if (rule.match(/\bdeflate\b/)){ res.setHeader('Content-Encoding', 'deflate'); originStream=originStream.pipe(zlib.createDeflate()) } } // 輸出處理後的可讀流 originStream.pipe(res) }).listen(3000);
初級操做大多隻須要靠配置HEADER便可以實現,中級咱們固然要難一點,大多須要client和server打配合。測試
Content-Type
、Content-Length
)Range
、Content-Range
)server | client |
---|---|
給你了一串數據,你給處理下 | |
沒頭沒腦,誰知道你要作什麼,請設置好HEADER | |
好吧,告訴你Content-Type 和Content-Length |
|
能夠能夠,數據的內容類型是長度是很必要的 | |
把數據傳給你了,你看一下 | |
收到~監聽收到的數據是一組Buffer | |
接受完畢,合併Buffer | |
根據Content-Type 對數據進行處理 |
|
格式化數據,end |
Server代碼網站
let http = require('http'); let server = http.createServer(); let arr=[] server.on('request', (req, res)=>{ req.on('data',function (data) { //把獲取到的Buffer數據都放入熟組 arr.push(data); }); req.on('end',function() { // 請求結束了,好了能夠開始處理斷斷續續收到的Buffer了 // 合併buffer let r = Buffer.concat(arr).toString(); if (req.headers['content-type'] === 'x-www-form-urlencoded'){ let querystring = require('querystring'); r = querystring.parse(r); // a=1&b=2而後格式化 console.log("querystring",r); } else if (req.headers['content-type'] === 'application/json'){ //據說是JSON格式的 console.log("json",JSON.parse(r)); } else{ //沒有格式?那原來是啥就是啥吧。 console.log("no type",r); } arr=[] res.end('結束了!'); }); }) server.listen(3000,()=>{ console.log(`server start`); });
Client代碼ui
// 設置請求地址的配置 let opts = { host:'localhost', port:3000, path:'/', // 頭部設置很重要,頭部設置很重要,頭部設置很重要 headers:{ 'Content-Type':'x-www-form-urlencoded', //長度超過3就沒有人理你了 "Content-Length":7 } } let http = require('http'); let client = http.request(opts,function (res) { res.on('data',function (data) { console.log(data); }) }); client.end("a=1&b=2");
server | client |
---|---|
我想要資源的部份內容 | |
能夠啊,告訴我範圍 | |
我放在HEADER中的Range 了,bytes=0-3 |
|
Content-Range:bytes 0-3/7 ,請接受,此文件一共8字節,前3字節已經給你了 |
好的,那麼把接下來的給我吧,bytes=4-7 |
給你給你都給你 | end |
你們都發現了吧,這樣的range獲取數據,徹底是斷點續傳的簡陋版啊!不過這邊有一個點容易犯錯就是文件大小的計算,由於文件字節的位置是按照0開始算,因此range的全範圍都是0~size-1/size-1
,你們注意下。
server 端
let http = require('http'); let fs = require('fs'); let path = require('path'); // 當前要下載的文件的大小 let size = fs.statSync(path.join(__dirname, 'my.txt')).size; let server = http.createServer(function (req, res) { let range = req.headers['range']; //獲取client請求訪問的部份內容 if (range) { let [, start, end] = range.match(/(\d*)-(\d*)/); start = start ? Number(start) : 0; end = end ? Number(end) : size - 1; // 10個字節 size 10 (0-9) console.log(`bytes ${start}-${end}/${size - 1}`) res.setHeader('Content-Range', `bytes ${start}-${end}/${size - 1}`); fs.createReadStream(path.join(__dirname, 'my.txt'), { start, end }).pipe(res); } else { // 會把文件的內容寫給客戶端 fs.createReadStream(path.join(__dirname, 'my.txt')).pipe(res); } }); server.listen(3000);
client端
let opts = { host:'localhost', port:3000, headers:{} } let http = require('http'); let start = 0; let fs = require('fs'); function download() { //分流下載,部分下載 opts.headers.Range = `bytes=${start}-${start+3}`; start+=4; let client = http.request(opts,function (res) { let total = res.headers['content-range'].split('/')[1]; res.on('data',function (data) { fs.appendFileSync('./download.txt',data); }); res.on('end',function () { //結束以後,1s以後再下載 setTimeout(() => { console.log(start,total) if (start <= total) download(); }, 1000); }) }); client.end(); } download()
這一塊的操做其實很簡單,只要建一個請求獲取到網頁就能夠了。
難點在於:如何將遊遊有用信息剝離網頁,過濾掉無用信息。
我這裏抓去了百度的娛樂版,百度還算良心,是utf8的,否則就要亂碼了。
let http = require('http'); let opts = { host:'news.baidu.com', path:'/ent' } //建立一個請求,獲取網站內容 let client = http.request(opts,function (r) { let arr= []; //資源不可能一次下載完成,所以每次獲取到數據都要push到arr中 r.on('data',function (data) { arr.push(data); }); r.on('end',function() { //合併資源 let result = Buffer.concat(arr).toString(); //對資源進行處理,能夠是變成我這樣的對象,以後無論作什麼處理都很方便 let content = result.match(/<ul class="ulist mix-ulist">(?:[\s\S]*?)<\/ul>/img).toString().match(/<li>(?:[\s\S]*?)<\/li>/img); content=content.map((c)=>{ let href=/<a href="(?:[\S]*?)"/img.exec(c) let title=/">(?:[\s\S]*?)<\/a>/img.exec(c) return { href:href[0].replace(/"/img,"").replace("<a href=",""), title:title[0].replace(/">/img,"").replace("</a>","") } }) console.log(JSON.stringify(content)) arr= []; }) }); client.end();