创建REST API错误响应模型和错误代码系统的最佳方法是什么?


15

我的REST实现将使用以下结构在JSON中返回错误:

{
 "http_response":400,
 "dev_message":"There is a problem",
 "message_for_user":"Bad request",
 "some_internal_error_code":12345
}

我建议创建一个特殊的响应模型,在该模型中,我可以传递所需的属性值(dev_message,message_for_user,some_internal_error_code)并返回它们。在代码中将类似于以下内容:

$responseModel = new MyResponseModel(400,"Something is bad", etc...);

此模型应如何显示?是否应该在仅传递文本信息的地方实现例如successResponse()这样的方法,并且那里的代码默认为200?我坚持这一点。这是我的问题的第一部分:我是否需要实现此模型,这是一种很好的做法吗?因为现在,我只是直接从代码返回数组。

第二部分是关于错误代码系统。错误代码将在文档中进行描述。但是我遇到的问题是代码。管理错误代码的最佳方法是什么?我应该在模型中编写它们吗?还是最好创建单独的服务来处理此问题?

更新1

我已经实现了响应的模型类。这类似于格雷格的答案,逻辑相同,但是另外,我在模型中对错误进行了硬编码,这是它的样子:

    class ErrorResponse
    {
     const SOME_ENTITY_NOT_FOUND = 100;
     protected $errorMessages = [100 => ["error_message" => "That entity doesn't exist!"]];

     ...some code...
    }

我为什么要这样做?那又是为了什么呢?

  1. 代码看起来很酷: return new ErrorResponse(ErrorResponse::SOME_ENTITY_NOT_FOUND );
  2. 易于更改错误消息。所有消息都放在一个位置,而不是控制器/服务/等或放置它的位置。

如果您有任何改进建议,请发表评论。

Answers:


13

在这种情况下,我总是首先考虑接口,然后编写PHP代码以支持该接口。

  1. 它是REST API,因此必须具有有意义的HTTP状态代码。
  2. 您希望与客户端之间发送一致,灵活的数据结构。

让我们考虑所有可能出错的地方及其HTTP状态代码:

  • 服务器抛出错误(500)
  • 身份验证失败(401)
  • 找不到请求的资源(404)
  • 自加载数据以来,您正在修改的数据已更改(409)
  • 保存数据时的验证错误(422)
  • 客户超出了他们的请求率(429)
  • 不支持的文件类型(415)

请注意,您还可以在以后进行其他研究。

对于大多数故障情况,仅返回一条错误消息。的422 Unprocessable Entity我用于“验证错误”响应可能会返回多个错误---每个表单字段一个或多个错误。

我们需要灵活的数据结构来进行错误响应。

以以下示例为例500 Internal Server Error

HTTP/1.1 500 Internal Server Error
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "general": [
            "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
        ]
    }
}

尝试将某些内容发布到服务器时,将其与简单的验证错误进行对比:

HTTP/1.1 422 Unprocessable Entity
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "first_name": [
            "is required"
        ],
        "telephone": [
            "should not exceed 12 characters",
            "is not in the correct format"
        ]
    }
}

它们的关键是内容类型为text/json。这告诉客户端应用程序可以使用JSON解码器解码响应主体。例如,如果没有发现内部服务器错误,而是传递了通用的“发生了问题”网页,则内容类型应该为,text/html; charset=utf-8以便客户端应用程序不会尝试将响应主体解码为JSON。

在您需要支持JSONP响应之前,这看起来都很花哨。200 OK即使失败,也必须返回响应。在这种情况下,您必须检测到客户端正在请求JSONP响应(通常是通过检测称为callback)并稍微改变数据结构:

(获取/ posts / 123?callback = displayBlogPost)

<script type="text/javascript" src="/posts/123?callback=displayBlogPost"></script>

HTTP/1.1 200 OK
Content-Type: text/javascript
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

displayBlogPost({
    "status": 500,
    "data": {
        "errors": {
            "general": [
                "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
            ]
        }
    }
});

然后,客户端(在Web浏览器中)的响应处理程序应具有一个名为的全局JavaScript函数displayBlogPost,该函数接受单个参数。此函数必须确定响应是否成功:

function displayBlogPost(response) {
    if (response.status == 500) {
        alert(response.data.errors.general[0]);
    }
}

因此,我们已经照顾了客户。现在,让我们来照顾服务器。

<?php

class ResponseError
{
    const STATUS_INTERNAL_SERVER_ERROR = 500;
    const STATUS_UNPROCESSABLE_ENTITY = 422;

    private $status;
    private $messages;

    public function ResponseError($status, $message = null)
    {
        $this->status = $status;

        if (isset($message)) {
            $this->messages = array(
                'general' => array($message)
            );
        } else {
            $this->messages = array();
        }
    }

    public function addMessage($key, $message)
    {
        if (!isset($message)) {
            $message = $key;
            $key = 'general';
        }

        if (!isset($this->messages[$key])) {
            $this->messages[$key] = array();
        }

        $this->messages[$key][] = $message;
    }

    public function getMessages()
    {
        return $this->messages;
    }

    public function getStatus()
    {
        return $this->status;
    }
}

并在发生服务器错误的情况下使用此命令:

try {
    // some code that throws an exception
}
catch (Exception $ex) {
    return new ResponseError(ResponseError::STATUS_INTERNAL_SERVER_ERROR, $ex->message);
}

或验证用户输入时:

// Validate some input from the user, and it is invalid:

$response = new ResponseError(ResponseError::STATUS_UNPROCESSABLE_ENTITY);
$response->addMessage('first_name', 'is required');
$response->addMessage('telephone', 'should not exceed 12 characters');
$response->addMessage('telephone', 'is not in the correct format');

return $response;

之后,您只需要使用返回的响应对象并将其转换为JSON并以愉快的方式发送响应的对象即可。


谢谢您的回答!我已经实现了类似的解决方案。我自己没有传递任何消息的唯一区别是它们已经设置(请参阅我更新的问题)。
Grokking

-2

我面临类似的事情,做了三件事,

  1. 为我自己创建了一个名为ABCException的ExceptionHandler。

由于我使用的是Java和Spring,

我定义为

 public class ABCException extends Exception {
private String errorMessage;
private HttpStatus statusCode;

    public ABCException(String errorMessage,HttpStatus statusCode){
            super(errorMessage);
            this.statusCode = statusCode;

        }
    }

然后在需要的地方调用它,就像这样,

throw new ABCException("Invalid User",HttpStatus.CONFLICT);

是的,如果您使用的是基于REST的Web服务,则需要在控制器中创建ExceptionHandler。

@ExceptionHandler如果使用Spring 注释


程序员是关于概念性问题的,答案应该可以解释事情。抛出代码转储而不是进行解释就像将代码从IDE复制到白板一样:它看起来很熟悉,甚至有时是可以理解的,但是感觉很奇怪……只是很奇怪。白板没有编译器
蚊蚋
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.