Como projetar a pesquisa / filtragem RESTful? [fechadas]

457

Atualmente, estou projetando e implementando uma API RESTful em PHP. No entanto, não consegui implementar meu design inicial.

GET /users # list of users
GET /user/1 # get user with id 1
POST /user # create new user
PUT /user/1 # modify user with id 1
DELETE /user/1 # delete user with id 1

Até agora, bastante padrão, certo?

Meu problema é com o primeiro GET /users. Eu estava pensando em enviar parâmetros no corpo da solicitação para filtrar a lista. Isso ocorre porque eu quero poder especificar filtros complexos sem obter um URL super longo, como:

GET /users?parameter1=value1&parameter2=value2&parameter3=value3&parameter4=value4

Em vez disso, eu queria ter algo como:

GET /users
# Request body:
{
    "parameter1": "value1",
    "parameter2": "value2",
    "parameter3": "value3",
    "parameter4": "value4"
}

o que é muito mais legível e oferece grandes possibilidades para definir filtros complexos.

De qualquer forma, file_get_contents('php://input')não retornou o corpo da GETsolicitação. Eu também tentei http_get_request_body(), mas a hospedagem compartilhada que estou usando não tem pecl_http. Não tenho certeza de que teria ajudado de qualquer maneira.

Encontrei essa pergunta e percebi que o GET provavelmente não deveria ter um corpo de solicitação. Foi um pouco inconclusivo, mas eles desaconselharam.

Então agora não tenho certeza do que fazer. Como você cria uma função de pesquisa / filtragem RESTful?

Suponho que eu poderia usar POST, mas isso não parece muito RESTful.

Erik B
fonte
7
possível duplicação do design de URL RESTful para pesquisa
out 2/12/12
60
Seja cuidadoso!!! O método GET deve ser IDEMPOTENT e deve ser "armazenável em cache". Se você enviar informações no corpo Como o sistema pode armazenar em cache sua solicitação? O HTTP permite armazenar em cache a solicitação GET usando apenas a URL, não o corpo da solicitação. Por exemplo, essas duas solicitações: example.com {test: "some"} example.com {anotherTest: "some2"} são consideradas iguais pelo sistema de cache: ambas têm exatamente a mesma URL
jfcorugedo
15
Apenas para adicionar, você deve POSTAR para / users (coleção) e não / user (usuário único).
precisa
1
Outro ponto a considerar é que a maioria dos servidores de aplicativos tem logs de acesso que registram a URL e, portanto, pode ser algo entre eles. Portanto, pode haver algum vazamento de informação não intencional no GET.
user3206144
2
Possível duplicata do design de URL RESTful para pesquisa
ivan_pozdeev 17/11

Respostas:

396

A melhor maneira de implementar uma pesquisa RESTful é considerar a própria pesquisa como um recurso. Em seguida, você pode usar o verbo POST porque está criando uma pesquisa. Você não precisa literalmente criar algo em um banco de dados para usar um POST.

Por exemplo:

Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
  "terms": {
    "ssn": "123456789"
  },
  "order": { ... },
  ...
}

Você está criando uma pesquisa do ponto de vista do usuário. Os detalhes de implementação são irrelevantes. Algumas APIs RESTful podem nem precisar de persistência. Esse é um detalhe de implementação.

Jason Harrelson
fonte
209
Uma limitação significativa ao uso de uma solicitação POST para um terminal de pesquisa é que ele não pode ser marcado. Marcar os resultados da pesquisa (consultas particularmente complexas) pode ser bastante útil.
precisa saber é o seguinte
73
O uso do POST para fazer pesquisas pode quebrar a restrição de cache REST. whatisrest.com/rest_constraints/cache_excerps
Filipe
56
As pesquisas, por natureza, são transitórias: os dados evoluem entre duas pesquisas com os mesmos parâmetros, portanto, acho que uma solicitação GET não é mapeada corretamente para o padrão de pesquisa. Em vez disso, a solicitação de pesquisa deve ser POST (/ Resource / search), então você pode salvar essa pesquisa e redirecionar para um resultado de pesquisa, por exemplo, / Resource / search / iyn3zrt. Dessa forma, as solicitações GET são bem-sucedidas e fazem sentido.
sleblanc
32
Não acho que o post seja o método adequado para pesquisar, os dados para solicitações GET normais também podem variar ao longo do tempo.
pergunto
82
Esta é absolutamente a pior resposta possível. Não acredito que tenha tantos votos positivos. Esta resposta explica o porquê: programmers.stackexchange.com/questions/233164/…
richard
141

