Когда в моем REST API я должен использовать конверт?Если я использую это в одном месте, я должен всегда использовать это? - PullRequest
25 голосов
/ 03 апреля 2012

Я работаю над созданием веб-сервиса RESTful.Я ознакомился с принципами использования HTTP для каждого механизма настолько, насколько это возможно, и большую часть времени, например, при извлечении ресурсов, он работает довольно хорошо.

Но когда мне нужно POST aНовая запись в некотором роде, в интересах ясности и надежности, независимо от того, что может делать клиент, я хочу предложить конкретные ошибки проверки, при которых новая запись могла потерпеть неудачу.Кроме того, существуют конкретные ошибки, когда, скажем, данные для создания нового пользователя являются абсолютно действительными, но можно использовать псевдоним или адрес электронной почты.Простое возвращение 409 Conflict не дает достаточно точных сведений о том, какое из псевдонимов или адресов электронной почты было взято.

Так что обойти это не ракетостроение: задокументировать кучу конкретных кодов ошибок и вернуть объект сошибки:

{ errors: [4, 8, 42] }

Это означает, что в случае неудачных запросов я не возвращаю ресурс или его ключ, как это может ожидать философия REST.Точно так же, когда я возвращаю много ресурсов, мне нужно каким-то образом разместить их в массиве.

Поэтому мой вопрос: буду ли я по-прежнему предоставлять веб-сервис RESTful с хорошим поведением, если я стандартизирую конверт для использования?для каждого запроса, такого, что, например, всегда есть объект типа { errors, isSuccessful, content }?

Ранее я создавал веб-службы в стиле RPC, которые использовали это, но я не хочу делать что-то, что «почти ОТДЫХАЕТ».Если и будет какой-то смысл быть ОТДЫХАМ, я бы хотел вести себя как можно лучше.

Если ответ «черт возьми, нет», что, я думаю, возможно, я бы хотелузнайте, правильно ли оно решает проблему проверки, и какой может быть хорошая справка для такого рода решения проблем, потому что в большинстве руководств, которые я нашел, подробно описаны только простые случаи.

Ответы [ 5 ]

24 голосов
/ 19 сентября 2014

Я бы сказал "черт возьми, да!" (вопреки тому, кто здесь сказал «черт возьми, нет») конверту! Всегда есть некоторая дополнительная информация, которую нужно отправить с некоторых конечных точек. Пагинация, errorMessages, debugMessages например. Пример того, как это делает Facebook:

Response from get friends request

{
  "data": [
    {
      "id": "68370", 
      "name": "Magnus"
    }, 
    {
      "id": "726497", 
      "name": "Leon"
    }, 
    {
      "id": "57034", 
      "name": "Gonçalo"
    }
  ], 
  "paging": {
    "next": "https://graph.facebook.com/v2.1/723783051/friends?fields=id,name&limit=5000&offset=5000&__after_id=enc_AeyGEGXHV9pJmWq2OQeWtQ2ImrJmkezZrs6z1WXXdz14Rhr2nstGCSLs0e5ErhDbJyQ"
  }, 
  "summary": {
    "total_count": 200
  }
}

Здесь у нас есть нумерация страниц со следующей ссылкой, чтобы запросить следующую группу пользователей, и сводка с общим количеством друзей, которых можно получить. Однако они не всегда отправляют этот конверт, иногда данные могут идти прямо в корень тела. Постоянная отправка данных одним и тем же способом значительно упрощает анализ данных клиентами, поскольку они могут делать это одинаково для всех конечных точек. Небольшой пример того, как клиенты могут обрабатывать ответы конвертов:

public class Response<T> {
  public T data;
  public Paging paging;
  public Summary summary;
}

public class Paging {
  public String next;
}

public class Summary {
  public int totalCount;
}

public class WebRequest {
  public Response<List<User>> getFriends() {
    String json = FacebookApi.getFriends();
    Response<List<User>> response = Parser.parse(json);
    return response;
  }
}

Этот объект Response можно затем использовать для всех конечных точек, просто изменив List на данные, которые возвращаются конечными точками.

15 голосов
/ 03 апреля 2012

HTTP - это ваш конверт. Вы делаете правильно, возвращая код ошибки 4 **.

Сказав это, нет ничего плохого в том, что в ответе есть описательное тело - на самом деле в HTTP RFC , большинство кодов ошибок HTTP рекомендуют возвращать описание почему произошла ошибка. См. 403, например:

Если метод запроса не был HEAD и сервер желает сообщить, почему запрос не был выполнен, он ДОЛЖЕН описать причину отказа в объекте.

