view Lib/IMPL/Web/Application/Response.pm @ 59:0f3e369553bd

Rewritten property implementation (probably become slower but more flexible) Configuration infrastructure in progress (in the aspect of the lazy activation) Initial concept for the code generator
author wizard
date Tue, 09 Mar 2010 02:50:45 +0300
parents Lib/IMPL/Web/Response.pm@a35b60b16a99
children 76b878ad6596
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 IMPL::Class::Property;

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;

sub CTOR {
	my ($this,%args) = @_;
	
	$this->query(CGI->new($this->query() | {})) unless $this->query;
	$this->charset($this->query->charset) unless $this->charset;
	
	$this->streamOut(*STDOUT) unless $this->streamOut;
}

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;
	
	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 CGI::Cookie->new(-name => $_, $refCookies->{$_} ), keys %$refCookies] if $refCookies;
		
		my $hOut = $this->streamOut;
		
		print $hOut $this->query->header(
			%opt
		);
	}
}

sub getStreamBody {
	my ($this) = @_;
	
	return undef unless $this->streamOut;
	
	unless ($this->_streamBody) {
		if ($this->buffered) {
			my $buffer = "";
			$this->_bufferBody(\$buffer);
				
			open my $hBody, ">", \$buffer or die new IMPL::Exception("Failed to create 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();
	
	if ($this->buffered) {
		print $hOut ${$this->_bufferBody};	
	}
	
	$this->_streamBody(undef);
	$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 DESCRIPTION

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

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

Свойство C< isHeaderPrinted > можно использовать для определения были ли отправлены какие-нибудь
данные клиенту. 

=head1 PROPERTIES

=head2 HTTP Header

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

=over

=item C< query >

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

=item C< status >

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

=item C< contentType >

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

=item C< charset >

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

=item C< expires >

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

=item C< cookies >

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

=back

=head2 Response behaviour

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

=over

=item C< buffered >

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

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

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

=item C< streamOut >

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

=item C< streamBody >

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

=item C< isHeadPrinted >

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

=back

=head1 METHODS

=over

=item C< Complete >

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

=item C< Discard >

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

=back

=cut