Разработка приложений PSGI с поддержкой нескольких сайтов

Plack:: Builder позволяет монтировать несколько хостов, например что-то вроде следующего фрагмента:

my @sites = load_site_names();
my $apps;
for my $site (@sites) {
    $apps->{$site} = Some::PsgiFramework::MyApp->new( config => get_config($site) );
}

use Plack::Builder;
builder {
    for my $site (@sites) {
        mount "$site" => $apps->{$site};
    }
    mount '/' => sub { ... };
}

например

  • load_site_names возвращает список сайтов, таких как http://example.com, http://some.other.site.com...
  • каждый виртуальный хост будет использовать один и тот же Some::PsgiFramework::MyApp
  • просто их конфиг отличается

Мне нужно именно это - нужно разработать одно простое веб-приложение, которое должно быть развернуто для разных сайтов (с небольшим трафиком) и не требовать установки разных серверов PSGI для каждого сайта.

Тем не менее, автор самого Plack говорит (в Plack:: Request)

Обратите внимание, что этот модуль предназначен для разработчиков промежуточного программного обеспечения Plack и разработчиков инфраструктуры веб-приложений, а не для разработчиков приложений (конечных пользователей).

Написание вашего веб-приложения напрямую с использованием Plack:: Request, безусловно, возможно, но не рекомендуется: это похоже на то, как это делается с Apache:: Request от mod_perl: пока слишком низкий уровень.

