Dịch vụ JSON sẽ trả lại gì khi bị lỗi / lỗi


79

Tôi đang viết một dịch vụ JSON trong C # (tệp .ashx). Khi yêu cầu dịch vụ thành công, tôi trả lại một số dữ liệu JSON. Nếu yêu cầu không thành công, do một ngoại lệ được đưa ra (ví dụ: thời gian chờ cơ sở dữ liệu) hoặc do yêu cầu sai theo một cách nào đó (ví dụ: ID không tồn tại trong cơ sở dữ liệu được đưa ra dưới dạng đối số) thì dịch vụ sẽ phản hồi như thế nào? Mã trạng thái HTTP nào hợp lý và tôi có nên trả lại bất kỳ dữ liệu nào không, nếu có?

Tôi dự đoán rằng dịch vụ chủ yếu sẽ được gọi từ jQuery bằng cách sử dụng plugin jQuery.form, jQuery hoặc plugin này có bất kỳ cách mặc định nào để xử lý phản hồi lỗi không?

CHỈNH SỬA: Tôi đã quyết định sẽ sử dụng jQuery + .ashx + HTTP [mã trạng thái] khi thành công Tôi sẽ trả về JSON nhưng nếu có lỗi, tôi sẽ trả về một chuỗi, vì có vẻ như đó là tùy chọn lỗi cho jQuery. ajax mong đợi.

Câu trả lời:


34

Mã trạng thái HTTP bạn trả về phải phụ thuộc vào loại lỗi đã xảy ra. Nếu một ID không tồn tại trong cơ sở dữ liệu, hãy trả về 404; nếu người dùng không có đủ đặc quyền để thực hiện cuộc gọi Ajax đó, hãy trả về 403; nếu cơ sở dữ liệu hết thời gian chờ trước khi có thể tìm thấy bản ghi, hãy trả về lỗi 500 (lỗi máy chủ).

jQuery tự động phát hiện các mã lỗi như vậy và chạy hàm gọi lại mà bạn xác định trong lệnh gọi Ajax của mình. Tài liệu: http://api.jquery.com/jQuery.ajax/

Ví dụ ngắn gọn về $.ajaxlỗi gọi lại:

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

3
Bạn nghĩ tôi nên trả lại mã lỗi nào nếu ai đó cung cấp dữ liệu không hợp lệ, chẳng hạn như một chuỗi trong đó số nguyên được yêu cầu? hoặc một địa chỉ email không hợp lệ?
thaualityatt

một cái gì đó trong phạm vi 500, giống như bất kỳ lỗi mã phía máy chủ tương tự nào
annakata

7
Phạm vi 500 là một lỗi máy chủ, nhưng không có gì sai trên máy chủ. Họ đã đưa ra một yêu cầu tồi, vậy nó không nên nằm trong phạm vi 400 sao?
thaualityatt

38
Với tư cách là người dùng nếu tôi nhận được 500, tôi biết tôi không đáng trách, nếu nhận được 400, tôi có thể tìm ra những gì tôi đã làm sai, điều này đặc biệt quan trọng khi viết API, vì người dùng của bạn có kiến ​​thức kỹ thuật và 400 yêu cầu họ sử dụng API đúng cách. Tái
bút

4
Chỉ muốn chỉ ra rằng 404 có nghĩa là tài nguyên được địa chỉ hóa bị thiếu. Trong trường hợp này, tài nguyên là bộ xử lý POST của bạn, không phải thứ ngẫu nhiên nào đó trong DB của bạn có id. Trong trường hợp này 400 là thích hợp hơn.
StevenC

56

Xem câu hỏi này để biết một số thông tin chi tiết về các phương pháp hay nhất cho tình huống của bạn.

Đề xuất hàng đầu (từ liên kết đã nói) là chuẩn hóa cấu trúc phản hồi (cho cả thành công và thất bại) mà trình xử lý của bạn tìm kiếm, bắt tất cả Ngoại lệ ở lớp máy chủ và chuyển đổi chúng về cùng một cấu trúc. Ví dụ (từ câu trả lời này ):

{
    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"
    }
} 

Đây là cách tiếp cận stackoverflow sử dụng (trong trường hợp bạn đang tự hỏi làm thế nào những người khác làm loại điều này); viết các thao tác như biểu quyết có "Success""Message"các trường, bất kể cuộc biểu quyết có được phép hay không:

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

