Skip to main content

Simple HTTP server in Perl under xinetd

In this post we'll be looking at very simple impementation of HTTP server written in Perl that is using xinetd system (so called super-server) daemon to handle network connections. It's more prove of concept showing the simple mechanics behind the HTTP protocol, that usually is hidden in some library layer and web server handling all the details for us.

To setup new service handler for xinetd you need to add new service description in /etc/xinetd.d directory - in my case the file is called xinetdhttp, but the name of the file does not matter that much since xinetd reads all the files from it's configuration directory to setup services, although it should be descriptive (anyway it's always good idea and practice to give descriptive names to things).

service xinetdhttp
{
   socket_type  = stream
   protocol     = tcp
   wait         = no
   user         = bob
   server       = /home/bob/xinetdhttp.pl
}

The last bit missing, before restarting xinetd, is adding service definition to /etc/services file, that will be used systemwide to reserve the port number 5900 to newly added service - actually servicename in above description file uses the name, that should be registerd in /etc/services

$ grep xinetdhttp /etc/services
xinetdhttp     5900/tcp

Here's the complete implementation of the worker process written in Perl invoked by xined daemon for incomming HTTP requests on port 5900

#!/usr/bin/perl -w
use strict;
use CGI;
use Sys::Syslog;

# forcing non-buffered output
$| = 1;

# log to syslog if $debug = 1
my $debug = 0;

my $url;
my $method;
my %params;

# reading HTTP header
syslog('info', "[xinetdhttp] reading HTTP header") if $debug;
while(<STDIN>) {
  my $line = $_;

  # scan for an HTTP method and URL
  ($method, $url) = ($1,$2) if $line =~ /(GET|HEAD|POST|PUT|DELETE) (\S+)/i;

  # check if it's the end of the header
  last if $line =~ /^\r\n$/;
  syslog('info', "[xinetdhttp] $line") if $debug;
}

my ($rpath, $sparams) = split(/\?/, $url);
for my $p (split(/\&/, $sparams)) {
  my ($pname, $pvalue) = split(/=/, $p);
    $params{$pname} = $pvalue;
}

syslog('info', "[xinetdhttp] header received, sending response") if $debug;

# HTTP status OK (server side stuff)
print "HTTP/1.1 200 OK\r\n";

# sending actual response to the browser
my $q = CGI->new();
print $q->header('text/html');
print $q->start_html('hello world');
print $q->h1("URL: $rpath ($method)");
for my $p (keys(%params)) {
  print $q->p("PARAM: $p = $params{$p}");
}
print $q->end_html();

The resulting HTML produced by code listed above can be displayed using all kinds of help from curl command:

# curl 'http://192.168.43.20:5900/index.php?p1=1&p1=model&p2=volvo'
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>hello world</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
<h1>URL: /index.php (GET)</h1><p>PARAM: p2 = volvo</p><p>PARAM: p1 = model</p>
</body>
</html>

Comments