Quelle est la meilleure façon de créer un modèle de réponse aux erreurs REST API et un système de codes d'erreur?


15

Mon implémentation REST retournera des erreurs dans JSON avec la structure suivante:

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

Je suggère de créer un modèle de réponse spécial, où je peux transmettre les valeurs nécessaires pour les propriétés (dev_message, message_for_user, some_internal_error_code), et les renvoyer. Dans le code, cela ressemblerait à ceci:

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

À quoi devrait ressembler ce modèle? Dois-je implémenter des méthodes, par exemple successResponse () où je ne transmettrai que des informations textuelles, et le code sera par défaut 200 là-bas? Je suis coincé avec ça. Et ceci est la première partie de ma question: dois-je mettre en œuvre ce modèle, est-ce une bonne pratique? Parce que pour l'instant, je retourne simplement des tableaux directement à partir du code.

La deuxième partie concerne le système de codes d'erreur. Les codes d'erreur seront décrits dans la documentation. Mais le problème que je rencontre est dans le code. Quelle est la meilleure façon de gérer les codes d'erreur? Dois-je les écrire à l'intérieur du modèle? Ou serait-il préférable de créer un service distinct pour gérer cela?

MISE À JOUR 1

J'ai implémenté une classe de modèle pour la réponse. C'est la réponse de Greg similaire, la même logique, mais en plus j'ai des erreurs écrites codées en dur dans le modèle et voici à quoi cela ressemble:

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

     ...some code...
    }

Pourquoi j'ai fait ça? Et pour quoi faire?

  1. C'est cool dans le code: return new ErrorResponse(ErrorResponse::SOME_ENTITY_NOT_FOUND );
  2. Message d'erreur facile à modifier. Tous les messages sont au même endroit au lieu de contrôleur / service / etc ou quoi que vous le placiez.

Si vous avez des suggestions pour améliorer cela, veuillez commenter.

Réponses:


13

Dans cette situation, je pense toujours à l'interface d'abord, puis j'écris du code PHP pour la supporter.

  1. Il s'agit d'une API REST, des codes d'état HTTP significatifs sont donc indispensables.
  2. Vous souhaitez que des structures de données cohérentes et flexibles soient envoyées vers et depuis le client.

Pensons à toutes les choses qui pourraient mal tourner et à leurs codes d'état HTTP:

  • Le serveur renvoie une erreur (500)
  • Échec d'authentification (401)
  • La ressource demandée est introuvable (404)
  • Les données que vous modifiez ont été modifiées depuis que vous les avez chargées (409)
  • Erreurs de validation lors de l'enregistrement des données (422)
  • Le client a dépassé son taux de demande (429)
  • Type de fichier non pris en charge (415)

Notez qu'il y en a d'autres que vous pouvez rechercher plus tard.

Pour la plupart des conditions d'échec, un seul message d'erreur doit être renvoyé. La 422 Unprocessable Entityréponse, que j'ai utilisée pour les "erreurs de validation" pourrait renvoyer plus d'une erreur --- Une ou plusieurs erreurs par champ de formulaire.

Nous avons besoin d'une structure de données flexible pour les réponses aux erreurs.

Prenons comme exemple 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!"
        ]
    }
}

Comparez cela avec de simples erreurs de validation lorsque vous essayez de POSER quelque chose au serveur:

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

La clé ici est le type de contenu text/json. Cela indique aux applications clientes qu'elles peuvent décoder le corps de réponse avec un décodeur JSON. Si, par exemple, une erreur de serveur interne n'est pas détectée et que votre page Web générique «Quelque chose s'est mal passé» est livrée à la place, le type de contenu doit être text/html; charset=utf-8tel que les applications clientes n'essaieront pas de décoder le corps de réponse en JSON.

Cela semble tout trouver et dandy, jusqu'à ce que vous ayez besoin de prendre en charge JSONP réponses . Vous devez renvoyer une 200 OKréponse, même en cas d'échecs. Dans ce cas, vous devrez détecter que le client demande une réponse JSONP (généralement en détectant un paramètre de demande d'URL appelé callback) et modifier un peu la structure des données:

(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!"
            ]
        }
    }
});

Ensuite, le gestionnaire de réponse sur le client (dans un navigateur Web) doit avoir une fonction JavaScript globale appelée displayBlogPostqui accepte un seul argument. Cette fonction devrait déterminer si la réponse a réussi:

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

Nous avons donc pris soin du client. Maintenant, prenons soin du serveur.

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

Et pour l'utiliser en cas d'erreur de serveur:

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

Ou lors de la validation de la saisie utilisateur:

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

Après cela, vous avez juste besoin de quelque chose qui prend l'objet de réponse retourné et le convertit en JSON et envoie la réponse sur son chemin joyeux.


Merci pour une réponse! J'ai implémenté une solution similaire. La seule différence que je ne transmets aucun message par moi-même, ils sont déjà définis (voir ma question mise à jour).
Grokking

-2

Je faisais face à quelque chose de similaire, j'ai fait 3 choses,

  1. J'ai créé un gestionnaire d'exception appelé ABCException.

Depuis que j'utilise Java et Spring,

Je l'ai défini comme

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

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

        }
    }

Ensuite, je l'ai appelé là où c'était nécessaire, comme ça,

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

Et oui, vous devez créer un gestionnaire d'exception dans votre contrôleur si vous utilisez un service Web basé sur REST.

Annotez-le avec @ExceptionHandlersi vous utilisez Spring


Les programmeurs concernent les questions conceptuelles et les réponses sont censées expliquer les choses . Lancer des vidages de code au lieu d'explications revient à copier du code de l'IDE vers le tableau blanc: cela peut sembler familier et même parfois compréhensible, mais cela semble bizarre ... juste bizarre. Le tableau blanc n'a pas de compilateur
moucher
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.