Вызов синхронного XMLHttpRequest изнутри расширения (extension) Google Chrome
Пишу своё первое расширение под Google Chrome и застопорился на одном, казалось бы простом, моменте. Суть расширения такова:
1. перехватываем попытку обращения к mysite.com;
2. кроссдоменным синхронным запросом к нашему собственному сервису на api.service.com запрашиваем какой адрес хоста вместо mysite.com надо подставить;
3. меняем хост mysite.com в запросе пользователя на полученный адрес хоста и выполняем запрос.
Файл manifest.json:
/** * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann * (http://qbnz.com/highlighter/ and http://geshi.org/) */ .javascript.geshi_code {font-family:monospace;} .javascript.geshi_code .imp {font-weight: bold; color: red;} .javascript.geshi_code .kw1 {color: #000066; font-weight: bold;} .javascript.geshi_code .kw2 {color: #003366; font-weight: bold;} .javascript.geshi_code .kw3 {color: #000066;} .javascript.geshi_code .co1 {color: #006600; font-style: italic;} .javascript.geshi_code .co2 {color: #009966; font-style: italic;} .javascript.geshi_code .coMULTI {color: #006600; font-style: italic;} .javascript.geshi_code .es0 {color: #000099; font-weight: bold;} .javascript.geshi_code .br0 {color: #009900;} .javascript.geshi_code .sy0 {color: #339933;} .javascript.geshi_code .st0 {color: #3366CC;} .javascript.geshi_code .nu0 {color: #CC0000;} .javascript.geshi_code .me1 {color: #660066;} .javascript.geshi_code span.xtra { display:block; }
Файл background.js:
/** * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann * (http://qbnz.com/highlighter/ and http://geshi.org/) */ .javascript.geshi_code {font-family:monospace;} .javascript.geshi_code .imp {font-weight: bold; color: red;} .javascript.geshi_code .kw1 {color: #000066; font-weight: bold;} .javascript.geshi_code .kw2 {color: #003366; font-weight: bold;} .javascript.geshi_code .kw3 {color: #000066;} .javascript.geshi_code .co1 {color: #006600; font-style: italic;} .javascript.geshi_code .co2 {color: #009966; font-style: italic;} .javascript.geshi_code .coMULTI {color: #006600; font-style: italic;} .javascript.geshi_code .es0 {color: #000099; font-weight: bold;} .javascript.geshi_code .br0 {color: #009900;} .javascript.geshi_code .sy0 {color: #339933;} .javascript.geshi_code .st0 {color: #3366CC;} .javascript.geshi_code .nu0 {color: #CC0000;} .javascript.geshi_code .me1 {color: #660066;} .javascript.geshi_code span.xtra { display:block; }
Всё прекрасно получается, кроме одного момента: callback-функция для onBeforeRequest отрабатывает, возвращает redirectUrl раньше, чем функция getLink() возвращает нужное значение ссылки. В итоге адрес хоста mysite.com подменяется на undefined. И это несмотря на то, что в функции setLink() объект XMLHttpRequest открывает синхронное соединение.
Где моя ошибка, подскажите?
API всё возвращает правильно, заголовок Access-Control-Allow-Origin: * в ответе стоит, кукисы ставятся нормально - с этим проблем нет. Т.е. при второй и последующих попытках обращения к mysite.com значение ссылки берётся уже из кукисов и всё отрабатывает как надо, хост подменяется.
Описание объекта webRequest
Описание объекта cookies
1. перехватываем попытку обращения к mysite.com;
2. кроссдоменным синхронным запросом к нашему собственному сервису на api.service.com запрашиваем какой адрес хоста вместо mysite.com надо подставить;
3. меняем хост mysite.com в запросе пользователя на полученный адрес хоста и выполняем запрос.
Файл manifest.json:
/** * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann * (http://qbnz.com/highlighter/ and http://geshi.org/) */ .javascript.geshi_code {font-family:monospace;} .javascript.geshi_code .imp {font-weight: bold; color: red;} .javascript.geshi_code .kw1 {color: #000066; font-weight: bold;} .javascript.geshi_code .kw2 {color: #003366; font-weight: bold;} .javascript.geshi_code .kw3 {color: #000066;} .javascript.geshi_code .co1 {color: #006600; font-style: italic;} .javascript.geshi_code .co2 {color: #009966; font-style: italic;} .javascript.geshi_code .coMULTI {color: #006600; font-style: italic;} .javascript.geshi_code .es0 {color: #000099; font-weight: bold;} .javascript.geshi_code .br0 {color: #009900;} .javascript.geshi_code .sy0 {color: #339933;} .javascript.geshi_code .st0 {color: #3366CC;} .javascript.geshi_code .nu0 {color: #CC0000;} .javascript.geshi_code .me1 {color: #660066;} .javascript.geshi_code span.xtra { display:block; }
{
"name": "MyFirstChromeExtension",
"version": "0.1",
"description": "Description of my extension",
"permissions": ["webRequest", "webRequestBlocking", "cookies",
"*://mysite.com/*", "*://api.service.com/*"],
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2
}
"name": "MyFirstChromeExtension",
"version": "0.1",
"description": "Description of my extension",
"permissions": ["webRequest", "webRequestBlocking", "cookies",
"*://mysite.com/*", "*://api.service.com/*"],
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2
}
Файл background.js:
/** * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann * (http://qbnz.com/highlighter/ and http://geshi.org/) */ .javascript.geshi_code {font-family:monospace;} .javascript.geshi_code .imp {font-weight: bold; color: red;} .javascript.geshi_code .kw1 {color: #000066; font-weight: bold;} .javascript.geshi_code .kw2 {color: #003366; font-weight: bold;} .javascript.geshi_code .kw3 {color: #000066;} .javascript.geshi_code .co1 {color: #006600; font-style: italic;} .javascript.geshi_code .co2 {color: #009966; font-style: italic;} .javascript.geshi_code .coMULTI {color: #006600; font-style: italic;} .javascript.geshi_code .es0 {color: #000099; font-weight: bold;} .javascript.geshi_code .br0 {color: #009900;} .javascript.geshi_code .sy0 {color: #339933;} .javascript.geshi_code .st0 {color: #3366CC;} .javascript.geshi_code .nu0 {color: #CC0000;} .javascript.geshi_code .me1 {color: #660066;} .javascript.geshi_code span.xtra { display:block; }
var newlink = false;
function setLink() {
var req = new XMLHttpRequest();
// открываем синхронный запрос к API нашего сервиса
req.open('GET', 'http://api.service.com', false);
req.onload = function() {
// API в заголовках ответа, в "кастомном" заголовке
// Link-Url присылает нам нужную ссылку
// + одновременно при этом сервер ставит кукисы "newlink"
// для домена api.service.com, в которых сохраняет
// полученный адрес ссылки
newlink = this.getResponseHeader('Link-Url');
};
req.send(null);
return newlink;
}
function getLink() {
// смотрим, есть ли у нас в кукисах сохранённый адрес нужной ссылки
// Если есть - берём его. В противном случае выполняем запрос к API сервиса
chrome.cookies.get({url:'http://api.service.com', name:'newlink'}, function(cook) {
if ( ! cook)
return setLink();
else
return cook.value;
});
}
chrome.webRequest.onBeforeRequest.addListener(
function(info) {
// отключаем перехватывание запросов к нашему API
if(info.url.indexOf('api.service.com') != -1)
return {cancel:false};
var link = getLink();
return {redirectUrl: info.url.replace(/mysite.com/i, link)};
},
// filters
{
urls: [
"*://mysite.com/*",
"*://api.service.com/*"
]
},
// extraInfoSpec
["blocking"]
);
function setLink() {
var req = new XMLHttpRequest();
// открываем синхронный запрос к API нашего сервиса
req.open('GET', 'http://api.service.com', false);
req.onload = function() {
// API в заголовках ответа, в "кастомном" заголовке
// Link-Url присылает нам нужную ссылку
// + одновременно при этом сервер ставит кукисы "newlink"
// для домена api.service.com, в которых сохраняет
// полученный адрес ссылки
newlink = this.getResponseHeader('Link-Url');
};
req.send(null);
return newlink;
}
function getLink() {
// смотрим, есть ли у нас в кукисах сохранённый адрес нужной ссылки
// Если есть - берём его. В противном случае выполняем запрос к API сервиса
chrome.cookies.get({url:'http://api.service.com', name:'newlink'}, function(cook) {
if ( ! cook)
return setLink();
else
return cook.value;
});
}
chrome.webRequest.onBeforeRequest.addListener(
function(info) {
// отключаем перехватывание запросов к нашему API
if(info.url.indexOf('api.service.com') != -1)
return {cancel:false};
var link = getLink();
return {redirectUrl: info.url.replace(/mysite.com/i, link)};
},
// filters
{
urls: [
"*://mysite.com/*",
"*://api.service.com/*"
]
},
// extraInfoSpec
["blocking"]
);
Всё прекрасно получается, кроме одного момента: callback-функция для onBeforeRequest отрабатывает, возвращает redirectUrl раньше, чем функция getLink() возвращает нужное значение ссылки. В итоге адрес хоста mysite.com подменяется на undefined. И это несмотря на то, что в функции setLink() объект XMLHttpRequest открывает синхронное соединение.
Где моя ошибка, подскажите?
API всё возвращает правильно, заголовок Access-Control-Allow-Origin: * в ответе стоит, кукисы ставятся нормально - с этим проблем нет. Т.е. при второй и последующих попытках обращения к mysite.com значение ссылки берётся уже из кукисов и всё отрабатывает как надо, хост подменяется.
Описание объекта webRequest
Описание объекта cookies