Như @ Phil.H đã chỉ ra , bạn nên nhất quán trong bất cứ điều gì bạn chọn. Điều này nói thì dễ hơn làm (mọi thứ đang trong quá trình phát triển cũng vậy!).

Ví dụ: nếu bạn gửi nhận xét quá nhanh trên SO, thay vì nhất quán và trả về

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

SO sẽ ném một ngoại lệ máy chủ ( HTTP 500) và bắt nó trong errorcuộc gọi lại của họ .

Việc sử dụng jQuery + .ashx+ HTTP [mã trạng thái] IMO càng nhiều càng tốt, nó sẽ tăng thêm độ phức tạp cho cơ sở mã phía máy khách của bạn so với giá trị của nó. Nhận ra rằng jQuery không "phát hiện" mã lỗi mà là thiếu mã thành công. Đây là một điểm khác biệt quan trọng khi cố gắng thiết kế một ứng dụng khách xung quanh mã phản hồi http với jQuery. Bạn chỉ nhận được hai lựa chọn (đó là "thành công" hay "lỗi"?), Mà bạn phải tự phân nhánh thêm. Nếu bạn có một số lượng nhỏ các WebServices đang điều khiển một số lượng trang nhỏ thì điều đó có thể ổn, nhưng bất kỳ thứ gì có quy mô lớn hơn có thể trở nên lộn xộn.

Tự nhiên hơn nhiều trong .asmxWebService (hoặc WCF cho vấn đề đó) để trả về một đối tượng tùy chỉnh hơn là tùy chỉnh mã trạng thái HTTP. Thêm vào đó, bạn nhận được tuần tự hóa JSON miễn phí.


1
Phương pháp tiếp cận hợp lệ, chỉ một lần bấm nút: các ví dụ không phải là JSON hợp lệ (thiếu dấu ngoặc kép cho các tên khóa)
StaxMan

1
đây là những gì tôi đã từng làm, nhưng bạn thực sự cần phải sử dụng mã trạng thái http, đó là những gì họ đang có cho (đặc biệt nếu bạn đang làm công cụ RESTful)
Eva

Tôi nghĩ rằng cách tiếp cận này chắc chắn hợp lệ - mã trạng thái http rất hữu ích khi thực hiện những công việc hữu ích, nhưng không quá hữu ích khi bạn đang thực hiện lệnh gọi api đến một tập lệnh chứa truy vấn cơ sở dữ liệu. Ngay cả khi truy vấn cơ sở dữ liệu trả về một lỗi, mã trạng thái http vẫn sẽ là 200. Trong trường hợp này, tôi thường sử dụng 'thành công' chìa khóa để chỉ ra nếu truy vấn MySQL đã thành công hay không :)
Terry

17

Sử dụng mã trạng thái HTTP sẽ là một cách RESTful để làm điều đó, nhưng điều đó sẽ gợi ý bạn làm cho phần còn lại của giao diện RESTful bằng cách sử dụng các URI tài nguyên, v.v.

Trên thực tế, hãy xác định giao diện theo ý muốn của bạn (trả về một đối tượng lỗi, ví dụ: nêu chi tiết thuộc tính có lỗi và một đoạn HTML giải thích nó, v.v.), nhưng khi bạn đã quyết định một thứ hoạt động trong nguyên mẫu , hãy kiên định một cách tàn nhẫn.


Tôi thích những gì bạn đang đề xuất, tôi cho rằng bạn nghĩ rằng tôi nên trả lại JSON? Một cái gì đó như: {error: {message: "Đã xảy ra lỗi", chi tiết: "Đã xảy ra vì hôm nay là thứ Hai."}}
thaualityatt

@thaualityatt - Điều đó khá hợp lý, nếu lỗi luôn gây tử vong. Để chi tiết hơn, tạo errormột mảng (có thể trống) và thêm một fatal_error: booltham số sẽ cung cấp cho bạn một chút linh hoạt.
Ben Blank

2
Ồ, và +1 cho các phản hồi RESTful khi nào sử dụng và khi không sử dụng. :-)
Ben Blank

Ron DeVera đã giải thích những gì tôi đang nghĩ!
Phil H

3

