File: TestHTTPD.pm

package info (click to toggle)
sniproxy 0.6.1%2Bgit20240321-0.2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 644 kB
  • sloc: ansic: 5,594; perl: 1,673; sh: 237; makefile: 131
file content (126 lines) | stat: -rw-r--r-- 3,069 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package TestHTTPD;

use warnings;
use strict;
require IO::Socket::INET;
require Socket;
require Exporter;
require Time::HiRes;
our @ISA = qw(Exporter);
our @EXPORT = qw(new);
our $VERSION = '0.01';

my $http_methods = {
    'GET' => 1,
    'POST' => 1,
    'HEAD' => 1,
    'PUT' => 1,
    'DELETE' => 1,
    'TRACE' => 1,
    'DEBUG' => 1,
    'CONNECT' => 1,
    'OPTIONS' => 1,
};


sub default_response_parser {
    my $sock = shift;
    my $status = 500;

    # Read HTTP request
    for (my $i = 0; my $line = $sock->getline(); $i++) {
        if ($i == 0 && $line =~ m/\A(\S+) (\S+) HTTP\/(\S+)\r\n\z/) {
            $status = 200 if exists($http_methods->{$1});
        }

        # Wait for blank line indicating the end of the request
        last if $i > 0 && $line eq "\r\n";
    }

    return $status;
};

my $count = 0;

# This represents the sizes of chunks of our responses
my $responses = [
    [ 20 ],
    [ 20, 18000],
    [ 22 ],
    [ 200 ],
    [ 20, 1, 1, 1, 1, 1, 1, 200 ],
];

my $http_status_line = {
    200 => 'OK',
    203 => 'Non-Authoritative Information',
    500 => 'Internal Server Error',
};

sub default_response_generator {
    $count ++;

    return sub($$) {
        my $sock = shift;
        my $status = shift;

        my @chunks = @{$responses->[$count % scalar @{$responses}]};
        my $content_length = 0;
        map { $content_length += $_ } @chunks;

        print $sock "HTTP/1.1 $status " . $http_status_line->{$status} . "\r\n";
        print $sock "Server: TestHTTPD/$VERSION\r\n";
        print $sock "Content-Type: text/plain\r\n";
        print $sock "Content-Length: $content_length\r\n";
        print $sock "Connection: close\r\n";
        print $sock "\r\n";

        # Return data in chunks specified in responses
        while (my $length = shift @chunks) {
            print $sock 'X' x $length;
            $sock->flush();
            Time::HiRes::usleep(100) if @chunks;
        }
    };
}

sub httpd {
    my %args = @_;
    my $ip = $args{'ip'} || 'localhost';
    my $port = $args{'port'} || 8081;
    my $request_parser = $args{'parser'} || \&default_response_parser;
    my $responder_generator = $args{'generator'} || \&default_response_generator;

    my $server = IO::Socket::INET->new(Listen    => Socket::SOMAXCONN(),
                                       Proto     => 'tcp',
                                       LocalAddr => $ip,
                                       LocalPort => $port,
                                       ReuseAddr => 1)
        or die $!;

    $SIG{CHLD} = 'IGNORE';

    while(my $client = $server->accept()) {
        my $responder = $responder_generator->();

        my $pid = fork();
        next if $pid; # Parent
        die "fork: $!" unless defined $pid;

        # Child
        #
        my $status = $request_parser->($client);

        # Assume a GET request
        $responder->($client, $status);

        $client->close();
        exit 0;
    } continue {
        # close child sockets
        $client->close();
    }
    die "accept(): $!";
}

1;