[RFC/PATCH 3/5] gitweb: Enable running gitweb as PSGI app, via CGI::Emulate::PSGI
This commit makes it possible to run gitweb as a PSGI application (see http://plackperl.org), using any of PSGI web servers. This includes ability to run gitweb from command line via Plack::Runner module; just run "./gitweb.cgi --psgi" and point web browser to http://0:5000
Gitweb uses CGI mode by default; to trigger PSGI mode one must either pass `--psgi` or `--plackup` option to gitweb script, or the script must be run with *.psgi filename (either copy/rename script, or make symlink ending with *.psgi e.g. gitweb.psgi).
Besides running gitweb itself, converting its run_request() from CGI to PSGI via CGI::Emulate::PSGI, gitweb as PSGI app also serves static files: scripts, stylesheets and images via Plack::Middleware::Static. Currently it assumes that positions and URLs of those assets are left at their default values, namely that URLs end with "/static/<file>", and that files can be found in "static/" directory relative to the gitweb script itself. This assumption should be relaxed in future.
Currently "git instaweb --httpd=plackup" doesn't (yet!) use this newly introduced feature: it is left for future commit.
Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
The major part of this series.
"./gitweb.cgi --psgi" runs web server on http://0:5000/
Documentation/gitweb.txt | 16 +++++++++
gitweb/INSTALL | 3 ++
gitweb/README | 13 ++++++--
gitweb/gitweb.perl | 80 ++++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 107 insertions(+), 5 deletions(-)
Show changes to 4 files +107 −5
Documentation/gitweb.txt, gitweb/INSTALL, gitweb/README, gitweb/gitweb.perl
diff --git a/Documentation/gitweb.txt b/Documentation/gitweb.txt
index 605a085..157903b 100644
--- a/Documentation/gitweb.txt
+++ b/Documentation/gitweb.txt
@@ -473,6 +473,22 @@ With that configuration the full path to browse repositories would be:
http://server/gitweb
+As PSGI using plackup
+~~~~~~~~~~~~~~~~~~~~~
+Gitweb can run as PSGI app (via emulation with *CGI::Emulate::PSGI*(3pm)).
+First you need to rename, copy or symlink gitweb.cgi to gitweb.psgi.
+You can run gitweb as a PSGI application from 'plackup' command line
+utility, using any *PSGI*(3) web server (see http://plackperl.org for
+a list), for example:
+
+ $ plackup --server HTTP::Server::Simple --port 8080 \
+ --host 127.0.0.1 gitweb.psgi
+
+With that configuration the full path to browse repositories would be:
+
+ http://127.0.0.1:8080/
+
+See *plackup*(1) manpage for more details.
ADVANCED WEB SERVER SETUP
-------------------------
diff --git a/gitweb/INSTALL b/gitweb/INSTALL
index 6d45406..9f55ab4 100644
--- a/gitweb/INSTALL
+++ b/gitweb/INSTALL
@@ -36,6 +36,9 @@ Requirements
The following optional Perl modules are required for extra features
- Digest::MD5 - for gravatar support
- CGI::Fast and FCGI - for running gitweb as FastCGI script
+ - CGI::Emulate::PSGI, Plack::Builder, Plack::Middleware::Static
+ for running gitweb as PSGI application, and Plack::Runner to
+ make it runnable as a standalone script
- HTML::TagCloud - for fancy tag cloud in project list view
- HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds
diff --git a/gitweb/README b/gitweb/README
index 6da4778..a435f9b 100644
--- a/gitweb/README
+++ b/gitweb/README
@@ -56,9 +56,16 @@ See also gitweb.conf(5) manpage.
Web server configuration
------------------------
Gitweb can be run as CGI script, as legacy mod_perl application (using
-ModPerl::Registry), and as FastCGI script. You can find some simple examples
-in "Example web server configuration" section in INSTALL file for gitweb (in
-gitweb/INSTALL).
+ModPerl::Registry), as FastCGI script, and as PSGI application. You
+can find some simple examples in "Example web server configuration"
+section in INSTALL file for gitweb (in gitweb/INSTALL).
+
+Note that to run as FastCGI script gitweb must be run with *.fcgi
+extension or with `--fastcgi` / `--fcgi` parameter on command line.
+To run as PSGI application gitweb must be run with *.psgi extension,
+or with `--psgi` / `--plackup` parameter on command line, or from
+PSGI web server which sets 'PLACK_ENV' or 'PLACK_SERVER' environment
+variable.
See "Webserver configuration" and "Advanced web server setup" sections in
gitweb(1) manpage.
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 22efec2..0dbdd15 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -1221,16 +1221,22 @@ sub configure_as_fcgi {
# let each child service 100 requests
our $is_last_request = sub { ++$request_number > 100 };
}
+sub configure_as_psgi {
+ our $CGI = 'PSGI'; # fake
+}
sub evaluate_argv {
my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__;
configure_as_fcgi()
if $script_name =~ /\.fcgi$/;
+ configure_as_psgi()
+ if $script_name =~ /\.psgi$/;
return unless (@ARGV);
require Getopt::Long;
Getopt::Long::GetOptions(
'fastcgi|fcgi|f' => \&configure_as_fcgi,
+ 'psgi|plack' => \&configure_as_psgi,
'nproc|n=i' => sub {
my ($arg, $val) = @_;
return unless eval { require FCGI::ProcManager; 1; };
@@ -1244,8 +1250,78 @@ sub evaluate_argv {
);
}
+# it is very similar to run() subroutine, but it would be hard to
+# extract common code; note that $*_hook variables can be set only
+# for FastCGI, so they are absent here
+sub to_psgi_app {
+ require CGI::Emulate::PSGI;
+
+ our $CGI = 'CGI';
+ our $first_request = 1;
+
+ my $app = CGI::Emulate::PSGI->handler(sub {
+ CGI::initialize_globals();
+ our $cgi = CGI->new();
+
+ run_request();
+
+ $first_request = 0;
+ });
+ return $app;
+}
+
+sub build_psgi_app {
+ require Plack::Builder;
+ require Plack::Middleware::Static;
+
+ my $sigchld_mw = sub {
+ my $app = shift;
+ sub {
+ my $env = shift;
+ local $SIG{'CHLD'} = 'DEFAULT';
+ local $SIG{'CLD'} = 'DEFAULT';
+ $app->($env);
+ };
+ };
+
+ # you're supposed to "add" middleware from outer to inner.
+ # note: Plack::Builder DSL (builder, enable_if, enable) won't work
+ # with "require Plack::Builder" outside BEGIN section.
+ my $app = to_psgi_app();
+ $app = Plack::Middleware::Static->wrap($app,
+ path => qr{(?:^|/)static/.*\.(?:js|css|png)$},
+ root => __DIR__,
+ encoding => 'utf-8', # encoding for 'text/plain' files
+ );
+ $app = $sigchld_mw->($app)
+ if (defined $SIG{'CHLD'} && $SIG{'CHLD'} eq 'IGNORE');
+
+ return $app;
+}
+
+sub run_psgi_app {
+ my $app = build_psgi_app();
+
+ # make it runnable as standalone app,
+ # like it would be run via 'plackup' utility.
+ # PLACK_ENV is set by plackup etc.
+ if ($ENV{'PLACK_ENV'} || $ENV{'PLACK_SERVER'}) {
+ return $app;
+ } else {
+ require Plack::Runner;
+
+ my $runner = Plack::Runner->new();
+ $runner->parse_options(qw(--env deployment),
+ qw(--host 127.0.0.1));
+ $runner->run($app);
+ }
+}
+
sub run {
evaluate_argv();
+ if ($CGI eq 'PSGI' || $ENV{'PLACK_ENV'} || $ENV{'PLACK_SERVER'}) {
+ return run_psgi_app();
+ }
$first_request = 1;
$pre_listen_hook->()
@@ -1266,12 +1342,12 @@ sub run {
}
}
-run();
+our $app = run();
if (defined caller) {
# wrapped in a subroutine processing requests,
# e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI
- return;
+ return $app;
} else {
# pure CGI script, serving single request
exit;
--
1.7.9