...

воскресенье, 24 ноября 2013 г.

Как это сделано — доступ к контенту iFrame с другого домена

Сегодня я хочу рассказать о том, как мы в своем проекте indexisto.com сделали дешевую китайскую подделку аналог инструмента Google Webmaster Marker. Напомню, что Marker это инструмент в кабинете Google Webmaster, который позволяет аннотировать ваши страницы Open Graph тегами не вставляли их в код страницы. Для этого вы просто размечаете контент вашей страницы в админке Google Webmaster. Страница грузиться в Iframe, Далее вы просто выбираете мышкой текст на странице и в контекстном меню помечаете, что это title, а это published_time.


Теперь Google, встретив подобную страницу на вашем сайте, уже знает, что за контент на ней опубликован и как его красиво распарсить в сущность (статью, товар, видео..)


Нам был нужен подобный функционал. Задача казалась несложной и исключительно клиентсайд. Однако на практике решение лежит на стыке клиентсайда и серверсайда. При этом «чистые» JS программисты могу ничего не знать про различные прокси серверы и очень долго подходить к снаряду.


В нашем случае хотелось чтобы вебмастер мог удобно (пометив мышкой) получить значение xPath к определенным элементам на своей странице


Iframe «Same Origin»




И так в нашей админке человек должен ввести URL страницы своего сайта, мы отобразим ее в iFrame, человек тыкнет мышкой куда надо, мы получим искомый xPath. Все бы ОК, но у нас нет доступа к контенту страницы с другого домена загруженной в iframe в нашей админке (наш домен), из за политики безопасности браузера.

Post Message?




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

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


Посмотрим как сделано у Google



Обратим внимание на src="https://wmthighlighter.googleusercontent.com/webmasters/data-highlighter/RenderFrame/007....." — наша страница грузиться с домена Google. Далее еще более сурово: даже скрипты и картинки в исходном документе прогоняются через прокси. Все src, href… заменены прямо в html. Примерно вот так:




Все ресурсы которые использует ваша страница еще и сохраняются на прокси серверах Гугл. Вот например наш логотип на прокси сервере Google.


CGIProxy?




Сразу показалось, чтобы сделать тоже самое нужно поднимать полноценный прокси по типу CGIProxy. Этот прокси сервер делает примерно тоже самое, чем промышляет гугловский wmthighlighter.googleusercontent.com


Visit the script's URL to start a browsing session. Once you've gotten a page through the proxy, everything it links to will automatically go through the proxy. You can bookmark pages you browse to, and your bookmarks will go through the proxy as they did the first time.



Свой Proxy!




Однако, если сузить задачу, написать простой proxy намного проще самому. Дело в том, что делать так делает Google, прогоняя весь контент страницы через прокси совершенно не обязательно. Нам просто нужно чтобы html любой страницы отдавался с нашего домена, а ресурсы можно подгрузить и из оригинального домена. Https мы пока отбросили.

Задача супер производительности или удобств настроек здесь не стоит, и сделать это можно по быстрому и на чем угодно, от node.js до php. Мы написали сервлет на Java.

Качаем страницу




Что должен делать прокси сервлет? Через get параметр получаем url страницы которую нужно загрузить, далее качаем страницу.

Обязательно определяем кодировку страницы (через http ответ или их charset html) — наш прокси должен ответить в той же кодировке что и страница которую мы загрузили. Так же определим Content-Type на всякий случай, хотя и так понятно что мы получаем страницу в text/html и отдадим ее так же.



final String url = request.getParameter("url");
final HttpGet requestApache = new HttpGet(url);
final HttpClient httpClient = new DefaultHttpClient();
final HttpResponse responseApache = httpClient.execute(requestApache);
final HttpEntity entity = responseApache.getEntity();
final String encoding = EntityUtils.getContentCharSet( entity );
final String mime = EntityUtils.getContentMimeType(entity);
String responseText = IOUtils.toString(entity.getContent(), encoding);


Меняем относительные URL на абсолютные в коде страницы




Важный момент — надо пройтись по всем атрибутам с src и href в странице (пути файлов стилей, картинки), и заменить относительные урлы на абсолютные. Иначе страница будет пытаться загрузить картинки с каких-то папок на нашем прокси, которых у нас естественно нет. В любом языке есть готовые классы или можно найти сниппеты кода для этого дела на stackoverflow:

final URI uri = new URI(url);
final String host = "http://" + uri.getHost();
responseText = replaceRelativeLinks(host,responseText);


Готово!




Вот и все, страницу можно отдавать для отображения в iframe в нашей админке:

protected void sendResponse(HttpServletResponse response, String responseText, String encoding, String mime) throws ServletException, IOException {
response.setContentType(mime);
response.setCharacterEncoding(encoding);
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().print(responseText );
response.flushBuffer();
}


Вот получившийся прокси, можно подствить в параметр url любой сайт, и он как бы грузиться с нашего домена:

http://adminpanel.indexisto.com/adminpanel/marker.rpc?url=http://habrahabr.ru/


Код сервлета целиком.


Остальное дело техники.


JS часть — подсвечиваем дом элемент под мышкой и получаем xpath




Работать с документом в iFrame можно через iFrameDomElement.contentWindow.document.

Подсвечиваем dom элементы над которыми человек водит мышкой. Лучше делать это с помощью shadow так как тогда элемент не будет смещаться, а вся остальная страница постоянно прыгать. Тут же я определяю xpath элемента. Существует множество сниппетов того как получить xpath элемента dom. Эти сниппеты можно модифицировать под свои задачи, например вым нужны в xpath только тэги. Или вам нужны id если они есть и классы если нет id.

elmFrame.contentWindow.document.body.onmouseover= function(ev){
ev.target.style.boxShadow = "0px 0px 5px red";
curXpath = getXPathFromElement(ev.target);
}


JS часть — обрабатываем клик




Клик человека на странице в iframe сразу же «гасится» (перехода по ссылке в iframe не произойдет). Событие дергает только нашу функцию которая уже пошлет xPath куда нужно:

elmFrame.contentWindow.document.body.onclick = function(ev){
$wnd.setText(curXpath);
ev.preventDefault();
ev.stopPropagation();
}


Profit!




Вот и все, теперь в нашей админке вебмастер может намного проще быстро получить xpath пути к элементам на своих страницах.


Гугл в своем инструменте marker естественно ушел намного дальше. Для того чтобы четко идентифицировать элемент на странице, нужно пометить один и тот же элемент (например, заголовок статьи) на нескольких однотипных страницах, чтобы можно было точнее построить xpath и отбросить разные id типа «post-2334» которые явно сработают только на одной странице. В нашей админке пока xpath надо поправить руками, чтобы получить приемлимый результат


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий