作为一名专业的切图工程师,我从来不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:{
:\"你好\",
content:\"同学\"
},
en:{
:\"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. }</p><p>${lan.content}</p>`)
}).listen(3000);
防盗链
这个技术用的最多的应该就是对于图片的限制,只有本域名可以获取到,其他域名想都不要想。
| server | client |
|---|---|
| 在某网站上请求了一张图片 | |
通过Referer,Referered发现此网站域名不在我方白名单内 |
|
| 此图片不提供给某网站 | |
| 此时po上了一张万用土 | |
| 支持正版请上我们网站 |
实现原理,此处我用 来做例子,其实原理很简单就是对比来源,要么和请求资源一致要么在白名单内,不然就拒绝。当然如果没有来源的情况下就直接放行,万一人家是单独打开的呢,不是盗链:
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(/bgzipb/)){
//如果支持压缩!一定要设置头部!
res.setHeader(\'Content-Encoding\',\'gzip\');
originStream=originStream.pipe(zlib.createGzip())
} else if (rule.match(/bdeflateb/)){
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\">(?:[sS]*?)</ul>/img).toString().match(/<li>(?:[sS]*?)</li>/img);
content=content.map((c)=>{
let href=/<a href=\"(?:[S]*?)\"/img.exec(c)
let =/\">(?:[sS]*?)</a>/img.exec(c)
return {
href:href[0].replace(/\"/img,\"\").replace(\"<a href=\",\"\"),
: [0].replace(/\">/img,\"\").replace(\"</a>\",\"\")
}
})
console.log(JSON.stringify(content))
arr= [];
})
});
client.end(); 继续阅读与本文标签相同的文章
上一篇 :
句法敏感的实体表示用于神经网络关系抽取
下一篇 :
机器如何猜你所想?阿里小蜜预测平台揭秘
-
AES加密原理和AOE工程实践
2026-05-18栏目: 教程
-
Kubernetes 从懵圈到熟练:集群服务的三个要点和一种实现
2026-05-18栏目: 教程
-
蚂蚁金服走进北京大学,揭秘支付宝移动端最新黑科技
2026-05-18栏目: 教程
-
Fun Package 功能介绍
2026-05-18栏目: 教程
-
Funfile 功能介绍
2026-05-18栏目: 教程
