실패 / 오류시 JSON 서비스가 반환해야하는 사항


79

C # (. ashx 파일)으로 JSON 서비스를 작성하고 있습니다. 서비스에 대한 성공적인 요청에서 일부 JSON 데이터를 반환합니다. 예외가 발생했거나 (예 : 데이터베이스 시간 초과) 요청이 어떤 방식 으로든 잘못 되었기 때문에 (예 : 데이터베이스에 존재하지 않는 ID가 인수로 제공 되었기 때문에) 요청이 실패하면 서비스는 어떻게 응답해야합니까? 어떤 HTTP 상태 코드가 합리적이며 데이터가 있으면 반환해야합니까?

서비스가 주로 jQuery.form 플러그인을 사용하여 jQuery에서 호출 될 것으로 예상하고 있는데, jQuery 또는이 플러그인에 오류 응답을 처리하는 기본 방법이 있습니까?

편집 : 성공하면 jQuery + .ashx + HTTP [상태 코드]를 사용하기로 결정했습니다. JSON을 반환하지만 오류가 발생하면 문자열을 반환합니다. 이것이 jQuery의 오류 옵션 인 것처럼 보입니다. 아약스는 기대합니다.

답변:


34

반환하는 HTTP 상태 코드는 발생한 오류 유형에 따라 달라집니다. 데이터베이스에 ID가 없으면 404를 반환합니다. 사용자에게 Ajax 호출을 할 수있는 충분한 권한이 없으면 403을 반환합니다. 레코드를 찾기 전에 데이터베이스 시간이 초과되면 500 (서버 오류)을 반환합니다.

jQuery는 이러한 오류 코드를 자동으로 감지하고 Ajax 호출에서 정의한 콜백 함수를 실행합니다. 문서 : http://api.jquery.com/jQuery.ajax/

$.ajax오류 콜백 의 간단한 예 :

$.ajax({
  type: 'POST',
  url: '/some/resource',
  success: function(data, textStatus) {
    // Handle success
  },
  error: function(xhr, textStatus, errorThrown) {
    // Handle error
  }
});

3
누군가 정수가 필요한 문자열과 같이 잘못된 데이터를 제공하면 어떤 오류 코드를 반환해야한다고 생각하십니까? 또는 유효하지 않은 이메일 주소?
thatismatt

유사한 서버 측 코드 오류와 같은 500 범위에서 뭔가
annakata

7
500 범위는 서버 오류이지만 서버에는 아무런 문제가 없습니다. 그들은 잘못된 요청을했는데 400 범위에 있어야하지 않습니까?
thatismatt

38
사용자로서 500 점을 받으면 책임을지지 않는다는 것을 알고 있으며, 400 점을 받으면 제가 잘못한 일을 해결할 수 있습니다. 이는 API를 작성할 때 특히 중요합니다. 사용자는 기술적으로 지식이 풍부하고 400 점입니다. API를 올바르게 사용하도록 지시합니다. 추신-DB 타임 아웃이 500이어야한다는 데 동의합니다.
thatismatt

4
404 는 주소지정된 리소스 가 누락 되었음을 의미 합니다 . 이 경우 리소스는 DB에서 ID가있는 임의의 것이 아니라 POST 프로세서입니다. 이 경우 400이 더 적절합니다.
StevenC

56

상황에 맞는 모범 사례에 대한 통찰력을 얻으려면 이 질문 을 참조하십시오 .

(상기 링크에서) 최상위 제안은 핸들러가 찾는 응답 구조 (성공 및 실패 모두에 대해)를 표준화하고 서버 계층에서 모든 예외를 포착하여 동일한 구조로 변환하는 것입니다. 예를 들어 ( 이 답변에서 ) :

{
    success:false,
    general_message:"You have reached your max number of Foos for the day",
    errors: {
        last_name:"This field is required",
        mrn:"Either SSN or MRN must be entered",
        zipcode:"996852 is not in Bernalillo county. Only Bernalillo residents are eligible"
    }
} 

