Дизайн API запросов RESTful с длинным списком параметров запроса

Мне нужно спроектировать API запросов RESTful, который возвращает набор объектов на основе нескольких фильтров. Обычный метод HTTP для этого - GET. Единственная проблема состоит в том, что он может иметь по крайней мере дюжину фильтров, и если мы передадим все из них в качестве параметров запроса, URL-адрес может быть довольно длинным (достаточно длинным, чтобы быть заблокированным каким-либо брандмауэром).

Сокращение количества параметров не вариант.

Одна альтернатива, о которой я мог бы подумать, - это использовать метод POST для URI и отправить фильтры как часть тела POST. Это против того, чтобы быть RESTfull (сделать POST-вызов для запроса данных).

У кого-нибудь есть лучшие дизайнерские предложения?

Ответ 1

Помните, что с помощью REST API все это касается вашей точки зрения.

Двумя ключевыми понятиями в REST API являются конечные точки и ресурсы (сущности). Понятно, что конечная точка либо возвращает ресурсы через GET, либо принимает ресурсы через POST и PUT и т.д. (Или комбинацию выше).

Принимается, что с помощью POST отправленные вами данные могут или не могут привести к созданию нового ресурса и связанных с ним конечных точек, которые, скорее всего, не будут "жить" под POSTed url. Другими словами, когда вы отправляете POST, вы отправляете данные для обработки. Конечная точка POST не находится там, где обычно может быть найден ресурс.

Цитата из RFC 2616 (с несоответствующими деталями опущены и выделены соответствующие части):

9.5 POST

Метод POST используется для запроса, чтобы исходный сервер принял объект, заключенный в запрос в качестве нового подчиненного ресурса идентифицированных Request-URI в строке запроса. POST предназначен для позволяют единообразный метод охватывать следующие функции:

  • ...
  • Предоставление блока данных, например результата отправки формы, процессу обработки данных;
  • ...

...

Действие, выполняемое методом POST , может не привести к ресурсу, который может быть идентифицирован с помощью URI. В этом случае либо 200 (ОК), либо 204 (Нет содержимого) - это соответствующий статус ответа, в зависимости от , включает ли ответ объект, который описывает результат.

Если ресурс был создан на исходном сервере, ответ ДОЛЖЕН быть 201 (создан)...

Мы привыкли к конечным точкам и ресурсам, представляющим "вещи" или "данные", будь то пользователь, сообщение, книга - независимо от того, что диктует проблемная область. Однако конечная точка также может выставлять другой ресурс - например, результаты поиска.

Рассмотрим следующий пример:

GET    /books?author=AUTHOR
POST   /books
PUT    /books/ID
DELETE /books/ID

Это типичный REST CRUD. Однако что, если бы мы добавили:

POST /books/search

    {
        "keywords": "...",
        "yearRange": {"from": 1945, "to": 2003},
        "genre": "..."
    }

В этой конечной точке нет ничего лишнего. Он принимает данные (сущность) в форме тела запроса. Эти данные являются критериями поиска - DTO, как и любой другой. Эта конечная точка создает ресурс (объект) в ответ на запрос: Результаты поиска. Ресурс результатов поиска является временным, немедленно передается клиенту без переадресации и не подвергается какому-либо другому каноническому URL-адресу.

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

Ответ 2

Многие люди согласились с тем, что GET с слишком длинной или слишком сложной строкой запроса (например, строки запроса не обрабатывают вложенные данные легко) может быть отправлено как POST вместо этого, причем представленные сложные/длинные данные в теле запроса.

Посмотрите спецификацию для POST в спецификации HTTP. Он невероятно широк. (Если вы хотите отплыть линкор через лазейку в REST... используйте POST.)

Вы теряете некоторые преимущества семантики GET... например, автоматические повторы, потому что GET является идемпотентным, но если вы можете жить с ним, может быть проще просто принять обработку длинных или сложных запросов с помощью POST.

(lol long digression... Недавно я обнаружил, что по спецификации HTTP GET может содержать тело документа. Там один раздел, который говорит, перефразируя: "Любой запрос может иметь тело документа, кроме тех, которые перечислены в этом разделе"... и раздел, на который он ссылается, не отображает список. Я искал и нашел поток, в котором об этом говорили авторы HTTP, и это было намеренно, так что маршрутизаторам и таким не пришлось бы различать разные сообщения. Тем не менее, на практике многие объекты инфраструктуры бросают тело GET. Таким образом, вы можете ПОЛУЧИТЬ с фильтрами, представленными в теле, например, POST, но вы будете катать кости.)

Ответ 3

Вкратце: сделайте POST, но переопределите HTTP-метод, используя заголовок X-HTTP-Method-Override.

Реальный запрос

POST/books

Тело сущности

{ "title": "Ipsum", "год": 2017 год }

Заголовки

X-HTTP-метод-переопределение: GET

На стороне сервера проверьте, существует ли заголовок X-HTTP-Method-Override, а затем берет его значение как способ построения маршрута к конечной конечной точке в бэкэнд. Кроме того, возьмите тело объекта как строку запроса. С обратной стороны запрос стал просто GET.

Таким образом, вы держите дизайн в гармонии с принципами REST.

Изменить: Я знаю, что это решение изначально предназначалось для решения проблемы глагола PATCH в некоторых браузерах и серверах, но оно также работает для меня с глаголом GET в случае очень длинного URL-адреса, который является проблемой описанных в вопросе.

Ответ 4

Если вы разрабатываете на Java и JAX-RS, я рекомендую вам использовать @QueryParam с @GET

У меня был тот же вопрос, когда мне нужно было просмотреть список.

Смотрите пример:

import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;

@Path("/poc")
public class UserService {

    @GET
    @Path("/test/")
    @Produces(MediaType.APPLICATION_JSON)
    public Response test(@QueryParam("code") final List<Integer> code) {
                Integer int0 = codigo.get(0);
                Integer int1 = codigo.get(1);

        return Response.ok(new JSONObject().put("int01", int0)).build();
    }
}

Шаблон URI: 'poc/test? code = 1 & code = 2 & code = 3

@QueryParam автоматически преобразует параметр запроса 'orderBy = age & orderBy = name' в java.util.List.