Если вы пишете веб-приложение, а не фреймворк, вам рекомендуется использовать одну из фреймворков веб-приложений, поддерживающих PSGI ( http://plackperl.org/), или посмотреть такие модули, как HTTP::Engine обеспечить более высокий уровень API запросов и ответов поверх PSGI.

И это проблема.

Я проверил множество различных основанных на PSGI фреймворков в MetaCPAN. И каждый AFAIK основан на одиночном коде, например, не позволяет писать приложения, которые можно было бы многократно использовать (монтировать) для разных сайтов в одном и том же месте. app.psgi,

Итак, вопросы:

  • Я что-то пропустил в MetaCPAN (или в документах), и здесь существует любая (облегченная) веб-инфраструктура, которая позволяет разрабатывать приложения, многократно устанавливаемые в app.psgi?
  • или я вынужден развиваться Just Another My Own PSGI Framework? (Если честно, я не проверил катализатор - так как он слишком тяжелый)
  • или просто плохо разбираетесь в "креплении"?

2 ответа

Построение диспетчера в Плаке

Существует альтернатива Plack::App::URLMap под названием Plack:: App:: HostMap, которая ускоряет поиск, поскольку использует внутренний хеш, а не массив. Так что итераций не происходит. Он просто выполняет поиск по хешу, и это действительно быстро в Perl.

Компромисс в том, что теперь вы можете использовать только постоянные имена хостов. Так что если ваш список примерно такой:

example.org
example.com
example.de
example.am
example.cx

Или с поддоменами, такими как:

one.example.org
two.example.org
three.example.org
four.example.org
five.example.org
six.example.org

Тогда это идеально. С другой стороны, я не уверен, поддерживает ли он URL-адреса, которые также имеют постоянную часть пути, например http://foo.example.org/barгде много fooс, но все они разделяют одно и то же /bar путь, где приложение монтируется. Модуль не имеет никаких тестов вообще, и я не мог попробовать. Если вы посмотрите на изменения, то, по крайней мере, один человек предложил дополнительные функции, поэтому кто-то, кроме автора, использует его.

Чтобы использовать его, вы должны переключиться с Plack::Builder на использование Plack:: App:: HostMap в качестве приложения, для которого вы вызываете методы.

use Plack::App::HostMap;

# set up %apps (e.g. foo.example.org, bar.example.org)

my $host_map = Plack::App::HostMap->new;

for my $site (@sites) {
    $host_map->map( $site => $apps->{$site} );
}

Вы не говорите нам, что / Маршрут должен делать, но по сути он также нуждается в хосте Если на вашем сервере много имен хостов, то все они ответят на этот запрос. Вот и вся идея того, что вы хотите сделать. Но для чего нужен хост? /? Поэтому лучше всего включить дополнительную строку для sub { ... } Slash-приложение с реальным именем хоста. Может быть, это панель управления или что-то в этом роде. Так что подключите его к фактическому URL.

 $host_map->map( "example.org" => sub { ... } );

Веб-фреймворк для этого

Синглтон на самом деле не проблема здесь. Кажется, невозможно заставить Dancer2 загружать разные конфиги или среды с одним и тем же. Я не пробовал Mojo, Web::Simple или Catalyst для этого варианта использования.

Я много пробовал с D2, и ближе всего у меня был / маршрут в MyApp, и это приложение PSGI. Обратите внимание, это не работает.

use Plack::Builder;

my $builder = Plack::Builder->new;
foreach my $name (qw/development production/) {
    $builder->mount(
        "/$name" => builder {
            eval <<"APP";
package MyApp::$name {
    use Dancer2;
    use MyApp with => { environment => "$name" };
}
APP

            "MyApp::$name"->to_app;
        }
    );
}

$builder->to_app;

Он использует скелет по умолчанию, созданный с dancer2 -a MyApp и неизмененные файлы среды. Диспетчеризация из Plack работает, но Dancer2 запутывается.

HTTP::Server::PSGI: Accepting connections at http://0:5000/
[MyApp::production:4896] core @2017-02-10 02:14:42> looking for get / in /home/julien/perl5/perlbrew/perls/perl-5.20.1/lib/site_perl/5.20.1/Dancer2/Core/App.pm l. 35
[MyApp::production:4896] core @2017-02-10 02:14:42> Entering hook core.error.init in (eval 49) l. 1
[MyApp::production:4896] core @2017-02-10 02:14:42> Entering hook core.error.before in (eval 49) l. 1
[MyApp::production:4896] core @2017-02-10 02:14:42> Entering hook core.error.after in (eval 49) l. 1
127.0.0.1 - - [10/Feb/2017:02:14:42 +0100] "GET /production/ HTTP/1.1" 404 456 "-" "curl/7.47.0"
[MyApp::development:4896] core @2017-02-10 02:18:06> looking for get  in /home/julien/perl5/perlbrew/perls/perl-5.20.1/lib/site_perl/5.20.1/Dancer2/Core/App.pm l. 35
[MyApp::development:4896] core @2017-02-10 02:18:06> Entering hook core.error.init in (eval 49) l. 1
[MyApp::development:4896] core @2017-02-10 02:18:06> Entering hook core.error.before in (eval 49) l. 1
[MyApp::development:4896] core @2017-02-10 02:18:06> Entering hook core.error.after in (eval 49) l. 1
127.0.0.1 - - [10/Feb/2017:02:18:06 +0100] "GET /development HTTP/1.1" 404 457 "-" "curl/7.47.0"

Идея заключалась в том, чтобы использовать один и тот же файл пакета и создать его подкласс для получения другой конфигурации через with,

Тем не менее, можно просто определить одно и то же приложение в цикле, снова и снова. Вы могли бы, вероятно, переместить обработчик маршрута, используя код ref, такой как get '/' => \&main::get_slash, где sub get_slash не в eval,

use Plack::Builder;

my $builder = Plack::Builder->new;
foreach my $name (qw/development production/) {
    $builder->mount(
        "/$name" => builder {
            eval <<"APP";
package MyApp::$name {
use Dancer2;
    use Data::Printer;

    set environment => "$name";

    get "/" => sub { np(config) }
}
APP

            "MyApp::$name"->to_app;
        }
    );
}

$builder->to_app;

Строка eval это не так плохо, как выглядит здесь, поскольку этот код запускается только при запуске. D2 будет внутренне отслеживать все приложения, которые вы создали здесь программно. Но я понятия не имею, насколько это эффективно.

Я считаю, что цитируемая документация больше предназначена для Plack::Request, а не для Plack::Builder.

Вполне допустимо монтировать различные приложения (например, приложение Dancer/Catalyst/Mojolicious/homegrown), используя Plack:: Builder, и это действительно довольно часто.

Другие вопросы по тегам