From 015bd4b507a1b86d861e36ac74abc4c3cf5abd52 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Thu, 12 Apr 2018 20:42:13 +0200 Subject: [PATCH] Bug 20582: Turn Koha into a Mojolicious application This patch is a proof-of-concept of Koha as a Mojolicious application This has several benefits: - Development setup is easier. No need for apache or nginx. Just run `morbo bin/intranet` or `morbo bin/opac` and go to http://localhost:3000 (URL rewrites and static files are handled by the app) - apache2/nginx configuration is simpler too (an example of nginx configuration is included in the patch) - starman and plack middlewares can still be used for debug or gzip compression for instance (see app.psgi) - Using Test::Mojo we can test the whole application, as we do with the REST API (which is a Mojolicious application too) - It opens a way for converting CGI scripts into Mojolicious controllers and actions (even if that's not possible at the moment because of the authentication code) It uses the same mechanism as Plack::App::CGIBin to deal with CGI scripts, so it should be equivalent in terms of performance How to test ? - Run `morbo bin/intranet`, then go to http://localhost:3000/ and try to find bugs. Check the REST API at http://localhost:3000/api/v1 - Run `morbo bin/opac`, then go to http://localhost:3000/ and try to find bugs. Check the REST API at http://localhost:3000/api/v1 - Run `starman -l :5000 -l :5001` and verify that intranet (http://localhost:5000) and opac (http://localhost:5001) work normally - Read the code (and the comments), it's not very long Signed-off-by: Jerome Charaoui Signed-off-by: Victor Grousset/tuxayo Signed-off-by: Martin Renvoize --- Koha/App/Intranet.pm | 85 +++++++++++++++++++++++++++++ Koha/App/Opac.pm | 85 +++++++++++++++++++++++++++++ Koha/App/Plugin/CGIBinKoha.pm | 100 ++++++++++++++++++++++++++++++++++ Koha/App/Plugin/RESTV1.pm | 32 +++++++++++ app.psgi | 52 ++++++++++++++++++ bin/intranet | 43 +++++++++++++++ bin/opac | 27 +++++++++ etc/nginx.conf | 33 +++++++++++ 8 files changed, 457 insertions(+) create mode 100644 Koha/App/Intranet.pm create mode 100644 Koha/App/Opac.pm create mode 100644 Koha/App/Plugin/CGIBinKoha.pm create mode 100644 Koha/App/Plugin/RESTV1.pm create mode 100644 app.psgi create mode 100755 bin/intranet create mode 100755 bin/opac create mode 100644 etc/nginx.conf diff --git a/Koha/App/Intranet.pm b/Koha/App/Intranet.pm new file mode 100644 index 0000000000..3eddd49fab --- /dev/null +++ b/Koha/App/Intranet.pm @@ -0,0 +1,85 @@ +package Koha::App::Intranet; + +# Copyright 2020 BibLibre +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Mojo::Base 'Mojolicious'; + +use Koha::Caches; +use Koha::Cache::Memory::Lite; + +sub startup { + my ($self) = @_; + + push @{$self->plugins->namespaces}, 'Koha::App::Plugin'; + push @{$self->static->paths}, $self->home->rel_file('koha-tmpl'); + + # Create route for all CGI scripts, need to be loaded first because of + # CGI::Compile + $self->plugin('CGIBinKoha'); + + # Create routes for API + # FIXME This generates routes like this: /api/api/v1/... + $self->plugin('RESTV1'); + + $self->hook(before_dispatch => \&_before_dispatch); + $self->hook(around_action => \&_around_action); + + my $r = $self->routes; + + $r->any('/')->to(cb => sub { shift->redirect_to('/cgi-bin/koha/mainpage.pl') }); +} + +sub _before_dispatch { + my $c = shift; + + my $path = $c->req->url->path->to_string; + + # Remove Koha version from URL + $path =~ s/_\d{2}\.\d{7}\.(js|css)/.$1/; + + # See FIXME above + if ($path =~ m|^/api/v|) { + $path = '/api' . $path; + } + + $c->req->url->path->parse($path); +} + +sub _around_action { + my ($next, $c, $action, $last) = @_; + + # Flush memory caches before every request + my $caches = $Koha::Caches::singleton_caches; + if ($caches) { + foreach my $key (keys %$caches) { + my $cache = $caches->{$key}; + if (ref $cache->{cache} eq 'Cache::Memory') { + $cache->flush_all; + } + $cache->flush_L1_cache; + } + } + $Koha::Caches::singleton_caches = {}; + Koha::Cache::Memory::Lite->flush(); + + return $next->(); +} + +1; diff --git a/Koha/App/Opac.pm b/Koha/App/Opac.pm new file mode 100644 index 0000000000..47da075b82 --- /dev/null +++ b/Koha/App/Opac.pm @@ -0,0 +1,85 @@ +package Koha::App::Opac; + +# Copyright 2020 BibLibre +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Mojo::Base 'Mojolicious'; + +use Koha::Caches; +use Koha::Cache::Memory::Lite; + +sub startup { + my ($self) = @_; + + push @{$self->plugins->namespaces}, 'Koha::App::Plugin'; + push @{$self->static->paths}, $self->home->rel_file('koha-tmpl'); + + # Create route for all CGI scripts, need to be loaded first because of + # CGI::Compile + $self->plugin('CGIBinKoha', opac => 1); + + # Create routes for API + # FIXME This generates routes like this: /api/api/v1/... + $self->plugin('RESTV1'); + + $self->hook(before_dispatch => \&_before_dispatch); + $self->hook(around_action => \&_around_action); + + my $r = $self->routes; + + $r->any('/')->to(cb => sub { shift->redirect_to('/cgi-bin/koha/opac-main.pl') }); +} + +sub _before_dispatch { + my $c = shift; + + my $path = $c->req->url->path->to_string; + + # Remove Koha version from URL + $path =~ s/_\d{2}\.\d{7}\.(js|css)/.$1/; + + # See FIXME above + if ($path =~ m|^/api/v|) { + $path = '/api' . $path; + } + + $c->req->url->path->parse($path); +} + +sub _around_action { + my ($next, $c, $action, $last) = @_; + + # Flush memory caches before every request + my $caches = $Koha::Caches::singleton_caches; + if ($caches) { + foreach my $key (keys %$caches) { + my $cache = $caches->{$key}; + if (ref $cache->{cache} eq 'Cache::Memory') { + $cache->flush_all; + } + $cache->flush_L1_cache; + } + } + $Koha::Caches::singleton_caches = {}; + Koha::Cache::Memory::Lite->flush(); + + return $next->(); +} + +1; diff --git a/Koha/App/Plugin/CGIBinKoha.pm b/Koha/App/Plugin/CGIBinKoha.pm new file mode 100644 index 0000000000..a7fba5ae7c --- /dev/null +++ b/Koha/App/Plugin/CGIBinKoha.pm @@ -0,0 +1,100 @@ +package Koha::App::Plugin::CGIBinKoha; + +# Copyright 2020 BibLibre +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Plugin'; + +use CGI::Compile; +use CGI::Emulate::PSGI; +use IO::Scalar; + +sub register { + my ($self, $app, $conf) = @_; + + my $opac = $conf->{opac}; + + my $r = $app->routes; + + $r->any('/cgi-bin/koha/*script' => sub { + my ($c) = @_; + + my $script = $c->stash('script'); + + # Special case for installer which can takes a long time + $c->inactivity_timeout(300) if $script eq 'installer/install.pl'; + + # Remove trailing slash, if any (e.g. .../svc/config/systempreferences/) + $script =~ s|/$||; + + if ($opac) { + $script = "opac/$script"; + } + + unless (-e $c->app->home->rel_file($script)) { + return $c->reply->not_found; + } + + my $sub = CGI::Compile->compile($script); + my $app = CGI::Emulate::PSGI->handler($sub); + my $response = $app->($self->_psgi_env($c)); + + $c->res->code($response->[0]); + $c->res->headers->from_hash({ @{ $response->[1] } }); + $c->res->body(join('', @{$response->[2]})); + $c->rendered; + })->name('cgi'); +} + +sub _psgi_env { + my ($self, $c) = @_; + + my $env = $c->req->env; + + my $body = $c->req->build_body; + $env = { + %$env, + 'psgi.input' => IO::Scalar->new(\$body), + 'psgi.errors' => *STDERR, + REQUEST_METHOD => $c->req->method, + QUERY_STRING => $c->req->url->query->to_string, + SERVER_NAME => $c->req->url->to_abs->host, + SERVER_PORT => $c->req->url->to_abs->port, + SERVER_PROTOCOL => 'HTTP/1.1', + CONTENT_LENGTH => $c->req->headers->content_length, + CONTENT_TYPE => $c->req->headers->content_type, + REMOTE_ADDR => $c->tx->remote_address, + SCRIPT_NAME => $c->req->url->path->to_string, + }; + + # Starman sets PATH_INFO to the same value of SCRIPT_NAME, which confuses + # CGI and causes the redirect after OPAC login to fail + delete $env->{PATH_INFO} if ($env->{PATH_INFO} eq $env->{SCRIPT_NAME}); + + for my $name (@{ $c->req->headers->names }) { + my $value = $c->req->headers->header($name); + $name =~ s/-/_/g; + $name = 'HTTP_' . uc($name); + $env->{$name} = $value; + } + + return $env; +} + +1; diff --git a/Koha/App/Plugin/RESTV1.pm b/Koha/App/Plugin/RESTV1.pm new file mode 100644 index 0000000000..63e2a8d536 --- /dev/null +++ b/Koha/App/Plugin/RESTV1.pm @@ -0,0 +1,32 @@ +package Koha::App::Plugin::RESTV1; + +# Copyright 2020 BibLibre +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Plugin'; + +use Koha::REST::V1; + +sub register { + my ($self, $app) = @_; + + $app->routes->any('/api')->detour(app => Koha::REST::V1->new); +} + +1; diff --git a/app.psgi b/app.psgi new file mode 100644 index 0000000000..9a0d287fc4 --- /dev/null +++ b/app.psgi @@ -0,0 +1,52 @@ +# Copyright 2020 BibLibre +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use FindBin; +use Plack::Builder; +use Mojo::Server::PSGI; + +sub psgi_app { + my ($script) = @_; + + my $server = Mojo::Server::PSGI->new; + $server->load_app("$FindBin::Bin/$script"); + + return $server->to_psgi_app; +} + +my $opac = psgi_app('bin/opac'); +my $intranet = psgi_app('bin/intranet'); + +my $opac_port = $ENV{KOHA_OPAC_PORT} || 5001; +my $intranet_port = $ENV{KOHA_INTRANET_PORT} || 5000; +my $port2app = { + $opac_port => $opac, + $intranet_port => $intranet, +}; + +builder { + enable 'ReverseProxy'; + enable '+Koha::Middleware::SetEnv'; + enable '+Koha::Middleware::RealIP'; + sub { + my $env = shift; + my $app = $port2app->{$env->{SERVER_PORT}} || $intranet; + $app->($env); + }; +} diff --git a/bin/intranet b/bin/intranet new file mode 100755 index 0000000000..af5a08f48e --- /dev/null +++ b/bin/intranet @@ -0,0 +1,43 @@ +#!/usr/bin/env perl + +# Copyright 2020 BibLibre +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . +# Copyright 2020 BibLibre +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use FindBin; +use lib "$FindBin::Bin/.."; + +use Mojolicious::Commands; + +Mojolicious::Commands->start_app('Koha::App::Intranet'); diff --git a/bin/opac b/bin/opac new file mode 100755 index 0000000000..fe0b934d8f --- /dev/null +++ b/bin/opac @@ -0,0 +1,27 @@ +#!/usr/bin/env perl + +# Copyright 2020 BibLibre +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use FindBin; +use lib "$FindBin::Bin/.."; + +use Mojolicious::Commands; + +Mojolicious::Commands->start_app('Koha::App::Opac'); diff --git a/etc/nginx.conf b/etc/nginx.conf new file mode 100644 index 0000000000..4701a9a645 --- /dev/null +++ b/etc/nginx.conf @@ -0,0 +1,33 @@ +# This config file assume you are using starman with the app.psgi that can be +# found in the root directory of Koha, and that it listens on ports 5000-5001 + +upstream intranet { + server 127.0.0.1:5000; +} +upstream opac { + server 127.0.0.1:5001; +} + +server { + listen 80; + listen [::]:80; + + server_name intranet.koha-dev; # CHANGEME + + location / { + include proxy_params; + proxy_pass http://intranet; + } +} + +server { + listen 80; + listen [::]:80; + + server_name opac.koha-dev; # CHANGEME + + location / { + include proxy_params; + proxy_pass http://opac; + } +} -- 2.20.1