Итак, вы можете продолжать использовать тело ответа для более подробного описания ошибок. Если вы не уверены, какой конкретный ответ об ошибке HTTP использовать (например, несколько ошибок), и знаете, , что пользователь не должен повторять запрос, как он это сделал, я обычно прибегаю к использованию 400.

3 голосов
/ 03 апреля 2012

Я думаю, что, как и во многих особых случаях в REST, вам решать. Я смотрю в Интернете для примеров. Например, когда вы переходите на веб-страницу или URL-адрес, который не существует в WWW, вы обычно получаете 404 и HTML-страницу, на которой обычно есть гипермедиа для какого-либо ресурса. Эта гипермедиа - это то, что служба думает, что вы пытаетесь открыть, или это может быть домашняя страница {URL закладки}. В машинах REST senarios может не использовать HTML в качестве типа носителя, но вы все равно можете вернуть ресурс, который 1) предоставляет подробные сведения об ошибке и 2) предоставляет гипермедиа действительному ресурсу

409 - это код ошибки, который вы не видите в дикой WWW, поэтому вы как бы сами по себе. Я использую 404 в качестве параллели и возвращаю ресурс ошибок, как вы делаете, а также гипермедиа на ресурс, который вызвал 409 в первую очередь. Таким образом, если они намеревались создать то, что спровоцировало конфликт, они могут просто получить его.

Мы стандартизировали, как будут выглядеть ресурсы ошибок, чтобы клиенты знали, как использовать ошибку. Это, конечно же, задокументировано по ссылке на ресурс.

В вашем конкретном случае "псевдоним или адрес электронной почты" я мог видеть, используя 400 или 409, потому что это только одна часть информации ресурса.

Также у нас нет 1 единственного конверта. Мы используем http://stateless.co/hal_specification.html, и ресурс - это то, что они просили, или ошибка.

HTH

2 голосов
/ 03 апреля 2012

Если под «я стандартизировал конверт для использования для каждого запроса», вы буквально имеете в виду каждый запрос, а не только тот, который вы описали, я бы сказал, не делайте этого. В REST мы пытаемся использовать HTTP-джуты, как это делает веб, а не создавать поверх него совершенно новый проприетарный протокол, такой как SOAP. Такой подход делает REST простым и легким в использовании. Если вам интересно, я выложил больше связанных мыслей здесь:

http://theamiableapi.com/2012/03/04/rest-and-the-art-of-protocol-design/

Как говорится, можно возвращать подробное описание ошибки с кодом ошибки HTTP. Ваш первый инстинкт, возвращающий 409 и дополнительные коды ошибок, звучит довольно хорошо для меня. Причина 409 лучше, чем общий 400, в том, что путь обработки ошибок в клиентском коде является более чистым. Некоторые несвязанные ошибки могут вызвать 400, поэтому, если вы используете 400, вам нужно будет проверить, возвращено ли тело сущности, в каком формате оно и т. Д.

0 голосов
/ 24 апреля 2016

Раньше я сопротивлялся идее обернуть ответ из-за накладных расходов на необходимость инкапсулировать каждое действие WebApi.

Тогда я наткнулся на эту статью , которая делает это аккуратно, не требует никаких дополнительных усилий, и это просто работает

Обработчик

public class WrappingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);

        return BuildApiResponse(request, response);
    }

    private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
    {
        object content;
        string errorMessage = null;

        if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
        {
            HttpError error = content as HttpError;

            if (error != null)
            {
                content = null;
                errorMessage = error.Message;

#if DEBUG
                errorMessage = string.Concat(errorMessage, error.ExceptionMessage, error.StackTrace);
#endif
            }
        }

        var newResponse = request.CreateResponse(response.StatusCode, new ApiResponse(response.StatusCode, content, errorMessage));

        foreach (var header in response.Headers)
        {
            newResponse.Headers.Add(header.Key, header.Value);
        }

        return newResponse;
    }
}

Конверт

Пользовательский класс оболочки [DataContract]

public class ApiResponse
{
    [DataMember]
    public string Version { get { return "1.2.3"; } }

    [DataMember]
    public int StatusCode { get; set; }

    [DataMember(EmitDefaultValue = false)]
    public string ErrorMessage { get; set; }

    [DataMember(EmitDefaultValue = false)]
    public object Result { get; set; }

    public ApiResponse(HttpStatusCode statusCode, object result = null, string errorMessage = null)
    {
        StatusCode = (int)statusCode;
        Result = result;
        ErrorMessage = errorMessage;
    }
}

Зарегистрируйся!

в WebApiConfig.cs в App_Start

config.MessageHandlers.Add(new WrappingHandler());
...