이것은 stackoverflow가 사용하는 접근 방식입니다 (다른 사람들이 이런 종류의 일을 어떻게하는지 궁금한 경우); 투표가 허용되었는지 여부에 관계없이 voting have "Success""Message"fields 와 같은 쓰기 작업 :

{ Success:true, NewScore:1, Message:"", LastVoteTypeId:3 }

으로 Phil.H 지적 @ , 당신은 당신이 무엇을 선택에 일치해야합니다. 이것은 말처럼 쉬운 일입니다 (개발중인 모든 것이 그렇듯이!).

예를 들어, 일관성 있고 답답하지 않고 너무 빨리 댓글을 제출하면

{ Success: false, Message: "Can only comment once every blah..." }

그래서 서버 예외 ( HTTP 500)를 던지고 error콜백 에서 포착합니다 .

jQuery + .ashx+ HTTP [상태 코드] IMO 를 사용하는 것이 "적절하게 느껴지는"만큼 클라이언트 측 코드베이스에 가치가있는 것보다 더 복잡해집니다. jQuery는 오류 코드를 "감지"하는 것이 아니라 성공 코드의 부족을 인식합니다. 이것은 jQuery를 사용하여 http 응답 코드를 중심으로 클라이언트를 설계 할 때 중요한 차이점입니다. 두 가지 선택 ( "성공"또는 "오류"였습니까?) 만 얻을 수 있으며,이를 직접 분기해야합니다. 적은 수의 페이지를 구동하는 적은 수의 WebService가 있으면 괜찮을 수 있지만 더 큰 규모는 지저분해질 수 있습니다.

.asmxWebService (또는 WCF)에서는 HTTP 상태 코드를 사용자 지정하는 것보다 사용자 지정 개체를 반환하는 것이 훨씬 더 자연 스럽습니다 . 또한 JSON 직렬화를 무료로받을 수 있습니다.


1
유효한 접근 한 nitpick : 예는 유효한 JSON (키 이름에 따옴표 누락) 할 수 있습니다
StaxMan

1
이것이 제가 예전에했던 일이지만 실제로는 http 상태 코드를 사용해야합니다. 그게 바로 거기에있는 것입니다 (특히 RESTful 작업을하는 경우)
Eva

이 접근 방식은 확실히 유효하다고 생각합니다. http 상태 코드는 편안한 작업을 수행하는 데 유용하지만 데이터베이스 쿼리를 포함하는 스크립트에 대한 API 호출을 할 때는 그다지 도움이되지 않습니다. 데이터베이스 쿼리가 오류를 반환하더라도 http 상태 코드는 여전히 200입니다.이 경우 일반적으로 'success'키를 사용하여 MySQL 쿼리가 성공했는지 여부를 나타냅니다. :)
Terry

17

HTTP 상태 코드를 사용하는 것은 RESTful 방법이지만 리소스 URI 등을 사용하여 나머지 인터페이스를 RESTful로 만들 것을 제안합니다.

사실, 인터페이스를 원하는대로 정의하십시오 (예 : 오류가있는 속성을 자세히 설명하는 오류 개체 및이를 설명하는 HTML 청크 등을 반환).하지만 프로토 타입에서 작동하는 것을 결정한 후에는 , 무자비하게 일관성을 유지하십시오.


나는 당신이 제안하는 것을 좋아합니다. 나는 당신이 JSON을 반환해야한다고 생각합니까? 예 : {error : {message : "An Error Occurred", 세부 정보 : "월요일이기 때문에 발생했습니다."}}
thatismatt

@thatismatt — 오류가 항상 치명적이라면 상당히 합리적입니다. 더 세분화하기 error위해 (비어있을 수있는) 배열을 만들고 fatal_error: bool매개 변수를 추가 하면 상당한 유연성을 얻을 수 있습니다.
Ben Blank

2
아, 그리고 언제 사용하거나 사용하지 않을 때 RESTful 응답에 +1. :-)
Ben Blank

Ron DeVera가 제 생각을 설명했습니다!
Phil H

3

예외를 버블 링 하면 'error'옵션에 전달 되는 jQuery 콜백 에서 처리되어야한다고 생각합니다 . (또한 서버 측의이 예외를 중앙 로그에 기록합니다). 특별한 HTTP 오류 코드는 필요하지 않지만 다른 사람들도 무엇을하는지 궁금합니다.

이게 내가하는 일이지만 그건 내 $ .02

RESTful이되어 오류 코드를 반환하려면 W3C에서 규정 한 표준 코드를 따르십시오 : http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html


3

이 문제를 해결하는 데 몇 시간을 보냈습니다. 내 솔루션은 다음과 같은 희망 사항 / 요구 사항을 기반으로합니다.

  • 모든 JSON 컨트롤러 작업에 반복적 인 상용구 오류 처리 코드가 없어야합니다.
  • HTTP (오류) 상태 코드를 보존합니다. 왜? 높은 수준의 우려는 낮은 수준의 구현에 영향을 미치지 않기 때문입니다.
  • 서버에서 오류 / 예외가 발생하면 JSON 데이터를 가져올 수 있습니다. 왜? 풍부한 오류 정보를 원할 수 있기 때문입니다. 예 : 오류 메시지, 도메인 별 오류 상태 코드, 스택 추적 (디버그 / 개발 환경에서).
  • 사용 편의성 클라이언트 측-jQuery를 사용하는 것이 좋습니다.

HandleErrorAttribute를 만듭니다 (자세한 내용은 코드 주석 참조). "사용"을 포함한 몇 가지 세부 사항은 생략되었으므로 코드가 컴파일되지 않을 수 있습니다. 다음과 같이 Global.asax.cs에서 응용 프로그램을 초기화하는 동안 전역 필터에 필터를 추가합니다.

GlobalFilters.Filters.Add(new UnikHandleErrorAttribute());

속성:

namespace Foo
{
  using System;
  using System.Diagnostics;
  using System.Linq;
  using System.Net;
  using System.Reflection;
  using System.Web;
  using System.Web.Mvc;

