web-dev-qa-db-fra.com

Comment gérer idiomatiquement les codes d'erreur HTTP lors de l'utilisation de RestSharp?

Je construis un client API HTTP en utilisant RestSharp et j'ai remarqué que lorsque le serveur renvoie un code d'erreur HTTP (401 non autorisé, 404 introuvable, 500 erreur de serveur interne, etc.), la fonction RestClient.Execute() ne lève pas d'exception - à la place, je reçois une RestResponse valide avec une propriété null .Data. Je ne souhaite pas vérifier manuellement chaque code d'erreur HTTP possible dans mon client API. RestSharp fournit-il un meilleur moyen de transmettre ces erreurs à mon application client?

Un petit détail supplémentaire. RestSharp expose une propriété Response.ErrorException - si l'appel RestClient.Execute<T>() provoque une exception, il sera exposé via la propriété ErrorException au lieu d'être renvoyé. Leur documentation comprend l'exemple suivant:

// TwilioApi.cs
public class TwilioApi {
    const string BaseUrl = "https://api.twilio.com/2008-08-01";

    public T Execute<T>(RestRequest request) where T : new()
    {
    var client = new RestClient();
    client.BaseUrl = BaseUrl;
    client.Authenticator = new HttpBasicAuthenticator(_accountSid, _secretKey);
    request.AddParameter("AccountSid", _accountSid, ParameterType.UrlSegment); // used on every request
    var response = client.Execute<T>(request);

    if (response.ErrorException != null)
    {
        const string message = "Error retrieving response.  Check inner details for more info.";
        var twilioException = new ApplicationException(message, response.ErrorException);
        throw twilioException;
    }
    return response.Data;
    }

}

J'ai adopté ce modèle dans mon code, mais mon serveur d'API renvoie un 401 Unauthorized et pourtant la propriété ErrorException est toujours nulle. Je peux voir le RestResponse debug watch Code d'état non autorisé et message d'erreur dans les propriétés RestResponse.StatusCode et RestResponse.StatusDescription - mais je ne comprends pas pourquoi une réponse non autorisée ne ferait pas en sorte que le champ ErrorException soit rempli.

15
Dylan Beattie

J'ai rencontré ce même problème en essayant de créer un gestionnaire d'erreurs générique pour un client RestSharp WebAPI. Étant donné ces méthodes d'extension:

public static class RestSharpExtensionMethods
{
    public static bool IsSuccessful(this IRestResponse response)
    {
        return response.StatusCode.IsSuccessStatusCode()
            && response.ResponseStatus == ResponseStatus.Completed;
    }

    public static bool IsSuccessStatusCode(this HttpStatusCode responseCode)
    {
        int numericResponse = (int)responseCode;
        return numericResponse >= 200
            && numericResponse <= 399;
    }
}

J'ai fait une demande nécessitant la désérialisation de la réponse:

public async Task<ResponseModel<TResponse>> PerformRequestAsync<TResponse>(IRestRequest request)
{
    var response = await _client.ExecuteTaskAsync<ResponseModel<TResponse>>(request);
    ResponseModel<TResponse> responseData;

    if (response.IsSuccessful())
    {
        responseData = response.Data;
    }
    else
    {
        string resultMessage = HandleErrorResponse<TResponse>(request, response);

        responseData = new ResponseModel<TResponse>         
        {
            Success = false,
            ResultMessage = resultMessage
        };
    }

    return responseData;
}

Toutefois, lors des tests, j’ai constaté que, lorsque je n’avais aucune gestion des erreurs configurée pour ce cas, mon service Web renvoyait une page 404 au format HTML lorsqu'une URL non mappée était demandée. La propriété response.ErrorException contenait la chaîne suivante:

Référence à l'entité non déclarée 'nbsp'. Ligne n, position m.

Apparemment, RestSharp a essayé d'analyser la réponse au format XML, même si le type de contenu était text/html. Peut-être que je vais déposer un problème avec RestSharp pour cela.

