可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I\'m currently designing and implementing a RESTful API in PHP. However, I have been unsuccessful implementing my initial design.
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
So far pretty standard, right?
My problem is with the first one GET /users
. I was considering sending parameters in the request body to filter the list. This is because I want to be able to specify complex filters without getting a super long url, like:
GET /users?parameter1=value1¶meter2=value2¶meter3=value3¶meter4=value4
Instead I wanted to have something like:
GET /users
# Request body:
{
\"parameter1\": \"value1\",
\"parameter2\": \"value2\",
\"parameter3\": \"value3\",
\"parameter4\": \"value4\"
}
which is much more readable and gives you great possibilities to set complex filters.
Anyway, file_get_contents(\'php://input\')
didn\'t return the request body for GET
requests. I also tried http_get_request_body()
, but the shared hosting that I\'m using doesn\'t have pecl_http
. Not sure it would have helped anyway.
I found this question and realized that GET probably isn\'t supposed to have a request body. It was a bit inconclusive, but they advised against it.
So now I\'m not sure what to do. How do you design a RESTful search/filterng function?
I suppose I could use POST
, but that doesn\'t seem very RESTful.
回答1:
The best way to implement a RESTful search is to consider the search itself to be a resource. Then you can use the POST verb because you are creating a search. You do not have to literally create something in a database in order to use a POST.
For example:
Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
\"terms\": {
\"ssn\": \"123456789\"
},
\"order\": { ... },
...
}
You are creating a search from the user\'s standpoint. The implementation details of this are irrelevant. Some RESTful APIs may not even need persistence. That is an implementation detail.
回答2:
If you use the request body in a GET request, you\'re breaking the REST principle, because your GET request won\'t be able to be cached, because cache system uses only the URL.
And what\'s worse, your URL can\'t be bookmarked, because the URL doesn\'t contains all the information needed to redirect the user to this page
Use URL or Query parameters instead of request body parameters.
e.g.:
/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx
In fact, the HTTP RFC 7231 says that:
A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.
For more information take a look at here
回答3:
It seems that resource filtering/searching can be implemented in a RESTful way. The idea is to introduce a new endpoint called /filters/
or /api/filters/
.
Using this endpoint filter can be considered as a resource and hence created via POST
method. This way - of course - body can be used to carry all the parameters as well as complex search/filter structures can be created.
After creating such filter there are two possibilities to get the search/filter result.
A new resource with unique ID will be returned along with 201 Created
status code. Then using this ID a GET
request can be made to /api/users/
like:
GET /api/users/?filterId=1234-abcd
After new filter is created via POST
it won\'t reply with 201 Created
but at once with 303 SeeOther
along with Location
header pointing to /api/users/?filterId=1234-abcd
. This redirect will be automatically handled via underlying library.
In both scenarios two requests need to be made to get the filtered results - this may be considered as a drawback, especially for mobile applications. For mobile applications I\'d use single POST
call to /api/users/filter/
.
How to keep created filters?
They can be stored in DB and used later on. They can also be stored in some temporary storage e.g. redis and have some TTL after which they will expire and will be removed.
What are the advantages of this idea?
Filters, filtered results are cacheable and can be even bookmarked.
回答4:
I think you should go with request parameters but only as long as there isn\'t an appropriate HTTP header to accomplish what you want to do. The HTTP specification does not explicitly say, that GET can not have a body. However this paper states:
By convention, when GET method is
used, all information required to
identify the resource is encoded in
the URI. There is no convention in
HTTP/1.1 for a safe interaction (e.g.,
retrieval) where the client supplies
data to the server in an HTTP entity
body rather than in the query part of
a URI. This means that for safe
operations, URIs may be long.
回答5:
Don\'t fret too much if your initial API is fully RESTful or not (specially when you are just in the alpha stages). Get the back-end plumbing to work first. You can always do some sort of URL transformation/re-writing to map things out, refining iteratively until you get something stable enough for widespread testing (\"beta\").
You can define URIs whose parameters are encoded by position and convention on the URIs themselves, prefixed by a path you know you\'ll always map to something. I don\'t know PHP, but I would assume that such a facility exists (as it exists in other languages with web frameworks):
.ie. Do a \"user\" type of search with param[i]=value[i] for i=1..4 on store #1 (with value1,value2,value3,... as a shorthand for URI query parameters):
1) GET /store1/search/user/value1,value2,value3,value4
or
2) GET /store1/search/user,value1,value2,value3,value4
or as follows (though I would not recommend it, more on that later)
3) GET /search/store1,user,value1,value2,value3,value4
With option 1, you map all URIs prefixed with /store1/search/user
to the search handler (or whichever the PHP designation) defaulting to do searches for resources under store1 (equivalent to /search?location=store1&type=user
.
By convention documented and enforced by the API, parameters values 1 through 4 are separated by commas and presented in that order.
Option 2 adds the search type (in this case user
) as positional parameter #1. Either option is just a cosmetic choice.
Option 3 is also possible, but I don\'t think I would like it. I think the ability of search within certain resources should be presented in the URI itself preceding the search itself (as if indicating clearly in the URI that the search is specific within the resource.)
The advantage of this over passing parameters on the URI is that the search is part of the URI (thus treating a search as a resource, a resource whose contents can - and will - change over time.) The disadvantage is that parameter order is mandatory.
Once you do something like this, you can use GET, and it would be a read-only resource (since you can\'t POST or PUT to it - it gets updated when it\'s GET\'ed). It would also be a resource that only comes to exist when it is invoked.
One could also add more semantics to it by caching the results for a period of time or with a DELETE causing the cache to be deleted. This, however, might run counter to what people typically use DELETE for (and because people typically control caching with caching headers.)
How you go about it would be a design decision, but this would be the way I\'d go about. It is not perfect, and I\'m sure there will be cases where doing this is not the best thing to do (specially for very complex search criteria).
回答6:
As I\'m using a laravel/php backend I tend to go with something like this:
/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource
PHP automatically turns []
params into an array, so in this example I\'ll end up with a $filter
variable that holds an array/object of filters, along with a page and any related resources I want eager loaded.
If you use another language, this might still be a good convention and you can create a parser to convert []
to an array.
回答7:
FYI: I know this is a bit late but for anyone who is interested.
Depends on how RESTful you want to be, you will have to implement your own filtering strategies as the HTTP spec is not very clear on this. I\'d like to suggest url-encoding all the filter parameters e.g.
GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2
I know it\'s ugly but I think it\'s the most RESTful way to do it and should be easy to parse on the server side :)