Program Tip

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

programtip 2020. 10. 12. 08:05
반응형

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


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

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

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


반환하는 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
  }
});

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

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

{
    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 직렬화를 무료로받을 수 있습니다.


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

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


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

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

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


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

  • 모든 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);

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

  • None of my controller actions has requirements on HandleErrorAttributes.
  • None of my controller actions contains any repetitive boiler plate error handling code.
  • I have a single point of error handling code allowing me to easily change logging and other error handling related stuff.
  • A simple requirement: Controller actions returning JsonResult's must have return type JsonResult and not some base type like ActionResult. Reason: See code comment in FooHandleErrorAttribute.

Client side example:

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);

Comments are most welcome! I'll probably blog about this solution some day...


I would definitely return a 500 error with a JSON object describing the error condition, similar to how an ASP.NET AJAX "ScriptService" error returns. I believe this is fairly standard. It's definitely nice to have that consistency when handling potentially unexpected error conditions.

Aside, why not just use the built in functionality in .NET, if you're writing it in C#? WCF and ASMX services make it easy to serialize data as JSON, without reinventing the wheel.


Rails scaffolds use 422 Unprocessable Entity for these kinds of errors. See RFC 4918 for more information.


Yes, you should use HTTP status codes. And also preferably return error descriptions in a somewhat standardized JSON format, like Nottingham’s proposal, see apigility Error Reporting:

The payload of an API Problem has the following structure:

  • type: a URL to a document describing the error condition (optional, and "about:blank" is assumed if none is provided; should resolve to a human-readable document; Apigility always provides this).
  • title: a brief title for the error condition (required; and should be the same for every problem of the same type; Apigility always provides this).
  • status: the HTTP status code for the current request (optional; Apigility always provides this).
  • detail: error details specific to this request (optional; Apigility requires it for each problem).
  • instance: URI identifying the specific instance of this problem (optional; Apigility currently does not provide this).

If the user supplies invalid data, it should definitely be a 400 Bad Request (The request contains bad syntax or cannot be fulfilled.)


I don't think you should be returning any http error codes, rather custom exceptions that are useful to the client end of the application so the interface knows what had actually occurred. I wouldn't try and mask real issues with 404 error codes or something to that nature.


For server/protocol errors I would try to be as REST/HTTP as possible (Compare this with you typing in URL's in your browser):

  • a non existing item is called (/persons/{non-existing-id-here}). Return a 404.
  • an unexpected error on the server (code bug) occured. Return a 500.
  • the client user is not authorised to get the resource. Return a 401.

For domain/business logic specific errors I would say the protocol is used in the right way and there's no server internal error, so respond with an error JSON/XML object or whatever you prefer to describe your data with (Compare this with you filling in forms on a website):

  • a user wants to change its account name but the user did not yet verify its account by clicking a link in an email which was sent to the user. Return {"error":"Account not verified"} or whatever.
  • a user wants to order a book, but the book was sold (state changed in DB) and can't be ordered anymore. Return {"error":"Book already sold"}.

참고URL : https://stackoverflow.com/questions/674027/what-should-a-json-service-return-on-failure-error

반응형