perl实现HttpServer

perl实现HttpServer

目标

  • 请求常用静态资源,正确响应这些文件
  • 解析目录文件,显示文件及目录信息
  • 默认响应index.html
  • 解析.do,响应data目录下的同名json(特殊)
  • 端口、目录可配置

    code:

#!/usr/bin/env perl
#tcp_socket_server.pl

use warnings;
use Socket;
use Cwd;
use URI;
use POSIX qw(strftime);
use File::Spec;
use POSIX ":sys_wait_h";
my $port = 8080;     #port
my $root = getcwd;
my %request;         #save headers
my $mime;
my %mime = (
    "text" => "text/plain",
    "html" => "text/html",
    "css"  => "text/css",
    "js"   => "application/javascript",
    "json" => "application/json"
);
my $quit = 0;
$SIG{INT} = $SIG{TERM} = sub {
    $quit++;
    exit(0);
};

sub REAPER {
    while ( ( my $pid = waitpid( -1, WNOHANG ) ) > 0 ) {
        print "SIGCHLD pid $pid\n";
    }
}
$SIG{CHLD} = \&REAPER;

sub main {
    my $argstr = join( " ", @ARGV );    #server -p8080 -r /home/toor
    $argstr = " $argstr ";
    if ( $argstr =~ /\s-h\s/ ) {
        print "usage:\n";
        print "      perl server.pl -p8080 -r /home/toor/webapp\n";
        exit(0);
    }
    if ( $argstr =~ /\s-p\s*(\d{2,5})\s/ ) {
        $port = $1;
    }
    if ( $argstr =~ /\s-r\s?(\S+)\s/ ) {
        $root = $1;
    }
    socket( server_socket, AF_INET, SOCK_STREAM, getprotobyname('tcp') )
        or die "Socket $!\n";
    setsockopt( server_socket, SOL_SOCKET, SO_REUSEADDR, 1 )
        or die "Can't set SO_REUSADDR: $!";
    my $my_addr = sockaddr_in( $port, INADDR_ANY );

    bind( server_socket, $my_addr ) or die "Bind $!\n";

    listen( server_socket, 5 ) || die "Listen $!\n";

    print "http server start in http://127.0.0.1:/$port\n";
    print "http server work  in path $root\n";
    while ( !$quit ) {
        accept( client_socket, server_socket ) || do {

            # try again if accept() returned because a signal was received
            next if $!{EINTR};
            die "accept: $!";
        };
        defined( $pid = fork ) || die "Fork: $!\n";
        if ( $pid == 0 ) {
            &accept_request(client_socket);
            exit(0);
        }
        else {
            close(client_socket);
        }
    }

}

sub accept_request {    # handle a request
                                      # my $socket = shift;
    &parse_headers(client_socket);    #parse
    my $uri = $request{'uri'};
    if ( !$uri ) {
        close(client_socket);
        return;
    }
    $now = strftime( "%Y-%m-%d %H:%M:%S", localtime )
        ;                             #my $now = `date`; # $now =~ s/\n//;
    print "$now $request{'method'} $uri\n";
    $uri =~ s/(\?.*)// if ( $uri =~ /\?.*/ );
    if ( $uri =~ /\w+\.html$/ ) {
        $mime = $mime{'html'};
    }
    elsif ( $uri =~ /\w+\.css$/ ) {
        $mime = $mime{"css"};
    }
    elsif ( $uri =~ /\w+\.js$/ ) {
        $mime = $mime{"js"};
    }
    elsif ( $uri =~ /\w+\.json$/ ) {
        $mime = $mime{"json"};
    }
    elsif ( $uri =~ /\w+\.svg$/ ) {
        $mime = "image/svg+xml";
    }
    elsif ( $uri =~ /\w+\.do$/ ) {
        $mime = $mime{"json"};
        my $prefix;
        my $suffix = $uri;
        my $refer  = $request{'$Referer'};
        if ( $refer && $refer =~ /htmls(\/.*\/)\w+\.html/ ) {
            $prefix = "/data$1";
            $suffix =~ s/\/(\w+)\.do/$1.json/;
            $uri = "$prefix$suffix";
        }
        else {
            $suffix =~ s/\/(\w[\w\d\.]+)\.do/$1.json/;
            $uri = "/data/$suffix";

            # resp_error( 500, "Bad Request" );
            # close(client_socket);
            # exit(1);
        }
    }
    else {
        $mime = "text/html";
    }
    my $filename = File::Spec->catfile( $root, $uri );
    if ( -e -f $filename ) {
        send_success($filename);
    }
    elsif ( -e -d $filename ) {
        if ( -e -f "$filename/index.html" ) {
            send_success("$filename/index.html");
        }
        else {
            resp_filelist($filename);
        }
    }
    else {
        resp_error( 404, "Not Found" );
    }
    close(client_socket);
}

