#!/usr/local/bin/perl $Script = 'sascgi'; $Version=1.10; # 31 Mar 1997 11:27:35 $ScriptDocURL= 'http://www.math.yorku.ca/SCS/Online/sascgi/'; $ScriptIcon = ''; $ScriptAuthor='Michael Friendly (friendly@yorku.ca)'; # sascgi is a perl script designed to provide a gateway between a web # server and a SAS program which returns results to the browser via # stdout, a listing file, or graphic image(s). The output may be # interpolated into an 'output form', which can be defined for each # application. # The intention is to provide a relatively uncluttered, general # protocfol for running SAS on the web. The script handles most of # the interaction with the web server, making it much easier to # write SAS applications to be run on the web. # The script passes input parameters to the sas program via the # environment, which is much easier than trying to parse server queries # in SAS. # The SAS program is assumed to retrieve these parameters # via %sysget(PARAM), or sysget('PARAM') in a data step. # The SAS program can communicate success or failure (with an error message) # by writing a message to a .err file or by returning a message starting # with 'ERROR:' to stdout. [Not yet implemented] # Usage: # Embed a
block inside an HTML document, referencing # this script as follows: # # Only query items which are given a value in the form are placed # in the SAS environment. %sysget() will give a harmless warning if # the SAS program references an unset (null) string. # Note that *names* of query items passed from a form are case-sensitive # The values of these query items are generally case-insensitive. # Output methods presently handled: STDOUT and LST and IMAGE. # OUTPUT_METHOD = STDOUT assumes the SAS program produces output # which can be sent directly to the browser, e.g., as wrapped # with HTML tags like...or
...by this script, formfeeds are # translated to
Run time was %.3f CPU seconds\n", ((times)[0] - $start_time)); $config->{RUN_TIME} = $run_time; $config->{HREF_SASLOG} = qq($base.log); &croak("$sasfile produced no results") unless @output; # Should check for 'ERROR:' in @output # remove input record separator &print_output($config, $forms->{OUTPUT_FORM}, @output); } elsif ($output_method =~ /LST/i) { print "Output via LST\n" if $debug; # Assume the program will produce results in $base.lst in current dir system ("$SAS $sasopts -sysin $sasfile_path"); $run_time = sprintf("\n
Run time was %.2f CPU seconds\n",
((times)[0] - $start_time));
$config->{RUN_TIME} = $run_time;
$config->{HREF_SASLOG} = qq($base.log);
&croak("$sasfile produced no results") unless -f "$base.lst";
open (LST, "< $base.lst");
@output = Run time was %.2f CPU seconds\n",
((times)[0] - $start_time));
$config->{RUN_TIME} = $run_time;
$config->{HREF_SASLOG} = qq($base.log);
# assume the $sasfile produces an output file with same basename
&croak("$sasfile produced no image") unless -f $gsasfile;
if (($image_type =~ /gif/i) && $sysver < 6.11) {
# use PSTOGIF to create gifs from .ps
# WARNING: pstogif uses environment vars which can clash!!
undef($ENV{'DEPTH'}) if $ENV{'DEPTH'};
$ENV{'SILENT'} = 1 unless $debug;
print "Running $PSTOGIF -out $base.gif $gsasfile >/dev/null\n" if $debug;
system ("$PSTOGIF -out $base.gif $gsasfile >/dev/null");
$gext = ".gif";
}
# possibly the file creates more than one image
opendir(IMG, $httmp) || &croak("Couldnt open $httmp to look for images");
@images = grep(/$base[0-9]*$gext/, readdir(IMG));
print "Sending $#images images", join(" ",@images), "\n" if $debug;
closedir(IMG);
foreach $img (@images) {
print " Output image $img: An error occurred while processing this form:\n";
print " $msg\n";
&print_tail;
die $msg;
}
sub whine {
my($msg) = @_;
print " Input error: $msg\n";
}
__END__
###########################################################
# More user-configurable defaults. These can (and are
# intended to) be overridden by values provided in a
# configuration file whose address is passed to sascgi
# in the CONFIG field:
#
#
###########################################################
###
# You'll want to adjust these for your site
###
# The title for the output page, unless defined in the input form.
TITLE=SAS Results from @SASFILE@
# Author and address are printed at the bottom of the page. These should be all on
# one line. If any of these are defined as empty, they will not appear in the
# output.
AUTHOR_EMAIL=friendly\@yorku.ca
AUTHOR=Michael Friendly Email: (@AUTHOR_EMAIL@)
ADDRESS=Statistical Consulting Service, York University
# Background color of output form. Omit if you don't want to set background color
BGCOLOR=white
# Get a local copy of the SAS Powered Logo and replace this URL with your
# local address. Get the image file from SAS Institute at
# http://www2.sas.com/dispatcher/index.html
SASPOWEREDLOGO =
# Set this non-zero to display the running time in the output
SHOW_RUN_TIME=1
# MULTIVALUE_FORMAT determines how multivalue parameters from the input form
# are passed in the environment to the SAS program
# Set this field to BRACES to pass multivalued parameters as "{a,b,c}".
# Set to COMMAS to show multivalued parameters as "a, b, c".
# Set to SPACES to pass as "a b c"
MULTIVALUE_FORMAT=BRACES
############
# This defines the output form -- the way the output is returned to the browser.
# SAS output is interpolated back into what is returned to the browser in the
# place of '@OUTPUT@'. A link to the SASLOG is included if the OUTPUT_FORM
# contains @HREF_SASLOG@.
# Everything from OUTPUT_FORM=
# to the dot (.) at the very bottom is the form.
OUTPUT_FORM=
The following output was produced:
/; # ^L -> page separator
s/</;
s/>/>/;
}
@output = ( "\n", @output, "
\n")
&print_output($config, $forms->{OUTPUT_FORM}, @output);
}
# #### Graphic Output #### This code is not very general
elsif ($output_method =~ /IMAGE/i) {
($dum, $image_type) = split('/', $output_method);
$image_type =~ tr/A-Z/a-z/;
$image_type ='gif' unless $image_type;
$device = $devices{$image_type} || 'nextclr';
print "Output via IMAGE, type: $image_type, driver: $device\n" if $debug;
$sasopts .= " -device $device";
$gext = ($device eq "nexteps") ? ".eps" : ".ps";
# let SAS file know what filename to use for output
$gsasfile = $base . $gext;
print "gsasfile : $gsasfile\n" if $debug;
$ENV{'GSASFILE'} = $gsasfile;
system ("$SAS $sasopts -sysin $sasfile_path");
$run_time = sprintf("\n
\n";
# print qq();
push (@output, qq());
}
&print_output($config, $forms->{OUTPUT_FORM}, @output);
}
else {
&croak("$Script: Output method $output_method is not defined.");
}
# print "
Ran:\n
$SAS $sasopts -sysin $sasfile_path
\n" if $debug;
# Determine if the program wrote a .ERR file in the $httmp directory to
# signal an error
if (-f "$base.err") {
open(ERR, "$base.err");
chop($sas_rc =
$config->{AUTHOR}
$config->{ADDRESS}
END_OF_ADDRESS
;
print <
\n";
print "File: $sasfile -> $sasfile_path\n
";
print "Title: $title\n
";
print "CGI.pm Version: $CGI::VERSION\n
";
print "Script Query Values
\n", $query->dump, "\n";
print "CGI Environment
\n";
print "\n";
foreach $var (keys %ENV){
print "$var = $ENV{$var}\n" unless $ignore_env{$var};
}
print "\n\n
\n";
print "SASCGI Configuration Info
\n";
print "\n";
foreach $var (keys %$config){
print "$var = $config->{$var}\n";
}
print "\n\n
\n";
}
# -------- Find file -----------------
# First look for an absolute file path, then for a ~user/path
# then in defined $file_paths, then in defined @sas_dirs
sub find_file {
my($file) = @_;
return $file if ($file =~ m|^/| && -r $file); # absolute path?
$file=~s@~([^/]+)@&getlogin($1)@e; # ~name/path ?
return $file if -r $file;
if (defined $file_paths{$file}) {
return $file_paths{$file} if -r $file_paths{$file};
}
foreach $dir (@sas_dirs) {
return "$dir/$file" if -e "$dir/$file";
}
# if looking for config file, check in the same dir holding the sasfile
if (defined($sasfile_path)) {
return "$sasfile_path/$file" if -e "$sasfile_path/$file";
}
return undef;
}
####
# Get login account for ~name substitution
#
####
sub getlogin {
return (getpwnam($_[0]))[7] . "/$PUBLIC_HTML";
}
# -------- Parse configuration information -------
# Pass the routine a filehandle open to the configuration
# file
sub read_configuration {
my($query) = @_;
my($config) = {};
my($forms) = {};
my($user_config_file);
&config(DATA,$config,$forms); # read our built-in defaults
$config_param = $query->param('CONFIG');
$user_config_file = &find_file($config_param);
if ($user_config_file) {
open (USER_CONFIG,"$user_config_file") || &croak("$user_config_file: $!");
&config(USER_CONFIG,$config,$forms);
close(USER_CONFIG);
}
return ($config,$forms);
}
# Read an individual configuration file.
sub config {
my($filehandle,$config,$forms)=@_;
my($tag,$value,$old);
&fill_in_predefines($config);
while (<$filehandle>) {
chomp;
next if /^#/;
next unless ($tag,$value) = /(^\w+)\s*=\s*(.*)/;
if ($tag=~/OUTPUT_FORM|INPUT_FORM/) {
($old,$/)=($/,"\n.\n"); # look for a lonely dot
chomp($forms->{$tag}=<$filehandle>);
$forms->{$tag} .= "\n"; # add back a newline
$/=$old;
} else {
$config->{$tag} = &do_substitutions($value,$config);
}
}
}
# Fill in a few of the special predefined tags.
sub fill_in_predefines {
my $config = shift;
$config->{SASFILE} = $sasfile;
$config->{DATE}=localtime;
$config->{SCRIPT}=$query->script_name;
$config->{REFERER}=$query->referer;
$config->{REMOTE_USER}=$ENV{HTTP_FROM} ||
$query->remote_ident || $query->remote_user;
$config->{USER_AGENT}=$query->user_agent;
$config->{REMOTE_HOST}=$query->remote_host;
my($name,$junk,$junk,$junk,$junk,$junk,$gcos) = getpwuid($<);
$config->{SERVER} = "$name\@" . $query->server_name . " ($gcos)";
$config->{SERVER_HOST} = $query->server_name;
$config->{ADMINISTRATOR} = $ENV{SERVER_ADMIN};
}
# Do the variable substitutions.
sub do_substitutions {
my($scalar,$config) = @_;
my($key);
my(@keys) = $scalar=~/\@(\w+)\@/g;
# Note that we use the unoptimized s/// form here for
# security reasons -- check the performance!
foreach $key (@keys) {
$scalar=~s/\@$key\@/$config->{$key}/g;
}
return $scalar;
}
# -------------- error reporting utility ---------------
sub croak {
my($msg) = @_;
print "