FunctionalPerl

 view release on metacpan or  search on metacpan

lib/Chj/HTTP/Daemon.pm  view on Meta::CPAN

            if ($buf =~ /^\w+[^\012]+HTTP\/\d+\.\d+\015?\012/) {
                if ($buf =~ /\015?\012\015?\012/) {
                    last READ_HEADER;    # we have it
                } elsif (length($buf) > 16 * 1024) {
                    $self->send_error(413);    # REQUEST_ENTITY_TOO_LARGE
                    $self->reason("Very long header");
                    return;
                }
            } else {
                last READ_HEADER;              # HTTP/0.9 client
            }
        } elsif (length($buf) > 16 * 1024) {
            $self->send_error(414);            # REQUEST_URI_TOO_LARGE
            $self->reason("Very long first line");
            return;
        }
        print STDERR "Need more data for complete header\n" if $DEBUG;
        return unless $self->_need_more($buf, $timeout, $fdset);
    }
    if ($buf !~ s/^(\S+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012//) {
        ${*$self}{'httpd_client_proto'} = _http_version("HTTP/1.0");
        $self->send_error(400);                # BAD_REQUEST
        $self->reason("Bad request line: $buf");
        return;
    }
    my $method = $1;
    my $uri    = $2;
    my $proto  = $3 || "HTTP/0.9";
    $uri = "http://$uri" if $method eq "CONNECT";
    $uri = $HTTP::URI_CLASS->new($uri, $self->daemon->url);
    my $r = HTTP::Request->new($method, $uri);
    $r->protocol($proto);
    ${*$self}{'httpd_client_proto'} = $proto = _http_version($proto);
    ${*$self}{'httpd_head'}         = ($method eq "HEAD");

    if ($proto >= $HTTP_1_0) {

        # we expect to find some headers
        my ($key, $val);
    HEADER:
        while ($buf =~ s/^([^\012]*)\012//) {
            $_ = $1;
            s/\015$//;
            if (/^([^:\s]+)\s*:\s*(.*)/) {
                $r->push_header($key, $val) if $key;
                ($key, $val) = ($1, $2);
            } elsif (/^\s+(.*)/) {
                $val .= " $1";
            } else {
                last HEADER;
            }
        }
        $r->push_header($key, $val) if $key;
    }

    my $conn = $r->header('Connection');
    if ($proto >= $HTTP_1_1) {
        ${*$self}{'httpd_nomore'}++ if $conn && lc($conn) =~ /\bclose\b/;
    } else {
        ${*$self}{'httpd_nomore'}++
            unless $conn && lc($conn) =~ /\bkeep-alive\b/;
    }

    if ($only_headers) {
        ${*$self}{'httpd_rbuf'} = $buf;
        return $r;
    }

    # Find out how much content to read
    my $te  = $r->header('Transfer-Encoding');
    my $ct  = $r->header('Content-Type');
    my $len = $r->header('Content-Length');

    # Act on the Expect header, if it's there
    for my $e ($r->header('Expect')) {
        if (lc($e) eq '100-continue') {
            $self->send_status_line(100);
            $self->send_crlf;
        } else {
            $self->send_error(417);
            $self->reason("Unsupported Expect header value");
            return;
        }
    }

    if ($te && lc($te) eq 'chunked') {

        # Handle chunked transfer encoding
        my $body = "";
    CHUNK:
        while (1) {
            print STDERR "Chunked\n" if $DEBUG;
            if ($buf =~ s/^([^\012]*)\012//) {
                my $chunk_head = $1;
                unless ($chunk_head =~ /^([0-9A-Fa-f]+)/) {
                    $self->send_error(400);
                    $self->reason("Bad chunk header $chunk_head");
                    return;
                }
                my $size = hex($1);
                last CHUNK if $size == 0;

                my $missing = $size - length($buf) + 2;    # 2=CRLF at chunk end
                     # must read until we have a complete chunk
                while ($missing > 0) {
                    print STDERR "Need $missing more bytes\n" if $DEBUG;
                    my $n = $self->_need_more($buf, $timeout, $fdset);
                    return unless $n;
                    $missing -= $n;
                }
                $body .= substr($buf, 0, $size);
                substr($buf, 0, $size + 2) = '';

            } else {

                # need more data in order to have a complete chunk header
                return unless $self->_need_more($buf, $timeout, $fdset);
            }
        }
        $r->content($body);

lib/Chj/HTTP/Daemon.pm  view on Meta::CPAN

    my ($self, $status, $message, $proto) = @_;
    return if $self->antique_client;
    $status  ||= RC_OK;
    $message ||= status_message($status) || "";
    $proto   ||= $Chj::HTTP::Daemon::PROTO || "HTTP/1.1";
    print $self "$proto $status $message$CRLF" or die $!;
}

sub send_crlf {
    my $self = shift;
    print $self $CRLF or die $!;
}

sub send_basic_header {
    my $self = shift;
    return if $self->antique_client;
    $self->send_status_line(@_);
    print $self "Date: ", time2str(time), $CRLF or die $!;
    my $product = $self->daemon->product_tokens;
    print $self "Server: $product$CRLF" or die $! if $product;
}

sub send_header {
    my $self = shift;
    while (@_) {
        my ($k, $v) = splice(@_, 0, 2);
        $v = "" unless defined($v);
        print $self "$k: $v$CRLF" or die $!;
    }
}

sub send_response {
    my $self = shift;
    my $res  = shift;
    if (!ref $res) {
        $res ||= RC_OK;
        $res = HTTP::Response->new($res, @_);
    }
    my $content = $res->content;
    my $chunked;
    unless ($self->antique_client) {
        my $code = $res->code;
        $self->send_basic_header($code, $res->message, $res->protocol);
        if ($code =~ /^(1\d\d|[23]04)$/) {

            # make sure content is empty
            $res->remove_header("Content-Length");
            $content = "";
        } elsif ($res->request && $res->request->method eq "HEAD") {

            # probably OK
        } elsif (ref($content) eq "CODE") {
            if ($self->proto_ge("HTTP/1.1")) {
                $res->push_header("Transfer-Encoding" => "chunked");
                $chunked++;
            } else {
                $self->force_last_request;
            }
        } elsif (length($content)) {
            $res->header("Content-Length" => length($content));
        } elsif (lc($res->header('connection') // "") =~ /\bkeep-alive\b/) {

            # don't close connection if user asks for it to stay open
        } else {
            $self->force_last_request;
            $res->header('connection', 'close');
        }
        print $self $res->headers_as_string($CRLF) or die $!;
        print $self $CRLF or die $!;    # separates headers and content
    }
    if ($self->head_request) {

        # no content
    } elsif (ref($content) eq "CODE") {
        while (1) {
            my $chunk = &$content();
            last unless defined($chunk) && length($chunk);
            if ($chunked) {
                printf $self "%x%s%s%s", length($chunk), $CRLF, $chunk, $CRLF
                    or die $!;
            } else {
                print $self $chunk or die $!;
            }
        }
        print $self "0$CRLF$CRLF" or die $! if $chunked;    # no trailers either
    } elsif (length $content) {
        print $self $content or die $!;
    }
}

sub send_redirect {
    my ($self, $loc, $status, $content) = @_;
    $status ||= RC_MOVED_PERMANENTLY;
    Carp::croak("Status '$status' is not redirect") unless is_redirect($status);
    $self->send_basic_header($status);
    my $base = $self->daemon->url;
    $loc = $HTTP::URI_CLASS->new($loc, $base) unless ref($loc);
    $loc = $loc->abs($base);
    print $self "Location: $loc$CRLF" or die $!;

    if ($content) {
        my $ct = $content =~ /^\s*</ ? "text/html" : "text/plain";
        print $self "Content-Type: $ct$CRLF" or die $!;
    }
    print $self $CRLF    or die $!;
    print $self $content or die $! if $content && !$self->head_request;
    $self->force_last_request;    # no use keeping the connection open
}

sub send_error {
    my ($self, $status, $error) = @_;
    $status ||= RC_BAD_REQUEST;
    Carp::croak("Status '$status' is not an error") unless is_error($status);
    my $mess = status_message($status);
    $error ||= "";
    $mess = <<EOT;
<title>$status $mess</title>
<h1>$status $mess</h1>
$error
EOT
    unless ($self->antique_client) {



( run in 1.968 second using v1.01-cache-2.11-cpan-39bf76dae61 )