2009-08-24 23:01:58 +04:00
|
|
|
#!/usr/bin/perl
|
2013-07-19 18:59:12 +04:00
|
|
|
# Standalone HTTP server for running Bugzilla based on HTTP::Server::Simple and Net::Server
|
2013-08-12 19:15:11 +04:00
|
|
|
# USAGE: perl HTTPServerSimple.pl [--option=value] [bugzilla.conf]
|
2013-07-19 18:59:12 +04:00
|
|
|
# See bugzilla.conf sample in the end of this file
|
2009-08-24 23:01:58 +04:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
2013-07-19 18:59:12 +04:00
|
|
|
BEGIN
|
|
|
|
{
|
2009-08-24 23:01:58 +04:00
|
|
|
require File::Basename;
|
2013-07-19 19:06:38 +04:00
|
|
|
my $dir = File::Basename::dirname($0);
|
2013-07-19 19:42:43 +04:00
|
|
|
($dir) = $dir =~ /^(.*)$/s;
|
2013-07-19 19:06:38 +04:00
|
|
|
chdir($dir);
|
2009-08-24 23:01:58 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
use lib qw(.);
|
|
|
|
use CGI ();
|
2013-07-19 18:59:12 +04:00
|
|
|
CGI->compile(qw(:cgi -no_xhtml -oldstyle_urls :private_tempfiles :unique_headers SERVER_PUSH :push));
|
2009-08-24 23:01:58 +04:00
|
|
|
$CGI::USE_PARAM_SEMICOLONS = 0;
|
|
|
|
|
2013-07-19 18:59:12 +04:00
|
|
|
# Fake exit() function to only terminate current request
|
|
|
|
my $in_eval = 0;
|
|
|
|
*CORE::GLOBAL::exit = sub
|
|
|
|
{
|
|
|
|
if ($in_eval)
|
|
|
|
{
|
|
|
|
die bless { rc => shift }, 'Bugzilla::HTTPServerSimple::FakeExit';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CORE::exit(@_);
|
|
|
|
}
|
|
|
|
};
|
2009-08-26 22:03:52 +04:00
|
|
|
$SIG{INT} = sub { warn "Terminating"; CORE::exit(); };
|
2013-07-19 18:59:12 +04:00
|
|
|
|
|
|
|
# Create and run
|
2013-08-12 19:15:11 +04:00
|
|
|
my $server = Bugzilla::HTTPServerSimple->new(@ARGV);
|
2009-08-24 23:01:58 +04:00
|
|
|
$server->run();
|
|
|
|
|
2013-07-19 18:59:12 +04:00
|
|
|
# HTTP::Server::Simple subclass (the real server)
|
2009-08-24 23:01:58 +04:00
|
|
|
package Bugzilla::HTTPServerSimple;
|
|
|
|
|
2009-08-26 22:03:52 +04:00
|
|
|
use Bugzilla;
|
2013-08-16 15:40:00 +04:00
|
|
|
use Bugzilla::Util qw(html_quote);
|
2009-08-25 18:29:48 +04:00
|
|
|
use Time::HiRes qw(gettimeofday tv_interval);
|
2009-08-24 23:01:58 +04:00
|
|
|
use IO::SendFile qw(sendfile);
|
2013-07-19 18:59:12 +04:00
|
|
|
use POSIX qw(strftime);
|
|
|
|
use LWP::MediaTypes qw(guess_media_type);
|
|
|
|
|
2009-08-24 23:01:58 +04:00
|
|
|
use base qw(HTTP::Server::Simple::CGI);
|
|
|
|
|
2013-07-19 18:59:12 +04:00
|
|
|
# Code cache
|
2009-08-24 23:01:58 +04:00
|
|
|
my %subs = ();
|
|
|
|
|
2013-07-19 18:59:12 +04:00
|
|
|
sub new
|
|
|
|
{
|
2013-08-12 19:15:11 +04:00
|
|
|
my ($class, @args) = @_;
|
2013-07-19 18:59:12 +04:00
|
|
|
my $self = HTTP::Server::Simple::new($class);
|
2013-08-12 19:15:11 +04:00
|
|
|
my @cfg;
|
|
|
|
for (@args)
|
|
|
|
{
|
|
|
|
if (/^--([^=]+)(?:=(.*))?/s)
|
|
|
|
{
|
|
|
|
push @cfg, $1, $2||1;
|
|
|
|
}
|
|
|
|
elsif (!$self->{_config})
|
|
|
|
{
|
|
|
|
$self->{_config} = Bugzilla::NetServerConfigParser->_read_conf($_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
push @{$self->{_config} ||= []}, @cfg;
|
2013-07-19 18:59:12 +04:00
|
|
|
$self->{_config_hash} = {};
|
|
|
|
for (my $i = 0; $i < @{$self->{_config}}; $i += 2)
|
|
|
|
{
|
|
|
|
$self->{_config_hash}->{$self->{_config}->[$i]} ||= $self->{_config}->[$i+1];
|
|
|
|
if ($self->{_config}->[$i] eq 'port')
|
|
|
|
{
|
|
|
|
# Remove first 'port' option (workaround hardcode from HTTP::Server::Simple)
|
|
|
|
splice @{$self->{_config}}, $i, 2;
|
|
|
|
$i -= 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub run
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
$self->SUPER::run(@{$self->{_config}});
|
|
|
|
}
|
|
|
|
|
|
|
|
sub port
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
return $self->{_config_hash}->{port};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub net_server
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
return $self->{_config_hash}->{class};
|
|
|
|
}
|
|
|
|
|
2013-08-16 15:40:00 +04:00
|
|
|
sub print_error
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
my ($status_code, $status_line, $error_text) = @_;
|
|
|
|
warn $error_text;
|
|
|
|
print $self->{_cgi}->header(-status => $status_code).
|
|
|
|
"<html><head><title>$status_line</title></head>".
|
|
|
|
"<body><h1>$status_line</h1><p>".html_quote($error_text).
|
|
|
|
"</p><hr /><p>".$ENV{SERVER_SOFTWARE}."</p></body></html>";
|
|
|
|
return $status_code;
|
|
|
|
}
|
|
|
|
|
2009-08-24 23:01:58 +04:00
|
|
|
sub handle_request
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
my ($cgi) = @_;
|
2013-07-19 18:59:12 +04:00
|
|
|
# Set non-parsed-headers CGI mode
|
2009-08-24 23:01:58 +04:00
|
|
|
$cgi->nph(1);
|
2013-08-16 15:40:00 +04:00
|
|
|
$self->{_cgi} = $cgi;
|
2009-08-24 23:01:58 +04:00
|
|
|
$CGI::USE_PARAM_SEMICOLONS = 0;
|
2013-07-19 18:59:12 +04:00
|
|
|
# Determine SCRIPT_FILENAME
|
2009-08-24 23:01:58 +04:00
|
|
|
my $script = $ENV{SCRIPT_FILENAME};
|
2013-08-12 18:56:49 +04:00
|
|
|
$ENV{REQUEST_URI} =~ s!^/*!/!iso;
|
2009-08-24 23:01:58 +04:00
|
|
|
unless ($script)
|
|
|
|
{
|
|
|
|
($script) = $ENV{REQUEST_URI} =~ m!/+([^\?\#]*)!so;
|
|
|
|
}
|
|
|
|
$script ||= 'index.cgi';
|
2013-08-16 15:40:00 +04:00
|
|
|
# Check access
|
|
|
|
if ($self->{_config_hash}->{deny_regexp} &&
|
|
|
|
$script =~ /$self->{_config_hash}->{deny_regexp}/s)
|
|
|
|
{
|
|
|
|
return $self->print_error('403', 'Access Denied', "You are not allowed to access URL $script on this server.");
|
|
|
|
}
|
2013-07-19 18:59:12 +04:00
|
|
|
# Serve static files (should be done by nginx, but we support it for completeness)
|
2009-08-24 23:01:58 +04:00
|
|
|
my $fd;
|
|
|
|
$script =~ s!^/*!!so;
|
|
|
|
if (($script !~ /\.cgi$/iso || $script =~ /\//so) && open $fd, '<', $script)
|
|
|
|
{
|
2013-07-19 18:59:12 +04:00
|
|
|
print $cgi->header(-type => guess_media_type($script), -Content_length => -s $script);
|
2009-08-24 23:01:58 +04:00
|
|
|
sendfile(fileno(STDOUT), fileno($fd), 0, -s $script);
|
|
|
|
close $fd;
|
2013-07-19 18:59:12 +04:00
|
|
|
print STDERR strftime("[%Y-%m-%d %H:%M:%S]", localtime)." Served $script via sendfile()\n";
|
2009-08-24 23:01:58 +04:00
|
|
|
return 200;
|
|
|
|
}
|
2013-07-19 19:42:43 +04:00
|
|
|
if ($self->{_config_hash}->{http_env})
|
|
|
|
{
|
|
|
|
# Allow to set environment variables from additional HTTP headers
|
|
|
|
foreach (split /[\s,]*,[\s,]*/, $self->{_config_hash}->{http_env})
|
|
|
|
{
|
|
|
|
$ENV{$_} = $ENV{'HTTP_X_'.uc $_};
|
|
|
|
}
|
|
|
|
}
|
2013-07-25 20:10:09 +04:00
|
|
|
# Bugzilla-specific tweaks
|
|
|
|
binmode STDOUT, ':utf8' if Bugzilla->can('params') && Bugzilla->params->{utf8};
|
|
|
|
# Clear request cache for new versions
|
2009-08-26 22:03:52 +04:00
|
|
|
$Bugzilla::_request_cache = {};
|
2013-07-25 20:10:09 +04:00
|
|
|
# Reload Bugzilla.pm for old versions on each request
|
|
|
|
my $preload = Bugzilla->can('request_cache') ? '' : "delete \$INC{'Bugzilla.pm'}; require 'Bugzilla.pm';";
|
2013-07-19 18:59:12 +04:00
|
|
|
# Use require() instead of sub caching under NYTProf profiler for more correct reports
|
2013-07-25 20:10:09 +04:00
|
|
|
my $content;
|
|
|
|
if ((!$subs{$script} || $ENV{NYTPROF} && $INC{'Devel/NYTProf.pm'}) && open $fd, "<$script")
|
|
|
|
{
|
|
|
|
local $/ = undef;
|
|
|
|
$content = <$fd>;
|
|
|
|
close $fd;
|
|
|
|
# untaint
|
|
|
|
($content) = $content =~ /^(.*)$/s;
|
2013-08-12 20:04:01 +04:00
|
|
|
$content =~ s/\n__END__.*/\n/s;
|
2013-07-25 20:10:09 +04:00
|
|
|
}
|
2009-08-26 22:03:52 +04:00
|
|
|
if ($ENV{NYTPROF} && $INC{'Devel/NYTProf.pm'})
|
|
|
|
{
|
|
|
|
my $start = [gettimeofday];
|
2013-07-25 20:10:09 +04:00
|
|
|
eval "$preload package main; $content";
|
2013-08-16 15:40:00 +04:00
|
|
|
if ($@)
|
|
|
|
{
|
|
|
|
return $self->print_error(500, 'Internal Server Error', "Error while running $script:\n$@");
|
|
|
|
}
|
2009-08-26 22:03:52 +04:00
|
|
|
my $elapsed = tv_interval($start) * 1000;
|
2013-07-19 18:59:12 +04:00
|
|
|
print STDERR strftime("[%Y-%m-%d %H:%M:%S]", localtime)." Served $script via require() in $elapsed ms\n";
|
2009-08-26 22:03:52 +04:00
|
|
|
return 200;
|
|
|
|
}
|
2013-07-19 18:59:12 +04:00
|
|
|
# Simple "FastCGI" implementation - cache *.cgi in subs
|
2013-07-25 20:10:09 +04:00
|
|
|
if (!$subs{$script} && $content)
|
2009-08-24 23:01:58 +04:00
|
|
|
{
|
2013-07-25 20:10:09 +04:00
|
|
|
$subs{$script} = eval "package main; sub { $preload$content }";
|
|
|
|
if ($@)
|
2009-08-24 23:01:58 +04:00
|
|
|
{
|
2013-08-16 15:40:00 +04:00
|
|
|
return $self->print_error(500, 'Internal Server Error', "Error while loading $script:\n$@");
|
2009-08-24 23:01:58 +04:00
|
|
|
}
|
|
|
|
}
|
2013-07-19 18:59:12 +04:00
|
|
|
# Run cached sub
|
2009-08-24 23:01:58 +04:00
|
|
|
if ($subs{$script})
|
|
|
|
{
|
2009-08-25 18:29:48 +04:00
|
|
|
my $start = [gettimeofday];
|
2013-07-19 18:59:12 +04:00
|
|
|
$in_eval = 1;
|
2009-08-24 23:01:58 +04:00
|
|
|
eval { &{$subs{$script}}(); };
|
2009-08-25 20:19:15 +04:00
|
|
|
if ($@ && (!ref($@) || ref($@) ne 'Bugzilla::HTTPServerSimple::FakeExit'))
|
2009-08-24 23:01:58 +04:00
|
|
|
{
|
2013-08-16 15:40:00 +04:00
|
|
|
return $self->print_error(500, 'Internal Server Error', "Error while running $script:\n$@");
|
2009-08-24 23:01:58 +04:00
|
|
|
}
|
2013-08-14 19:42:09 +04:00
|
|
|
eval { Bugzilla::_cleanup(); };
|
|
|
|
if ($@ && (!ref($@) || ref($@) ne 'Bugzilla::HTTPServerSimple::FakeExit'))
|
|
|
|
{
|
|
|
|
warn "Error in _cleanup():\n$@";
|
|
|
|
}
|
|
|
|
$in_eval = 0;
|
2009-08-25 18:29:48 +04:00
|
|
|
my $elapsed = tv_interval($start) * 1000;
|
2013-07-19 18:59:12 +04:00
|
|
|
print STDERR strftime("[%Y-%m-%d %H:%M:%S]", localtime)." Served $script in $elapsed ms\n";
|
2013-08-16 15:40:00 +04:00
|
|
|
return 200;
|
2009-08-24 23:01:58 +04:00
|
|
|
}
|
2013-08-16 15:40:00 +04:00
|
|
|
return $self->print_error(404, 'Not Found', "The requested URL $script was not found on this server.")
|
2009-08-24 23:01:58 +04:00
|
|
|
}
|
|
|
|
|
2013-07-19 18:59:12 +04:00
|
|
|
# Override bad HTTP::Server::Simple::parse_headers implementation with a good one
|
2009-08-26 22:03:52 +04:00
|
|
|
sub parse_headers
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
my @headers;
|
|
|
|
my $chunk;
|
|
|
|
while ($chunk = <STDIN>)
|
|
|
|
{
|
|
|
|
$chunk =~ s/[\r\l\n\s]+$//so;
|
2013-07-19 18:59:12 +04:00
|
|
|
if ($chunk =~ /^([^()<>\@,;:\\"\/\[\]?={} \t]+):\s*(.*)/i)
|
|
|
|
{
|
2009-08-26 22:03:52 +04:00
|
|
|
push @headers, $1 => $2;
|
|
|
|
}
|
|
|
|
last if $chunk =~ /^$/so;
|
|
|
|
}
|
|
|
|
return \@headers;
|
|
|
|
}
|
|
|
|
|
2013-07-19 18:59:12 +04:00
|
|
|
# Net::Server fake subclass used to call _read_conf()
|
|
|
|
package Bugzilla::NetServerConfigParser;
|
|
|
|
|
|
|
|
use base 'Net::Server';
|
|
|
|
|
|
|
|
# _read_conf() could call fatal() in case of parse error
|
|
|
|
sub fatal
|
|
|
|
{
|
|
|
|
shift;
|
|
|
|
die @_;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Fake "exit" exception class
|
2009-08-25 20:19:15 +04:00
|
|
|
package Bugzilla::HTTPServerSimple::FakeExit;
|
|
|
|
|
2009-08-24 23:01:58 +04:00
|
|
|
1;
|
|
|
|
__END__
|
2013-07-19 19:07:58 +04:00
|
|
|
|
2013-08-12 19:16:09 +04:00
|
|
|
Sample bugzilla.conf (all parameters can be specified on
|
|
|
|
commandline using --option=value or here in config):
|
2013-07-19 19:07:58 +04:00
|
|
|
|
2013-08-12 19:16:09 +04:00
|
|
|
# Net::Server subclass to use for serving
|
2013-07-19 19:07:58 +04:00
|
|
|
class Net::Server::PreFork
|
2013-08-12 19:16:09 +04:00
|
|
|
|
|
|
|
# That subclass's parameters
|
2013-07-19 19:21:03 +04:00
|
|
|
port 0.0.0.0:8157
|
2013-07-19 19:07:58 +04:00
|
|
|
min_servers 4
|
|
|
|
max_servers 20
|
|
|
|
min_spare_servers 4
|
|
|
|
max_spare_servers 8
|
|
|
|
max_requests 1000
|
|
|
|
user www-data
|
|
|
|
group www-data
|
2013-07-19 19:21:03 +04:00
|
|
|
log_file /var/log/bugzilla.log
|
|
|
|
log_level 2
|
2013-07-19 19:07:58 +04:00
|
|
|
pid_file /var/run/bugzilla.pid
|
|
|
|
background 1
|
2013-07-19 19:42:43 +04:00
|
|
|
|
2013-08-16 15:40:00 +04:00
|
|
|
# HTTP 403 Access Denied will be shown for URLs matching deny_regexp:
|
|
|
|
# You are URGED also to disable these URLs on your frontend.
|
|
|
|
deny_regexp ^(localconfig|data/|.*\.(pm|pl|sh)($|\?)|.*\.(ht|svn|hg|bzr|git).*)
|
2013-07-19 19:42:43 +04:00
|
|
|
|
2013-08-16 15:40:00 +04:00
|
|
|
# 'http_env' specifies which environment variables to set from
|
|
|
|
# a corresponding 'X-<name>' HTTP header (value is comma-separated).
|
|
|
|
# For example to support multiple Bugzilla 'projects' specify:
|
2013-07-19 19:42:43 +04:00
|
|
|
http_env PROJECT
|
|
|
|
|
2013-08-16 15:40:00 +04:00
|
|
|
# For http_env to work you need to push an appropriate header from your
|
|
|
|
# frontend. For example, for nginx:
|
|
|
|
# proxy_set_header X-Project 'project';
|
|
|
|
# Or for Apache:
|
|
|
|
# RequestHeader set X-Project project
|