sub parse_headers {

    # my ($socket) = @_;    #client socket
    my $content = "";
    while (1) {
        my $buffer;
        my $flag = sysread( client_socket, $buffer, 1024 );
        $content .= $buffer;
        last if ( $flag < 1024 );
    }
    if ( $content =~ m/^(.*)\s(\/.*)\s(HTTP\/\d\.\d)/ ) {
        $request{'method'}   = $1;
        $request{'uri'}      = URI::Escape::uri_unescape($2);
        $request{'protocol'} = $3;
    }
    my @header = split( /\n/, $content );
    foreach (@header) {
        if (/^([^()<>\@,;:\\"\/\[\]?={} \t]+):\s*(.*)/i) {
            $request{$1} = $2;
        }
    }
}

sub resp_headers {
    print client_socket "HTTP/1.0 200 OK\n";
    print client_socket "Content-Type: $mime;charset: utf-8\n";
    print client_socket "Date: $now\n";
    print client_socket "Server: xyserver\n";
    print client_socket "\n";
}

sub resp_filelist {
    my ($directory) = shift;
    opendir( DIR, $directory ) or die "cannot open $directory:$!";
    resp_headers();
    ( my $shortdir = $directory ) =~ s{$root}{};
    $shortdir =~ s/\/\//\//g;
    print client_socket
        "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8' /> <title>Index of ./</title></head><body><h1>Directory:$shortdir</h1><table border='0'><tbody>";
    print client_socket
        "<tr><td><a href='../'>Parent Directory</a></td><td></td><td></td></tr>";
    foreach ( sort readdir DIR ) {
        next if (/^\./);
        my @info = stat("$directory/$_");
        ( my $href = "$shortdir/$_" ) =~ s/\/\//\//g;
        $href = "$href/" if ( -d "$directory/$_" );
        my $size = $info[7];
        my $mtime = strftime( "%Y-%m-%d %H:%M:%S", localtime( $info[9] ) );
        $href =~ s/\/\//\//g;
        print client_socket
            "<tr><td><a href='$href'>$_</a></td><td style='text-align:right'>$size  bytes</td><td> $mtime</td></tr>";
    }
    closedir DIR;
    print client_socket "</tbody></table></body></html>";
}

sub resp_error {    #status, message
    my ( $status, $error ) = @_;
    print client_socket "HTTP/1.0 $status $error\n";
    print client_socket "Content-Type: text/html;charset: utf-8\n";
    print client_socket "Date: $now\n";
    print client_socket "Server: xyserver\n";
    print client_socket "\n";
    print client_socket
        "<html><head><title>Http Error</title></head><body><h2>Http Error...</h2><p>errror status:$status</p><pre>error message:$error</pre><hr><i><small>Powered by javaway</i></body></html>";
}

sub send_success {
    my $filename = shift;
    resp_headers();
    open FILE, "<$filename"
        or die "cannot open $filename:$!";
    foreach (<FILE>) {
        print client_socket $_;
    }
}

main();

问题:

  • 阻塞式socket判断数据读完的问题? 临时解决:假定读取的数据长度为1024的概率为0,每次读取1024字节,则读取到数据不为1024时读取完成!
  • 使用perl,fork子进程导致的僵尸进程问题?waitpid判断

发表评论