Междоменный, все, что вам нужно знать, здесь
Эта статья была написана ранее. Я реорганизовал ее. Прежде чем читать эту статью, я надеюсь, что у вас есть определенная основа JS/Node. Я не буду рассказывать, как использовать Ajax для выполнения асинхронных запросов. Если вы не понимаете, вы можете прочитать ее первый:
Картирование системы знаний Ajax
В последнее время меня часто спрашивали, как решать кроссдоменные проблемы во время интервью.После прочтения некоторых статей в интернете многие статьи не объяснялись внятно, что часто приводило читателей (меня) в замешательство, поэтому сегодня я разберу часто используемые кросс- Навыки домена. , цель написания этой статьи о междоменном взаимодействии состоит в том, чтобы:
- Расскажите о распространенных междоменных решениях, их преимуществах и недостатках.
- Смоделируйте реальные междоменные сценарии и приведите простой пример после каждого решения, чтобы читатели могли программировать вместе со мной и интуитивно понимать эти междоменные навыки.
Если вы считаете эту статью полезной, вы можете нажать звездочку, чтобы поощрить ее. Весь код в этой статье можно загрузить из репозитория github. Читатели могут открыть его следующим образом:
git clone https://github.com/happylindz/blog.git
cd blog/code/crossOrigin/
yarn
Рекомендуется клонировать его, чтобы вы могли прочитать код и протестировать его вместе со мной.
Та же политика происхождения
Студенты, которые использовали Ajax, знают его удобство и могут добиться частичного обновления, не отправляя полную страницу на сервер.Он обычно используется в современных приложениях SPA, но браузеры учитывают безопасность и не разрешают междоменные вызовы Объекты других страниц, которые приносит много проблем для нас, чтобы внедрить в приложения iframe или ajax.
Проще говоря, только когда протокол, доменное имя и номер порта совпадают, это одно и то же доменное имя, в противном случае считается, что требуется междоменная обработка.
междоменный подход
На сегодняшний день представлено в общей сложности семь широко используемых междоменных методов.Междоменные методы можно условно разделить на междоменные запросы iframe и междоменные запросы API.
Давайте сначала представим три метода междоменного API:
1. JSONP:
Просто говоря о междоменном доступе, JSONP должен быть протестирован, JSONP называется: JSON with Padding, который можно использовать для решения проблем с междоменным доступом к данным в старой версии браузера.
Поскольку на вызов js-файлов на веб-страницах не распространяется политика браузера в отношении одного и того же источника, междоменные запросы можно выполнять с помощью тегов script:
- Прежде всего, интерфейс должен установить функцию обратного вызова и использовать ее в качестве параметра URL-адреса.
- После того, как сервер получает запрос, он получает имя функции обратного вызова через этот параметр и помещает данные в параметр, чтобы вернуть его.
- После получения результата, поскольку это тег скрипта, браузер запустит его как скрипт, чтобы достичь цели получения данных между доменами.
Ключом к междоменной способности jsonp является то, что на страницу, вызывающую сценарий JS, не влияет политика того же источника, что эквивалентно инициированию http-запроса к серверной части, согласованию имени функции с серверной частью, и серверная часть получает имя функции и динамически вычисляет его.Возвращает результат и возвращает его во внешний интерфейс для выполнения сценария JS, что эквивалентно «динамическому сценарию JS».
Далее попробуем на примере:
Логика бэкенда:
// jsonp/server.js
const url = require('url');
require('http').createServer((req, res) => {
const data = {
x: 10
};
// 拿到回调函数名
const callback = url.parse(req.url, true).query.callback;
console.log(callback);
res.writeHead(200);
res.end(`${callback}(${JSON.stringify(data)})`);
}).listen(3000, '127.0.0.1');
console.log('启动服务,监听 127.0.0.1:3000');
Фронтальная логика:
// jsonp/index.html
<script>
function jsonpCallback(data) {
alert('获得 X 数据:' + data.x);
}
</script>
<script src="http://127.0.0.1:3000?callback=jsonpCallback"></script>
Затем запустите службу в терминале:
Причина, по которой я могу использовать команду сценария, заключается в том, что я установил команду сценария в package.json:
{
// 输入 yarn jsonp 等于 "node ./jsonp/server.js & http-server ./jsonp"
"scripts": {
"jsonp": "node ./jsonp/server.js & http-server ./jsonp",
"cors": "node ./cors/server.js & http-server ./cors",
"proxy": "node ./serverProxy/server.js",
"hash": "http-server ./hash/client/ -p 8080 & http-server ./hash/server/ -p 8081",
"name": "http-server ./name/client/ -p 8080 & http-server ./name/server/ -p 8081",
"postMessage": "http-server ./postMessage/client/ -p 8080 & http-server ./postMessage/server/ -p 8081",
"domain": "http-server ./domain/client/ -p 8080 & http-server ./domain/server/ -p 8081"
},
// ...
}
скопировать код
yarn jsonp
// 因为端口 3000 和 8080 分别属于不同域名下
// 在 localhost:3000 查看效果,即可收到后台返回的数据 10
Открытый доступ в браузереlocalhost:8080
Вы можете увидеть полученные данные.
До сих пор сбор междоменных данных через JSONP был успешным, но этот метод также имеет определенные преимущества и недостатки:
преимущество:
- Он не ограничен политикой того же источника, поскольку объект XMLHttpRequest реализует запросы Ajax.
- Совместимость хорошая и хорошо работает в старых браузерах
- Поддержка XMLHttpRequest или ActiveX не требуется, и результат можно вернуть, вызвав обратный вызов после завершения запроса.
недостаток:
- Он поддерживает запросы GET, но не поддерживает другие типы HTTP-запросов, такие как POST.
- Он поддерживает только случай междоменных HTTP-запросов и не может решить проблему передачи данных между двумя страницами или фреймами разных доменов.
- Не удалось поймать исключение соединения по запросу Jsonp, можно обработать его только по тайм-ауту
CORS:
CORS — это стандарт W3C, полное название — «Совместное использование ресурсов между источниками» (Cross-origin resource sharing). Он позволяет браузерам выдавать запросы XMLHttpRequest к серверам из разных источников, тем самым преодолевая ограничение, заключающееся в том, что ajax может использоваться только того же происхождения.
CORS требует поддержки как браузера, так и сервера.Для разработчиков связь CORS ничем не отличается от связи ajax того же происхождения, и код точно такой же. Как только браузер обнаружит, что запрос ajax является перекрестным, он автоматически добавит некоторую дополнительную информацию в заголовок, а иногда будет сделан дополнительный запрос, но пользователь этого не почувствует.
Таким образом, ключом к реализации связи CORS является сервер. Междоменная связь возможна, если сервер реализует интерфейс CORS.
Логика внешнего интерфейса очень проста, если обычно инициируется запрос ajax:
// cors/index.html
<script>
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:3000', true);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
alert(xhr.responseText);
}
}
xhr.send(null);
</script>
Похоже, это ничем не отличается от обычного асинхронного запроса ajax, ключом является обработка после того, как сервер получает запрос:
// cors/server.js
require('http').createServer((req, res) => {
res.writeHead(200, {
'Access-Control-Allow-Origin': 'http://localhost:8080',
'Content-Type': 'text/html;charset=utf-8',
});
res.end('这是你要的数据:1111');
}).listen(3000, '127.0.0.1');
console.log('启动服务,监听 127.0.0.1:3000');
Клавиша находится в настройке заданного заголовка Control-Allow-Accepress, значение соответствует значению заголовка запроса, чтобы вступить в силу, в противном случае он будет терпеть неудачу через домены.
Затем выполняем команду:yarn cors
Открытый доступ в браузереlocalhost:3000
Вы можете увидеть эффект:
Ключом к успеху является то, содержит ли Access-Control-Allow-Origin доменное имя запрошенной страницы, иначе браузер сочтет это неудачным асинхронным запросом и вызовет функцию в xhr.onerror.
Преимущества и недостатки CORS:
- Простой и удобный в использовании, более безопасный
- Поддержка метода запроса POST
- CORS — это новый тип решения междоменных проблем, который имеет проблемы с совместимостью и поддерживает только IE 10 и выше.
Это лишь краткое введение в CORS, если вы хотите более подробно разобраться в его принципе, вы можете прочитать следующую статью:
Подробное объяснение CORS для междоменного обмена ресурсами - веб-журнал Руан Ифэн
3. Прокси-сервер:
Прокси-сервер, как следует из названия, отправляет запрос на серверную часть, когда вам нужна операция междоменного запроса, позволяет серверной части сделать запрос за вас и, наконец, отправляет вам полученный результат.
Предположим, есть такой сценарий, ваша страница должна получитьCNode: профессиональное китайское сообщество Node.jsНекоторые данные на форуме, например черезhttps://cnodejs.org/api/v1/topics
, в то время из-за разных доменов вы можете запросить серверную часть и позволить ему перенаправить запрос от своего имени.
код показывает, как показано ниже:
// serverProxy/server.js
const url = require('url');
const http = require('http');
const https = require('https');
const server = http.createServer((req, res) => {
const path = url.parse(req.url).path.slice(1);
if(path === 'topics') {
https.get('https://cnodejs.org/api/v1/topics', (resp) => {
let data = "";
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8'
});
res.end(data);
});
})
}
}).listen(3000, '127.0.0.1');
console.log('启动服务,监听 127.0.0.1:3000');
Из кода видно, что при посещенииhttp://127.0.0.1:3000/topics
Когда сервер получит запрос, он отправит запрос от вашего имени.https://cnodejs.org/api/v1/topics
Наконец, полученные данные отправляются в браузер.
запустить службуyarn proxy
и посетитьhttp://localhost:3000/topics
Вы можете увидеть эффект:
Междоменный запрос выполнен успешно. Введен чистый метод получения междоменных запросов для получения внутренних данных, а также введены четыре метода междоменной связи с другими страницами через iframe.
местоположение.хэш:
в URL,http://www.baidu.com#helloworld
#helloworld — это location.hash. Изменение значения хеша не приведет к обновлению страницы, поэтому значение хеша можно использовать для передачи данных. Конечно, объем данных ограничен.
Предположениеlocalhost:8080
Существует файл index.html подlocalhost:8081
Data.html ниже передает сообщение, index.html сначала создает скрытый iframe, src iframe указывает наlocalhost:8081/data.html
, то хеш-значение может быть передано в качестве параметра.
// hash/client/index.html 对应 localhost:8080/index.html
<script>
let ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = "http://localhost:8081/data.html#data";
document.body.appendChild(ifr);
function checkHash() {
try {
let data = location.hash ? location.hash.substring(1) : '';
console.log('获得到的数据是:', data);
}catch(e) {
}
}
window.addEventListener('hashchange', function(e) {
console.log('获得的数据是:', location.hash.substring(1));
});
</script>
После того, как data.html получает сообщение, хеш-значение index.html изменяется значением parent.location.hash, чтобы обеспечить передачу данных.
// hash/server/data.html 对应 localhost:8081/data.html
<script>
switch(location.hash) {
case "#data":
callback();
break;
}
function callback() {
const data = "data.html 的数据"
try {
parent.location.hash = data;
}catch(e) {
// ie, chrome 下的安全机制无法修改 parent.location.hash
// 所以要利用一个中间的代理 iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'http://localhost:8080/proxy.html#' + data; // 该文件在 client 域名的域下
document.body.appendChild(ifrproxy);
}
}
</script>
Поскольку две страницы не находятся в одном домене, IE и Chrome не позволяют изменять значение parent.location.hash, поэтому необходимо использоватьlocalhost:8080
Страница proxy.html для прокси iframe под доменным именем
// hash/client/proxy.html 对应 localhost:8080/proxy.html
<script>
parent.parent.location.hash = self.location.hash.substring(1);
</script>
После этого запустите службуyarn hash
, ты сможешьlocalhost:8080
Наблюдается ниже:
Конечно, у этого метода много недостатков:
- Данные отображаются непосредственно в URL-адресе
- Объем и тип данных ограничены и т. д.
window.name:
Значение window.name(обычно появляется в коде js) не обычная глобальная переменная, а имя текущего окна.Здесь следует отметить, что у каждого iframe есть окно, которое его обертывает, и это окно является дочерним элементом верхнее окно.Window, и оно, естественно, имеет свойство window.name. Магия свойства window.name заключается в том, что значение имени все еще существует после загрузки разных страниц (даже разных доменных имен) (значение не изменится, если оно не изменено) и может поддерживать очень длинное имя (2 МБ).
Возьмем простой пример:
Вы вводите в консоли страницы:
window.name = "Hello World"
window.location = "http://www.baidu.com"
Страница переходит на домашнюю страницу Baidu, но имя окна сохраняется, оно по-прежнему Hello World, и похоже, что междоменное решение готово:
Фронтальная логика:
// name/client/index.html 对应 localhost:8080/index.html
<script>
let data = '';
const ifr = document.createElement('iframe');
ifr.src = "http://localhost:8081/data.html";
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function() {
ifr.onload = function() {
data = ifr.contentWindow.name;
console.log('收到数据:', data);
}
ifr.src = "http://localhost:8080/proxy.html";
}
</script>
Страница данных:
// name/server/data.html 对应 localhost:8081/data.html
<script>
window.name = "data.html 的数据!";
</script>
localhost:8080index.html
на стороне данных запросаlocalhost:8081/data.html
, мы можем создать новый iframe на странице, src iframe указывает на адрес конца данных (используя междоменные возможности тега iframe), а значение window.name устанавливается в конце данных файл.
Однако, если src страницы index.html и iframe страницы отличаются от источника, то ничто в iframe не может быть изменено, поэтому значение имени iframe не может быть получено, поэтому нам нужно изменить src снова после загрузки data.html.Укажите на html-файл того же происхождения или установите для него значение «about:blank;», тогда мне просто нужно создать пустую страницу proxy.html в том же каталоге, что и index. HTML. Если имя окна, полученное напрямую без повторного указания на src, сообщит об ошибке:
бежать заyarn name
Вы можете увидеть эффект:
6.postMessage
postMessage — это недавно добавленная функция HTML5, Cross Document Messaging, в настоящее время: Chrome 2.0+, Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+ и Safari 4.0+ поддерживают эту функцию, используйте ее также очень просто.
Фронтальная логика:
// postMessage/client/index.html 对应 localhost:8080/index.html
<iframe src="http://localhost:8081/data.html" style='display: none;'></iframe>
<script>
window.onload = function() {
let targetOrigin = 'http://localhost:8081';
window.frames[0].postMessage('index.html 的 data!', targetOrigin);
}
window.addEventListener('message', function(e) {
console.log('index.html 接收到的消息:', e.data);
});
</script>
Создайте iframe и используйте метод postMessage iframe, чтобы представитьhttp://localhost:8081/data.html
Отправьте сообщение, а затем прослушайте сообщение, чтобы получить сообщение из его документа.
Логика на стороне данных:
// postMessage/server/data.html 对应 localhost:8081/data.html
<script>
window.addEventListener('message', function(e) {
if(e.source != window.parent) {
return;
}
let data = e.data;
console.log('data.html 接收到的消息:', data);
parent.postMessage('data.html 的 data!', e.origin);
});
</script>
Запустите службу:yarn postMessage
и откройте браузер, чтобы посетить:
Подробнее о postMessage см. в руководстве:
Сообщение PostMessage — Alchetron, Бесплатная социальная энциклопедия
Window.postMessage()
7.document.domain
В случае, когда основной домен один и тот же, но поддомены разные, это можно решить, установив document.domain, Конкретный метод можно найти вhttp://www.example.com/index.html
а такжеhttp://sub.example.com/data.html
Добавьте два файла по отдельностиdocument.domain = "example.com"
Затем создайте iframe через файл index.html, чтобы управлять окном iframe для взаимодействия.Конечно, этот метод может решить только ситуацию, когда основной домен тот же, но имя домена второго уровня другое.
Установка домена script.example.com на qq.com, очевидно, бесполезна, так как же это проверить?
Тестовый метод немного сложнее, вам нужно установить nginx для сопоставления доменных имен.Если на вашем компьютере не установлен nginx, сначала установите его:nginx
Фронтальная логика:
// domain/client/index.html 对应 sub1.example.com/index.html
<script>
document.domain = 'example.com';
let ifr = document.createElement('iframe');
ifr.src = 'http://sub2.example.com/data.html';
ifr.style.display = 'none';
document.body.append(ifr);
ifr.onload = function() {
let win = ifr.contentWindow;
alert(win.data);
}
</script>
Логика на стороне данных:
// domain/server/data 对应 sub2.example.com/data.html
<script>
document.domain = 'example.com';
window.data = 'data.html 的数据!';
</script>
Откройте файл hosts под операционной системой: mac находится в файле /etc/hosts и добавьте:
127.0.0.1 sub1.example.com
127.0.0.1 sub2.example.com
Затем откройте файл конфигурации nginx: /usr/local/etc/nginx/nginx.conf и добавьте его в модуль http, не забудьте ввести nginx, чтобы запустить службу nginx:
/usr/local/etc/nginx/nginx.conf
http {
// ...
server {
listen 80;
server_name sub1.example.com;
location / {
proxy_pass http://127.0.0.1:8080/;
}
}
server {
listen 80;
server_name sub2.example.com;
location / {
proxy_pass http://127.0.0.1:8081/;
}
}
// ...
}
скопировать код
эквивалентно разговоруsub1.example.com
а такжеsub2.example.com
Эти адреса доменных имен указывают на локальный127.0.0.1:80
, а затем используйте nginx в качестве обратного прокси-сервера для сопоставления с портами 8080 и 8081 соответственно.
доступ вот такsub1(2).example.com
равен доступу127.0.0.1:8080(1)
запустить службуyarn domain
Зайдите в браузер, чтобы увидеть эффект:
Суммировать:
Я уже рассмотрел все первые семь междоменных методов. На самом деле это имеет смысл. Обычно используются первые три метода. Возможность упомянуть эти междоменные навыки во время интервью, несомненно, является плюсом для интервьюера. .
Приведенное выше описание метода может быть несколько неясным. Надеюсь, вы будете следовать за мной, чтобы вводить код в процессе чтения. Когда вы откроете браузер и увидите результат, вы сможете понять этот метод.