  /// <summary>
  /// Generel error handler attribute for Foo MVC solutions.
  /// It handles uncaught exceptions from controller actions.
  /// It outputs trace information.
  /// If custom errors are enabled then the following is performed:
  /// <ul>
  ///   <li>If the controller action return type is <see cref="JsonResult"/> then a <see cref="JsonResult"/> object with a <c>message</c> property is returned.
  ///       If the exception is of type <see cref="MySpecialExceptionWithUserMessage"/> it's message will be used as the <see cref="JsonResult"/> <c>message</c> property value.
  ///       Otherwise a localized resource text will be used.</li>
  /// </ul>
  /// Otherwise the exception will pass through unhandled.
  /// </summary>
  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  public sealed class FooHandleErrorAttribute : HandleErrorAttribute
  {
    private readonly TraceSource _TraceSource;

    /// <summary>
    /// <paramref name="traceSource"/> must not be null.
    /// </summary>
    /// <param name="traceSource"></param>
    public FooHandleErrorAttribute(TraceSource traceSource)
    {
      if (traceSource == null)
        throw new ArgumentNullException(@"traceSource");
      _TraceSource = traceSource;
    }

    public TraceSource TraceSource
    {
      get
      {
        return _TraceSource;
      }
    }

    /// <summary>
    /// Ctor.
    /// </summary>
    public FooHandleErrorAttribute()
    {
      var className = typeof(FooHandleErrorAttribute).FullName ?? typeof(FooHandleErrorAttribute).Name;
      _TraceSource = new TraceSource(className);
    }

    public override void OnException(ExceptionContext filterContext)
    {
      var actionMethodInfo = GetControllerAction(filterContext.Exception);
      // It's probably an error if we cannot find a controller action. But, hey, what should we do about it here?
      if(actionMethodInfo == null) return;

      var controllerName = filterContext.Controller.GetType().FullName; // filterContext.RouteData.Values[@"controller"];
      var actionName = actionMethodInfo.Name; // filterContext.RouteData.Values[@"action"];

      // Log the exception to the trace source
      var traceMessage = string.Format(@"Unhandled exception from {0}.{1} handled in {2}. Exception: {3}", controllerName, actionName, typeof(FooHandleErrorAttribute).FullName, filterContext.Exception);
      _TraceSource.TraceEvent(TraceEventType.Error, TraceEventId.UnhandledException, traceMessage);

      // Don't modify result if custom errors not enabled
      //if (!filterContext.HttpContext.IsCustomErrorEnabled)
      //  return;

      // We only handle actions with return type of JsonResult - I don't use AjaxRequestExtensions.IsAjaxRequest() because ajax requests does NOT imply JSON result.
      // (The downside is that you cannot just specify the return type as ActionResult - however I don't consider this a bad thing)
      if (actionMethodInfo.ReturnType != typeof(JsonResult)) return;

      // Handle JsonResult action exception by creating a useful JSON object which can be used client side
      // Only provide error message if we have an MySpecialExceptionWithUserMessage.
      var jsonMessage = FooHandleErrorAttributeResources.Error_Occured;
      if (filterContext.Exception is MySpecialExceptionWithUserMessage) jsonMessage = filterContext.Exception.Message;
      filterContext.Result = new JsonResult
        {
          Data = new
            {
              message = jsonMessage,
              // Only include stacktrace information in development environment
              stacktrace = MyEnvironmentHelper.IsDebugging ? filterContext.Exception.StackTrace : null
            },
          // Allow JSON get requests because we are already using this approach. However, we should consider avoiding this habit.
          JsonRequestBehavior = JsonRequestBehavior.AllowGet
        };

      // Exception is now (being) handled - set the HTTP error status code and prevent caching! Otherwise you'll get an HTTP 200 status code and running the risc of the browser caching the result.
      filterContext.ExceptionHandled = true;
      filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; // Consider using more error status codes depending on the type of exception
      filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);

      // Call the overrided method
      base.OnException(filterContext);
    }

    /// <summary>
    /// Does anybody know a better way to obtain the controller action method info?
    /// See http://stackoverflow.com/questions/2770303/how-to-find-in-which-controller-action-an-error-occurred.
    /// </summary>
    /// <param name="exception"></param>
    /// <returns></returns>
    private static MethodInfo GetControllerAction(Exception exception)
    {
      var stackTrace = new StackTrace(exception);
      var frames = stackTrace.GetFrames();
      if(frames == null) return null;
      var frame = frames.FirstOrDefault(f => typeof(IController).IsAssignableFrom(f.GetMethod().DeclaringType));
      if (frame == null) return null;
      var actionMethod = frame.GetMethod();
      return actionMethod as MethodInfo;
    }
  }
}

클라이언트 측 사용 편의성을 위해 다음 jQuery 플러그인을 개발했습니다.

