2016-09-15 7 views
3

Catalyst::Controller::REST 기반 API에서 예기치 않은 오류를 처리하는 방법을 찾는 데 어려움을 겪고 있습니다. default_GET 예기치 죽으면Catalyst :: Controller :: REST를 사용한 오류 처리의 가장 좋은 방법은 무엇입니까

BEGIN { extends 'Catalyst::Controller::REST' } 

__PACKAGE__->config(
    json_options => { relaxed => 1, allow_nonref => 1 }, 
    default  => 'application/json', 
    map   => { 'application/json' => [qw(View JSON)] }, 
); 

sub default : Path : ActionClass('REST') { } 

sub default_GET { 
    my ($self, $c, $mid) = @_; 

    ### something happens here and it dies 
} 

이 애플리케이션 표준 상태 500 에러 페이지가 도시되어있다. 컨트롤러 뒤에있는 REST 라이브러리를 통해 컨트롤을 제어하고 JSON 오류 (또는 REST 요청이 받아들이는 직렬화 응답)를 표시 할 것으로 기대됩니다.

조치로 조치 오류 (예 : Try::Tiny 포함)를 추가하는 것은 옵션이 아닙니다. 나는 모든 오류 처리를 중앙 집중화하려고한다. sub end 동작을 사용해 보았지만 작동하지 않았습니다.

sub error :Private { 
    my ($self, $c, $code, $reason) = @_; 

    $reason ||= 'Unknown Error'; 
    $code ||= 500; 

    $c->res->status($code); 

    $c->stash->{data} = { error => $reason }; 
} 

답변

3

이것은 권장되지 않습니다. 그것은 내가 그것을 어떻게 할 것인지입니다.

Try::Tiny을 사용하면 컨트롤러 및 Catalyst :: Action :: REST의 오류로 인해 적절한 응답 코드를 보낼 수 있습니다. 적절한 형식 (예 : JSON)으로 응답을 변환합니다.

하지만 여전히 각 유형의 오류에 대해이 작업을 수행해야합니다. 기본적으로이 귀결 : 응답의 그 종류

use Try::Tiny; 
BEGIN { extends 'Catalyst::Controller::REST' } 

__PACKAGE__->config(
    json_options => { relaxed => 1, allow_nonref => 1 }, 
    default  => 'application/json', 
    map   => { 'application/json' => [qw(View JSON)] }, 
); 

sub default : Path : ActionClass('REST') { } 

sub default_GET { 
    my ($self, $c, $mid) = @_; 

    try { 
     # ... (there might be a $c->detach in here) 
    } catch { 
     # this is thrown by $c->detach(), so don't 400 in this case 
     return if $_->$_isa('Catalyst::Exception::Detach'); 

     $self->status_bad_request($c, message => q{Boom!}); 
    } 
} 

