О расширении
Данное расширение предоставляет возможность просматривать сайт результата поиска в Google, что значительно сокращает время на поиск и обработку информации.
Предыстория
После того как Google закрыла проект Instant Preview, поиск нужной информации стал занимать гораздо больше времени, открытых вкладок и нервов! :(
После чего я решил исправить данную ситуацию и написать небольшое расширение облегчающее мне жизнь.
Разработка
Благодаря тому, что в Google Chrome поддерживалась технология HTML5 (а именно iframe sandbox), реализовать расширение не составило труда и за один вечер был написан прототип. Я несомненно был рад этому, но было печально за других людей, которые тоже были жертвами закрытия Instant Preview. Поэтому решил выложить в открытый доступ расширение.
Само расширение базировалось на возможности загружать в iframe любую страницу при этом атрибут sandbox позволял отключать javascript и редирект главного окна т.е. нельзя было выполнить из iframe такой код
top.location = <redirect_url>;
И всё было хорошо пока на некоторых сайтах не встречался заголовок запрета к отображению в iframe «X-Frame-Options», это было ещё пол беды, но когда Google Chrome в новой версии запретил загружать на защищённом сайте незащищённый контент, т.е. нельзя было внедрить на сайте https данные в том числе и iframe http. В результате чего большая часть сайтов не отображалась в iframe. Пользователи начали жаловаться, расширение стало не справляться со своей задачей, опять приходилось переходить прямиком на сайт из результатов поиска, всё вернулось на начало!
Поиск решения
Но меня это не остановило, было интересно решить данную проблему. Первое что пришло на ум использовать web proxy, т.е. в iframe загружать данные не напрямую с сайта, а через web proxy по защищенному протоколу https тем самым решалась проблема с незащищенным контентом, а также web proxy мог обрезать заголовок X-Frame-Options.
На разработку своего web proxy не хотелось тратить время, задачу нужно решить быстро. Немного погуглив я наткнулся на данный скрипт google-proxy — это web proxy для appengine написан на python. Установив его и выкатив новую версию расширения, я не надолго забыл о проблеме. Но проблема пришла вновь в виде нехватки бесплатной квоты appengine, пришлось делать несколько зеркал для web proxy. Сделав четыре зеркала казалось, что всё хорошо, но в отзывах расширения мне написали, что опять появляется ошибка нехватки квоты. В appengine квота обнуляется каждые 24 часа, т.е. в то время когда я пользуюсь расширением квоты еще хватает, а вот для жителей другого края света :) квота уже исчерпана.
Свой web proxy
Немного подумав я решил всё таки потратить время на написание web proxy и выбрал язык Go. На освоение языка Go ушло немного времени, т.к. до этого я писал на нём небольшие скрипты в обучающих целях. Но с разработкой web сервера на Go не сталкивался. Почитав документацию и выбрав в качестве web инструмента gorillatoolkit, а именно роутер gorilla/mux. Писать роутер довольно легко:
r := mux.NewRouter()
r.HandleFunc("/env", environ)
r.HandleFunc("/web_proxy", web_proxy.WebProxyHandler)
r.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir(staticDir))))
r.Methods("GET")
Дальше для замены ссылок и адресов, чистки html выбрал библиотеку gokogiri это оболочка к libxml. Нужно было все адреса (url) зароутить на мой web proxy скрипт. Потом отключил некоторые теги полностью удалив их из DOM в том числе и тег script. Интерактивность preview сайта не нужна. CSS были тоже обработаны, т.к. там встречаются url картинок, шрифтов и т.д.
Получается такой меппинг тегов при обходе DOM вызывается функция соответствующая тегу.
var (
nodeElements = map[string]func(*context, xml.Node){
// proxy URL
"a": proxyURL("href"),
"img": proxyURL("src"),
"link": proxyURL("href"),
"iframe": proxyURL("src"),
// normalize URL
"object": normalizeURL("src"),
"embed": normalizeURL("src"),
"audio": normalizeURL("src"),
"video": normalizeURL("src"),
// remove Node
"script": removeNode,
"base": removeNode,
// style css
"style": proxyCSSURL,
}
xpathElements string = createXPath()
includeScripts = []string{
"/js/analytics.js",
}
cssURLPattern = regexp.MustCompile(`urls*(s*(?:["']?)([^)]+?)(?:["']?)s*)`)
)
Фильтрация html.
func filterHTML(t *WebTransport, body, encoding []byte, baseURL *url.URL) ([]byte, error) {
if encoding == nil {
encoding = html.DefaultEncodingBytes
}
doc, err := html.Parse(body, encoding, []byte(baseURL.String()),
html.DefaultParseOption, encoding)
if err != nil {
return []byte(""), err
}
defer doc.Free()
c := &context{t: t, baseURL: baseURL}
nodes, err := doc.Root().Search(xpathElements)
if err == nil {
for _, node := range(nodes) {
name := strings.ToLower(node.Name())
nodeElements[name](c, node)
}
}
// add scripts
addScripts(doc)
return []byte(doc.String()), nil
}
Фильтрация CSS
func filterCSS(t *WebTransport, body, encoding []byte, baseURL *url.URL) ([]byte, error) {
cssReplaceURL := func (m []byte) []byte {
cssURL := string(cssURLPattern.FindSubmatch(m)[1])
srcURL, err := replaceURL(t, baseURL, cssURL)
if err == nil {
return []byte("url(" + srcURL.String() + ")")
}
return m
}
return cssURLPattern.ReplaceAllFunc(body, cssReplaceURL), nil
}
Также были отфильтрованы cookie, заголовок X-Frame-Options и выставлен кеш на 24 часа.
Web proxy решено было установить на heroku, также подумывал об openshift, но насколько мне известно там ограничение на количество соединений.
Решение на heroku + Go меня полностью устроило и нет никаких проблем на квоты никто не жалуется (по сравнению с appengine), с марта месяца как говорится полёт нормальный.
Расширение
Расширение работает хорошо, добавил кнопки zoom. С зумированием была ещё та история, сразу пошёл по неверному пути и применил к iframe CSS transform scale, пришлось каждый раз как изменится зум или главное окно, вычислять размер iframe по новой. Но потом встретилось довольно простое решение :)
zoom: <zoom>%;
Этот зум применяется к странице web proxy к document.documentElement. Но на этом я не остановился, мне не хватало подсветки в preview того что ищу. Алгоритм подсветки ключевых слов был выбран простой — поиск строки в подстроке. Это работало хорошо для простых случаев, но когда в тексте встречаются слова с разным окончанием, то поиск не работал или того хуже короткие подстроки встречались в не в тех словах, ну вы сами понимаете как работает такой поиск.
Fulltext search подсветка
Решение было найдено в виде стеммера Портера, а именно его реализации в виде snowball, также была применена библиотека xregexp и аддон Unicode для создания токена, который передавался стеммеру. Я не стал писать алгоритм определения языка для стеммера, а применял стеммер для разных языков к токену и в случае успеха стеммер возвращал true.
Инструменты
В качестве языка я выбрал coffeescript, мне он нравится похож на питон, но есть свои недочёты. За сборку dev и production версии отвечает gulp. Очень простой сборщик, большое количество plugin'ов. Не малую часть времени я потратил на изучение и допиливание gulpfile.coffee, зато это оправдывается скоростью сборки. Также в сборке участвует browserify.
P.S. ах, да! Чуть не забыл про ссылку на расширение
Вопросы, предложения?
Автор: DevEx