(function ($, undefined) {
  "using strict";

  $.FooGetJSON = function (url, data, success, error) {
    /// <summary>
    /// **********************************************************
    /// * UNIK GET JSON JQUERY PLUGIN.                           *
    /// **********************************************************
    /// This plugin is a wrapper for jQuery.getJSON.
    /// The reason is that jQuery.getJSON success handler doesn't provides access to the JSON object returned from the url
    /// when a HTTP status code different from 200 is encountered. However, please note that whether there is JSON
    /// data or not depends on the requested service. if there is no JSON data (i.e. response.responseText cannot be
    /// parsed as JSON) then the data parameter will be undefined.
    ///
    /// This plugin solves this problem by providing a new error handler signature which includes a data parameter.
    /// Usage of the plugin is much equal to using the jQuery.getJSON method. Handlers can be added etc. However,
    /// the only way to obtain an error handler with the signature specified below with a JSON data parameter is
    /// to call the plugin with the error handler parameter directly specified in the call to the plugin.
    ///
    /// success: function(data, textStatus, jqXHR)
    /// error: function(data, jqXHR, textStatus, errorThrown)
    ///
    /// Example usage:
    ///
    ///   $.FooGetJSON('/foo', { id: 42 }, function(data) { alert('Name :' + data.name); }, function(data) { alert('Error: ' + data.message); });
    /// </summary>

    // Call the ordinary jQuery method
    var jqxhr = $.getJSON(url, data, success);

    // Do the error handler wrapping stuff to provide an error handler with a JSON object - if the response contains JSON object data
    if (typeof error !== "undefined") {
      jqxhr.error(function(response, textStatus, errorThrown) {
        try {
          var json = $.parseJSON(response.responseText);
          error(json, response, textStatus, errorThrown);
        } catch(e) {
          error(undefined, response, textStatus, errorThrown);
        }
      });
    }

    // Return the jQueryXmlHttpResponse object
    return jqxhr;
  };
})(jQuery);

이 모든 것에서 무엇을 얻습니까? 최종 결과는

  • 내 컨트롤러 작업에는 HandleErrorAttributes에 대한 요구 사항이 없습니다.
  • 내 컨트롤러 작업에는 반복적 인 보일러 플레이트 오류 처리 코드가 포함되어 있지 않습니다.
  • 로깅 및 기타 오류 처리 관련 항목을 쉽게 변경할 수있는 단일 오류 처리 코드가 있습니다.
  • 간단한 요구 사항 : JsonResult를 반환하는 컨트롤러 작업에는 ActionResult와 같은 일부 기본 유형이 아닌 반환 유형 JsonResult가 있어야합니다. 이유 : FooHandleErrorAttribute의 코드 주석을 참조하십시오.

클라이언트 측 예 :

var success = function(data) {
  alert(data.myjsonobject.foo);
};
var onError = function(data) {
  var message = "Error";
  if(typeof data !== "undefined")
    message += ": " + data.message;
  alert(message);
};
$.FooGetJSON(url, params, onSuccess, onError);

의견을 환영합니다! 언젠가는이 솔루션에 대해 블로그를 작성할 것입니다.


우와! 특정 상황을 만족시키기 위해서는 큰 대답보다는 필요한 설명 만있는 간단한 대답을하는 것이 낫습니다. 모든 사람이 사용할 수 있도록, 시간 옆에 일반적인 대답 갈
pythonian29033

2

ASP.NET AJAX "ScriptService"오류가를 반환하는 방식 과 유사하게 오류 조건을 설명하는 JSON 개체와 함께 500 오류를 확실히 반환 합니다 . 나는 이것이 상당히 표준이라고 믿습니다. 잠재적으로 예상치 못한 오류 조건을 처리 할 때 일관성을 유지하는 것이 좋습니다.

따로, C #으로 작성하는 경우 .NET의 기본 제공 기능을 사용하지 않는 이유는 무엇입니까? WCF 및 ASMX 서비스를 사용하면 바퀴를 다시 만들지 않고도 데이터를 JSON으로 쉽게 직렬화 할 수 있습니다.


이 컨텍스트에서 500 오류 코드를 사용해야한다고 생각하지 않습니다. w3.org/Protocols/rfc2616/rfc2616-sec10.html 사양 에 따라 가장 좋은 대안은 400 (잘못된 요청)을 보내는 것입니다. 500 오류는 처리되지 않은 예외에 더 적합합니다.
Gabriel Mazetto 2013-08-14

2

Rails 스캐 폴드 422 Unprocessable Entity는 이러한 종류의 오류에 사용됩니다. 자세한 내용은 RFC 4918 을 참조하십시오.