방법은 Catalyst::Controller::REST under STATUS HELPERS에 나열되어 있습니다. 그들은 :

  • status_not_found
  • gone
  • . 구성 방법은 Refer to one of them입니다. 여기에 예제가 있습니다.
    *Catalyst::Controller::REST::status_teapot = sub { 
        my $self = shift; 
        my $c = shift; 
        my %p = Params::Validate::validate(@_, { message => { type => SCALAR }, },); 
    
        $c->response->status(418); 
        $c->log->debug("Status I'm A Teapot: " . $p{'message'}) if $c->debug; 
        $self->_set_entity($c, { error => $p{'message'} }); 
        return 1; 
    } 
    

    당신이 행동을 많이 가지고 있기 때문에 그 너무 지루한 경우

    , 내가 의도처럼 당신이 end 행동의 사용을 권장합니다. 그 방법에 대한 자세한 내용은 아래에서 조금 더 자세히 설명합니다.

    그런 경우 Try :: Tiny 구문을 동작에 추가하지 마십시오. 대신 사용중인 모든 모델 또는 다른 모듈이 좋은 예외를 throw하는지 확인하십시오.각각의 경우에 대한 예외 클래스를 생성하고 어떤 경우에 발생해야하는지에 대한 제어를 전달합니다.

    이 모든 작업을 수행하는 좋은 방법은 Catalyst::ControllerRole::CatchErrors을 사용하는 것입니다. 오류를 처리 할 catch_error 메서드를 정의 할 수 있습니다. 이 방법에서는 어떤 예외가 어떤 종류의 응답을 야하는지 알아내는 디스패치 테이블을 작성합니다. 여기에 몇 가지 중요한 정보가 있으므로 documentation of $c->error을보십시오.

    package MyApp::Controller::Root; 
    use Moose; 
    use Safe::Isa; 
    
    BEGIN { extends 'Catalyst::Controller::REST' } 
    with 'Catalyst::ControllerRole::CatchErrors'; 
    
    __PACKAGE__->config(
        json_options => { relaxed => 1, allow_nonref => 1 }, 
        default  => 'application/json', 
        map   => { 'application/json' => [qw(View JSON)] }, 
    ); 
    
    sub default : Path : ActionClass('REST') { } 
    
    sub default_GET { 
        my ($self, $c, $mid) = @_; 
    
        $c->model('Foo')->frobnicate; 
    } 
    
    sub catch_errors : Private { 
        my ($self, $c, @errors) = @_; 
    
        # Build a callback for each of the exceptions. 
        # This might go as an attribute on $c in MyApp::Catalyst as well. 
        my %dispatch = (
         'MyApp::Exception::BadRequest' => sub { 
          $c->status_bad_request(message => $_[0]->message); 
         }, 
         'MyApp::Exception::Teapot' => sub { 
          $c->status_teapot; 
         }, 
        ); 
    
        # @errors is like $c->error 
        my $e = shift @errors; 
    
        # this might be a bit more elaborate 
        if (ref $e =~ /^MyAPP::Exception/) { 
         $dispatch{ref $e}->($e) if exists $dispatch{ref $e}; 
         $c->detach; 
        } 
    
        # if not, rethrow or re-die (simplified) 
        die $e; 
    } 
    

    은 조, 안된 예이다. 정확히 이렇게 작동하지 않을 수도 있지만 좋은 시작입니다. 디스 패칭을 주 Catalyst 응용 프로그램 객체 (컨텍스트 $c)의 속성으로 이동하는 것이 좋습니다. 그것을하기 위해 MyApp :: Catalyst에 배치하십시오.

    package MyApp::Catalyst; 
    # ... 
    
    has error_dispatch_table => (
        is => 'ro', 
        isa => 'HashRef', 
        traits => 'Hash', 
        handles => { 
         can_dispatch_error => 'exists', 
         dispatch_error => 'get', 
        }, 
        builder => '_build_error_dispatch_table', 
    ); 
    
    sub _build_error_dispatch_table { 
        return { 
         'MyApp::Exception::BadRequest' => sub { 
          $c->status_bad_request(message => $_[0]->message); 
         }, 
         'MyApp::Exception::Teapot' => sub { 
          $c->status_teapot; 
         }, 
        }; 
    } 
    

    그리고는 다음과 같이 파견을 수행

    $c->dispatch_error(ref $e)->($e) if $c->can_dispatch_error(ref $e); 
    

    지금 당신이 필요로하는 모든 좋은 예외입니다. 이를 수행하는 데는 여러 가지 방법이 있습니다. 나는 Exception::Class 또는 Throwable::Factory을 좋아한다.

    package MyApp::Model::Foo; 
    use Moose; 
    BEGIN { extends 'Catalyst::Model' }; 
    
    # this would go in its own file for reusability 
    use Exception::Class (
        'MyApp::Exception::Base', 
        'MyApp::Exception::BadRequest' => { 
         isa => 'MyApp::Exception::Base', 
         description => 'This is a 400', 
         fields => [ 'message' ], 
        }, 
        'MyApp::Exception::Teapot' => { 
         isa => 'MyApp::Exception::Base', 
         description => 'I do not like coffee', 
        }, 
    ); 
    
    sub frobnicate { 
        my ($self) = @_; 
    
        MyApp::Exception::Teapot->throw; 
    } 
    
    다시 말해서 예외를 자체 모듈로 옮겨서 어디에서나 재사용 할 수 있습니다.

    이것은 훌륭하게 확장 될 수 있습니다. 비즈니스 로직이나 모델을 웹 앱이라는 사실에 너무 강하게 결합하는 것은 나쁜 디자인이라는 점도 명심하십시오. 그런 식으로 설명하기 쉽기 때문에 아주 예외적 인 이름을 사용하기로했습니다. 좀 더 일반적이고 덜 웹 중심적인 이름을 원할 수도 있습니다. 그리고 당신의 디스패치 이름은 실제로 그것들을 매핑해야합니다. 그렇지 않으면 웹 레이어에 너무 많이 묶여 있습니다.


    1) 예, 이것은 복수형입니다. here을 참조하십시오.

  • +1

    우수 답변, 매우 철저히 답변 해 주셔서 감사합니다. 나는'Catalyst :: ControllerRole :: CatchErrors' 소스를 살펴 봤고'before 'end'=> sub {...}'를 사용했다. 유일한 문제는'@ error' 객체가 REST 컨트롤러 클래스에 의해 아마도 추가 된'App :: API :: Foo-> default ...에 잡힌 예외 '와 같은 문자열에 래핑되어 예외가 될 수 없다는 것입니다 쉽게 파견. – ojosilva

    +0

    @ojosilva 흠, 그건 나쁘군요. 거기에 실제 물건이 없습니까? 아니면 그냥 죽었 니? – simbabque

    +0

    내가 사용하고있는이 새로운 REST API가 사용되기 전에 존재하는 내 모델은 '죽는다'라는 말을 많이 사용합니다. 액션별로 Try :: Tiny 액션을 사용하는 것은 좋은 옵션이 아니지만, 내부 모델 에러가 외부 메시지 문자열로 싸여있는 것을 피할 수있는 유일한 방법 일 수 있습니다. – ojosilva

    관련 문제