How to run cgi scripts in a simple perl web server - perl

For an upcoming school project I need to implement a simple web server, It is from the book "Net Programming with Perl". I am trying to get my head around it all as it is all new to me. For now, all I want to do is have a cgi script run as the home page.
I need to get the code to run from the Web.pm script that comes with the webserver (chapter 15 of the book).
I can get it to serve the cgi file as the home page, but it just show the code. I have tried numerous things and the closest I got was the html that the script is supposed to generate was displayed in the command line window that was running the web server but the server output the message from the not_found subroutine.
Here is the beginning of the Web.pm code with the handle_connection and lookup_file subroutines (straight from the book) the subroutines that I have left our are:
invalid_request, redirect and not_found
package Web;
use strict;
use vars '#ISA','#EXPORT';
use IO::File;
use CGI;
require Exporter;
#ISA = 'Exporter';
#EXPORT = qw(handle_connection docroot);
# set to your home directory
my $DOCUMENT_ROOT = '.';
my $CRLF = "\015\012";
###############################
sub docroot {
$DOCUMENT_ROOT = shift if #_;
return $DOCUMENT_ROOT;
}
###############################
# Outline of Handle_Connection()
# Set the socket handle supplied as a parameter
# Set the standard end-of-line character for HTTP messages
# Read the contents from the socket handle into a request variable
# SECTION TO CHECK FOR ERRORS
# Check to make sure the main request line has the right string format. Call invalid_request() otherwise. Set $method to GET or HEAD, and $url to the supplied URL
# Call lookup_file() to find the specified $url in the file system. Call not_found() if lookup_file() fails
# If the type of 'file' return from lookup_file() is actually a directory, call redirect()
# Print the status line and the headers for the response to the socket handle (ie. to the client)
# If the HTTP method is “GET”, print the file requested in the URL to the socket handle (ie. to the client)
sub handle_connection {
my $c = shift; # socket
my ($fh,$type,$length,$url,$method);
local $/ = "$CRLF$CRLF"; # set end-of-line character
my $request = <$c>; # read the request header
print $request; # print request to the command line
# error checking
return invalid_request($c)
unless ($method,$url) = $request =~ m!^(GET|HEAD) (/.*) HTTP/1\.[01]!;
return not_found($c) unless ($fh,$type,$length) = lookup_file($url);
return redirect($c,"$url/") if $type eq 'directory';
# print the header to socket
print $c "HTTP/1.0 200 OK$CRLF";
print $c "Content-length: $length$CRLF";
print $c "Content-type: $type$CRLF";
print $c $CRLF;
return unless $method eq 'GET';
# print the content to socket
my $buffer;
while ( read($fh,$buffer,1024) ) {
print $c $buffer;
}
close $fh;
}
The cgi file is as follows
#!/usr/bin/perl -w
# from http://perl.about.com/od/cgiweb/a/perlcgipm.htm
use CGI qw/:standard/;
print header,
start_html('Hello World'),
h1('CGI.pm is simple.'),
end_html;
The cgi works fine on Apache.
I believe I need to make a system call, but all of my efforts have been unsuccessful.
Any help would be greatly appreciated.

Related

Undefined subroutine CGI::remote_port

Why am I getting this error? server_port() works as intended, afaik REMOTE_PORT is a valid environment variable too.
Are there any alternative ways of getting the client's port number?
Here's my full code:
#!/usr/bin/perl -w
use CGI qw(:all);
use CGI::Carp qw(fatalsToBrowser);
use strict;
use Cwd;
#################################
my $time = localtime();
my $dir = cwd();
my $parameter = param('name');
my $q = new CGI;
my $addr = $q->remote_host();
my $request = $q->request_method();
my $port = $q->server_port();
print "Content-type:text/html\r\n\r\n";
print '<html>';
print '<head>';
print '<title>Auth2</title>';
print '</head>';
print '<body>';
print "<h1> The time is $time </h1>";
print "<p> Current directory is $dir</p>";
print "<p> Request parameter: $parameter</p>";
print "<p> Remote address: $addr</p>";
print "<p> Remote port: $port</p>";
print "<p> Request method: $request </p>";
print '</body>';
print '</html>';
1;
The available methods are documented in the man page and it looks like remote_port is not one of them.
afaik REMOTE_PORT is a valid environment variable too.
If it is an environment variable then you can simply access it as one:
my $port = $ENV{REMOTE_PORT};
Apart from that it is not clear why you need the remote port number at all, because it will probably simply be some arbitrary number from the ephemeral port range of the remote system. This was maybe also the reasoning to not provide a method to access it.

