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になりますか?これにこだわっています。これは私の質問の最初の部分です。このモデルを実装する必要がありますか、これは良い練習ですか?今のところ、コードから直接配列を返すだけだからです。

2番目の部分は、エラーコードシステムについてです。エラーコードについては、ドキュメントで説明します。しかし、私が遭遇している問題はコードにあります。エラーコードを管理する最良の方法は何ですか?それらをモデル内に記述する必要がありますか?または、これを処理するための別のサービスを作成する方が良いでしょうか?

更新1

応答用のモデルクラスを実装しました。これはGregの答えと似ていますが、同じロジックですが、さらに、モデルに記述れたエラーをハードコーディングしました。ここでは、次のようになります。

    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. エラーメッセージを簡単に変更できます。すべてのメッセージは、controller / service / etcなどの代わりに1か所に配置されます。

これを改善するための提案があれば、コメントしてください。

回答:


13

この状況では、私は常に最初にインターフェイスを考え、次にそれをサポートするPHPコードを記述します。

  1. これはREST APIなので、意味のあるHTTPステータスコードは必須です。
  2. クライアントとの間で送信される一貫した柔軟なデータ構造が必要です。

失敗する可能性のあるすべてのことと、それらのHTTPステータスコードを考えてみましょう。

  • サーバーはエラーをスローします(500)
  • 認証失敗(401)
  • 要求されたリソースが見つかりませんでした(404)
  • 変更中のデータは、ロード後に変更されました(409)
  • データの保存時の検証エラー(422)
  • クライアントが要求率を超えました(429)
  • サポートされていないファイルタイプ(415)

後で調査できる他のものがあることに注意してください。

ほとんどの障害状態では、返されるエラーメッセージは1つだけです。の422 Unprocessable Entity私は「検証エラー」のために使用してきた応答は、複数のエラー---フォームフィールドごとに1つの以上のエラーを返すことができます。

エラー応答には柔軟なデータ構造が必要です。

、例として取ります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!"
        ]
    }
}

対照的に、サーバーに何かをPOSTしようとするときの単純な検証エラー:

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デコーダーで応答本文をデコードできることを伝えます。たとえば、内部サーバーエラーがキャッチされず、代わりに一般的な「何かが間違っている」Webページが配信される場合text/html; charset=utf-8、クライアントアプリケーションが応答本文をJSONとしてデコードしないようにコンテンツタイプを設定する必要があります。

JSONP応答をサポートする必要があるまで、これはすべての発見とダンディに見えます。200 OK失敗した場合でも、応答を返す必要があります。この場合、クライアントがJSONP応答を要求していることを検出する必要があります(通常は、callback)、データ構造を少し変更する必要があります。

(GET / 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ブラウザー内)の応答ハンドラーにdisplayBlogPostは、単一の引数を受け入れるグローバルJavaScript関数が必要です。この関数は、応答が成功したかどうかを判断する必要があります。

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

私は似たようなことに直面していました、私は3つのことをしました、

  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を作成する必要があります。

@ExceptionHandlerSpringを使用している場合は注釈を付けます


プログラマがさについての概念的な質問と回答されていると期待を説明し、物事を。説明の代わりにコードダンプをスローすることは、コードをIDEからホワイトボードにコピーするようなものです。ホワイトボードにはコンパイラがありません
-gnat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.