Как профессиональный монтажник, я никогда не забочусь о заголовках веб-страниц, меня больше всего волнуетStatus Code
Да или нет200
. Но HEADER действительно важен, клиент получает контент от сервера, и в первую очередь это общение через HEADER! HEADER может помочь нам выполнить множество сложных операций, улучшить производительность сайта и удобство для пользователей. Ладно, давай пощупаем.
первичная шутка
- многоязычный(
Accept-Language
) - противоугонная цепь (
Referer
,Referered
) - Gzip, проще говоря, для сохранения данных (
Accept-Encoding
,Content-Encoding
)
многоязычный
Многоязычность означает, что веб-сайт может переключаться между несколькими языками.Мы не будем обсуждать здесь создание N веб-сайтов, и один язык соответствует одному веб-сайту. Вот обсуждение того, как разумно вернуть желаемый язык пользователя.
server | client |
---|---|
Закинуть на сервер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
|
|
Что делать, если у меня нет языкового пакета, необходимого другой стороне? Спешите, ждите онлайн! | |
Нет другого выбора, кроме как дать друг другу наш официальный язык (по умолчанию) | |
отправьте, пожалуйста, получите | |
Ваш ПРИНЯТЬ язык соответствует | Этот веб-сайт довольно хорош.Хотя это иностранный веб-сайт, я знаю, что я китаец. |
У нас нет языкового пакета для вашего региона | эммм, это марсианский текст? |
Поставляется с простой версией реализации на нескольких языках:
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 Обнаружено, что доменное имя этого веб-сайта отсутствует в нашем белом списке. |
|
Это изображение недоступно для веб-сайта | |
В это время на землю положили кусок универсального грунта. | |
Поддержка подлинной, пожалуйста, посетите наш веб-сайт |
Принцип реализации, здесь я использую 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 |
---|---|
Закинуть на серверAccept-Encoding
|
|
наверное такая структураgzip, deflate, br
|
|
добраться до намерения другой стороны, начать настраивать сжатие | |
Если сжатие поддерживается, сначала установите заголовокContent-Encoding
|
|
Существует много методов сжатия, в зависимости от соответствия, поддерживаемого сервером. | |
Сжать веб-страницы онлайн и вернуться к клиенту после успеха | |
Huanhuaxi экономит трафик, не влияя на опыт |
С предложенным кодом при тестировании не забудьте создать тестовый 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 для достижения ***, конечно, нам немного сложнее для промежуточного уровня, большинство из которых требует сотрудничества клиента и сервера.
- Клиент отправляет содержимое на сервер (
Content-Type
,Content-Length
) - Клиент получает содержимое с сервера (
Range
,Content-Range
) - клиентский сканер, сканирование веб-страниц
клиент отправляет контент на сервер
server | client |
---|---|
Я дал вам кучу данных, вы их обрабатываете | |
Мозгов нет, кто знает, что вы собираетесь делать, пожалуйста, настройте ЗАГОЛОВОК | |
хорошо, позвольте мне сказать вамContent-Type иContent-Length
|
|
Да, необходимо, чтобы тип содержимого данных был length | |
Я передал вам данные, посмотрите | |
Получение ~ мониторинг полученных данных представляет собой набор буферов | |
После принятия объедините Buffer | |
в соответствии сContent-Type обрабатывать данные |
|
форматировать данные, конец |
Код сервера
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`);
});
Код клиента
// 设置请求地址的配置
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 |
---|---|
Я хочу часть ресурса | |
Хорошо, скажи мне диапазон | |
Я вставил ЗАГОЛОВОКRange в настоящее время,bytes=0-3
|
|
Content-Range:bytes 0-3/7 , пожалуйста, примите, этот файл имеет в общей сложности 8 байт, первые 3 байта были предоставлены вам |
Хорошо, тогда дай мне следующий,bytes=4-7
|
дать это тебе | end |
Все находили, такой диапазон для получения данных — это совсем простой вариант продолжения по брейкпойнту! Однако есть момент, в котором легко ошибиться, это вычисление размера файла, потому что позиция байтов файла считается от 0, поэтому полный диапазон диапазона0~size-1/size-1
, всем внимание.
серверная часть
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);
сторона клиента
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();