Когда в моем REST API я должен использовать конверт? Если я использую его в одном месте, должен ли я его всегда использовать?

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

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

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

{ errors: [4, 8, 42] }

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

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

Я ранее использовал веб-службы в стиле RPC, которые использовали это, но я не хочу делать что-то "почти REST". Если будет какой-то момент для REST, я бы хотел быть как можно лучше.

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

Ответ 1

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

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

Если метод запроса не был HEAD, и сервер хочет сообщить, почему запрос не был выполнен, СЛЕДУЕТ описать причину отказа в сущности.

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

Ответ 2

Я бы сказал "черт возьми!" (вопреки тому, кто сказал "ад нет" ) в конверт! Всегда есть дополнительная информация, которая должна быть отправлена ​​с некоторых конечных точек. Например, разбиение на страницы, 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 на данные, которые возвращают конечные точки.

Ответ 3

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

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

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

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

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

HTH

Ответ 4

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

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

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

Ответ 5

Я сопротивлялся идее окутывания ответа из-за накладных расходов, требующих инкапсуляции каждого действия 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());