作为一名专业的切图工程师,我从来不care网页的header,最多关心Status Code
是不是200
。但是HEADER真的很重要啊,客户端从服务器端获取内容,首先就是通过HEADER进行各种沟通!HEADER可以帮助我们完成许多骚操作,提高网站的性能,用户的体验。好了让我们来feel一下。
初级骚操作
多语言(
Accept-Language
)防盗链(
Referer
、Referered
)gzip,简单地说就是省流量(
Accept-Encoding
,Content-Encoding
)
多语言
多语言就是一个网站可以实现多种语言的切换,这里不讨论建N个网站,一个网站也个语言。这里讨论如何智能返回用户所需的语言。
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);
gzip
现代浏览器很高级,已经可以接受压缩包了。佩服佩服。那么该如何传输压缩的网页呢?
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打配合。
client给server发送内容(
Content-Type
、Content-Length
)client从server获取内容(
Range
、Content-Range
)client爬虫,抓取网页
client给server发送内容
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代码
// 设置请求地址的配置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");
client从server获取部分内容
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()
client抓取网页内容,简易爬虫
这一块的操作其实很简单,只要建一个请求获取到网页就可以了。
难点在于:如何将游游有用信息剥离网页,过滤掉无用信息。
我这里抓去了百度的娱乐版,百度还算良心,是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();
原文出处:https://www.cnblogs.com/cherryvenus/p/9504109.html
共同学习,写下你的评论
评论加载中...
作者其他优质文章