Cute Gopher

Както някои читатели на блога знаят (здравей, мамо!), вече няколко години с група приятели водим курса по Golang във ФМИ през зимния семестър. Това е изключително полезно и забавно за нас, не само защото имаме възможност да се запознаваме със страхотни хора всяка години. Но както всичко хубаво, идва с определна цена. Част от нея е поддръжката на сайта на курса и архивите на всичките му издания през годините. Може би малко хора са ги забелязали, но тези архиви представляват съдържанито на сайта в края на всяка yчебна години, заедно с всички новини, лекции, задачи, решения и форуми. Линкове: 2013, 2014, 2015, 2016. Вярваме, че това биха били полезни материали на български за всеки решил да учи езика. В тази статия ще разкажа за досегашните ни проблеми с поддръжката на архивите и последното решение, до което стигнах. Надявам се да е голямо подобрение от сегашната ситуация и да изисква много по - малко поддръжка.

Преди да продължите ще трябва да предупредя, че почти със сигурност в тази статия ще се срещнат пингвини. Не искам да има оплакали се след това!

Контекст

Първо малко за нашия set up. Сайта използва evans, на който стъпват всички изборни курсове от фамилията ни “Програмиране с Х”. Макар и написан на ruby през годините е бил прекрасен инструмент без който щеше да ни е много по - трудно. Толкова е полезен, че дори сме се примирили как някои жизнено важни подобрения никога не успяват да се материализират! 🏳️ Лекциите ни, от своя страна, използват present и пускат собствен HTTP сървър.

Проблема

Последното е известен проблем за поддръжка тъй като е допълнителна сложност, за която трябва да се мисли. Точно поради тази причина често лекциите ни не са работили на сайта извън семестъра, в който преподаваме. Към момента на писане на тази статия (Юли 2018г.) това отново е факт - present на сървъра не работи и лекциите не са достъпни. Ситуацията става още по - сложна ако искаме да имаме лекциите за всички предишни години. За целта би трябвало да имаме по един present, пуснат на различен порт за всяка от предишните години. И съответните конфигурации за reverse proxy на външния web server. Всичко това докато трябва да измисляме различни домейни тъй като на lectures.fmi.golang.bg може да има само един единствен “virtual host”. Такива неефективни решения за прости неща мен ме притесняват много, дори понякога губя част от съня си. Честно! Надявам се, че всеки от вас е също толкова обсебен! 😆 За това и до сега дори не се опитвахме да показваме лекции от предишните години и те можеха да се видят само в хранилището им.

Друг много по - малък проблем, е че за момента старите версии на сайта все още се изпълняват от rails. Макар че на практика са статични страници, които никога няма да се променят. И тъй като интерпретируемия код не е най - бързото нещо, особено когато е “студен”, то архивите са леко бавни на първо посещение. Далеч съм от мисълта, че по цял ден има хора четящи форумите от 2014 година! Да не говорим и за добавената сложност да има няколко rails инстации работещи едновременно.

Може би има по - добър начин?

Без изпълняване на интерпретируем код би било толкова хубаво. Само ако имаше начин за представяне на статични сайтове по ефективен начин! Ох, май получавам обаждане по домашния телефон. Изчакайте малко…

Ало, Здравейте! Говорите с 1992г. Тук в CERN чухме, че имате проблем за който тъкмо на скоро измислихме един прототип-решение. Може и да се хареса. С повече късмет ще се задържи известно време. Значи, започвате да си разменяте форматирани файлове. В тях отделните елементи са отбелязани със служебни думи написани само с главни букви и…

Аха! Статични HTML файлове, значи. Да, мисля че са достатъчно просто решение за мен. Преди време дори бях обмислял нещо подобно само за лекциите. Но пък защо да не е за целите сайтове - нека си генерираме статични HTML, CSS и JS файлове, които да даваме директно от файловата система.

Решено. Но как точно?

Тук на помощ идва верния wget. След като се зачетох в man страницата му по - внимателно се сетих защо от време на време дарявам пари на FSF. Сега, остава само да намеря правилното заклинание, което да извлече истинската мощ на този потентен инструмент.

От тук задачата ми за всяка година от архива се състоеше от две части. Генериране на HTML от сайта и отделно генериране на HTML от лекциите.

Основния сайт

Първия ми опит беше с доста оптимистичното

wget -r -N -l inf  http://2013.fmi.golang.bg

Правилните файлове бяха свалени, изглежда си имат правилното съдържание. Супер, да ги сложим зад nginx. Важната част от конфигурацията му беше:

server {
    listen 80;
    server_name gofmi-2013.doycho.com 2013.fmi.golang.bg;

    root /path/to/2013.fmi.golang.bg;
    try_files $uri $uri/index.html =404;

    ...
}

И разбира се, не беше толкова лесно. Когато отида на някой адрес, различен от индекса, вместо хубав HTML браузъра ми предлага да сваля файла. Ох? Алооо, браузърите, не трябваше ли да показвате HTML-ите? Не и когато нямат Content-Type: plain/html някъде из header-ите в отговора от сървъра. “Но защо го няма?” питате. Причината е че файловете ни нямат разширения .html. wget послушно е направил имената на файловете да съответстват с URI-то си. Ето как изглеждаше директорията ми:

$ ls -lh 2013.fmi.golang.bg
total 116K
drwxr-xr-x  18 iron4o staff  612 Jul  4 00:32 announcements
-rw-r--r--   1 iron4o staff  12K Jul  4 00:32 'announcements?page=1'
-rw-r--r--   1 iron4o staff 7.8K Jul  4 00:32 'announcements?page=2'
drwxr-xr-x   7 iron4o staff  238 Jul  4 00:32 assets
drwxr-xr-x   5 iron4o staff  170 Jul  4 00:32 challenges
-rw-r--r--   1 iron4o staff 6.1K Jul  4 00:32 index.html
-rw-r--r--   1 iron4o staff  14K Jul  4 00:32 leaderboard

