- PVSM.RU - https://www.pvsm.ru -

Lifehack для Same-Origin-Policy; Google Chrome и другие

debuger

Задача:
— есть REST-сервер
— есть одно страничное приложение (HTML/CSS/Javascript) которое берет данные с сервера через XMLHttpRequest
— нужно разработать новую фичу

Когда перед front-end разработчиком стоит такая задача на вскидку есть два варианта:
— поднять тестовый сервер у себя на localhost
— попросить Back-end добавить в ответ сервера заголовок «Allow-Control-Allow-Origin: *» что-бы использовать XMLHttpRequest2 [1]

Первый вариант затратен по времени, и не всегда возможен. Второй вариант тоже не лучший — ставить «Allow-Control-Allow-Origin: *» вроде как не безопасно, да и back-end может это делать долго.

И тут мне пришла в голову вроде бы тривиальная идея — а зачем менять на сервере заголовки, если это можно сделать прямо на клиенте.

Про Fiddler я уже пробовал писать [2] когда-то.

Решение с Fiddler

Собственно тут все банально и просто.
— На вкладке «Filters» Включаем Use filters
— включаем «Set response header» и добавляем нужный заголовок «Allow-Control-Allow-Origin: *»
— включаем «Set request header» нужен для Хрома не нужен он для хрома смотрите ниже про флаги в хроме
Lifehack для Same Origin Policy; Google Chrome и другие
Все!

Боюсь что если бы на этом статья закончилась публика не оценила бы, и заминусовала меня и пост чуть менее чем полностью, поэтому далее идет решение для тех кто не хочет иметь ничего общего с Фидлером и кто не ищет легких путей.

Среди расширений для Хрома мне почему-то не попалось ни одного, которое могло бы изменять Response-заголовки, хотя возможно я плохо искал. Поэтому решил по быстрому написать свой велосипед. ссылка на расширение [3] и github ведь это модно и молодежно [4]

Расширение для Chrome

manifest.json [5]

{
	"name": "Allow-Control-Allow-Origin: *",
	"version": "1.0",
	"manifest_version": 2,
	"description": "Allow to you request any site with ajax from any source. Add to response - 'Allow-Control-Allow-Origin: *' header",
	"background": {  // собственно подключили скрипт нашего расширения 
		"scripts": ["background.js"]
	},
	"browser_action": { //добавим кнопку расширения с картинкой и заголовком, "default_popup" мне не нужен
	"default_icon": "off.gif",
	"default_title": "Allow-Control-Allow-Origin"
	},
	"permissions": [ 
		"storage",     // для localStorage
		"webRequest",    //для webRequest API
		"webRequestBlocking",
		"*://*/*" //хочу посылать запросы на любой адрес 
	],
	"web_accessible_resources": [     //ресурсы расширения - чтобы не писать полный путь к картинке когда мы ее будем менять
		"on.gif","off.gif"
	]
}

Чуть более подробно:
«background»один [6], два [7] вкратце — для того чтобы расширение работало при запуске Хрома в фоне.
В «permissions»: [ "*://*/*"] указывается адреса сайтов где расширение работает и куда есть доступ послать ajax-запрос из расширения. Альтернатива «content_scripts» [8]: [...],
если нужно встроить скрипт прямо на страницы открытого сайта (полезно если нужно использовать функции и переменные объявленные в скриптах сайта, иначе расширение работает в своей области видимости а скрипты сайта в своей)
«browser_action» [9] — Нужен чтобы добавить кнопочку включения и выключения расширения
альтернатива «page_action» [10]
одновременно page_action и browser_action использовать нельзя
webRequest API [11] и webRequestBlocking — подменять заголовки запроса и ответа
- «web_accessible_resources» — иначе путь к картинке должен выглядеть так:
нужно путь:
background-image:url("/sprites.png");
CSS
background-image:url('chrome-extension://__MSG_@@extension_id__/sprites.png');
JS
var url = chrome.extension.getURL('sprites.png');

backgound.js

var requestListener = function(details){
	var flag = false,
		rule = {
			name: "Origin",
			value: "http://evil.com/"
		};

	for (var i = 0; i < details.requestHeaders.length; ++i) {
		if (details.requestHeaders[i].name === rule.name) {
			flag = true;
			details.requestHeaders[i].value = rule.value;
			break;
		}
	}
	if(!flag) details.requestHeaders.push(rule);
	return {requestHeaders: details.requestHeaders};
};

