nginx try_files в продакшине
likhatskiy
Поступила задача: закрывать сайт с 2:00 по 5:00МСК и показывать в это время определенную страницу.
Решил попробовать решить задачу при помощи директивы try_files в nginx

server {
listen 80;
server_name example.ru;
root /var/www/example.ru/;

location / {
try_files /tech_page.html @example;
}
location @example {
proxy_pass http://192.168.202.2:7777;
break;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

По крону стартует скрипт, который в 2:00 переименнует файл stop_tech_page.html в tech_page.html и 5:00 обратно. Получаем, что  файл tech_page.html существует с 2:00 по 5:00 и nginx показывает нам эту страницу 
Метки: ,

Mojolicious application with Virtual Host support
likhatskiy
Часто требуется создание нескольких сайтов относящихся к одному проекту, например основной сайт, админка и допустим блог. Если второстепенные приложения не большие, то не хочется их запускать отдельно от основного. На помощь приходят Virtual Host. В apache это реализуется просто. А как же это сделать на mojolicious.

Все достаточно просто. Их можно разделить по заголовку Host, используя плагин header_condition.
Выложил на github 
В последствии напишу плагин специально заточенный под эту задачу

OAUTH в Mojo и Mojolicious
likhatskiy
 После написания модуля для всех версий протокола OAUTH преступил к использованию этого добра в проектах на Mojolicious. В итоге получился очень удобный плагин. Прикрутить его проще простого:

my $config = {
provider1 => {
# provider1 config params
},
provider2 => {
# provider2 config params
},
};

# Mojolicious
$self->plugin('o_auth', {
'config' => $config,
});

# Mojolicious::Lite
plugin 'o_auth', {
'config' => $config,
};

Плагин сам создает роутеры для инициализации и коллбэка от провайдера. На github есть пример реализации.
Метки: , , , ,

OAUTH 1.0, 1.0A и 2.0 в одном течении
likhatskiy
 В силу стремительного распространения OAUTH, решил внедрить его в своих проектах. Начал разбираться, выяснилось что есть аж три версии протокола: 1.0, 1.0A и 2.0. Побродил по cpan и нашел реализацию только 1.0 и 1.0A. Казалось бы этого должно хватить, да вот у twitter 1.0A, а у facebook 2.0. Хочется поддерживать обе социальные сети, в силу их популярности. Протокол 2.0 никто не реализовал на перл. Ну что же, занялся этим сам.

Почитал спецификацию всех протоколов. Составил схему в mindmanager, чтобы понять как их объединить. И написал модуль, который поддерживает все существующие версии протокола. Пока выложил только на github . Документации думаю пока хватит, никак не соберусь ее дописать ...
Метки: , ,

default layout in Mojolicious
likhatskiy
  В Mojolicious, чтобы указать layout, его надо
либо прописывать в шаблоне
% layout 'new_design'
либо передавать контроллере
$self->render(layout => 'new_design');
либо передавать в роутере
$r->route('/')->to('main#welcome', layout => 'new_design');
и так в каждом((((
 
На днях задался вопросом задания layout по умолчанию. Не вижу смысла его указывать каждый раз, если он у меня реально один. Решил попробовать указать его не в роутере, а в мосте (bridge) - СРАБОТАЛО.
 
for my $b ($r->bridge->to('user-auth#enter', layout => 'design')) {
$b->route('/')->to('main#welcome');
$b->route('/test')->to('main#test');
$b->route('/page1')->to('main#page1', layout => 'design1');#его также можно сменить
$b->route('/rss')->to('main#rss', layout => undef);#или убрать, например для rss
}

Теперь все что проходит через этот мост будет иметь один layout. 
 

truncate string by length and word
likhatskiy
Часто требуется обрезать строку по длине (ну возможно поставить в конце "..."). Проблема в том, что обрезание может попасть на часть слова, поэтому надо это слово обойти.
sub trunc {
    s/\s+?\S+?$//, return $_ for my $str = substr shift, 0, (shift || 1) + 1;
}
Вывод:
print trunc("11111 222  4444", 9);
#output: 11111 222
print trunc("11111 222  4444", 8);
#output: 11111
Метки: , ,

Hex последовательность строки в perl
likhatskiy
В IPHONE приложениях есть поддержка смайлов. Так вот если писать из телефона в базу mysql, и смотреть результат в телефоне, то все корректно отображается. Но браузер не понимает iphone-их смайлов. Так вот стала задача: понять как их выцепить, чтобы заменять на свои картинки. Задачу начал решать через hex представление.

print join '', map { sprintf "%2x", ord } $str =~ /./g;

Оказалось, что мне смайлы в hex начинаются с буквы "e".

print join '', grep { /^e\d+/ } map { sprintf "%2x", ord } $t =~ /./g;


На входе: 
На выходе: e057
Метки: , , ,

Encode шаблонов в Mojo или я на глагне
likhatskiy
Столкнулся с большой проблемой в Mojo.
У меня есть проект на cp1251, и перейти на utf-8 я не могу по техническим причинам. Этот проект является сервером для j2me клиента, и для кросс-платформенности на всех телефонах я сделал на нем универсальный decode, но привязка была к cp1251. Поэтому кодировку менять я не могу. Решил начинать переписывать все на Mojo. Столкнулся с тем, что Mojo делает encode всех шаблонов в utf-8. Естественно у меня все поплыло на фанере до парижа. И я решил сделать возможность смены кодировки, просто в startup пишем:
         $self->renderer->encoding('cp1251');
И он все шаблоны будет переводить в cp1251. По умолчанию будет UTF-8.
Автор эту возможность одобрил, да не полностью. Изначально я сделал возможность не только сменить кодировку, но и вовсе выключить encode шаблонов ($self->renderer->encoding('');). Но это автор уже не одобрил, долго с ним спорил. Ну не пойму я, зачем делать encode шаблонов, если я уверен, что у меня с данными все в порядке. Говорит, что это может быть не предсказуемо. Ну дело его, проект его, он о нем больше знает, и решать ему - все равно человек рульный.
Начал тестить нововведение с правками автора. И тут не задача. Надо делать decode всех переменных. Я решил вопрос так:
% use encoding 'cp1251';
во всех шаблонах. Да, не удобно, но что делать...

Pango-WARNING - munin
likhatskiy
Поставил на VPS - Gentoo munin. Все хорошо, да вот картинки он генерил кривые, весь текст в квадратах.
/usr/bin/munin-cron --force-root написал:
(process:15364): Pango-WARNING **: failed to choose a font, expect ugly output. engine-type='PangoRenderFc', script='common'

(process:15364): Pango-WARNING **: failed to choose a font, expect ugly output. engine-type='PangoRenderFc', script='latin'


Пытался переставлять зависимые пакеты, но безуспешно. Решение проблемы: поставить corefonts. Теперь все заработало

Метки: , , ,

Мини экскурс в AnyEvent - пишем паука
likhatskiy
Мы рассмотрим AnyEvent на примере yandex паука, который собирает организации с maps.yandex.ru по поисковому слову (например "аптека"). Но перед этим разберемся с тем, что нам понадобится.

Не много о главном - или что такое AnyEvent

AnyEvent — это прежде всего фрэймворк для событийно-ориентированного программирования (event loops). Особенностью подобных фрэймворков является одиночное применение, т.е если ты используешь один из них, то нет возможности использовать какой-либо другой в том же коде. AnyEvent другой. Он является некой абстракцией событийных машин, как DBI является абстракцией многих апи баз данных.

Событийно-ориентированное программирование

Событийный подход к программированию включает использование объектов, способных реагировать на события, происходящие в системе. Событийный подход используется при разработке как самостоятельных программ, так и операционных систем, например, Microsoft Windows или OS/2 Presentation Manager. Вот пример ввода данных через STDIN:
$| = 1; print "enter your name> ";
my $name = <STDIN>;
Через событийную машину это можно реализовать при помощи коллбэков (callback) -  функций, которые срабатывают при появлении события:
use AnyEvent;

$| = 1; print "enter your name> ";

my $name;

my $wait_for_input = AnyEvent->io (
    fh   => \*STDIN, # за каким дескриптором смотрим
    poll => "r",     # какое событие ожидать (r - чтение, w - запись)
    cb   => sub {    # коллбэк
        $name = <STDIN>; # читаем
    }
);

# делаем что нибудь еще
Как правило, в реальных задачах оказывается недопустимым длительное выполнение обработчика события, поскольку при этом программа не может реагировать на другие события. Было бы хорошо выполнить какие-либо действия, пока мы ожидаем ввода данных. Ожидание в первом примере блокирует процесс до получение данных. Во втором примере мы просто зарегистрировали событие на чтение, которое не блокирует процесс. Просто когда произойдет ввод данных, вызовется коллбэк, который и прочитает данные.
Метод AnyEvent->io создает I/O "watcher", я его называю "смотрящим". Он так называется потому, что он слушает файловый (либо какой-либо другой) дескриптор, на факт происхождения нужного нам события.

Общий принцип - Condition Variables

Вернемся к I/O "watcher. Этот пример не совсем рабочий. Наш коллбэк не вызовется - мы должны запустить событийную машину. Событийная машина может блокироваться, если ей нечего делать или нет больше событий. Например, если использовать POE и не вызвать stop - то он будет висеть пока не сработает таймаут, а это порядка 10 сек. Мало кому это понравится.
В AnyEvent этого можно избежать используя "условные переменные" (condition variables). Это можно назвать синхронизатором или ядром AnyEvent. Condition variables имеет две стороны: заказчик (ждет выполнения условия) и исполнитель (их исполняет).
В нашем примере, в качестве исполнителя выступает коллбэк. Но у нас нет заказчика, надо это исправить:
use AnyEvent;

$| = 1; print "enter your name> ";

my $name;

my $name_ready = AnyEvent->condvar;

my $wait_for_input = AnyEvent->io (
    fh   => \*STDIN,
    poll => "r",
    cb   => sub {
        $name = <STDIN>;
        $name_ready->send;
    }
);

# делаем что нидь еще

# теперь ждем пока придут данные на вход:
$name_ready->recv;

undef $wait_for_input; # watcher нам больше не нужен

print "your name is $name\n";
Мы создаем AnyEvent condvar вызовом метода AnyEvent->condvar, это и есть наше ядро. Потом создаем watcher, но в коллбэке мы вызываем send у ядра. Заказчик вызывает recv, а исполнитель send. $name_ready->recv прекращает работу ядра, пока мы не получим $name - указав выполнение условия послав $name_ready->send. При помощи send и recv можно отправлять и получать данные:
use AnyEvent;

$| = 1; print "enter your name> ";

my $name_ready = AnyEvent->condvar;

my $wait_for_input = AnyEvent->io (
    fh => \*STDIN, poll => "r",
    cb => sub { $name_ready->send (scalar <STDIN>) }
);

# делаем что нидь еще

# теперь ждем и подставляем данные на входе
my $name = $name_ready->recv;

undef $wait_for_input; # watcher нам больше не нужен

print "your name is $name\n";

Получение данных при помощи AnyEvent::HTTP

AnyEvent::HTTP представляет собой не блокирующий HTTP/HTTPS клиент. Он поддерживает GET, POST и HEAD запросы, куки и многое другое, и все это на низком уровне.
  • http_request $method => $url, key => value..., $cb->($data, $headers)
  • делает HTTP запрос методом $method (например GET, POST). Урла ($url) должна быть абсолютной. Дополнительные параметры key => value не обязательны. Вот некоторые из них:
    headers => $hashref - заголовки запроса;
    timeout => $seconds - таймаут соединения;
    body => $string - тело запроса;
    cookie_jar => $hash_ref - включение поддержки куков, $hash_ref должна быть ссылкой на хеш, который будет автоматически обновляться.

    Последним параметром, метод http_request, получает коллбэк, в котором мы можем манипулировать полученными данными. В первый параметр он получает тело ответа (или undef в случае ошибки), во второй заголовки ответа.

  • http_head $url, key => value..., $cb->($data, $headers)
  • делает HTTP-HEAD запрос, описание параметров смотри в http_request

  • http_post $url, $body, key => value..., $cb->($data, $headers)
  • делает HTTP-POST запрос, описание параметров смотри в http_request

  • http_get $url, key => value..., $cb->($data, $headers)
  • делает HTTP-GET запрос, описание параметров смотри в http_request 

Внимание: AnyEvent::HTTP пока не делает url escape автоматически, приходится делать это руками
use AnyEvent::HTTP;

my $cv = AnyEvent->condvar;

http_get "http://www.someuri.com/",
    cookie_jar => {},
    headers    => {},
    sub {
        my ($body, $hdr) = @_;
        
        if ($hdr->{Status} =~ /^2/) {
            ... everything should be ok
        } else {
            print "error, $hdr->{Status} $hdr->{Reason}\n";
        }
        $cv->send;
    };
$cv->recv;
Как и в примере I/O watcher, в коллбэке мы вызываем send, тем самым завершая работу.

И наконец паук - AnyEvent yandex spider

Вот мы и добрались до паука. На http://maps.yandex.ru/ есть возможность найти не только улицу или город, но и любую организацию. После ввода данных в строке запроса, через JS отправляется запрос на урлу:
http://maps.yandex.ru/?text=что_ищем&where=где_ищем
в where можно указать не только улицу, но и город. Например, по запросу
http://maps.yandex.ru/?text=аптека&where=ростов-на-дону
мы получим все аптеки в ростове-на-дону. Данные от сервера приходят в JSON формате - это очень удобно. Но вот незадача, он присылает только по 10 организаций на странице, и делает постраничную навигацию. Тут надо использовать AnyEvent::HTTP. Посмотрев на урлу каждой страницы, можно заметить, что в качестве дополнительного параметра, к выше описанному запросу, просто добавляется skip:
http://maps.yandex.ru/?text=аптека&where=ростов-на-дону&skip=число
этот параметр указывает на то, сколько данных надо пропустить (на второй странице skip=10 и т.д.). Итак, получается, что нам надо сделать число GET запросов, равное кол-ву страниц. Но как нам узнать сколько всего данных найдено, чтобы узнать сколько страниц? Напомню - приходит JSON. Разработчики yandex предусмотрели нашу проблему, и в ответе на каждый запрос они присылают общее кол-во найденных записей. Напомню, что надо руками делать url escape. Порядок работы:
  1. первым запросом получить первую партию данных и кол-во записей
  2. потом сделать $page_cnt-1 запросов для получения остальных данных
Если мы вызовем http_get n раз, то он откроет n соединений с хостом, сделает n запросов и будет ждать от них ответа. Как только придет один из ответов, тут же сработает коллбэк, тем самым достигается не блокирующее соединение.
AnyEvent::HTTP пока не поддерживает keep-alive соединения, поэтому он откроет $page_cnt отдельных соединений. По умолчанию он может сделать к одному хосту только 4 соединения. Поэтому если нам надо сделать 5 запросов, то сразу он сделает 4, а пятое поставит в очередь. И как только одно из соединений закроется, он тут же откроет следующее. Для завершения работы паука нам нужно считать кол-во обработанных ответов, оно равно кол-ву отправленных запросов.
use strict;
use AnyEvent::HTTP;

use URI::Escape;
use JSON::XS;
use utf8;

my $conf = {
    'search' => {
        'str' => 'аптека',
    },
    'where' => {
        'str' => 'ростов-на-дону',
    },
};
$conf->{$_}->{'encode'} = url_encode($conf->{$_}->{'str'}) for qw/search where/;

my $items = [];
my $cv = AnyEvent->condvar;

http_get gen_uri(), get_params(), sub {
    my ($body, $hr) = @_;
    
    $cv->send("yandex don`t enable!") unless $hr->{'Status'} == 200;
    
    my $pages_cnt = get_data($body);
    $cv->send("result is empty!") unless $pages_cnt;
    
    my $resp_cnt = 0;
    for (1..$pages_cnt) {
        http_get gen_uri($_*10), get_params(), sub {
            my ($body, $hr) = @_;
            
            $resp_cnt++;
            my $temp = get_data($body);
            $cv->send("OK") if $resp_cnt >= $pages_cnt;
        };
    }
};

print $cv->recv;

sub url_encode {URI::Escape::uri_escape_utf8(shift)}

sub gen_uri {
    sprintf("http://maps.yandex.ru/?text=%s&where=%s&skip=%d", $conf->{'search'}->{'encode'}, $conf->{'where'}->{'encode'}, shift || 0);
}

sub get_data {
    my $json_str = (shift =~ /<\!\[CDATA\[(.*)\]\]><\/script>/)[0];
    $json_str =~ s/NaN/"NaN"/g;
    my $json = JSON::XS->new->decode ($json_str);
    
    push @$items, @{$json->{'service'}->{'data'}->{'businesses'}->{'items'} || []};
    int(($json->{'service'}->{'data'}->{'businesses'}->{'length'} || 0)/10);
}
my $user_agents = [
    'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.4.154.25 Safari/525.19',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; YPC 3.0.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
    'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)',
    'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1',
    'Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3',
    'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1) Gecko/20090624 Firefox/3.5',
    'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50215) Netscape/8.0.1',
    'Opera/9.50 (Windows NT 5.1; U; ru)',
    'Opera/9.0 (Windows NT 5.1; U; en)',
    'Opera/9.60 (J2ME/MIDP; Opera Mini/4.2.13337/724; U; ru) Presto/2.2.0',
    'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/521.25 (KHTML, like Gecko) Safari/521.24',
];

sub get_params {
    %{{
        cookie_jar => {},
        headers    => {
            'Host'    => 'maps.yandex.ru',
            'Referer' => 'http://maps.yandex.ru/',
            'User-Agent' => $user_agents->[int rand @$user_agents],
        },
    }};
}

?

Log in