view Lib/IMPL/Web/Application/Response.pm @ 134:44977efed303

Significant performance optimizations Fixed recursion problems due converting objects to JSON Added cache support for the templates Added discovery feature for the web methods
author wizard
date Mon, 21 Jun 2010 02:39:53 +0400
parents 964587c5183c
children 4267a2ac3d46
line wrap: on
line source

package IMPL::Web::Application::Response;
use strict;

use base qw(IMPL::Object IMPL::Object::Autofill);

require IMPL::Exception;
require CGI;
require CGI::Cookie;

use Carp;
use Encode;
use IMPL::Class::Property;

#todo: add binary method to set a binary encoding, set it automatic when type isn't a text 

BEGIN {
	# автозаполнение буде происходить в порядке объявления
	public property query => prop_get | owner_set; # cgi query
	public property status => prop_all, { validator => \&_checkHeaderPrinted };
	public property contentType => prop_all, { validator => \&_checkHeaderPrinted }; # String
	public property charset => { get => \&_charset, set => \&_charset }, { validator => \&_checkHeaderPrinted };
	public property expires => prop_all, { validator => \&_checkHeaderPrinted };
	public property cookies => prop_all, { validator => \&_checkHeaderPrinted }; # Hash
	
	public property buffered => prop_all, { validator => \&_canChangeBuffer }; # Boolean
	public property streamOut => prop_get | owner_set; # stream
	public property streamBody => {get => \&getStreamBody }; # stream
	public property isHeaderPrinted => prop_get | owner_set; # Boolean 
	
	private property _bufferBody => prop_all;
	private property _streamBody => prop_all;
}

__PACKAGE__->PassThroughArgs;

our %CTOR = (
	'IMPL::Object::Autofill' => sub {
		my %args = @_;
		
		$args{query} = CGI->new($args{query} || {});
		
		%args;
	}
);

sub CTOR {
	my ($this,%args) = @_;
	
	if (lc $this->streamOut eq 'memory') {
		my $dummy = '';
		open my $hout, '>:encoding(utf8)', \$dummy or die new IMPL::Exception("Failed to create memory stream",$!);
		$this->streamOut($hout);
	} elsif (not $this->streamOut) {
		$this->streamOut(*STDOUT);	
	} else {
		die new IMPL::InvalidArgumentException("Invalid parameter value",$this->streamOut);
	}
	
	$this->buffered(1) unless defined $this->buffered;
	binmode $this->streamOut, ":encoding(".$this->charset.")";
}

sub _checkHeaderPrinted {
	my ($this,$value) = @_;
	
	die new IMPL::InvalidOperationException() if $this->isHeaderPrinted;
}

sub _canChangeBuffer {
	my ($this,$value) = @_;
	
	die new IMPL::InvalidOperationException() if $this->isHeaderPrinted or $this->_streamBody;
}

sub _charset {
	my $this = shift;
	
	if (@_) {
		my $charset = $this->query->charset(@_);
		
		my $hout = $this->streamOut;
		
		binmode $hout;
		binmode $hout, ":encoding($charset)";
		
		return $charset;
	} else {
		return $this->query->charset;
	}
}

sub _PrintHeader {
	my ($this) = @_;
	
	unless ($this->isHeaderPrinted) {
		$this->isHeaderPrinted(1);
		
		my %opt;
		
		$opt{-type} = $this->contentType if $this->contentType;
		$opt{-status} = $this->status if $this->status;
		$opt{-expires} = $this->expires if $this->expires;
		
		my $refCookies = $this->cookies;
		$opt{-cookie} = [map _createCookie($_,$refCookies->{$_}), keys %$refCookies] if $refCookies;
		
		my $hOut = $this->streamOut;
		
		print $hOut $this->query->header(
			%opt
		);
	}
}

sub _createCookie {
	return UNIVERSAL::isa($_[1], 'CGI::Cookie') ? $_[1] : CGI::Cookie->new(-name => $_[0], -value => $_[1] );
}

sub setCookie {
	my ($this,$name,$value) = @_;
	
	unless ($this->cookies) {
		$this->cookies({$name,$value});
	} else {
		$this->_checkHeaderPrinted(); 
		$this->cookies->{$name} = $value;
	}
	return $value;
}

