欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

通过HTTP的HEADER完成各种骚操作

程序员文章站 2022-06-27 14:14:01
作为一名专业的切图工程师,我从来不care网页的header,最多关心 是不是 。但是HEADER真的很重要啊,客户端从服务器端获取内容,首先就是通过HEADER进行各种沟通!HEADER可以帮助我们完成许多骚操作,提高网站的性能,用户的体验。好了让我们来feel一下。 初级骚操作 多语言( ) 防 ......

作为一名专业的切图工程师,我从来不care网页的header,最多关心status code是不是200。但是header真的很重要啊,客户端从服务器端获取内容,首先就是通过header进行各种沟通!header可以帮助我们完成许多骚操作,提高网站的性能,用户的体验。好了让我们来feel一下。

初级骚操作

  • 多语言(accept-language
  • 防盗链(refererreferered
  • gzip,简单地说就是省流量(accept-encodingcontent-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
在某网站上请求了一张图片
通过refererreferered发现此网站域名不在我方白名单内
此图片不提供给某网站
此时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-typecontent-length)
  • client从server获取内容(rangecontent-range)
  • client爬虫,抓取网页

client给server发送内容

server client
给你了一串数据,你给处理下
没头没脑,谁知道你要做什么,请设置好header
好吧,告诉你content-typecontent-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();