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

в 8:53, , рубрики: java, javascript, Блог компании Mail.Ru Group, Веб-разработка, вебразработка, метки: , ,

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

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

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

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

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

Iframe «Same Origin»

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

Post Message?

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

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

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

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

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

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

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

Все ресурсы которые использует ваша страница еще и сохраняются на прокси серверах Гугл. Вот например наш логотип на прокси сервере 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 пути к элементам на своих страницах.
Как это сделано — доступ к контенту iFrame с другого домена

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

Автор: Cher

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js