Tôi nghĩ nếu bạn chỉ đánh dấu một ngoại lệ, nó sẽ được xử lý trong lệnh gọi lại jQuery được chuyển cho tùy chọn 'error' . (Chúng tôi cũng ghi ngoại lệ này ở phía máy chủ vào nhật ký trung tâm). Không yêu cầu mã lỗi HTTP đặc biệt, nhưng tôi cũng tò mò muốn xem những người khác làm gì.

Đây là những gì tôi làm, nhưng đó chỉ là 0,02 đô la của tôi

Nếu bạn sắp RESTful và trả về mã lỗi, hãy cố gắng tuân theo các mã tiêu chuẩn do W3C quy định: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html


3

Tôi đã dành vài giờ để giải quyết vấn đề này. Giải pháp của tôi dựa trên những mong muốn / yêu cầu sau:

  • Không có mã xử lý lỗi biên soạn lặp lại trong tất cả các hành động của bộ điều khiển JSON.
  • Giữ nguyên mã trạng thái HTTP (lỗi). Tại sao? Vì mối quan tâm của cấp cao hơn sẽ không ảnh hưởng đến việc thực hiện cấp dưới.
  • Có thể lấy dữ liệu JSON khi xảy ra lỗi / ngoại lệ trên máy chủ. Tại sao? Bởi vì tôi có thể muốn thông tin lỗi phong phú. Ví dụ: thông báo lỗi, mã trạng thái lỗi miền cụ thể, dấu vết ngăn xếp (trong môi trường gỡ lỗi / phát triển).
  • Phía máy khách dễ sử dụng - thích sử dụng jQuery.

Tôi tạo một HandleErrorAttribute (xem phần nhận xét mã để giải thích chi tiết). Một vài chi tiết bao gồm "usings" đã bị bỏ sót, vì vậy mã có thể không được biên dịch. Tôi thêm bộ lọc vào bộ lọc chung trong quá trình khởi tạo ứng dụng trong Global.asax.cs như sau:

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

Thuộc tính:

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

Tôi đã phát triển plugin jQuery sau để dễ sử dụng cho phía khách hàng:

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

Tôi nhận được gì từ tất cả những điều này? Kết quả cuối cùng là

  • Không có hành động nào trong bộ điều khiển của tôi có yêu cầu trên HandleErrorAttributes.
  • Không có hành động nào trong bộ điều khiển của tôi chứa bất kỳ mã xử lý lỗi tấm lò hơi lặp lại nào.
  • Tôi có một điểm mã xử lý lỗi duy nhất cho phép tôi dễ dàng thay đổi ghi nhật ký và các nội dung liên quan đến xử lý lỗi khác.
  • Một yêu cầu đơn giản: Các hành động của bộ điều khiển trả về JsonResult phải có kiểu trả về JsonResult chứ không phải một số kiểu cơ sở như ActionResult. Lý do: Xem bình luận mã trong FooHandleErrorAttribute.

Ví dụ về phía khách hàng:

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

Nhận xét được hoan nghênh nhất! Có thể một ngày nào đó tôi sẽ viết blog về giải pháp này ...


boooo! Tốt hơn hết bạn nên có một câu trả lời đơn giản với chỉ một lời giải thích cần thiết hơn là một câu trả lời khổng lồ chỉ vì mục đích thỏa mãn một tình huống cụ thể. hãy tìm câu trả lời chung vào lần sau, để mọi người có thể sử dụng nó
pythonian29033

2

Tôi chắc chắn sẽ trả về lỗi 500 với một đối tượng JSON mô tả điều kiện lỗi, tương tự như cách trả về lỗi ASP.NET AJAX "ScriptService" . Tôi tin rằng điều này là khá tiêu chuẩn. Thật tuyệt khi có được sự nhất quán đó khi xử lý các điều kiện lỗi không mong muốn.

Ngoài ra, tại sao không chỉ sử dụng chức năng tích hợp sẵn trong .NET, nếu bạn đang viết nó bằng C #? Các dịch vụ WCF và ASMX giúp dễ dàng tuần tự hóa dữ liệu dưới dạng JSON mà không cần phát minh lại bánh xe.


