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

    debuger

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



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

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

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

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

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

    Все!

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

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

    Расширение для Chrome
    manifest.json
    {
    	"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»один, два вкратце — для того чтобы расширение работало при запуске Хрома в фоне.
    В «permissions»: [ "*://*/*"] указывается адреса сайтов где расширение работает и куда есть доступ послать ajax-запрос из расширения. Альтернатива «content_scripts»: [...],
    если нужно встроить скрипт прямо на страницы открытого сайта (полезно если нужно использовать функции и переменные объявленные в скриптах сайта, иначе расширение работает в своей области видимости а скрипты сайта в своей)
    «browser_action» — Нужен чтобы добавить кнопочку включения и выключения расширения
    альтернатива «page_action»
    одновременно page_action и browser_action использовать нельзя
    webRequest API и 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 соответственно для того что бы после включении и выключении плагина заголовки не подменялись. Удаляем навешенные обработчики

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

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

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

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

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

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

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

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

    P.S.S
    К теме поста не относится, но может кто хотел и забыл — вчера (21 января) начался очередной круг онлайн курсов по MongoDB
    Впечатления от курса почитать можно тут. Регистрация на курс должна быть доступна в течении пары недель (если судить по предыдущему кругу)
    Share post

    Similar posts

    Comments 20

      +14
      команда для запуска Хрома):
      chrome --disable-web-security
      
        +1
        Как я уже написал выше «мы не ищем легких путей», за мной это было замечено не единожды.
        А этот ключик я почему-то не заметил, спасибо
          0
          По-моему, в этом случае достаточно --allow-file-access-from-files.
            +1
            не совсем так, по дефолту в хроме нельзя с localhost делать ajax-запрос так как один файл не может читать/загрузить другой
            по сути флаг дает возможность подгружать файлы, но при этом сделать запрос с localhost на google.com нельзя. Флаг с --disable-web-security отключает Same Origin Policy — то о чем по сути был этот пост.
              0
              Понял, спасибо!
            0
            На ubuntu не сработало. Не знаете почему?
            google-chrome-stable --disable-web-security
            
              0
              нет, не подскажу. Возможно запуск через shell-script или
              /usr/bin/google-chrome --disable-web-security
              
              поможет
              0
              На ubuntu не сработало. Не знаете почему?
              google-chrome-stable --disable-web-security
              
                0
                Может, у вас уже открыт Хром и вы пытаетесь открыть ещё один, но уже с флагом?
                  0
                  Установил chromium — на нем заработало. Вероятно сборка chrome просто не поддерживает этот флаг =/
              0
              Спасибо вам большое аккурат столкнулся с таким вопросом час назад. Жаль, что расширение для хрома только (который решается легко ключиком). Но хотелось бы для Firefox чего-нибудь похожего.
              Единственное, чем я пока воспользовался — это Apache работающем как обратный прокси, и слал запросы сам на себя.
                –3
                У меня похоже карма(та что по жизни, а не та что на хабре) плохая…
                Win 7 / Google Chrome Canary Версия 26.0.1389.0 canary
                при закрытии спойлера крешится вкладка… в обычном хроме все нормально.
                Отправил баг репорт, но стало скучно. Вот что из этого получилось. Видео через jing
                Видео можно озаглавить — «Вред от использования sliding effects для анимации в jQuery»

                — на хабре jQuery 1.6.1
                slideUp сорцы
                кто первый найдет какой параметр при анимации вызывает креш тому пряник ^_^

                Легким движением руки пост превращается в чатик.
                  0
                  а развен ельзя указать точный хост для разрешения доступа
                    0
                    если открыть просто markup — Origin будет null
                    есть поднять какой нить сервер к примеру через xampp Origin == htttp://localhost
                    точный адрес указывать надо на продакшене если у нас будет присутствовать CORS.
                    Задача стояла не подымая сервера у себя и не меняя ничего на рабочем/тестовом сервере все же получать данные от него.
                    Или я не правильно понял вопрос?
                  0
                  Спасибо за расширение! Определенно пригодится.
                  Обновил иконки, если ок — можете делать merge :)
                    0
                    обновил, иконки брал первые попавшиеся
                    0
                    Не смог установить.
                    Система: Ubuntu 12.10, Chrome: 24.0.1312.52

                    An error has occurred
                    Download interrupted

                    Подозрение падает на * в названии расширения
                      0
                      виртуалка, ubuntu 12.04, версия хрома та же… расширение стало без проблем

                    Only users with full accounts can post comments. Log in, please.