perl cgi script that accepts parameter url for redirect

I need to create a Perl CGI script that will accept a single parameter as input. The parameter will be a fully qualified URL and the script will redirect the browser to the URL that has been passed as the parameter. The method is a GET and not a POST.
The browser address bar will accept the full script with the URL parameter like this: http://webserver/cgi-bin/myscript.pl?URL=http://www.google.com
I am new to Perl and I can figure out how to do it with a POST, but not a GET. Any help would be greatly appreciated.
I stole this code but it does not do a GET and I think I am using a bad example or one that does not apply to what I need to do:
UPDATE: This was my solution
#!/usr/local/bin/perl
use strict;
use warnings;
use CGI qw(:standard);
use CGI::Carp qw(warningsToBrowser fatalsToBrowser);
use DBI;
use URI::Escape;
use strictures;
use CGI;
use URI;
my $q = new CGI ;
my $url = "httpcandy";
# Process an HTTP request
#my #values = $q->multi_param('form_field');
my $value = $q->param('param_name');
print "Content-type: text/html\n\n";
#print "<pre>\n";
#
#foreach my $key (sort keys(%ENV)) {
# print "$key = $ENV{$key}<br/>";
#}
#print "</pre>\n";
my $requested = URI->new( CGI::url() );
$requested->query( $ENV{QUERY_STRING} || $ENV{REDIRECT_QUERY_STRING} )
if url_param();
#print header(),
# start_html(),
# h1("requested:"),
# blockquote($requested),
# h1("url:"),
# blockquote($value),
# h1("nothing else"),
#
# end_html();
#
if ($value =~ /http/)
{
print "<META HTTP-EQUIV=refresh CONTENT=\"1;$value\">\n";
}
else
{
print "<META HTTP-EQUIV=refresh CONTENT=\"1;URL=http://$value\">\n";
};
exit;
Your solution seems rather over-engineered. I think this does all that you need.
#!/bin/env perl
use strict;
use warnings;
use CGI ':cgi'; # Only the CGI functions
my $url = param('URL');
if ($url !~ /^http/) {
$url = "http://$url";
}
print redirect($url);
In particular, your solution of using META HTTP-EQUIV in an HTML page seems really strange. HTTP-EQUIV is for situations where you can't alter the web server's headers. But as we're writing a CGI program, we can return whatever headers we warn. So using the redirect() function to return a 302 response seems to the be most obvious solution.
Note: If you get the input using the param() function, then it doesn't matter if it's a GET or POST request.

Make Perl web server deliver an ogg through HTTP to Chrome for a HTML5 audio element