Se você usar o corpo da solicitação em uma solicitação GET, estará violando o princípio REST, porque sua solicitação GET não poderá ser armazenada em cache, porque o sistema de cache usa apenas a URL.

E o pior é que seu URL não pode ser marcado como favorito, porque o URL não contém todas as informações necessárias para redirecionar o usuário para esta página.

Use parâmetros de URL ou consulta em vez de parâmetros do corpo da solicitação.

por exemplo:

/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx 

De fato, o HTTP RFC 7231 diz que:

Uma carga útil dentro de uma mensagem de solicitação GET não possui semântica definida; o envio de um corpo de carga útil em uma solicitação GET pode fazer com que algumas implementações existentes rejeitem a solicitação.

Para mais informações, dê uma olhada aqui

jfcorugedo
fonte
29
Aprenda com meu erro - projetei uma API usando a sugestão de resposta aceita (POSTing json), mas estou passando para os parâmetros de URL. A capacidade de marcar marcadores pode ser mais importante do que você pensa. No meu caso, havia uma necessidade de direcionar o tráfego para determinadas consultas de pesquisa (campanha publicitária). Além disso, o uso da API de histórico faz mais sentido com os parâmetros de URL.
Jake
2
Depende de como é usado. Se você está vinculando a um URL que carrega a página com base nesses parâmetros, faz sentido, mas se a página principal está fazendo uma chamada AJAX apenas para obter os dados com base nos parâmetros do filtro, você não pode marcar isso de qualquer maneira, porque é um chamada ajax e não tem rumo. Naturalmente, você também pode marcar um URL que, quando for criar um filtro e POSTs para a chamada ajax, funcionaria perfeitamente.
Daniel Lorenz
@DanielLorenz Para obter a melhor experiência do usuário, o URL ainda deve ser alterado por meio da API do histórico. Não suporto quando um site não permite o uso da funcionalidade voltar do navegador para navegar para as páginas anteriores. E, se for uma página gerada no lado do servidor padrão, a única maneira de torná-lo possível de ser marcado é usar uma solicitação GET. Parece que bons parâmetros de consulta são a melhor solução.
Nathan
@ Nathan Acho que interpretei mal esta resposta. Eu estava falando sobre o uso de parâmetros de seqüência de caracteres de consulta em um get. Você nunca deve usar parâmetros do corpo em uma chamada GET, porque isso seria completamente inútil. Eu estava falando mais sobre um GET com string de consulta que poderia ser usado / marcado e, na inicialização da página, você pode usar esses parâmetros para criar um filtro para o POST, usando esses parâmetros para obter os dados. A história ainda funcionaria bem nesse cenário.
Daniel Lorenz
@DanielLorenz Ah, tudo bem, isso faz sentido. Acho que não entendi o que você estava dizendo.
Nathan
70

Parece que a filtragem / pesquisa de recursos pode ser implementada de maneira RESTful. A idéia é introduzir um novo terminal chamado /filters/or /api/filters/.

O uso desse filtro de terminal pode ser considerado como um recurso e, portanto, criado via POSTmétodo. Dessa maneira, é claro, o corpo pode ser usado para transportar todos os parâmetros, bem como estruturas complexas de pesquisa / filtro.

Depois de criar esse filtro, há duas possibilidades para obter o resultado da pesquisa / filtro.

  1. Um novo recurso com ID exclusivo será retornado junto com o 201 Createdcódigo de status. Em seguida, usando esse ID, uma GETsolicitação pode ser feita /api/users/como:

    GET /api/users/?filterId=1234-abcd
    
  2. Após a criação do novo filtro, POSTele não responderá com, 201 Createdmas ao mesmo tempo 303 SeeOtherjunto com o Locationcabeçalho apontando para /api/users/?filterId=1234-abcd. Esse redirecionamento será tratado automaticamente através da biblioteca subjacente.

