Tricky way to instantiate a class in Perl

Today I started to design a CGI engine for my light weighted Perl Web container and I wanted to share my solution about how to instantiate the CGI class here.

Let’s see how a form works on Perl CGI:

1. User submit a form request,

</pre>
<form action="/firstapp/test.pl" method="post"><select name="userid">
<option value="user1">1</option><option value="user2">2</option>
</select><select name="dbid">
<option value="db1">1</option>
<option value="db2">2</option></select>
<input type="submit" name="Submit" value="submit" />

</form>
<pre>

2. The form request comes to server side in below format,

POST /firstapp/test.pl HTTP/1.1
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-us,en;q=0.5
Host: localhost:8080
Referer: http://localhost:8080/firstapp/login.html
User-Agent: Mozilla/5.0 (X11; Linux i686; rv:7.0.1) Gecko/20100101 Firefox/7.0.1
Content-Length: 37
Content-Type: application/x-www-form-urlencoded

userid=user2&dbid=db1&Submit=submit

3. On Server side, CGI engine injects parameters submitted via form ‘hahaid=haha2&abcid=abc1&Submit=submit’ to the action page ‘/firstapp/test.pl’,

4. Developer can access those paramters using method my  param(‘$key’); E.g,

use CGI qw(:standard);

my $userid  = param('userid'); # Here it will return value "user2".

In my CGI implementation, I design it as below process,

Step 1, 2 are the same as above as both are client operation.

Starting from Step 3,

On Server side, my CGI engine class,

sub new{
my $classname = shift;
my $self = {};
bless($self, $classname);
$self->_init(@_);
return $self;
}

sub _init {
my $self = shift;
if (@_) {
my %extra = @_;
my $paramstr = $extra{"PARAMS"};# PARAMS is a keyword here
if (defined $paramstr) {
my %params = getParams($paramstr);
@$self{keys %params} = values %params;
}
}
}

sub getParams{
my $s = shift;
chomp($s);
my %params = {};
my @pairs = split(/\&/,$s);
foreach (@pairs){
my ($key, $value) = split(/=/,$_);
$params{$key} = $value;
}
return %params;
}

Step 4. Developer can access those paramters with below steps:

1. import my CGI,

use camelcgi;

2. Instantiate it,

my $cgiinst = camelcgi-&gt;new(PARAMS =&gt; $ARGV[0]); 
# In my HTTP Daemon, it will pass the request content to it 
# as $ARG[0] automatically.

# E.g, $ARGV[0]=”userid=user2&dbid=db1&Submit=submit”

3. Retrieve those parameters with,

$cgiinst-&gt;{userid} ;

Let’s summarize the snippet here,


use camelcgi;

my $cgiinst = camelcgi-&gt;new(PARAMS =&gt; $ARGV[0]);

$cgiinst-&gt;{userid} ;

It is tricky, right? 🙂

HTTP:Daemon in Perl

Yesterday I tried socket in Perl in post Socket in Perl. Today I will share my experience in HTTP:Daemon.

Basically HTTP package implements the necessary elements defined in http://tools.ietf.org/pdf/rfc2616.pdf (Hypertext Transfer Protocol — HTTP/1.1). For example, on my post Socket in Perl yesterday, the connection is established using socket and it doesn’t support cache and this brings unnecessary network load.  However from http://tools.ietf.org/pdf/rfc2616.pdf, it defines the GET method as below. (I tested HTTP:Daemon seems it is implemented well to support cache).

9.3 GET
The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.
If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity
in the response and not the source text of the process, unless that text happens to be the output of the process.
The semantics of the GET method change to a “conditional GET” if the request message includes an IfModified-Since, If-Unmodified-Since, If-Match, If-None-Match, or If-Range header field. A
conditional GET method requests that the entity be transferred only under the circumstances described by the
conditional header field(s). The conditional GET method is intended to reduce unnecessary network usage by
allowing cached entities to be refreshed without requiring multiple requests or transferring data already held by the
client