Tôi không nghĩ rằng mã lỗi 500 nên được sử dụng trong ngữ cảnh này. Dựa trên đặc điểm kỹ thuật: w3.org/Protocols/rfc2616/rfc2616-sec10.html , giải pháp thay thế tốt nhất là gửi 400 (yêu cầu không hợp lệ). Lỗi 500 phù hợp hơn với một ngoại lệ chưa được xử lý.
Gabriel Mazetto


2

Có, bạn nên sử dụng mã trạng thái HTTP. Và cũng nên trả lại các mô tả lỗi ở định dạng JSON được chuẩn hóa một chút, như đề xuất của Nottingham , hãy xem Báo cáo lỗi linh hoạt :

Tải trọng của Vấn đề API có cấu trúc sau:

  • type : một URL đến tài liệu mô tả tình trạng lỗi (tùy chọn và "about: blank" được giả định nếu không có gì được cung cấp; sẽ giải quyết thành tài liệu có thể đọc được của con người ; Apigility luôn cung cấp điều này).
  • title : tiêu đề ngắn gọn cho tình trạng lỗi (bắt buộc; và phải giống nhau cho mọi vấn đề cùng loại ; Apigility luôn cung cấp điều này).
  • trạng thái : mã trạng thái HTTP cho yêu cầu hiện tại (tùy chọn; Apigility luôn cung cấp mã này).
  • detail : chi tiết lỗi cụ thể cho yêu cầu này (tùy chọn; Apigility yêu cầu nó cho từng vấn đề).
  • instance : URI xác định trường hợp cụ thể của vấn đề này (tùy chọn; Apigility hiện không cung cấp điều này).

1

Nếu người dùng cung cấp dữ liệu không hợp lệ, nó chắc chắn phải là một 400 Bad Request( Yêu cầu chứa cú pháp sai hoặc không thể thực hiện được. )


MỌI của dãy 400 là chấp nhận được và 422 là lựa chọn tốt nhất cho dữ liệu mà không thể xử lý
jamesc

0

Tôi không nghĩ rằng bạn nên trả lại bất kỳ mã lỗi http nào, thay vì các ngoại lệ tùy chỉnh hữu ích cho phần cuối của ứng dụng khách để giao diện biết điều gì đã thực sự xảy ra. Tôi sẽ không thử và che giấu các vấn đề thực sự với mã lỗi 404 hoặc điều gì đó tương tự.


Bạn đang đề nghị tôi trả lại 200 ngay cả khi có sự cố? Ý bạn là "ngoại lệ tùy chỉnh" là gì? Ý bạn là một đoạn JSON mô tả lỗi?
thaualityatt

4
Blah, trả lại mã http không có nghĩa là bạn CŨNG không thể trả lại thông báo mô tả lỗi. Trả lại 200 sẽ là ngớ ngẩn hơn nếu không đề cập đến sai.
StaxMan

Đồng ý với @StaxMan - luôn luôn trả về mã trạng thái tốt nhất nhưng bao gồm các mô tả trong thông tin trở lại
schmoopy

0

Đối với lỗi máy chủ / giao thức, tôi sẽ cố gắng sử dụng REST / HTTP càng nhiều càng tốt (So sánh điều này với việc bạn nhập URL trong trình duyệt của mình):

  • một mục không tồn tại được gọi là (/ people / {non-current-id-here}). Trả lại 404.
  • đã xảy ra lỗi không mong muốn trên máy chủ (lỗi mã). Trả lại 500.
  • người dùng máy khách không được phép lấy tài nguyên. Trả lại 401.

Đối với các lỗi cụ thể của miền / logic nghiệp vụ, tôi sẽ nói rằng giao thức được sử dụng đúng cách và không có lỗi nội bộ máy chủ, vì vậy hãy phản hồi bằng đối tượng JSON / XML bị lỗi hoặc bất kỳ thứ gì bạn muốn mô tả dữ liệu của mình (So sánh điều này với việc bạn điền vào biểu mẫu trên một trang web):

  • người dùng muốn thay đổi tên tài khoản của mình nhưng người dùng chưa xác minh tài khoản của mình bằng cách nhấp vào liên kết trong email đã được gửi đến người dùng. Trả lại {"error": "Tài khoản chưa được xác minh"} hoặc bất cứ điều gì.
  • người dùng muốn đặt hàng nhưng sách đã được bán (trạng thái đã thay đổi trong DB) và không thể đặt hàng được nữa. Trả lại {"error": "Sách đã được bán"}.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.