Nos dois cenários, é necessário fazer duas solicitações para obter os resultados filtrados - isso pode ser considerado uma desvantagem, especialmente para aplicativos móveis. Para aplicativos móveis, eu usaria uma POSTchamada para /api/users/filter/.

Como manter os filtros criados?

Eles podem ser armazenados no DB e usados ​​posteriormente. Eles também podem ser armazenados em algum armazenamento temporário, por exemplo, redis e possuem algum TTL após o qual expiram e serão removidos.

Quais são as vantagens dessa idéia?

Filtros, resultados filtrados são armazenados em cache e podem até ser marcados.

Opala
fonte
2
bem, esta deve ser a resposta aceita. Você não viola os princípios REST e pode fazer longas e complexas consultas aos recursos. É bom, limpo e compatível com favoritos. A única desvantagem adicional é a necessidade de armazenar pares de chave / valor para filtros criados e as duas etapas de solicitação já mencionadas.
Dantebarba 2/11
2
A única preocupação com essa abordagem é se você tiver filtros de data e hora na consulta (ou um valor em constante mudança). Em seguida, o número de filtros para armazenar no banco de dados (ou cache) é inumerável.
Rvy Pandey
17

Eu acho que você deve seguir os parâmetros de solicitação, mas apenas enquanto não houver um cabeçalho HTTP apropriado para realizar o que deseja fazer. A especificação HTTP não diz explicitamente que GET não pode ter um corpo. No entanto, este documento afirma:

Por convenção, quando o método GET é usado, todas as informações necessárias para identificar o recurso são codificadas no URI. Não há convenção no HTTP / 1.1 para uma interação segura (por exemplo, recuperação) em que o cliente fornece dados ao servidor em um corpo de entidade HTTP, e não na parte de consulta de um URI. Isso significa que, para operações seguras, os URIs podem ser longos.

Daff
fonte
6
ElasticSearch também obtém com corpo e funciona bem!
Tarun Sapra
Sim, mas eles controlam a implementação do servidor, talvez não seja o caso nas interwebs.
usar o seguinte comando
7

Como eu estou usando um back-end do laravel / php, eu costumo usar algo assim:

/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource

O PHP transforma automaticamente os []parâmetros em uma matriz, portanto, neste exemplo, terminarei com uma $filtervariável que contém uma matriz / objeto de filtros, juntamente com uma página e quaisquer recursos relacionados que eu queira carregar com urgência.

Se você usar outro idioma, isso ainda poderá ser uma boa convenção e você poderá criar um analisador para converter []em uma matriz.

o trem
fonte
Esta abordagem parece bom, mas poderia haver problemas com o uso de colchetes em URLs, consulte que-personagens-can-um-use-in-a-url
Sky
2
@ Sky Isso pode ser evitado com a codificação URI de [e ]. Usar representações codificadas desses caracteres para agrupar parâmetros de consulta é uma prática bem conhecida. É usado até na especificação JSON: API .
jelhan
6

Não se preocupe muito se sua API inicial for totalmente RESTful ou não (especialmente quando você está apenas nos estágios alfa). Faça com que o encanamento de back-end funcione primeiro. Você sempre pode fazer algum tipo de transformação / reescrita de URL para mapear as coisas, refinando iterativamente até obter algo estável o suficiente para testes generalizados ("beta").

Você pode definir URIs cujos parâmetros são codificados por posição e convenção nos próprios URIs, prefixados por um caminho que você sabe que sempre mapeará para alguma coisa. Eu não sei PHP, mas suponho que esse recurso exista (como existe em outras linguagens com estruturas da web):

.ie. Faça um tipo de pesquisa "usuário" com o parâmetro [i] = valor [i] para i = 1..4 na loja 1 (com valor1, valor2, valor3, ... como uma abreviação para os parâmetros de consulta do URI):

1) GET /store1/search/user/value1,value2,value3,value4

ou

2) GET /store1/search/user,value1,value2,value3,value4

ou da seguinte forma (embora eu não o recomende, mais sobre isso mais tarde)

3) GET /search/store1,user,value1,value2,value3,value4