API provided by HTTP:Daemon from http://search.cpan.org/~gaas/libwww-perl-5.837/lib/HTTP/Daemon.pm

Additionally, I introduced the fork in this Daemon script.

The logic in the script:

1. Accept a http request connection.

2. Fork a process to take care of the request.

3. Return to 1.

camelhttp.pm:

package camelhttp;

use strict;
use HTTP::Daemon;
use HTTP::Status;
use Carp;
use FileHandle;
use camellogger;
use camelutils;

my $portno = "8080";
my $base_dir = "../webapps";
my $SERVER = HTTP::Daemon->new(LocalPort => $portno, LocalAddr => 'localhost')
 or die;
loader();
camelHttpCore();

END{
_destroy();
}

sub camelHttpCore{

# autoreaping of zombies
$SIG{CHLD} = 'IGNORE';
while (1)
{
while (my $con = $SERVER->accept){
logger(0, "$0 - Camel is forking a worker");
next if my $pid = fork; #parent
die logger(0, "$0 - Camel can't fork a worker : $!") unless defined $pid;
while (my $req = $con->get_request){

my $path = $base_dir . $req->uri->path;
if ($req->method eq 'GET'){
if (_handleGet($path) eq "YES"){
$con->send_file_response("$path");
} else {
$con->send_error(RC_NOT_FOUND);
}

} elsif ($req->method eq 'POST') {
$con->send_error(RC_FORBIDDEN);
}
}
close($con);
exit;
} continue {
# nothing here so far.
}
}
}

sub _handleGet{
my ($path) = @_;
if ($fshash{"$path"}){
return "YES";
} else {
return "NO";
}
}

sub _destroy {
logger(0, "$0 - Camel is destroying itself");
close ($SERVER) if (defined $SERVER);
}

 1;

Socket in Perl

Today I started to wrote a light weighted Web container in Perl (I named it as Camel – hats off to Tomcat). Basically my target is, it can take care of all of the static requests (html, txt, etc) in async, and it can support Perl CGI. It should have an elegant architecture (basically I will steal the filesystem architecture from Tomcat). Below is the snippet which can provide HTTP service for static requests in sync mode.

I think it is a tiniest web container with Perl’s Socket support.
#Pls. comment out the logger() from below snippet if you want to run it from your side.

package camel;

use strict;
use Socket;
use Carp;
use FileHandle;
use camellogger;
use camelutils;

my $portno = "8080";
my $base_dir = "../webapps";

logger(0, "$0 - Loading Camel Core");
logger(2, "$0 - Making, Setting and binding socket");
socket(SERVER, PF_INET, SOCK_STREAM, getprotobyname('tcp')) 
or die "can't make the socket : $! \n";
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1) 
or die "can't set socket options : $! \n";
my $my_addr = sockaddr_in($portno, INADDR_ANY);
bind(SERVER, $my_addr) 
or die "can't bind to port $portno : $! \n";
logger(0, "$0 - Camel is listening on port $portno");
listen(SERVER, SOMAXCONN) 
or die "can't listen on port $portno : $! \n";

while (1)
{
while (my $newcon = accept(CLIENT, SERVER)){
CLIENT-&gt;autoflush(1);
my ($portno, $ipaddr) = sockaddr_in($newcon);
my $hostname = gethostbyaddr($ipaddr, AF_INET);

logger(0, "$0 - Camel is connecting with $hostname");
my $getinfo = &lt;CLIENT&gt; ;
print "getinfo: $getinfo \n";
my $rsppage = $base_dir . "/" . dispatch($getinfo);

open IN, "&lt; $rsppage" 
or warn logger(0, "$0 - Camel can't talk with $hostname : $!");
while (&lt;IN&gt;)
{
print CLIENT $_ . "\n";
}
close(CLIENT);
close IN;
logger(0, "$0 - Goodbye with $hostname");
}
}

