Como desenhar uma pesquisa/filtragem repousante?
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 normal, não é?
O meu problema é com o primeiro. Estava a considerar enviar parâmetros no corpo do pedido para filtrar a lista. Isto porque eu quero ser capaz de especificar filtros complexos sem obter um url super longo, como:
GET /users?parameter1=value1¶meter2=value2¶meter3=value3¶meter4=value4
Em vez disso, queria ter alguma coisa. tipo:
GET /users
# Request body:
{
"parameter1": "value1",
"parameter2": "value2",
"parameter3": "value3",
"parameter4": "value4"
}
que é muito mais legível e lhe dá grandes possibilidades de definir filtros complexos.
De qualquer forma, não devolvi o corpo do pedido para pedidos. Eu também tenteihttp_get_request_body()
, mas a hospedagem compartilhada que eu estou usando não tem pecl_http
. Não sei se teria ajudado.
Encontrei esta pergunta e percebi que o GET provavelmente não devia ter um corpo de pedido. Foi um pouco inconclusivo, mas aconselharam-no a não o fazer.
Então agora não sei o que fazer. Como é que se desenha uma função RESTful search/filterng?
Acho que preciso, mas não me parece muito descansado.7 answers
A melhor maneira de implementar uma busca repousante é considerar a busca em si como um recurso. Então você pode usar o verbo POST porque você está criando uma busca. Você não tem que literalmente criar algo em um banco de dados, a fim de usar um POST.
Por exemplo:
Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
"terms": {
"ssn": "123456789"
},
"order": { ... },
...
}
Está a criar uma pesquisa do ponto de vista do utilizador. Os pormenores de aplicação são irrelevantes. Algumas API descansadas podem nem mesmo precisar de persistência. Trata-se de um pormenor de execução.
Se você usar o corpo do pedido em um pedido do GET, você está quebrando o princípio do resto, porque seu pedido do GET não será capaz de ser cache, porque o sistema de cache usa apenas a URL.
E o que é pior, a sua URL não pode ser marcada, porque a URL não contém todas as informações necessárias para redireccionar o utilizador para esta página
Utilize URL ou parâmetros da consulta em vez de parâmetros do corpo do pedido.
Por exemplo:
/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx
Na verdade, o HTTP RFC 7231 diz que: [3]
uma carga útil dentro de uma mensagem de pedido de GET não tem semântica definida; enviar um corpo de carga útil em um pedido de GET pode causar algumas implementações existentes para rejeitar o pedido.
Para mais informações, dê uma vista de olhos aqui.Parece que a filtragem/pesquisa de recursos pode ser implementada de uma forma repousante. A idéia é introduzir um novo endpoint chamado /filters/
ou /api/filters/
.
Usando este parâmetro O filtro pode ser considerado como um recurso e, portanto, criado através do método POST
. Desta forma - é claro-o corpo pode ser usado para carregar todos os parâmetros, bem como estruturas complexas de pesquisa/filtro podem ser criadas.
Depois de criar esse filtro, existem duas possibilidades de obter o filtro/pesquisa resultado.
-
Um novo recurso com ID único será devolvido juntamente com o código de Estado
201 Created
. Em seguida, usando este ID aGET
pedido pode ser feito para/api/users/
Como:GET /api/users/?filterId=1234-abcd
Após o novo filtro ser criado através de
POST
, não irá responder com201 Created
mas de uma vez com303 SeeOther
, juntamente com o cabeçalhoLocation
apontando para/api/users/?filterId=1234-abcd
. Este redirecionamento será tratado automaticamente através da biblioteca subjacente.
POST
para /api/users/filter/
.
Como manter os filtros criados?
Podem ser armazenados em DB e utilizados posteriormente. Eles também podem ser armazenados em algum armazenamento temporário, por exemplo redis e ter algum TTL após o qual eles vão expirar e serão removidos.
quais são as vantagens desta ideia?
Filtros, os resultados filtrados são cacheable e pode até ser marcado.
Eu acho que você deve ir com os parâmetros do pedido, mas apenas enquanto não houver um cabeçalho HTTP apropriado para realizar o que você quer fazer. A especificação HTTP não diz explicitamente, que o GET não pode ter um corpo. No entanto este documento diz:
Por convenção, quando o método de obtenção é utilizado, todas as informações necessárias identificar o recurso em que está codificado o URI. Não existe convenção em HTTP / 1.1 para uma interação segura (e.g., recuperacao) onde o cliente fornece dados para o servidor em uma entidade HTTP corpo e não na parte da consulta de a URI. Isto significa que, por segurança operações, URIs pode ser longo.
Não se preocupe muito se a sua API inicial estiver totalmente descansada ou não (especialmente quando estiver apenas nos estágios Alfa). Pôr a canalização a funcionar primeiro. Você pode sempre fazer algum tipo de transformação de URL/re-escrita para mapear as coisas, refinando iterativamente até que você obtenha algo estável o suficiente para testes generalizados ("beta").
Você pode definir URIs cujos parâmetros são codificados pela posição e Convenção nos próprios URIs, prefixados por um caminho que você sabe que irá sempre mapear a alguma coisa. Eu não sei PHP, mas eu assumiria que tal facilidade existe (como existe em outras línguas com frameworks web):
. ie.do a "user" type of search with param[I]=value[i] for i=1..4 on store #1 (with value1,value2,value3,... como abreviatura para parâmetros de pesquisa URI):
1) GET /store1/search/user/value1,value2,value3,value4
Ou
2) GET /store1/search/user,value1,value2,value3,value4
Ou como se segue (embora eu não o recomendasse, 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 com /store1/search/user
para o controlador de pesquisa (ou seja qual for a designação do PHP) faltando para fazer buscas por recursos sob store1 (equivalente a /search?location=store1&type=user
.
Por convenção documentada e aplicada pela API, os valores 1 a 4 são separados por vírgulas e apresentados por essa ordem.
A Opção 2 adiciona o tipo de pesquisa (neste caso user
) como parâmetro posicional #1. Qualquer das opções é apenas uma escolha cosmética.
A vantagem disto sobre os parâmetros de passagem no URI é que a pesquisa é parte do URI (assim 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.
Uma vez que faças algo assim, podes usar o GET, e ... seria um recurso apenas de leitura (uma vez que você não pode postar ou colocá - lo-ele é atualizado quando ele é GET'ed). Seria também um recurso que só existe quando é invocado.Também se pode adicionar mais semântica a ele, Cache os resultados por um período de tempo ou com uma DELETE fazendo com que o cache seja excluído. Isto, no entanto, pode ser contrário ao que as pessoas normalmente usam para excluir (e porque as pessoas tipicamente controlam cache com cabeçalhos de cache.)
Como vais fazer? seria uma decisão de design, mas seria assim que eu faria. Não é perfeito, e estou certo de que haverá casos em que fazer isso não é a melhor coisa a fazer (especialmente para critérios de pesquisa muito complexos).Como estou a usar uma infra-estruturalaravel/php tenho tendência a usar algo assim:
/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource
O PHP transforma automaticamente []
Os parâmetros num array, por isso neste exemplo vou acabar com uma variável $filter
que contém um array/objecto de filtros, juntamente com uma página e quaisquer recursos relacionados que eu queira carregar eager.
Se você usar outra língua, esta ainda pode ser uma boa Convenção e você pode criar um analisador para converter []
para uma matriz.
GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2
Eu sei que é feio, mas acho que é a maneira mais descansada de o fazer e deve ser fácil de processar do lado do servidor.