2

예, HTTP 상태 코드를 사용해야합니다. 또한 Nottingham의 제안 과 같이 다소 표준화 된 JSON 형식으로 오류 설명을 반환하는 것이 좋습니다. apigility Error Reporting을 참조 하세요 .

API 문제의 페이로드는 다음 구조를 갖습니다.

  • type : 오류 조건을 설명하는 문서에 대한 URL (선택 사항이며 제공되지 않으면 "about : blank"로 간주됩니다. 사람이 읽을 수있는 문서로 해석되어야 합니다. Apigility는 항상이를 제공합니다).
  • title : 오류 조건에 대한 간략한 제목 (필수; 동일한 유형 의 모든 문제에 대해 동일해야 함 ; Apigility는 항상이를 제공함).
  • status : 현재 요청에 대한 HTTP 상태 코드 (선택 사항, Apigility는 항상이를 제공함).
  • detail :이 요청과 관련된 오류 세부 정보 (선택 사항, 각 문제에 대해 민첩성이 필요함).
  • instance :이 문제의 특정 인스턴스를 식별하는 URI (선택 사항, 현재 Apigility는이를 제공하지 않음).

1

사용자가 유효하지 않은 데이터를 제공하는 경우 반드시 400 Bad Request( 요청에 잘못된 구문이 포함되어 있거나 이행 할 수 없습니다. )


400 범위 중 하나라도 허용되며 422는 처리 할 수없는 데이터에 가장 적합한 옵션입니다.
jamesc

0

나는 당신이 어떤 http 오류 코드도 반환해야한다고 생각하지 않는다. 오히려 인터페이스가 실제로 무슨 일이 일어 났는지 알 수 있도록 응용 프로그램의 클라이언트쪽에 유용한 사용자 정의 예외를 반환해야한다. 나는 404 오류 코드 또는 그 성격과 관련된 실제 문제를 숨기려고 시도하지 않을 것입니다.


무슨 일이 있어도 200을 돌려 주겠다는 건가요? "사용자 지정 예외"란 무엇을 의미합니까? 오류를 설명하는 JSON 조각을 의미합니까?
thatismatt

4
http 코드를 반환한다고해서 오류 설명 메시지를 반환 할 수 없다는 의미는 아닙니다. 200을 반환하는 것은 잘못 언급하지 않는 것이 다소 어리석은 일입니다.
StaxMan

@StaxMan과 동의-항상 최상의 상태 코드를 반환하지만 반환 정보에 설명 포함
schmoopy

0

서버 / 프로토콜 오류의 경우 가능한 한 REST / HTTP를 사용하려고합니다 (브라우저에 URL을 입력하는 것과 비교해보십시오).

  • 존재하지 않는 항목을 (/ persons / {non-existing-id-here})라고합니다. 404를 반환합니다.
  • 서버에서 예기치 않은 오류 (코드 버그)가 발생했습니다. 500을 반환합니다.
  • 클라이언트 사용자에게 리소스를 가져올 권한이 없습니다. 401을 반환합니다.

도메인 / 비즈니스 로직 특정 오류의 경우 프로토콜이 올바른 방식으로 사용되고 서버 내부 오류가 없다고 말하고 있으므로 오류 JSON / XML 개체 또는 데이터를 설명하고 싶은 모든 항목으로 응답합니다. 웹 사이트의 양식) :

  • 사용자가 자신의 계정 이름을 변경하려고하지만 사용자에게 전송 된 이메일의 링크를 클릭하여 계정을 아직 ​​확인하지 않았습니다. { "error": "계정이 확인되지 않음"} 등을 반환합니다.
  • 사용자가 책을 주문하려고하는데 책이 팔렸고 (DB에서 상태가 변경됨) 더 이상 주문할 수 없습니다. { "error": "책이 이미 판매 됨"}을 반환합니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.