From 9ed75659a1c06fefcf442d4de2e4d9906556719e 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: - Run `morbo script/koha` and you're set : fully functional Koha without apache2/nginx (URL rewrites and static files are handled by the app) - apache2/nginx configuration is much simpler (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 script/koha.psgi) - All that is needed to run koha is in the source code and not hidden in an obscure directory (I'm looking at you, debian/templates/plack.psgi) - Using Test::Mojo we can test the whole application, as we do with the REST API (which is a Mojolicious application too) - It's another step in the direction of dropping CGI support - It also 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 requires zero changes to existing files, so it should apply nicely forever (no rebase needed \o/) A few downsides too: - It is not complete : some things from debian/templates/plack.psgi are missing, like the L1 cache flush or Koha::Middleware::SetEnv. But that shouldn't be too hard to add. - Already mentioned above, but the actual state of get_template_and_user and checkauth completely forbids the use of Mojolicious controllers. We really need to rewrite C4::Auth - Not sure if it's a downside or not, but as OPAC and Intranet are on the same host:port, if you authenticate on OPAC you will be authenticated on Intranet too. This can probably be changed if it's really a problem. - ... see FIXME in the code Other remarks: - It uses the same mechanism as Plack::App::CGIBin to deal with CGI scripts, so it should be equivalent in terms of performance - I did not test the install and upgrade process (yet) How to test ? - Run `morbo script/koha`, then go to: - http://localhost:3000/ - A basic page that redirects you to OPAC - Then navigate in the OPAC, searching for bugs - http://localhost:3000/intranet - The same page that redirects you to intranet - Then navigate in the Intranet, still searching for bugs - http://localhost:3000/api/v1/... - The REST API - Read the code (and the comments), it's not very long --- etc/nginx.conf | 15 +++++++ lib/Koha/App/Koha.pm | 67 ++++++++++++++++++++++++++++++++ lib/Koha/App/Plugin/CGIBinKoha.pm | 69 +++++++++++++++++++++++++++++++++ lib/Koha/App/Plugin/RESTV1.pm | 15 +++++++ lib/Koha/Controller/Index.pm | 10 +++++ lib/Koha/Controller/Intranet/Index.pm | 15 +++++++ script/koha | 11 ++++++ script/koha.psgi | 15 +++++++ templates/index/index.html.tt2 | 13 +++++++ templates/intranet/index/index.html.tt2 | 13 +++++++ 10 files changed, 243 insertions(+) create mode 100644 etc/nginx.conf create mode 100644 lib/Koha/App/Koha.pm create mode 100644 lib/Koha/App/Plugin/CGIBinKoha.pm create mode 100644 lib/Koha/App/Plugin/RESTV1.pm create mode 100644 lib/Koha/Controller/Index.pm create mode 100644 lib/Koha/Controller/Intranet/Index.pm create mode 100755 script/koha create mode 100755 script/koha.psgi create mode 100644 templates/index/index.html.tt2 create mode 100644 templates/intranet/index/index.html.tt2 diff --git a/etc/nginx.conf b/etc/nginx.conf new file mode 100644 index 0000000000..da2184b1de --- /dev/null +++ b/etc/nginx.conf @@ -0,0 +1,15 @@ +upstream koha { + server 127.0.0.1:3000; +} + +server { + listen 80; + listen [::]:80; + + server_name koha-dev; # CHANGEME + + location / { + include proxy_params; + proxy_pass http://koha; + } +} diff --git a/lib/Koha/App/Koha.pm b/lib/Koha/App/Koha.pm new file mode 100644 index 0000000000..ce18ca20c7 --- /dev/null +++ b/lib/Koha/App/Koha.pm @@ -0,0 +1,67 @@ +package Koha::App::Koha; + +use Modern::Perl; + +use Mojo::Base 'Mojolicious'; + +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); + + # Everything after this comment is only needed to use Mojolicious + # controllers. They are only two defined (one for opac, one for intranet) + # and they just display a basic html page. + # We can't use them for anything serious right now because of the + # authentication code that needs a full rewrite + + $self->plugin('TemplateToolkit', template => {PLUGIN_BASE => 'Koha::Template::Plugin'}); + $self->renderer->default_handler('tt2'); + + $self->routes->namespaces(['Koha::Controller']); + + my $r = $self->routes; + + my $root = $r->under('/' => { + KOHA_VERSION => C4::Context->preference('Version'), + interface => '/opac-tmpl', + theme => C4::Context->preference('opacthemes'), + }); + my $intranet = $root->under('/intranet' => { + interface => '/intranet-tmpl', + theme => C4::Context->preference('template'), + }); + + $intranet->any('/:controller/:action')->to('index#index', namespace => 'Koha::Controller::Intranet'); + $root->any('/:controller/:action')->to('index#index'); +} + +sub _before_dispatch { + my $c = shift; + + # URL rewriting in Koha! No Apache/Nginx conf needed + my $url = $c->req->url->path->to_string; + + # Remove Koha version from URL + $url =~ s/_\d{2}\.\d{7}\.(js|css)/.$1/; + + # See FIXME above + if ($url =~ m|^/api/v|) { + $url = '/api' . $url; + } + + $c->req->url->parse($url); +} + +1; diff --git a/lib/Koha/App/Plugin/CGIBinKoha.pm b/lib/Koha/App/Plugin/CGIBinKoha.pm new file mode 100644 index 0000000000..23c9275014 --- /dev/null +++ b/lib/Koha/App/Plugin/CGIBinKoha.pm @@ -0,0 +1,69 @@ +package Koha::App::Plugin::CGIBinKoha; + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Plugin'; + +use CGI::Compile; +use CGI::Emulate::PSGI; +use IO::Scalar; + +sub register { + my ($self, $app) = @_; + + $app->routes->any('/cgi-bin/koha/*name' => sub { + my ($c) = @_; + my $script = $c->stash('name'); + + # Remove trailing slash, if any (e.g. .../svc/config/systempreferences/) + $script =~ s|/$||; + + # Guess if it's an OPAC script, or an Intranet script + # FIXME: errors scripts (400.pl, 401.pl, ...) exist in both intranet and + # opac. The intranet ones will never be executed + if (-e $c->app->home->rel_file("opac/$script")) { + $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($response->[2][0]); + $c->rendered; + })->name('cgi'); +} + +sub _psgi_env { + my ($self, $c) = @_; + + my $body = $c->req->body; + my $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, + }; + 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/lib/Koha/App/Plugin/RESTV1.pm b/lib/Koha/App/Plugin/RESTV1.pm new file mode 100644 index 0000000000..6f59d044a8 --- /dev/null +++ b/lib/Koha/App/Plugin/RESTV1.pm @@ -0,0 +1,15 @@ +package Koha::App::Plugin::RESTV1; + +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/lib/Koha/Controller/Index.pm b/lib/Koha/Controller/Index.pm new file mode 100644 index 0000000000..4b00ad9307 --- /dev/null +++ b/lib/Koha/Controller/Index.pm @@ -0,0 +1,10 @@ +package Koha::Controller::Index; + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; + +sub index { +} + +1; diff --git a/lib/Koha/Controller/Intranet/Index.pm b/lib/Koha/Controller/Intranet/Index.pm new file mode 100644 index 0000000000..5e5e8b4b0a --- /dev/null +++ b/lib/Koha/Controller/Intranet/Index.pm @@ -0,0 +1,15 @@ +package Koha::Controller::Intranet::Index; + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; + +sub index { + my ($c) = @_; + + # FIXME: It should be possible to tell Mojo to search templates in intranet/ + # for routes under /intranet + $c->render('intranet/index/index'); +} + +1; diff --git a/script/koha b/script/koha new file mode 100755 index 0000000000..6faeec5688 --- /dev/null +++ b/script/koha @@ -0,0 +1,11 @@ +#!/usr/bin/env perl + +use Modern::Perl; + +# No need for PERL5LIB +use FindBin; +use lib "$FindBin::Bin/../lib", "$FindBin::Bin/.."; + +use Mojolicious::Commands; + +Mojolicious::Commands->start_app('Koha::App::Koha'); diff --git a/script/koha.psgi b/script/koha.psgi new file mode 100755 index 0000000000..0bb191d907 --- /dev/null +++ b/script/koha.psgi @@ -0,0 +1,15 @@ +#!/usr/bin/env starman + +use Modern::Perl; + +use FindBin; +use Mojo::Server::PSGI; +use Plack::Builder; + +builder { + enable 'Debug'; + + my $server = Mojo::Server::PSGI->new; + $server->load_app("$FindBin::Bin/koha"); + $server->to_psgi_app; +}; diff --git a/templates/index/index.html.tt2 b/templates/index/index.html.tt2 new file mode 100644 index 0000000000..d2f7011c9a --- /dev/null +++ b/templates/index/index.html.tt2 @@ -0,0 +1,13 @@ + + + + Default page for OPAC + + +

Default page for OPAC

+

Koha version is [% KOHA_VERSION %]

+ + [%# No need to hardcode '/cgi-bin/koha' %] + [% link_to('Homepage', 'cgi', { name = "opac-main.pl" }) %] + + diff --git a/templates/intranet/index/index.html.tt2 b/templates/intranet/index/index.html.tt2 new file mode 100644 index 0000000000..fb63514f53 --- /dev/null +++ b/templates/intranet/index/index.html.tt2 @@ -0,0 +1,13 @@ + + + + Default page for Intranet + + +

Default page for Intranet

+

Koha version is [% KOHA_VERSION %]

+ + [%# No need to hardcode '/cgi-bin/koha' %] + [% link_to('Homepage', 'cgi', { name = "mainpage.pl" }) %] + + -- 2.14.2