Bien sûr, en production, vous ne devriez jamais obtenir un 404 lorsque vous appelez votre propre service, mais je veux que ce client soit complet et réutilisable.

Il y a donc deux solutions auxquelles je peux penser:

  • Inspecter le code d'état et afficher la description
  • Assurez-vous que le service retourne un objet d'erreur que vous pouvez analyser.

Le premier se fait assez facilement. Dans HandleErrorResponse(), je construis le message de résultat (utilisateur présentable) et la chaîne d'erreur (connectable) en fonction de la valeur numérique du code d'état:

public string HandleErrorResponse(IRestRequest request, IRestResponse response)
{
    string statusString = string.Format("{0} {1} - {2}", (int)response.StatusCode, response.StatusCode, response.StatusDescription);
    string errorString = "Response status: " + statusString;

    string resultMessage = "";
    if (!response.StatusCode.IsScuccessStatusCode())
    {
        if (string.IsNullOrWhiteSpace(resultMessage))
        {
            resultMessage = "An error occurred while processing the request: "
                          + response.StatusDescription;
        }
    }
    if (response.ErrorException != null)
    {
        if (string.IsNullOrWhiteSpace(resultMessage))
        {
            resultMessage = "An exception occurred while processing the request: "
                          + response.ErrorException.Message;
        }
        errorString += ", Exception: " + response.ErrorException;
    }

    // (other error handling here)

    _logger.ErrorFormat("Error response: {0}", errorString);

    return resultMessage;
}

Maintenant que mes réponses d'API sont toujours entourées d'un ResponseModel<T> de ma création, je peux configurer un filtre d'exception et une route NotFound pour renvoyer un modèle de réponse analysable avec le message d'erreur ou d'exception de la propriété ResultMessage:

public class HandleErrorAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        // (log context.Exception here)

        context.Response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, new ResponseModel<object>
        {
            Success = false,
            ResultMessage = "An exception occurred while processing the request: " + context.Exception.Message
        });
    }
}

Et:

public class ErrorController : ApiController
{
    public HttpResponseMessage Handle404()
    {
        const string notFoundString = "The requested resource could not be found";

        var responseMessage = Request.CreateResponse(HttpStatusCode.NotFound, new ResponseModel<object>
        {
            Success = false,
            ResultMessage = notFoundString
        });

        responseMessage.ReasonPhrase = notFoundString;

        return responseMessage;
    }
}

De cette façon, la réponse de mon service peut toujours être analysée par RestSharp, et je peux utiliser la méthode de journalisation générique:

public string HandleErrorResponse<TResponseModel>(IRestRequest request, IRestResponse<<ResponseModel<TResponseModel>> response)

Et enregistrez la réponse réelle à // (other error handling here), si disponible:

if (response.Data != null && !string.IsNullOrWhiteSpace(response.Data.ResultMessage))
{
    resultMessage = response.Data.ResultMessage;
    errorString += string.Format(", Service response: \"{0}\"", response.Data.ResultMessage);
}
18
CodeCaster

RestSharp a ajouté la propriété booléenne IRestResponse.IsSuccessful qui couvre votre cas d'utilisation. Je n'ai trouvé aucune documentation faisant référence à cette propriété, mais voici la ligne qui définit la méthode de la propriété .

Il est intéressant de noter que RestSharp considère que les codes 200-299 ont réussi, alors que CodeCaster considère que les codes 200-399 ont réussi.

1
Bret

Il devrait être suffisant de rechercher un code de succès et d'envoyer ou de signaler l'erreur si vous obtenez un code autre que le succès. Cela signifie généralement la vérification du statut HTTP 200 après chaque demande. Si vous créez une nouvelle ressource, vous devez vous attendre à l'état 201. 

Avec la plupart des API/frameworks, il est très inhabituel de voir un autre code d'état sauf ceux-là si rien ne s'est mal passé.

0
jwg