За това, разбира се има съответния флаг --adjust-extension на wget. Ооо, вече файловте имат .html и след малка промяна на try_files в nginx на $uri $uri.html $uri/index.html =404; всичко потръгна перфектно и това беше края на статията.

Нали?

Не :(

Изглежда всички картинки липсват. И за това има флаг на wget - --page-requisites. Това вече е всичко, нали? НЕ! Този път липсват всички аватари на потребителите на сайта. Просто wget отказва да ги свали. С какво са пък толкова по - различни от останалите? Този въпрос ми погълна срамно много време да разгадая. В крайна сметка забелязах, че в robots.txt на сайта на курса има следното:

# Disallow indexing of the users' photos
User-agent: *
Disallow: /uploads/photos/

Еееее, защо? Аз много ги искам, пък! wget може да спре да следва стандарта и да игнорира тези директиви с -e robots=off. Това вече изглежда като всичко! Крайната команда след още няколко дребни подобрения беше следния ред:

wget -e robots=off -r -N -l inf \
    --timestamping --page-requisites \
    --no-remove-listing --adjust-extension \
    -D 2013.fmi.golang.bg \
    http://2013.fmi.golang.bg

Разцъкване из сайта показва, че всичко си е там. Нещата изглеждат наред… докато се опитах да видя втора страница с новините. Тя е на URI /announcements?page=2, но въпреки това все още показваше първата страница. Странно! Добре… нали си имам файл announcements?page=1.html, защо не работи? Защото ?page=2 е част от query string-а и nginx стига само до /announcements в търсенето си на файл. Това може да се промени чрез още малко разбутване на try_files и добавяне на $uri$is_args$args.html в списъка. Така и query “args” ще участват в търсенто на файл. Крайния резултат е следния:

try_files $uri $uri$is_args$args.html $uri.html $uri.1.html $uri/index.html =404;

Не питайте за $uri.1.html. За него ме мързи да обяснявам, но трябва да е там. Подсказка: документацията на wget. Но с това вече имаме статичен сайта готов за разглеждане чрез обикновен web server, който чете само файловата система. Общо всеки сайт беше около 25МВ.

Лекциите

Първо, взех си лекциите от хранилището ни. Имали сме добри намерения и сме направили по един branch за всяка година, кръстени съответно 2013, 2014, 2015 и 2016. Но не ни се е получило съвсем и бранчовете 2014 и 2015 сочеха към един и същи commit. Oooops. Както и да е, поправих ги след малко git археология. След това във всеки branch си пуснах present:

$ git checkout 2013
$ present -base="assets" -play false
2018/07/04 01:14:55 Open your web browser and visit http://127.0.0.1:3999

Тук, вече подготвен с прясно знание за wget използвах следното:

wget -r -N -l inf \
    --timestamping --page-requisites \
    --no-remove-listing --adjust-extension \
    -k \
    http://127.0.0.1:3999/

Тук интересното е флага -k, който преработва HTML-ите, така че линковете да се запазят след преобразуванията от --adjust-extension. И, разбира се, това не беше достатъчно. Все още не се зареждаха. Някои статични файлове липсваха тъй като се зареждат през JavaScript. За да се поправи това ако резултата от wget е в директория http://127.0.0.1:3999:

cp -a $GOPATH/src/github.com/fmi/go-lectures/assets/static/* "http://127.0.0.1:3999/static"

Чудесно! Вече имаме и лекциите в статични файлове. 🎉

Да ги съединим в едно!

Идеята ми тук е лекциите да стоят в директория lectures/ вътре в дървото на сайта. Така ще могат да бъдат видяни на същия домейн, на който е и сайта. Пример: http://2013.fmi.golang.bg/lectures/01-intro.slide вместо оригиналното http://lectures.fmi.golang.bg/01-intro.slide.

Просто копиране на файловете не работи. present предполага, че работи в основната директория и използва URL-и започващи с /. Това се поправя чрез промяна в lectures/static/slides.js, където се заменя:

// from
var PERMANENT_URL_PREFIX = '/static/';

// to
var PERMANENT_URL_PREFIX = 'static/';

Почти сме там, обещавам! Последната стъпка е да се поправят URL-ите към лекциите в lectures.html от сайта. Там трябва да се направи замяната

'http://lectures.fmi.golang.bg/*' -> '/lectures/*'

И с това имах всичко събрано на едно място и работещо. Време да се почерпя!

Резултата

В момента тези архиви могат да бъдат видяни на следните адреси:

Занимателни наблюдения

В една от годините студент е имал решение на задача, което е генерирало близо 200MB лог след изпълнение на тестовете. Този файл го премахнах и няма да го посочвам. Но любопитните могат да си го намерят!

По някаква причина в досегашните ни rails-powered архиви страницата със лекции за 2015г. е повторила тази за 2016г. Това го поправих по малко дървен начин като я замених с index-а на present.

И като говорим за него, човек може да види индекса генериран от present вместо нашия списък с лекции ако вместо на /lectures отиде на /lectures/. Разликата е slash-а накрая. Това не ми се променяше и реших да го обява за “feature”.

Какво остава?

“Официалните” домейни (2013.fmi.golang.bg etc.) все още сочат към старите rails инстанции. Трябва да се преместят към горените архиви. Или алтернативно архивите да се копират на сегашния ни сървър и да се спрат rails инстанциите. Така най - накрая официалните страници ще имат всичко в себе си за обозримото бъдеще. Най - вече лекциите!

Архивирането на web страници било трудно, кой да знае!