I am writing a Perl script that acts as a simple web server that serves audio files over HTML5. I have succeeded in getting it to show a page to a web browser with an HTML5 audio element. It continues to listen to the socket for when the browser asks for an audio file via a GET request; hh.ogg in this example and tries to respond with the ogg inside the message body. It works over port 8888.
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
my $port = 8888;
my $server = new IO::Socket::INET( Proto => 'tcp',
LocalPort => $port,
Listen => SOMAXCONN,
ReuseAddr => 1)
or die "Unable to create server socket";
# Server loop
while(my $client = $server->accept())
{
my $client_info;
my $faviconRequest = 0;
while(<$client>)
{
last if /^\r\n$/;
$faviconRequest = 1 if ($_ =~ m/favicon/is);
print "\n$_" if ($_ =~ m/GET/is);
$client_info .= $_;
}
if ($faviconRequest == 1)
{
#Ignore favicon requests for now
print "Favicon request, ignoring and closing client";
close($client);
}
incoming($client, $client_info) if ($faviconRequest == 0);
}
sub incoming
{
print "\n=== Incoming Request:\n";
my $client = shift;
print $client &buildResponse($client, shift);
print "Closing \$client";
close($client);
}
sub buildResponse
{
my $client = shift;
my $client_info = shift;
my $re1='.*?';
my $re2='(hh\\.ogg)';
my $re=$re1.$re2;
print "client info is $client_info";
# Send the file over socket if it's the ogg the browser wants.
return sendFile($client) if ($client_info =~ m/$re/is);
my $r = "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n
<html>
<head>
<title>Hello!</title>
</head>
<body>
Hello World.
<audio src=\"hh.ogg\" controls=\"controls\" preload=\"none\"></audio>
</body>
</html>";
return $r;
}
sub sendFile
{
print "\n>>>>>>>>>>>>>>>>>>>>>>> sendFile";
my $client = shift;
open my $fh, '<' , 'hh.ogg';
my $size = -s $fh;
print "\nsize: $size";
print $client "Allow: GET\015\012";
print $client "Accept-Ranges: none\015\012";
print $client "Content-Type: \"audio/ogg\"\015\012";
print $client "Content-Length: $size\015\012";
print "\nsent headers before sending file";
############################################
#Take the filehandle and send it over the socket.
my $scalar = do {local $/; <$fh>};
my $offset = 0;
while(1)
{
print "\nsyswriting to socket. Offset: $offset";
$offset += syswrite($client, $scalar, $size, $offset);
last if ($offset >= $size);
}
print "Finished writing to socket.";
close $fh;
return "";
}
The sendFile subroutine is called when the GET request matches a regex for hh.ogg.
I send a few headers in the response before writing the ogg to the socket before closing.
This code works exactly as I'd expect in Firefox. When I press play the script receives a GET from Firefox asking for the ogg, I send it over and Firefox plays the track.
My problem is the script crashes in Google Chrome. Chrome's developer tools just says it cannot retrieve hh.ogg. When I visit 127.0.0.1:8888 in my browser while the script is running I can download hh.ogg. I have noticed that Chrome will make multiple GET requests for hh.ogg whereas Firefox just makes one. I've read that it may do this for caching reasons? This could be a reason as to why the script crashes.
I have
print $client "Accept-Ranges: none\015\012";
to try and stop this behaviour but it didn't work.
I'm not sure of exactly what headers to respond to Chrome to let it receive the file within one HTTP response. When the script crashes I also occasionally get this message printed out from Perl; otherwise there are no other errors. It will quit somewhere inside the while loop where I syswrite() to the socket.
Use of uninitialized value in addition (+) at ./test.pl line 91, <$fh> line 1.
Which is referring to this line.
$offset += syswrite($client, $scalar, $size, $offset);
I don't know why there would be any uninitialized values.
Would anyone have any ideas why this could be happening? If at all possible I'd like to accomplish this without requiring additional modules from CPAN.
Use a real web server instead that is already working and thorougly debugged instead of messing with sockets yourself. The Web is always more complicated than you think. Run the following app with plackup --port=8888.
use HTTP::Status qw(HTTP_OK);
use Path::Class qw(file);
use Plack::Request qw();
use Router::Resource qw(router resource GET);
my $app = sub {
my ($env) = #_;
my $req = Plack::Request->new($env);
my $router = router {
resource '/' => sub {
GET {
return $req->new_response(
HTTP_OK,
[Content_Type => 'application/xhtml+xml;charset=UTF-8'],
[ '… HTML …' ] # array of strings or just one big string
)->finalize;
};
};
resource '/hh.ogg' => sub {
GET {
return $req->new_response(
HTTP_OK,
[Content_Type => 'audio/vorbis'],
file(qw(path to hh.ogg))->resolve->openr # file handle
)->finalize;
};
};
};
$router->dispatch($env);
};
Your error says Use of uninitialized value in addition which means it is not inside the syswrite, but in the += operation. syswrite() returns undef if there is an error. Which seems consistent with your overall error with Chrome. The $! variable contains some info about the writing error.

