Add general proxy support (fixes #259)
Change-Id: I63a3eb1e14a35067522fa8ee9efc905059dea087
diff --git a/Changes b/Changes
index 7cc30b2..1cf6c54 100644
--- a/Changes
+++ b/Changes
@@ -19,6 +19,7 @@
- Improve explanation of timed-out match counts (diewald)
- Support response pipes (preliminary; diewald)
- Support default alignment changes (diewald)
+ - Add general proxies support (diewald)
0.59 2025-03-28
- Docker only release (diewald)
diff --git a/kalamar.conf b/kalamar.conf
index f5bed8e..e3aa00b 100644
--- a/kalamar.conf
+++ b/kalamar.conf
@@ -71,6 +71,12 @@
# items_per_page => 20,
# context => '20-t,20-t',
# alignment => 'left' # or 'right' or 'center'
- # }
+ # },
+
+ # proxies => [{
+ # root_path => '/plugin/export',
+ # mount => 'http://export-plugin:3333',
+ # service => 'export-plugin-proxy'
+ # }]
}
}
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index 462e9ba..313055a 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -400,8 +400,32 @@
$r->get('/contact')->mail_to_chiffre('documentation#contact');
# API proxy route
- $r->any('/api/v#apiv' => [apiv => ['1.0']])->name('proxy')->to('Proxy#pass');
- $r->any('/api/v#apiv/*api_path' => [apiv => ['1.0']])->to('Proxy#pass');
+ $r->any('/api/v#apiv' => [apiv => ['1.0']])->name('proxy')->to('Proxy#api_pass');
+ $r->any('/api/v#apiv/*proxy_path' => [apiv => ['1.0']])->to('Proxy#api_pass');
+
+ # General proxy mounts
+ my $proxies = $conf->{'proxies'} // [];
+ foreach (@$proxies) {
+ next if $_ eq 'PROXY_STUB';
+
+ my $root_path = Mojo::Path->new($_->{root_path})
+ ->canonicalize->leading_slash(1)
+ ->trailing_slash(1);
+
+ my %stash_hash = (
+ service => $_->{service},
+ root_path => $root_path,
+ mount => $_->{mount},
+ );
+
+ $r->any($root_path)->name($_->{service})->to(
+ 'Proxy#pass', %stash_hash
+ );
+
+ $r->any($root_path . '*proxy_path')->to(
+ 'Proxy#pass', %stash_hash
+ );
+ };
# Match route
# Corpus route
diff --git a/lib/Kalamar/Controller/Proxy.pm b/lib/Kalamar/Controller/Proxy.pm
index 675a06a..18e6c30 100644
--- a/lib/Kalamar/Controller/Proxy.pm
+++ b/lib/Kalamar/Controller/Proxy.pm
@@ -1,12 +1,34 @@
package Kalamar::Controller::Proxy;
use Mojo::Base 'Mojolicious::Controller';
-# Pass proxy command to API
-sub pass {
+sub api_pass {
my $c = shift;
my $apiv = $c->stash('apiv');
- my $path = $c->stash('api_path') // '';
+
+ # Get API request for proxying
+ # External URL!
+ my $base_url = Mojo::URL->new($c->korap->api($apiv));
+ $c->stash('root_path', $base_url->to_string);
+
+ $c->stash(service => 'proxy');
+
+ return $c->pass($base_url);
+};
+
+# Pass proxy command to API
+sub pass {
+ my $c = shift;
+ my $base_url = shift // Mojo::URL->new($c->stash('mount'));
+
+ my $base_path = $base_url->path->trailing_slash(1);
+
+ my $proxy_path = Mojo::Path->new(
+ $c->stash('proxy_path') ? $c->stash('proxy_path') : ''
+ )->leading_slash(0);
+
+ $base_path = $base_path->merge($proxy_path);
+ $base_url = $base_url->path($base_path);
# Get the original request
my $req = $c->req;
@@ -25,8 +47,7 @@
# Get parameters of the request
my $params = $req->query_params->clone;
- # Get API request for proxying
- my $url = Mojo::URL->new($c->korap->api($apiv))->path($path)->query($params);
+ my $url = $base_url->query($params);
# Resend headers
my $tx = $c->kalamar_ua->build_tx(
@@ -39,15 +60,16 @@
before_korap_request => ($c, $tx)
);
- my $h = $c->res->headers;
- $h->access_control_allow_origin('*');
+ my $origin = $c->req->headers->origin;
# Retrieve CORS header
if ($c->req->method eq 'OPTIONS') {
+ my $h = $c->res->headers;
# Remember this option for a day
$h->header('Access-Control-Max-Age' => '86400');
$h->header('Access-Control-Allow-Headers' => '*');
+ $h->header('Access-Control-Allow-Origin', $origin || '*');
$h->header('Access-Control-Allow-Methods' => 'GET, OPTIONS');
return $c->render(
status => 204,
@@ -75,7 +97,7 @@
my $h = $c->res->headers;
$h->header('X-Proxy' => 'Kalamar');
$h->header('X-Robots' => 'noindex');
- $h->access_control_allow_origin('*');
+ $h->header('Access-Control-Allow-Origin', $origin || '*');
$h->header('Access-Control-Allow-Methods' => 'GET, OPTIONS');
# Response is a redirect
@@ -83,16 +105,16 @@
# Rewrite redirect location to surface URL
my $location_url = $h->location;
- my $base_url = Mojo::URL->new($c->korap->api)->to_abs->to_string;
+ my $root_path = $c->stash('root_path');
# Remove the api part
# ".*?" is just required for non-absolute base_urls
- $location_url =~ s/^.*?${base_url}//;
+ $location_url =~ s/^.*?${root_path}//;
# Turn the rewritten location into a URL object
$location_url = Mojo::URL->new($location_url);
- my $proxy_url = $c->url_for('proxy');
+ my $proxy_url = $c->url_for($c->stash('service') || 'proxy');
$proxy_url->path->trailing_slash(1);
# Rebase to proxy path
diff --git a/t/proxy.t b/t/proxy.t
index 1db8ac9..5d5280a 100644
--- a/t/proxy.t
+++ b/t/proxy.t
@@ -158,6 +158,55 @@
->header_is('Access-Control-Max-Age', '86400')
;
+# General proxy mounts
+my $plugin_path = '/realplugin';
+
+$t = Test::Mojo->new('Kalamar' => {
+ Kalamar => {
+ proxies => [
+ 'PROXY_STUB',
+ {
+ root_path => '/plugin/hello',
+ mount => $plugin_path,
+ service => 'export-plugin-proxy'
+ }
+ ],
+ proxy_inactivity_timeout => 99,
+ proxy_connect_timeout => 66,
+ }
+});
+
+my $fake_plugin = $t->app->plugin(
+ Mount => {
+ $plugin_path =>
+ $fixtures_path->child('plugin-ex.pl')
+ }
+);
+
+# Configure fake plugin
+my $fake_plugin_app = $fake_plugin->pattern->defaults->{app};
+$fake_plugin_app->log($t->app->log);
+
+# Globally set server
+$t->app->ua->server->app($t->app);
+
+$t->get_ok('/realplugin')
+ ->status_is(200)
+ ->content_is('Hello base world!');
+
+$t->get_ok('/realplugin/huhux')
+ ->status_is(200)
+ ->content_is('Hello world! huhux');
+
+$t->get_ok('/plugin/hello/huhux')
+ ->status_is(200)
+ ->content_is('Hello world! huhux');
+
+# require Mojolicious::Command::routes;
+# use Mojo::Util qw(encode tablify);
+# my $rows = [];
+# Mojolicious::Command::routes::_walk($_, 0, $rows, 0) for @{$t->app->routes->children};
+# warn encode('UTF-8', tablify($rows));
done_testing;
__END__
diff --git a/t/server/plugin-ex.pl b/t/server/plugin-ex.pl
new file mode 100644
index 0000000..d7ef4e3
--- /dev/null
+++ b/t/server/plugin-ex.pl
@@ -0,0 +1,16 @@
+#!/usr/bin/env perl
+use Mojolicious::Lite;
+use strict;
+use warnings;
+
+# Base page
+get '/' => sub {
+ shift->render(text => 'Hello base world!');
+};
+
+get '/*all' => sub {
+ my $c = shift;
+ $c->render(text => 'Hello world! ' . $c->stash('all'));
+};
+
+app->start;