sub getStreamBody {
	my ($this) = @_;
	
	return undef unless $this->streamOut;
	
	unless ($this->_streamBody) {
		if ($this->buffered) {
			my $buffer = "";
			
			$this->_bufferBody(\$buffer);
				
			open my $hBody, ">:encoding(utf-8)", \$buffer or die new IMPL::Exception("Failed to create buffer",$!);
			
			Encode::_utf8_on($buffer);
				
			$this->_streamBody($hBody);
		} else {
			$this->_PrintHeader();
			$this->_streamBody($this->streamOut);
		}
	}
		
	return $this->_streamBody;
}

sub Complete {
	my ($this) = @_;
	
	return 0 unless $this->streamOut;
	
	my $hOut = $this->streamOut;
	
	$this->_PrintHeader();

	close $this->_streamBody();
	
	if ($this->buffered) {
		print $hOut ${$this->_bufferBody};	
	}	
	
	$this->_bufferBody(undef);
	$this->streamOut(undef);
	
	return 1;
}

sub Discard {
	my ($this) = @_;
	
	carp "Discarding sent response" if $this->isHeaderPrinted;
	
	$this->_streamBody(undef);
	$this->_bufferBody(undef);
	$this->streamOut(undef);
}

1;

__END__

=pod

=head1 NAME

C<IMPL::Web::Application::Response> - Ответ веб сервера непосредственно клиенту.

=head1 DESCRIPTION

C<[Infrastructure]>

Позволяет сформировать основные свойства заголовка и тело ответа.

Создается объектом C<IMPL::Web::Application::Action> в процессе обработки запроса.

Может использоваться обработчиками C<IMPL::Web::QueryHandler> в процессе выполнения запроса.

Объект позволяет буфферизировать вывод в тело ответа, что позволяет отменить или изменить
ответ в последний момент. Свойство C< isHeaderPrinted >  используется для определения факта
отправлки данных клиенту. 

=head1 PROPERTIES

=head2 HTTP Header

Свойства отвечающие за заголовок HTTP ответа. Эти своства могут быть изменены до тех пор пока
не будет отправлен заголовок. В противном случае выдается исключение C< IMPL::InvalidOperationException >.

=over

=item C< [get] query >

CGI запрос, который используется для вывода данных, заголовка и пр. Существует всегда.

=item C< [get,set] status >

Код ошибки HTTP. Например, '200 OK'. По умолчанию не установлен, при отправке клиенту бедт отправлен '200 ОК'.

=item C< [get,set] contentType >

Тип MIME. По умолчанию не установлен, подразумивается 'text/html'.

=item C< [get,set] charset >

Кодировка, синоним свойства query->charset.

=item C< [get,set] expires >

Определяет время жизни контента, например '+10m'. По умолчанию не задано и не передается.

=item C< [get,set] cookies >

Хеш массив с cookies, например C< { cart => ['foo','bar'], display => 'list' } >.

=back

=head2 Response behaviour

Свойства отвечающие за поведение ответа.

=over

=item C< [get,set] buffered >

C< True > - то тело ответа пишется в буффер и будет отправлено при вызове метода C< Complete >,
заголовок также будет отправлен после вызова метода C< Complete >. 

C< False > - тело ответа пишется непосредственно в поток к клиенту, при этом заголовок
будет отправлен при первом обращении к свойству C< streamBody >

Это свойство можно менять до первого обращения к потоку для записи в тело ответа.

=item C< [get] streamOut >

Стандартный вывод CGI приложения.

=item C< [get] streamBody >

Поток для записи в тело ответа.

=item C< [get] isHeadPrinted >

Признак того, что заголовок уже был отправлен клиенту.

=back

=head1 METHODS

=over

=item C< Complete >

Завершает отправку ответа.

=item C< Discard >

Отменяет отправку ответа, при этом если часть данных (например, заголовок)
уже была отправлена, выдает предупреждение в STDERR.

=back

=head1 REMARKS

Данный объект является автозаполняемым, т.е. все его свойства можно задать через
именованные параметры конструктора.

=cut