Sockd Server

Zeus Server provides a high-performance CGI replacement which allows dynamic documents to be provided to its clients. The dynamic document API utilizes a TCP/IP socket interface to allow the document generation code to be implemented on a separate machine to facilitate load-balancing, and also makes no restrictions on the implementation language.

The dynamic document generation code, called sockd here in, runs as an external daemon to the server and listens for the request from the server to generate a page. The server then passes information, similar to the environment setup for a CGI process, to the sockd server, which can then use this information to determine the data for the page to generate. This information is then routed back through the server down to the client. This offers significant performance increases over frequently forking a large CGI script.

Simple Example

Here is an example of a sockd server. This is a simple page-counter implemented in Perl which demonstrates how to interact with the API. It is intended to interact with the non-forking CGI API of Zeus Server.

CGI daemon protocol: See specifications for more information.

Input:

ENVIRONMENT\r\n
DATA\r\n\r\n

Where:

ENVIRONMENT = (LINE\r\n)+
LINE = environment variable line (eg. HTTP_USER_AGENT=ZeusBrowser)
DATA = data supplied to POST, or arguments supplied to GET
ENVIRONMENT contains all of sames fields that are set in the environment of a normal CGI program.

Output: [as normal CGI]

Presented here is a small example of a CGId daemon (herein called sockd) capable of interacting with the Zeus Server non-forking CGI API.

Upon startup, the sockd requires a port number to run on. On recieving a complete request, the sockd opens up a counter file, increments the value in there, and returns a page to the user displaying the counter value.

The webserver should be configured to map requests to this sockd server.

#!/usr/bin/perl

require 5.001;
use Socket;
use POSIX;

# complete(string)
# Determines whether a complete request has been recieved yet.  Returns
# non-zero if so.
sub complete { return ($_[0] =~ /\r\n\r\n.*\r\n\r\n/) }

# process(string)
# Takes a complete request, and returns the data to output.

# Trivial example simply opens up the counter file specified in the request,
# increments it, and returns a page containing the value.
sub process {
    my $buff = $_[0];
    my $environ, $data;
    my $countdir = "/tmp/";	# Directory where counter files are stored
    my $num;

    $buff =~ /\r\n\r\n/;
    $environ = $`;
    $data    = $';
    substr( $data, -4, 4, '' ); # eat \r\n\r\n

    if (! -f "$countdir/$data")
    {
        open(NUM,">$countdir/$data");
        close(NUM);
    }
    open(NUM,"+<$countdir/$data");
    $num = <NUM>;
    $num++;
    seek(NUM,0,0);
    print NUM "$num\n";
    close NUM;

    return
      join( '' ,
            "Content-Type: text/html\r\n\r\n"                            ,
            "<body bgcolor=#ffffff><h3>"                                 ,
            "You've seen this page $num times before!"                   ,
            "<p><hr noshade size=2>"                                     ,
            "<a href=\"http://www.zeus.co.uk/products/server/\">"        ,
            "<center><font size=7>P<i>owered</i> B<i>y</i> Z<i>eus</i> " ,
            "S<i>erver</i></a>"                                          );

}


# ----------------------------------------------------------------------------
# Server code

if( $#ARGV!=0 )
{
    print "Usage: sockd port\n";
    exit 1;
}
$port = shift;

my $proto = getprotobyname('tcp');
socket(SERVER, PF_INET, SOCK_STREAM, $proto)        || die "socket: $!";
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1)     || die "setsockopt: $!";

$sockaddr = 'S n a4 x8';
$this = pack($sockaddr, AF_INET, $port, "\0\0\0\0");
bind(SERVER, $this)                                 || die "bind: $!";
listen(SERVER,5)                                    || die "listen: $!";
print "CGId server started on port $port\n";

$child = fork;
if ($child < 0) {           # fork failed
    die "error on fork: $!";
} elsif ($child > 0) {      # parent
    exit(0);
} else {                    # child
    if (!setsid()) {
	die "failed setsid: $!";
    }
}


$rin = ''; vec($rin,fileno(SERVER),1) = 1;
$win = '';
$ein = '';

# Main program loop
while(1) {
    $nfound = select($rout=$rin,$wout=$win,$eout=$ein, undef);

    if(vec($rout,fileno(SERVER),1)) {       # new connection
        my $NS = FileHandle->new();
	if(accept($NS,SERVER)) {
	    vec($rin,fileno($NS),1) = 1;
	    vec($ein,fileno($NS),1) = 1;
	    $input[fileno($NS)] = $output[fileno($NS)] = '';
	    $fd2file[fileno($NS)] = $NS;
	    push ((@clients),fileno($NS));
	}
    }
    else {
	# look through read/write/except bits for clients
	@cl = @clients;
	while($fd = shift @cl) {
	    if(vec($eout,$fd,1)) {      # Exception on $fd
		&killclient($fd);
	    }
	    if(vec($wout,$fd,1)) {      # Write on $fd
		local($len) = length($output[$fd]);
		if($len) {          	# data left to write
		    $w=syswrite $fd2file[$fd],$output[$fd],$len;
		    if(!defined($w)) { &killclient($fd); }
		    else { $output[$fd] = substr($output[$fd],$w,$len-$w); }
		}
		else { &killclient($fd); }    # finished
	    }
	    if(vec($rout,$fd,1)) {      # Read on $fd
		$r=sysread $fd2file[$fd],$input[$fd],128,length($input[$fd]);
		if(!defined($r) || !$r) { &killclient($fd); }
		else { &parse($fd); }
	    }
	}
    }
}

# Removes a client of given fd from the system
sub killclient {
    local($fd) = $_[0];
    local(@cl);
    local($i);

    vec($rin,$fd,1) = 0;
    vec($win,$fd,1) = 0;
    vec($ein,$fd,1) = 0;
    $input[$fd] = $output[$fd] = '';
    close $fd2file[$fd];
    while($i = shift @clients) {
	if($i != $fd) { push ((@cl),$i); }
    }
    @clients = @cl;
}

# Determines whether a complete request on an fd has been recieved, and if so
# processes it
sub parse {
    local($fd) = $_[0];
    if(&complete($input[$fd])) {      # got a complete request
	vec($rin,$fd,1) = 0;
	vec($win,$fd,1) = 1;
	$output[$fd] = &process($input[$fd]);
    }
}


FILEHANDLE:
{
    use strict;
    require FileHandle;  # make sure real one is loaded
    package FileHandle;

    sub new {
	my $self = shift;
	my $class = ref($self) || $self;  # for inheritance
	return bless(&_genfh, $class);
    }

    sub DESTROY {
        my $self = shift;
        if (defined fileno $self) {
            close $self;
        }
    }

    ########
    #        internal only
    ########
    sub _genfh {
        no strict 'refs';
        local *{'FileHandle::DEMI_ANON_GLOB'};
        return \delete $FileHandle::{DEMI_ANON_GLOB};
    }
    1;
}
Content Manager [Administrator] 16 December 2005  Permalink  
Download Free Trial

Recent Articles

Other Resources



www.zeus.com