Com a opção 1, você mapeia todos os URIs prefixados /store1/search/userpara o manipulador de pesquisa (ou qualquer que seja a designação do PHP) por padrão para fazer pesquisas por recursos em store1 (equivalente a /search?location=store1&type=user.

Por convenção documentada e aplicada pela API, os valores de parâmetros 1 a 4 são separados por vírgulas e apresentados nessa ordem.

A opção 2 adiciona o tipo de pesquisa (neste caso user) como parâmetro posicional # 1. Qualquer uma das opções é apenas uma escolha cosmética.

A opção 3 também é possível, mas acho que não gostaria. Eu acho que a capacidade de pesquisa dentro de certos recursos deve ser apresentada no próprio URI que precede a pesquisa em si (como se estivesse indicando claramente no URI que a pesquisa é específica dentro do recurso).

A vantagem disso ao passar parâmetros no URI é que a pesquisa faz parte do URI (tratando uma pesquisa como um recurso, um recurso cujo conteúdo pode - e irá - mudar ao longo do tempo.) A desvantagem é que a ordem dos parâmetros é obrigatória .

Depois de fazer algo assim, você pode usar GET, e ele seria um recurso somente leitura (já que você não pode POST ou PUT) - ele é atualizado quando é obtido. Também seria um recurso que só existe quando é invocado.

Também se pode adicionar mais semântica armazenando em cache os resultados por um período de tempo ou com um DELETE fazendo com que o cache seja excluído. Isso, no entanto, pode ser contrário ao que as pessoas normalmente usam DELETE (e porque as pessoas normalmente controlam o cache com cabeçalhos de cache).

Como você agiria seria uma decisão de design, mas seria assim que eu agiria. Não é perfeito, e tenho certeza de que haverá casos em que fazer isso não é a melhor coisa a se fazer (especialmente para critérios de pesquisa muito complexos).

luis.espinal
fonte
7
Sim, se você (alguém, quem / o que quer que seja) se apropriar de voto negativo na minha resposta, machucaria seu ego ao menos colocar um comentário indicando o que exatamente você discorda? Eu sei que é o interweebz, mas ...;)
luis.espinal
107
Não diminuí a votação, mas o fato de a pergunta começar com: "No momento, estou projetando e implementando uma API RESTful" e sua resposta começa com "Não se preocupe muito se sua API inicial for totalmente RESTful ou não" errado para mim. Se você está criando uma API, está criando uma API. A questão é perguntar como projetar melhor a API, não se a API deve ser projetada.
gardarh
14
A API é o sistema, trabalha primeiro na API, não no encanamento de back-end, a primeira implementação pode / deve ser apenas uma farsa. O HTTP tem um mecanismo para transmitir parâmetros, você está sugerindo que seja reinventado, mas pior (parâmetros ordenados em vez de pares de valores de nomes). Daí o voto negativo.
Steven Herod
14
@ Gardarh - sim, parece errado, mas às vezes é pragmático. O objetivo principal é projetar uma API que funcione para o contexto de negócios em questão. Se uma abordagem totalmente RESTFULL for apropriada para os negócios em questão, faça isso. Se não for, não tente. Ou seja, crie uma API que atenda aos seus requisitos comerciais específicos. Sair por aí tentando torná-lo RESTfull como seu principal requisito não é muito diferente de perguntar "como faço para usar o padrão do adaptador no problema X / Y". Não use paradigmas de buzina, a menos que resolvam problemas reais e valiosos.
Luis.espinal
1
Eu vejo um recurso como uma coleção de estados e parâmetros como um meio de manipular parametricamente a representação desse estado. Pense dessa maneira: se você pudesse usar botões e interruptores para ajustar a maneira como o recurso é exibido (mostrar / ocultar certos bits dele, ordená-lo de forma diferente, etc ...), esses controles são parâmetros. Se é realmente um recurso diferente ('/ albums' vs '/ artists', por exemplo), é quando deve ser representado no caminho. Isso é o que é intuitivo para mim, de qualquer maneira.
Eric Elliott
2

FYI: Eu sei que é um pouco tarde, mas para quem estiver interessado. Depende do quão RESTful você deseja ser, você terá que implementar suas próprias estratégias de filtragem, pois a especificação HTTP não é muito clara. Gostaria de sugerir a codificação de URL de todos os parâmetros de filtro, por exemplo

GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2

Eu sei que é feio, mas acho que é a maneira mais RESTful de fazer isso e deve ser fácil de analisar no lado do servidor :)

canelas
fonte