var responseListener = function(details){
	var rule = {
			"name": "Access-Control-Allow-Origin",
			"value": "*"
		};

	details.responseHeaders.push(rule);

	return {responseHeaders: details.responseHeaders};
};


/*On install*/
chrome.runtime.onInstalled.addListener(function(){
	localStorage.active = false;
});

/*Icon change*/
chrome.browserAction.onClicked.addListener(function(tab){
	if(localStorage.active === "true"){
		localStorage.active = false;
		chrome.browserAction.setIcon({path: "off.gif"});

		/*Remove Response Listener*/
		chrome.webRequest.onHeadersReceived.removeListener(responseListener);
		chrome.webRequest.onBeforeSendHeaders.removeListener(requestListener);
	}else{
		localStorage.active = true;
		chrome.browserAction.setIcon({path: "on.gif"});

		/*Add Response Listener*/
		chrome.webRequest.onHeadersReceived.addListener(responseListener,{
			urls: [
				"*://*/*"
			]
		},["blocking", "responseHeaders"]);

		chrome.webRequest.onBeforeSendHeaders.addListener(requestListener,{
			urls: [
				"*://*/*"
			]
		},["requestHeaders"]);
	}
});

Подробнее по коду:
Callback'и для onHeadersReceived и onBeforeSendHeaders вынес в отдельные функции responseListener и requestListener соответственно для того что бы после включении и выключении плагина заголовки не подменялись. Удаляем навешенные обработчики [12]

chrome.runtime.onInstalled — выполнится один раз — там можно инициализировать default состояние плагина

chrome.browserAction.onClicked.addListener — когда клацаем по кнопке расширения — отлавливаем событие и имитируем включение и выключение плагина

if(localStorage.active === «true») — почему-то я каждый раз спотыкаюсь на этом и забываю что ключи в localStorage хранятся в <String>

По сути onBeforeSendHeaders нам не нужен, но так как Хром не дает отсылать с localhost ajax-запросы пришлось добавить.
В первой версии расширения я этого не заметил так как при запуске хрома у меня стоит флаг --allow-file-access-from-files

Подробнее про флаги можно почитать тут [13] и тут [14]

У меня стоят такие:
Правой кнопкой по ярлыку -> Properties -> Targets
C:UsersOloloAppDataLocalGoogleChromeApplicationchrome.exe --allow-file-access-from-files --remote-debugging-port=9222 --allow-file-access --allow-cross-origin-auth-prompt

Что касается FF — почему-то и здесь я не нашел расширений для подмены ответа от сервера, но опять же может плохо искал. Если кто подскажет вставлю в пост.

P.S.
Я надеюсь вам пригодится расширение или совет. У тех кому это показалось банально и не достойно хабра я прошу прощения, но как минимум одному человеку это было не так очевидно как вам и стало полезно (мне).
У меня есть сомнения по поводу безопастности, по идее с включенным расширением можно с любого сайта забивать на CORS [15], но мы ведь все честные и будем использовать это только для разработки.

Автор: vitvad

Источник [16]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/25335

Ссылки в тексте:

[1] XMLHttpRequest2: http://habrahabr.ru/post/120917/

[2] писать: http://habrahabr.ru/post/140147/

[3] ссылка на расширение: https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi

[4] github ведь это модно и молодежно: https://github.com/vitvad/Access-Control-Allow-Origin

[5] manifest.json: https://developer.chrome.com/extensions/manifest.html

[6] один: https://developer.chrome.com/extensions/event_pages.html

[7] два: https://developer.chrome.com/extensions/background_pages.html

[8] «content_scripts»: https://developer.chrome.com/extensions/content_scripts.html

[9] «browser_action» : https://developer.chrome.com/extensions/browserAction.html

[10] «page_action»: https://developer.chrome.com/extensions/pageAction.html

[11] webRequest API: https://developer.chrome.com/extensions/webRequest.html

[12] обработчики: http://developer.chrome.com/extensions/events.html

[13] тут : http://peter.sh/experiments/chromium-command-line-switches/

[14] тут: http://www.chromium.org/developers/how-tos/run-chromium-with-flags

[15] CORS: http://en.wikipedia.org/wiki/Cross-Origin_Resource_Sharing

[16] Источник: http://habrahabr.ru/post/166539/