Mercurial > pub > Impl
comparison Lib/IMPL/Web/Handler/RestController.pm @ 229:47f77e6409f7
heavily reworked the resource model of the web application:
*some ResourcesContraact functionality moved to Resource
+Added CustomResource
*Corrected action handlers
| author | sergey | 
|---|---|
| date | Sat, 29 Sep 2012 02:34:47 +0400 | 
| parents | d6e2ea24af08 | 
| children | 6d8092d8ce1b | 
   comparison
  equal
  deleted
  inserted
  replaced
| 228:431db7034a88 | 229:47f77e6409f7 | 
|---|---|
| 1 package IMPL::Web::Handler::RestController; | 1 package IMPL::Web::Handler::RestController; | 
| 2 use strict; | 2 use strict; | 
| 3 | 3 | 
| 4 use IMPL::lang qw(:declare :constants); | 4 use IMPL::lang qw(:declare :constants); | 
| 5 | |
| 6 | |
| 7 use IMPL::declare { | 5 use IMPL::declare { | 
| 8 require => { | 6 require => { | 
| 7 ResourceInterface => 'IMPL::Web::Application::ResourceInterface', | |
| 9 Exception => 'IMPL::Exception', | 8 Exception => 'IMPL::Exception', | 
| 10 ArgumentExecption => '-IMPL::InvalidArgumentException', | 9 ArgumentExecption => '-IMPL::InvalidArgumentException', | 
| 11 HttpException => 'IMPL::Web::Exception', | 10 NotFoundException => 'IMPL::Web::NotFoundException' | 
| 12 NotFoundException => 'IMPL::Web::NotFoundException' | |
| 13 }, | 11 }, | 
| 14 base => { | 12 base => { | 
| 15 'IMPL::Object' => undef, | 13 'IMPL::Object' => undef, | 
| 16 'IMPL::Object::Autofill' => '@_', | 14 'IMPL::Object::Autofill' => '@_', | 
| 17 'IMPL::Object::Serializable' => undef | 15 'IMPL::Object::Serializable' => undef | 
| 18 } | 16 }, | 
| 17 props => [ | |
| 18 rootResource => PROP_GET | PROP_OWNERSET, | |
| 19 trailingSlash => PROP_GET | PROP_OWNERSET | |
| 20 ] | |
| 19 }; | 21 }; | 
| 20 | |
| 21 BEGIN { | |
| 22 public property root => PROP_GET | PROP_OWNERSET; | |
| 23 public property contract => PROP_GET | PROP_OWNERSET; | |
| 24 } | |
| 25 | 22 | 
| 26 sub CTOR { | 23 sub CTOR { | 
| 27 my ($this) = @_; | 24 my ($this) = @_; | 
| 28 | 25 | 
| 29 die ArgumentException->new("root") unless $this->root; | 26 die ArgumentException->new(rootResource => "A web-resource is required") | 
| 30 die ArgumentException->new("contract") unless $this->contract; | 27 unless eval { $this->rootResource->isa(ResourceInterface) }; | 
| 28 | |
| 31 } | 29 } | 
| 30 | |
| 31 sub GetResourcePath { | |
| 32 my ($this,$action) = @_; | |
| 33 | |
| 34 my $pathInfo = $action->pathInfo; | |
| 35 my @segments; | |
| 36 | |
| 37 if (length $pathInfo) { | |
| 38 | |
| 39 @segments = split(/\//, $pathInfo, $this->trailingSlash ? -1 : 0); | |
| 40 | |
| 41 # remove first segment since it's always empty | |
| 42 shift @segments; | |
| 43 | |
| 44 my ($obj,$view) = (pop(@segments) =~ m/(.*?)(?:\.(\w+))?$/); | |
| 45 push @segments, $obj; | |
| 46 | |
| 47 } | |
| 48 | |
| 49 return @segments; | |
| 50 } | |
| 51 | |
| 32 | 52 | 
| 33 sub Invoke { | 53 sub Invoke { | 
| 34 my ($this,$action) = @_; | 54 my ($this,$action) = @_; | 
| 35 | 55 | 
| 36 my $query = $action->query; | 56 my $method = $action->requestMethod; | 
| 37 | 57 | 
| 38 my $method = $query->request_method; | 58 my @segments = $this->GetResourcePath($action); | 
| 39 | 59 | 
| 40 #TODO: path_info is broken for IIS | 60 my $res = $this->rootResource; | 
| 41 my $pathInfo = $query->path_info; | |
| 42 my @segments; | |
| 43 | |
| 44 if (length $pathInfo) { | |
| 45 | |
| 46 @segments = split /\//, $pathInfo, -1; # keep trailing empty string if present | |
| 47 | |
| 48 # remove first segment since it's always empty | |
| 49 shift @segments; | |
| 50 | |
| 51 my ($obj,$view) = (pop(@segments) =~ m/(.*?)(?:\.(\w+))?$/); | |
| 52 push @segments, $obj; | |
| 53 | |
| 54 } | |
| 55 | |
| 56 | |
| 57 my $res = $this->contract->Transform($this->root, { id => '' } ); | |
| 58 | 61 | 
| 59 while(@segments) { | 62 while(@segments) { | 
| 60 my $id = shift @segments; | 63 my $id = shift @segments; | 
| 61 | 64 | 
| 62 $res = $res->FetchChildResource($id,$action); | 65 $res = $res->FetchChildResource($id); | 
| 63 | 66 | 
| 64 die NotFoundException->new($pathInfo,$id) unless $res; | 67 die NotFoundException->new($pathInfo,$id) unless $res; | 
| 65 } | 68 } | 
| 66 | 69 | 
| 67 $res = $res->InvokeHttpMethod($method,$action); | 70 $res = $res->InvokeHttpVerb($method,$action); | 
| 68 } | 71 } | 
| 69 | 72 | 
| 70 1; | 73 1; | 
| 71 | 74 | 
| 72 __END__ | 75 __END__ | 
| 73 | 76 | 
| 74 =pod | 77 =pod | 
| 75 | 78 | 
| 76 =head1 NAME | 79 =head1 NAME | 
| 77 | 80 | 
| 78 C<IMPL::Web::Handler::RestController> - Транслирует запросы к ресурсам в вызовы методов. | 81 C<IMPL::Web::Handler::RestController> - Обрабатывает C<HTTP> запрос передавая | 
| 82 его соответствующему ресурсу. | |
| 79 | 83 | 
| 80 =head1 SYNOPSIS | 84 =head1 SYNOPSIS | 
| 81 | 85 | 
| 82 Использует контракты для преобразования стандартных C<REST> запросов в вызовы методов объектов. | 86 Используется в конфигурации приложения как элемент цепочки обработчиков. | 
| 83 C<$ENV{PATH_INFO}> используется как путь к нужному ресурсу у которого будет вызван метод указанный в запросе. | 87 Как правило располагается на самом верхнем уровне. | 
| 88 | |
| 89 =begin code xml | |
| 90 | |
| 91 <handlers type="ARRAY"> | |
| 92 <item type="IMPL::Web::Handler::RestController"> | |
| 93 <rootResource type="My::App::Web::RootResource"/> | |
| 94 </item> | |
| 95 <item type="IMPL::Web::Handler::JSONView" /> | |
| 96 <item type="IMPL::Web::Handler::SecureCookie" /> | |
| 97 <item type="IMPL::Web::Handler::ErrorHandler" /> | |
| 98 </handlers> | |
| 99 | |
| 100 =end code xml | |
| 101 | |
| 84 | 102 | 
| 85 =head1 DESCRIPTION | 103 =head1 DESCRIPTION | 
| 86 | 104 | 
| 87 =head2 Resource model | 105 Использует C<PATH_INFO> для определения нужного ресурса, затем предает | 
| 106 найденному ресурсу управление для обработки запроса. | |
| 88 | 107 | 
| 89 Ресурсы имеют иерархическую структуру, аналогичную файлам и каталогам, которая описывается контрактом, также | 108 Если ресурс не найден, то возникает исключение C<IMPL::Web::NotFoundException>. | 
| 90 контрак описывает то, как должны обрабатываться методы C<HTTP> запроса, такие как C<GET> и C<POST>. | |
| 91 | 109 | 
| 92 За корректность реализации данных методов отвечает разработчик. | 110 Для определения нужного ресурса контроллер разбивает C<PATH_INFO> на фрагменты | 
| 111 и использует каждый фрагмент для получения дочернего ресурса начиная с корневого. | |
| 112 Для чего используется метод | |
| 113 C<< IMPL::Web::Application::ResourceInterface->FetchChildResource($childId) >>. | |
| 93 | 114 | 
| 94 Каждый ресурс представляет собой коллкецию вложенных ресурсов, путь указанный в C<HTTP> запросе разбивается на | 115 =head1 MEMEBERS | 
| 95 части, затем каждый сегмент последовательно используется для поиска дочернего ресурса. При обработки | |
| 96 первого сегмента используется корневой ресурс. Корневой ресурс должен существовать всегда. | |
| 97 | 116 | 
| 98 =head2 Contract | 117 =head2 C<[get]rootResource> | 
| 99 | 118 | 
| 100 Контрактом может быть любое преобразование которое определяет соответсвие между объектами приложения и | 119 Корневой ресурс приложения, должен быть всегда и реализовывать интерфес ресурса | 
| 101 ресурсами, доступными через протокол C<HTTP>. | 120 C<IMPL::Web::Application::ResourceInterface>. | 
| 102 | 121 | 
| 122 =head2 C<[get]trailingSlash> | |
| 103 | 123 | 
| 104 | 124 Если данная переменная имеет значение C<true>, то слеш в конце пути к ресурсу | 
| 125 будет интерпретироваться, как дочерний ресурс с пустым идентификатором. | |
| 105 | 126 | 
| 106 =cut | 127 =cut | 
