#!/usr/bin/perl # # DW::Request::Standard # # Abstraction layer for standard HTTP::Request/HTTP::Response based systems. # We don't care who's giving us the data, ... # # Authors: # Mark Smith # Andrea Nall # # Copyright (c) 2010 by Dreamwidth Studios, LLC. # # This program is free software; you may redistribute it and/or modify it under # the same terms as Perl itself. For a copy of the license, please reference # 'perldoc perlartistic' or 'perldoc perlgpl'. # package DW::Request::Standard; use strict; use DW::Request::Base; use base 'DW::Request::Base'; use Carp qw/ confess cluck /; use HTTP::Request; use HTTP::Response; use HTTP::Status qw//; use fields ( 'req', # The HTTP::Request object 'res', # a HTTP::Response object 'notes', 'pnotes', # we have to parse these out ourselves 'uri', 'querystring', 'read_offset' ); # creates a new DW::Request object, based on what type of server environment we # are running under sub new { my DW::Request::Standard $self = $_[0]; $self = fields::new($self) unless ref $self; $self->SUPER::new; # setup object $self->{req} = $_[1]; $self->{res} = HTTP::Response->new(200); $self->{uri} = $self->{req}->uri; $self->{notes} = {}; $self->{pnotes} = {}; $self->{read_offset} = 0; # now stick ourselves as the primary request ... unless ($DW::Request::cur_req) { $DW::Request::determined = 1; $DW::Request::cur_req = $self; } # done return $self; } # current document root sub document_root { confess "Not implemented, doesn't matter here ...\n"; } # method string GET, POST, etc sub method { my DW::Request::Standard $self = $_[0]; return $self->{req}->method; } # the URI requested (does not include host:port info) sub uri { my DW::Request::Standard $self = $_[0]; return $self->{uri}->path; } # This sets the content-type on the response. This is NOT a request method. For # that, use the header_in method and check Content-Type. sub content_type { my DW::Request::Standard $self = $_[0]; return $self->{res}->content_type( $_[1] ); } # returns the query string sub query_string { my DW::Request::Standard $self = $_[0]; return $self->{uri}->query; } # returns the raw content of the body; note that this can be particularly # slow, so you should only call this if you really need it... sub content { my DW::Request::Standard $self = $_[0]; # keep a local copy ... bloats memory, and useless, why? return $self->{content} if defined $self->{content}; return $self->{content} = $self->{req}->content; } # content of our response object sub response_content { my DW::Request::Standard $self = $_[0]; return $self->{res}->content; } # return a response as a string sub response_as_string { my DW::Request::Standard $self = $_[0]; return $self->{res}->as_string; } # searches for a given note and returns the value, or sets it sub note { my DW::Request::Standard $self = $_[0]; if ( scalar(@_) == 2 ) { return $self->{notes}->{ $_[1] }; } else { return $self->{notes}->{ $_[1] } = $_[2]; } } # searches for a given pnote and returns the value, or sets it sub pnote { my DW::Request::Standard $self = $_[0]; if ( scalar(@_) == 2 ) { return $self->{pnotes}->{ $_[1] }; } else { return $self->{pnotes}->{ $_[1] } = $_[2]; } } # searches for a given header and returns the value, or sets it sub header_in { my DW::Request::Standard $self = $_[0]; if ( scalar(@_) == 2 ) { return $self->{req}->header( $_[1] ); } else { return $self->{req}->header( $_[1] => $_[2] ); } } sub headers_in { my DW::Request::Standard $self = $_[0]; return $self->{req}->headers; } # searches for a given header and returns the value, or sets it sub header_out { my DW::Request::Standard $self = $_[0]; if ( scalar(@_) == 2 ) { return $self->{res}->header( $_[1] ); } else { return $self->{res}->header( $_[1] => $_[2] ); } } sub headers_out { my DW::Request::Standard $self = $_[0]; return $self->{res}->headers; } # appends a value to a header sub header_out_add { my DW::Request::Standard $self = $_[0]; return $self->{res}->push_header( $_[1], $_[2] ); } # this may not be precisely correct? maybe we need to maintain our # own set of headers that are separate for errors... FIXME: investigate *err_header_out = \&header_out; *err_header_out_add = \&header_out_add; # returns the ip address of the connected person sub get_remote_ip { my DW::Request::Standard $self = $_[0]; # FIXME: this needs to support more than just the header ... what if we're not # running behind a proxy? can we use the environment? do we fake it? for now, # assume that if there is no X-Forwarded-For or we don't trust it, we just put in # a bogus IP... return '127.0.0.100' unless $LJ::TRUST_X_HEADERS; my @ips = split /\s*,\s*/, $self->{req}->header('X-Forwarded-For'); return '127.0.0.101' unless @ips && $ips[0]; return $ips[0]; } # sets last modified, this is called so that we set it up on the response object sub set_last_modified { my DW::Request::Standard $self = $_[0]; return $self->{res}->header( 'Last-Modified' => LJ::time_to_http( $_[1] ) ); } # this is a response method sub status { my DW::Request::Standard $self = $_[0]; if ( scalar(@_) == 2 ) { # Set message to a default string, just setting code won't do it. my $code = $_[1] || 500; $self->{res}->code($code); $self->{res}->message( HTTP::Status::status_message($code) ); } return $self->{res}->code; } # build or return a status line (RESPONSE) sub status_line { my DW::Request::Standard $self = $_[0]; if ( scalar(@_) == 2 ) { # We must set code and message seperately. if ( $_[1] =~ m/^(\d+)\s+(.+)$/ ) { $self->{res}->code($1); $self->{res}->message($2); } } return $self->{res}->status_line; } # meets conditions # conditional GET triggered on: # If-Modified-Since # If-Unmodified-Since FIXME: implement # If-Match FIXME: implement # If-None-Match FIXME: implement # If-Range FIXME: implement sub meets_conditions { my DW::Request::Standard $self = $_[0]; return $self->OK if LJ::http_to_time( $self->header_in("If-Modified-Since") ) <= LJ::http_to_time( $self->header_out("Last-Modified") ); # FIXME: this should be pretty easy ... check the If headers (only time ones?) # and see if they're good or not. return proper status code here (OK, NOT_MODIFIED) # go see the one caller in LJ::Feed return 0; } sub print { my DW::Request::Standard $self = $_[0]; $self->{res}->add_content( $_[1] ); return; } # FIXME(dre): this may not be the most efficient way but is # totally fine when we are just using this for tests. # We *may* need to revisit this if we use this for serving pages # IMPORTANT: Do not pull out $_[1] to a variable in this sub sub read { my DW::Request::Standard $self = $_[0]; die "missing required arguments" if scalar(@_) < 3; my $prefix = ''; if ( exists $_[3] ) { die "Negative offsets not allowed" if $_[3] < 0; $prefix = substr( $_[1], 0, $_[3] ); } die "Length cannot be negative" if $_[2] < 0; my $ov = substr( $self->content, $self->{read_offset}, $_[2] ); # Given $_[1] and whatever was passed in as the first argument are the # same exact scalar this will set *that* variable too. $_[1] = $prefix . $ov; $self->{read_offset} += length($ov); return length($ov); } # return the internal Standard request object... in this case, we are # just going to return ourself, as anybody that needs the request object # is probably an old Apache style caller that needs updating sub r { my DW::Request::Standard $self = $_[0]; cluck "DW::Request::Standard->r called, please update the caller."; return $self; } # spawn a process for an external program sub spawn { confess "Sorry, spawning not implemented."; } sub no_cache { confess "Sorry, no_cache not implemented."; } 1;