sub dispatch(){
my ($info) = @_;
my ($s, $url) = split(" ",$info);
$url =~ s/^\s*|\s*$//g;
$url = substr($url, 1);
if ($url eq "") {
$url = "login.html";
}
return $url;
}

camel1

CGI – Upload Files

In our daily development, sometimes we need to upload files to our Unix file system (~username). For example, my username is luhuang, then my location is /home/luhuang and I can visit /home/luhuang/public_html using URL http://www-xxxxx.com/~luhuang/. Usually we will use ftp command to upload files to a Unix machine which provides ftp services and can be accessed from my Unix file system.

For example:

I need to upload C:/work/abc.txt to /home/luhuang/public_html,

1. CMD-> cd to C:/work, then issue: ftp appxxx@unix123machine

2. put abc.txt

3. from Unix /home/luhuang/public_html, scp appxxx@unix123machine:/slot/xxxx/abc.txt .

4. dosunix abc.txt

To save time, I composed a tiny upload script to do that. Below is the scripts:

upload.pl (Upload Form)

#!/local/bin/perl

#C:\Perl\strawberry\perl\bin\perl.exe

use strict;
use CGI;
use CGI::Carp qw ( fatalsToBrowser );
use File::Basename;

$CGI::POST_MAX = 1024 * 5000000;

my $query = new CGI;

print $query->header ( );
print <<END_HTML;
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN” “DTD/xhtml1-strict.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml&#8221; xml:lang=”en” lang=”en”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<title>File Upload Application</title>
<style type=”text/css”>
img {border: none;}
</style>
</head>
<body>
<p>File Upload Application</p>
<form action=”upload.cgi” method=”post”
enctype=”multipart/form-data”>
<p>File to Upload: <input type=”file” name=”file” /></p>
<p>Upload Location: <input type=”text” name=”location” /></p>
<p><input type=”submit” name=”Submit” value=”Submit Form” /></p>
</body>
</html>
END_HTML

 

upload.cgi (upload handler)

#!/local/bin/perl

#C:\Perl\strawberry\perl\bin\perl.exe

use strict;
use CGI;
use CGI::Carp qw ( fatalsToBrowser );
use File::Basename;

$CGI::POST_MAX = 1024 * 5000000;

my $query = new CGI;
my $filename = $query->param(“file”);
my $upload_dir = $query->param(“location”);
my $link = “”;

if ( !$filename )
{
print $query->header ( );
print “Failed to upload file: $! \n”;
exit;
}

my ( $name, $path, $extension ) = fileparse ( $filename, ‘\..*’ );
$filename = $name . $extension;
$filename =~ s/ /_/g;