Why do I get "Premature end of script headers" from my CGI proxy?

I have CGI proxy that works on my localhost, but when I try to get it work on another server I get Premature end of script headers. I have included the source below. I also tried print header instead of the text/xml and it worked localhost but it failed on the server.
use strict;
#use warnings;
use CGI qw(:standard);
use CGI::Carp qw(fatalsToBrowser);
use HTTP::Request::Common qw(POST);
use HTTP::Request::Common;
use LWP::UserAgent;
use URI::Escape;
use Data::Dumper;
my $url = param('url');
sub writeXML($) {
my $response = shift #_;
if ($response->is_success) {
print CGI->header('text/xml');
print $response->content;
print STDERR "content response:#" . $response->content . "#\n";
}
else {
print STDERR "Status Code: " . $response->status_line . "\n";
print STDERR Dumper ($response);
}
}
sub makeRequest(){
if ($url){
my $ua = LWP::UserAgent->new;
my $response = $ua->request(GET $url);
if ($response){
writeXML($response);
}
else{
print STDERR "No response exists";
}
}
else{
print STDERR "URL must be specified";
}
}
makeRequest();
0;
__END__
The script "works" when I try it from the command line:
$ t.pl url=http://www.unur.com/
gives me the home page of my web site.
That means, the host on which you are trying this is missing some libraries. To figure out which ones, you should examine the server's error log, or try running your script from the shell as shown above.
See DEBUGGING.
PS: There is absolutely no good reason for those prototypes on makeRequest and writeXML. Plus, try warn sprintf "Status: %s\n", $response->status_line; instead of those unsightly print STDERR lines.
See my Troubleshooting Perl CGI scripts guide for all the steps you can go through to find the problem.
You only output a header if the program succeeds, all your error conditions are going to cause the premature end of script headers.
Put a 'print CGI->header();' and a suitable error message to STDOUT at all the points where you're output an error message to STDERR, and you'll be to see what's going wrong.

How can I handle HTTP redirects in my Perl application?

I use this Perl subroutine to get a line from a webpage, which I then split to get the information I require to proceed. This has worked for a dozen years.
sub capture_line {
my $page_to_get = $_[0];
my $host_to_get_text = $_[1];
my $port = 80;
my $buf = &HTTPGet($page_to_get, $host_to_get_text, $port);
my $image_capture_text;
my #lines = split(/\n/,$buf);
# print "$lines[1]\n";
# print "$page_to_get, $host_to_get_text\n";
# print "$buf\n";
foreach (#lines) {
if (/$text_to_find/i) {
$image_capture_text = $_;
print "in_loop";
last;
}
}
return $image_capture_text;
}
Unforntuately, $page_to_get is now always a 301 redirect and $buf, when printed, gives me a 301 redirection page, which obviously does not contain the sought after text. Is there a $in value pair (for example) that I can use with HTTPGet to hop me over the redirection so that I get the page that I see when I type http://$host_to_get_text$page_to_get into my browser? Or is there a better way to accomplish the same thing (knowlege of an ever changing filename in the source of a viewed webpage)?
Thank you for your time.
Greg Marsh
Where is the HTTPGet function coming from?
If you were to use LWP (http://search.cpan.org/dist/libwww-perl/) to do the HTTP fetching, that will automatically follow redirects (you can specify how many times you want it to follow a redirect before giving up).
e.g.:
use LWP::Simple qw()
my ($page_to_get, $host_to_get_text) = #_;
my $url = "http://$host_to_get_text$page_to_get";
my $buf = LWP::Simple::get($url);
my $image_capture_text;
my #lines = split(/\n/,$buf);
# ...