REST URL design for greater than, less than operat

2020-02-20 07:32发布

问题:

I am having a bit of difficulty designing a url for a rest service that can handle requests for customers based on pagination as one type of operation or requesting greater than or less than operators as another type of operation. For example:

Pagination:

GET /customers/0/100

This will get 100 customers for page 0.

Greater/Less Than:

I also need a URL design to get customers that have an id greater than n (e.g. lets say 716). How would you incorporate "greater than" or "less than" in a url. I have to bear in mind that characters ">" and "<" are illegal in urls. I think this url design looks odd:

GET /customers/greaterthan/716
GET /customers/lessthan/716

I can't use a range as that would conflict with the pagination pattern specified above and is not a nice solution in any case e.g.:

GET /customers/716/999999999999
GET /customers/0/716

I'm sure that I'm missing something obvious - does anyone have a better solution?

回答1:

Pagination, greaterthan and lessthan, sound like query parameter to me, since you are queries your resource with these parameters. So you should do something like:

/customers?page=1, or
/customers?page=1&gt=716, or
/customers?page=1&gt=716&lt=819

You can even limit size of page:

/customers?page=1&gt=716&lt=819&maxpagesize=100

where gt stands for greater than (same as in xml-escaping) and lt stands for less than.



回答2:

If you have multiple parameters and need to apply some conditions for each params, I recommend you to pass a JSON object to params.

Consider you want to do a condition for id and the page:

/customers?id={"lt": 100, "gt": 30}&page={"start": 1, "size": 10}

It says that I want customers that have Id(s) less than 100 and greater than 30 in the page 1 and page number of 10.

So now simply if you want to apply another condition for other parameters, you can do it by:

/customers?id={"lt": 100, "gt": 30}&children={"lt": 5, "gt": 2}&page={"start": 1, "size": 10}

and this query means customers with Id(s) less than 100 and greater than 30, children less than 5 and greater than 2 in the page number 1 with page size of 10.

I highly recommend you to read this document about designing RESTful API: http://blog.luisrei.com/articles/rest.html



回答3:

@Julio Faerman:

Well, the problem starts when you get multiple parameters. Imagine the query string for "Customers older than 18 and younger than 60 with more than 2 children".

You can define whatever query parameters you like, such as:

/customers?min-age=19&max-age=59&min-children=3

in my example min and max are integers and are inclusive. You can change that if you prefer. Remember, anything in the URI connotes part of the resource identifier. My personal view is that things after the ? equate to clauses in the WHERE part of an SQL query (plus ORDER BY and LIMIT, not shown here):

SELECT * FROM customers WHERE age>=19 AND age<=59 AND children>=3

Edit:
Instead of min- and max- prefixes, you could allow >, < (and maybe !) as the final character of the parameter name, so instead of min-age you have a parameter called age>, which, when combined with a value in the query string, ends up looking like age>=19 :-)
Obviously you can only use this trick when the comparison has an equals sign in it.



回答4:

REST is an architectural style that should not be considered as specific to HTTP. The pattern of the URIs are not what makes an architecture RESTful.

With that said, you probably would want to make your URI so that these queries come as query parameters at the end of the string, e.g.

/customers?min=0&max=76


回答5:

Let's say you are going to fetch your server logs and suppose that your data looks like this:

{
    "protocol": "http",
    "host": "example.domain.com",
    "path": "/apis/classified/server/logs",
    "method": "GET",
    "ip": "::ffff:127.0.0.1",
    "time": 1483066346338,
    "usingTime": 12,
    "status": 200,
    "headers": {
        "user-agent": "Mozilla/5.0 Chrome"
    }
}

And you want to query like this way, where

  • protocol equals 'http'.
  • host equals 'example.domain.com', or not equals 'example.domain.me'.
  • path equals '/apis/classified/server/logs', or likes /*classified\/server*/.
  • method equals 'DELETE' or not equals 'GET' or in ['POST', 'PUT', 'DELETE'].
  • ip equals '127.0.0.1', or not equals '127.0.0.1'.
  • usingTime is greater than 3500, or is greater than or equal to 1500 and less than or equal to 3500.
  • status equals 404, or not equals 200, or is greater than or equal to 400 and is less than 500.
  • headers.user-agent likes /*chrome*/i.

Here the routes go like this:

  • /apis/classified/server/logs?path=/apis/classfied
  • /apis/classified/server/logs?path.regex=*classfied*
  • /apis/classified/server/logs?method.ne=GET
  • /apis/classified/server/logs?method=POST&method=PATCH&method=PUT
  • /apis/classified/server/logs?usingTime.gte=1500&usingTime.lte=2500
  • /apis/classified/server/logs?headers.user-agent.regex=*chrome*

If you are using express.js as the server, your req.query will be structured like:

  • {"path": "/apis/classfied"}
  • {"path": {"regex": "*classfied*"}}
  • {"method": "DELETE"}
  • {"method": ["GET","POST","PUT"]}
  • {"usingTime": {"gte": "1500","lte": "2500"}} (lte: is less than or equal to)
  • {"status": {"ne": "200"}}} (ne: is Not Equal to)
  • {"path": {"regex": "*classfied*"}}
  • {"headers": {"user-agent": {"regex": "*chrome*", "flag": "i"}}}

And you are gonna use a lot of if-else to make up your mongoose query method, or SQL string maybe.



回答6:

Some REST-based standarts offer adequate approaches to this problem. For example, https://www.hl7.org/fhir/stu3/search.html

Your problem can be solved like that: GET /customers?id=ge500&id=lt1000

Also, there's OData which is far more interoperable that any industry-level standard. And it proposes this style: GET /customers?$filter=id ge 500 and id lt 1000



回答7:

I would implement it as a range, and if either side is open, just do not fill it.

GET /customers/16-
GET /customers/-716

When requesting all customers, do not add all, just leave it empty

GET /customers

Or, when you need - signs in your number/code, use this:

GET /customers/16/
GET /customers//716
GET /customers/16/716

You can escape the forward slashes if they are part of the number.



回答8:

Here is how I have been doing it.

Let's say you have the field age which is a number.

This is what the urls would look like
Equals: /filter/age=5
Greater Than: /filter/age[gt]=5
Greater Than Equals: /filter/age[gte]=5
Less Than: /filter/age[lt]=5
Less Than Equals: /filter/age[lte]=5
Not Equals: /filter/age[ne]=5

Then when I pass these arguments to the back-end I have a script that just parses the key out and converts it to the correct filter based on the age[INSERT_OPERATOR_HERE]



回答9:

WARNING: Read disclaimer below

I dont know if this quetion is related to c#. But if you are using linq for your queries on the server side, I have another suggestion:

GET /customers?filter=CustomerNo>=16 AND CustomerNo<=716

Then, on the server side:

using System.Linq.Dynamic;

var filteredCustomers = myCustomerTable.Where(filter);

This should be the most flexible solution of all, I think.


WARNING added january 19, 2017 It has just come to my attention that a code injection exploit exists for Dynamic Linq. The example above opens up the possibility that the client can start code execution on the server.



回答10:

I'd take

 GET /customers/greaterthan=16;lessthan=716/

as the order is not important. You can even paginate on top:

 GET /customers/greaterthan=16;lessthan=716/page/10

Alternatively (with an appropriate Request router):

 GET /customers/16-716/page/10

And without "filter":

 GET /customers/all/page/10


标签: api url rest