if (length($upload_dir) < 1){
$upload_dir = “/home/luhuang/public_html/upload”;
$link = qq(<a href=\”http://www-xxxx/~luhuang/upload/$filename\”>$filename</a>);
}

my $upload_filehandle = $query->upload(“file”);

open ( UPLOADFILE, “>$upload_dir/$filename” ) or die “$!”;
binmode UPLOADFILE;

while ( <$upload_filehandle> )
{
print UPLOADFILE;
}

close UPLOADFILE;

system(“chmod 777 $upload_dir/$filename”);
system(“dos2unix $upload_dir/$filename”);

print $query->header ( );
print <<END_HTML;
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN” “DTD/xhtml1-strict.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml&#8221; xml:lang=”en” lang=”en”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<title>File Upload Application</title>
<style type=”text/css”>
img {border: none;}
</style>
</head>
<body>
<p>Uploaded Successfully</p>
<p>Location: $upload_dir/$filename $link </p>
</body>
</html>
END_HTML

 

Output:

cgi_upload

CGI Programming Introduction – Perl

In the world of internet, many of the documents exchanged are encoded in HTML. The Web is a client-server system, client requests a document from browsers like IE, Firefox and the web servers like Apache will take care of it and return the required document to client. If the requests from client is plain text requests then the server just merely sends back the file contents. Sometimes, however, the web server runs  another program to return a document. So, the problem here is, how can the server run the program from server side?

The program run from server side can be handled in two ways. 1. The program is part of the web server process, like Java Servlet or mod_perl; or 2. the program is an external program and in this way we call it CGI. I don’t want to discuss the disadvantages of CGI here. I will just share my study note below about CGI.

A CGI request generally means the invocation of a newly created process on the server. It is important to understand that CGI program doesn’t run continuously, with the browser calling different parts of the program. Each request for a paritial URL corresponding to the program starts a new copy. The CGI program generates a page for that request, then quits.

 

In CGI, client has three typical methods to communicate with server.

1. GET. The GET method is the most common, indicating a simple request for a document.

With the GET method, values are encoded directly in the URL, leading to ugly URLs like this: E.g, http://www-abc.def.com/cgi-bin/abc/def/shopapps/bill_status.pl?reqid=12345

2. POST. The POST method submits form values. With the POST method, values are encoded in a separate part of the HTTP request that the client browser sends the server. Basically we will use the POST method to submit a login form.

E.g, http://www-abc.def.com/cgi-bin/abc/def/shopapps/login.pl

The GET and POST methods is different with each other:

Making a GET request for a particular URL once or multiple times should no different. That is to say, GET is for static informational requests. The HTTP protocol definition says that a GET request may be cached by the browser, the server, or a proxy. POST requests cannot be cached, because each request is independent and matters. Typically POST requests any changes or depends on the state of the server (query or update a database, login, etc.).

3. HEAD. The HEAD method supplies information about the document without actually fetching it.

 

Security

CGI programs allow anyone run a program on your system by default. To control the access, you might need to introduce a mechanism to control the access. I will share the way how to write to cookies to guarantee the security below.

 

HTML and Forms

In CGI program, we can embed HTML tags to create forms and generate nice HTML UI.

 

So let me conclude my study note here:

1. We can use CGI to request server to run external programs.

2. In CGI, it will use GET to get a document and a POST method to submit requests. The HEAD method will just tell us information about the document.

3. In CGI, we can use cookies to implement security control.

4. In CGI, we can embed HTML tags to create beautiful HTML UI.

 

Let me write a simple CGI script firstly,

#!/local/bin/perl -w

use strict;

use CGI qw(:all);

$incheader = `cat /u01/abc/header.inc`;
$ToolHead=”Luohua’s Shop”;

print “Content-type: text/html\n\n”;

print “<TITLE> $ToolHead </TITLE>\n”;

print “${incheader}”

print “<center><h1> $ToolHead </h1>”;

##### Done !!! #####

Below is snippet to implement the POST method using a form,

print “<form method=POST action=\”\” name=\”asubmit\” id=\”Requestshop\”>\n”;
print “<input type=hidden name=UID size=50 value=${cookieemail} >”;
print “<input type=hidden name=listSz size=2 value=${produclist} >”;
print “</form>\n”;

 

Below is snippet how to set cookies,

# interactive with database to check the credential, if it matches, then set cookies.
print “Set-Cookie: user=$user;domain=www-abc.def.com;path=/cgi-bin/abc/def/shopapps/bill_status.pl;expires=Mon, 15-Jul-2013 12:00:00 GMT\n”;
print “Set-Cookie: passwd=$passwd;domain=www-abc.def.com;path=/cgi-bin/cgi-bin/abc/def/shopapps/bill_status.pl;expires=Mon, 15-Jul-2013 12:00:00 GMT\n”;

Below is snippet how to clean cookies,

print “Set-Cookie: user= ;domain=www-abc.def.com;path=/cgi-bin/abc/def/shopapps/bill_status.pl;expires=Mon, 15-Jul-2013 12:00:00 GMT\n”;
print “Set-Cookie: passwd= ;domain=www-abc.def.com;path=/cgi-bin/abc/def/shopapps/bill_status.pl;expires=Mon, 15-Jul-2013 12:00:00 GMT\n”;