Створення news-reader’а з веб-інтерфейсом
Кожен, хто починає програмувати на Перлі, стикається з абревіатурою CPAN, що означає Comprehensive Perl Archive Network (”всеосяжний архів по Перлу”) CPAN - прекрасний ресурс, де можна відшукати все що завгодно, пов’язане з Перлом. В світі багато дзеркал CPAN, так що вибирайте те, яке вам ближче географічно. Для цього зайдіть на ftp://ftp.funet.fi/pub/languages/perl/CPAN/CPAN, де є список всіх дзеркал.
Небагато про організацію архіву. Кожне дзеркало центрального сервера містить файл CPAN/Roadmap або CPAN/Roadmap.html, а також CPAN/modules/Readme, де є описи всіх модулів, що містяться. Ви скажете - Навіщо нам ці модулі? Ми і самі можемо написати… Ну якщо так - то будь ласка, пишіть самі. А ті, хто бажає заощадити час і сили, зайдіть на CPAN і знайдіть для себе вже готовий модуль.
Не так давно перед мною стало завдання - зробити щось на зразок news-reader’а прямо на веб-сторінці. Тобто: при зверненні до сервера користувач отримує список повідомлень в групі новин (конференції) у вигляді посилань на самі повідомлення. Клацнувши по посиланню, користувач зможе подивитися картинку, що міститься в повідомленні. Доступ до конференції здійснюється по протоколу NNTP.
Мені потрібний був CGI, NNTP і base64 для декодування картинок усередині повідомлень. Для забезпечення CGI-интерфейса був взятий модуль CGI.pm (CPAN/authors/id/LDS/CGI.pm-2.13.tar.gz). Для того, щоб спілкуватися з news-сервером по NNTP протоколу я знайшов NNTPClient все там же на CPAN - CPAN/authors/id/RVA/NNTPClient-0.22.pm.gz. Ну і останній компонент - base64 декодувальник я знайшов в модулі LWP (MIME:Base64) (CPAN/authors/id/LDS/CGI-modules-2.74.tar.gz.)
Використання бібліотек, написаних не мною самим, дозволило відвернутися від низькорівневих завдань, і концентруватися на найвищому рівні функціонування скрипта. Так і часу заощадив немало.
Ось такий скрипт вийшов в результаті:
Скрипт 1
=1= #!/usr/bin/perl
=2=
=3= use CGI; # must be version 2 or higher
=4= use News::NNTPClient;
=5= use MIME::Base64;
=6=
=7= $nntpserver = “news.teleport.com”; # location of news server
=8=
=9= ## because of the copyright nature of this material, you should
=10= ## put this script in а directory that has an appropriate htaccess file.
=11=
=12= @groups = (
=13= ["clari.living.comics.bizarro", "Bizarro"],
=14= ["clari.living.comics.cafe_angst","Cafe Angst"],
=15= ["clari.living.comics.doonesbury","Doonesbury"],
=16= ["clari.living.comics.forbetter","For Better or For Worse"],
=17= ["clari.living.comics.foxtrot","Foxtrot"],
=18= ["clari.living.comics.ozone_patrol","Ozone Patrol"],
=19= ["clari.editorial.cartoons.toles","Toles"],
=20= ["clari.editorial.cartoons.worldviews","Worldviews"],
=21= ["clari.news.photos","News photos (not а comic, but handy)"],
=22= );
=23=
=24= $Q = new CGI;
=25= $Qself = $Q->self_url;
=26=
=27= unless ($group = $Q->param(’group’)) { # nothing at all, give index
=28= $links = join “\n”
=29= map { “
[0]\”>$_->[1]” } @groups;
=30=
=31= print <<"GROK"; q/"/;
=32= @{[$Q->header]}
=33= @{[$Q->start_html('Comics','merlyn@stonehenge.com')]}
=34=
Read the Comics
=35=
Select the group you want to read:
=36=
=37= $links
=38=
=39=
Please respect the copyrights and license agreements of this service.
=40= @{[$Q->end_html]}
=41= GROK
=42= q/”/;
=43= exit 0;
=44= }
=45=
=46= unless ($article = $Q->param(’article’)) { # group but no art, give group
=47= $N = new News::NNTPClient($nntpserver,119,0);
=48= for ($N->xover($N->group($group))) {
=49= ($numb,$subj) = split /\t/;
=50= $links .= “
$subj\n”;
=51= }
=52=
=53= print <<"GROK"; q/"/;
=54= @{[$Q->header]}
=55= @{[$Q->start_html('Comics','merlyn@stonehenge.com')]}
=56=
Read the Comics
=57=
Select the article you wish to view:
=58=
=59= $links
=60=
=61=
Please respect the copyrights and license agreements of this service.
=62= @{[$Q->end_html]}
=63= GROK
=64= q/”/;
=65= exit 0;
=66= }
=67=
=68= ## $group and $article both valid:
=69= $N = new News::NNTPClient($nntpserver,119,0);
=70= $N->group($group);
=71= @art = $N->article($article);
=72= shift @art while @art and $art[0] !~ /^Content-Type: (image\/[-a-z]+)/;
=73= $type = $1;
=74= shift @art while @art and $art[0] !~ /^\s*$/;
=75= pop @art; # heh
=76= $gif = decode_base64(join “” @art);
=77= print “Content-type: $type\n\n”;
=78= print $gif;
=79= exit 0;
Коментарі:
Рядки 3-5: підключення модулів
Рядок 7: Адресу news-сервера можна звичайно ж поміняти.
Рядки 12-22 визначають масив назв груп, в довгій формі - для сервера і в короткій - для людини. Довге ім’я можна отримати, наприклад, так - $groups[2][0], а коротке - $groups[2][1]
У рядку 24 створюється CGI-об’єкт $Q. Вхідні дані скрипт може отримувати з командного рядка, через змінну оточення і із стандартного потоку введення.
Рядок 25 дозволяє отримати URL скрипта, який використовується надалі.
У 27 рядку перевіряється вхідний параметр ‘group’. І якщо його немає - то виводиться повний список груп. Рядки 28-44 створюють сторіночку із списком доступних груп на основі масиву @groups 28-29 рядків створюють змінну $links, що містить посилання на групи у вигляді:
Doonesbury
Де SOMEWHERE - це якраз URL скрипта.
Рядки з 32 по 40 виводять результат - заголовок, початок HTML-документа, список посилань і кінець HTML-документа. Конструкція @{[thing]} - це виведення ‘thing’ в списковому контексті. Можна було замість цього просто розбити оператора print на декілька операторів print.
Рядок 46: перевірка на наявність вхідного параметра article.
Рядки з 47 по 65 виводять список повідомлень у вибраній групі.
Рядок 47: встановлення з’єднання з news-сервером (порт 119 - стандартний)
Рядки з 48 по 51: виведення всіх повідомлень в даній конференції. Вираз в рядку 48 повертає масив рядків, де символом табуляції розділені номер і тема повідомлення. 49 рядок розбиває рядки, а 50-тий - створює рядок із списком вже в HTML-виду, де на кожне повідомлення - своє посилання:
Рядки 52-60: виведення результатів
Рядок 69 починає частину скрипта, яка безпосередньо видає картинки, що містяться в окремих повідомленнях. Знову встановлюється з’єднання з news-сервером, і забирається вже конкретне повідомлення.
Рядок 71: повідомлення вкладається в масив @art
Рядок 72: оскільки важлива тільки та частина повідомлення, яка починається з Content-type, то решту всіх рядків можна викинути. При цьому зберігається тип картинки (рядок 73).
Рядки 74-75: Порожній рядок після Content-type пропускається
Рядок 76: безпосередньо декодування з base64
Рядки 77-78: виведення картинки браузеру