restful API设计

版权声明:本文系原创,转发请注明出处,商业用途联系作者 https://blog.csdn.net/wenxueliu/article/details/83962182

RestFul API

Why RestFul

  • Scalability
  • Generality
  • Independence
  • Latency
  • Security
  • Encapsulation

Why Json

  • Ubiquity
  • Simplicity
  • Readability
  • Scalability
  • Flexibility

Content

First you should generate or write the reference information including:

  • Method (GET/PUT/POST/PATCH/HEAD/DELETE)
  • Resource (Identified by the URL)
  • Request parameters, type and description including whether it
    is optional
  • Request headers including media-type, content-type, accept, and others
  • Response headers (for some APIs)
  • Responses including types and description
  • Example request body and headers
  • Example response body and headers
  • Status codes: successful request and error responses
  • Resource model: describes data types that can be consumed and produced by
    operations.

The resource model may be created with either a parameters.yaml file or with
the OpenAPI Definitions object <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitionsObject>_
Swagger is governed by the OpenAPI initiative.

See authoring tools below for more information on writing or generating the
parameters information.

Also important is the conceptual or narrative information that explains the
REST API and what it provides as a service.

Documentation should also offer information about the concepts for each
resource, such as server status or volume status.

Documentation should provide a discussion about the consistency model the
service provides and synchronous or asynchronous behavior for certain methods.

As guidance, here is a list of topics to ensure you include in your API docs
beyond the reference information.

  • Authentication
  • Faults (synchronous and asynchronous)
  • Limits (rate limits, absolute limits, calls to find out limits)
  • Constraints (min and max for certain values even if chosen by provider)
  • Content compression
  • Encoding
  • Links and references
  • Pagination
  • Filtering
  • Sorting
  • Formats (request, response)
  • ETags
  • Endpoints, versions and discoverability
  • Capabilities and discoverability
  • Term definitions
    (by adding to the OpenStack glossary sourced in openstack-manuals)
  • Status or state values
  • Hierarchy information
  • Quotas
  • Extensions

Use nouns but no verbs

collection resource : /cars
instance resource : /cars/711

Resource 	    GET                         POST                    PUT                         DELETE
/cars 	    Returns a list of cars 	Create a new ticket 	    Bulk update of cars 	      Delete all cars
/cars/711 	Returns a specific car 	Method not allowed (405) 	Updates a specific ticket 	Deletes a specific ticket

Note: camelCase name rule is conventional for javascript

Timestamp : ISO 8601 UTC

{
“createAt” : “2018-03-21T18:00:00.234Z”
“upateAt” : “2018-03-22T18:00:00.234Z”
}

Use HTTP headers for serialization formats

Both, client and server, need to know which format is used for the communication. The format has to be specified in the HTTP-Header.

  • Content-Type defines the request format.
  • Accept defines a list of acceptable response formats.

Use HATEOAS

Hypermedia as the Engine of Application State is a principle that hypertext links should be used to create a better navigation through the API.

{
  "id": 711,
  "manufacturer": "bmw",
  "model": "X5",
  "seats": 5,
  "drivers": [
   {
    "id": "23",
    "name": "Stefan Jauker",
    "links": [
     {
     "rel": "self",
     "href": "/api/v1/drivers/23"
    }
   ]
  }
 ]
}

Provide filtering, sorting, field selection and paging for collections

Filtering:

Use a unique query parameter for all fields or a query language for filtering.

GET /cars?color=red Returns a list of red cars
GET /cars?seats<=2 Returns a list of cars with a maximum of 2 seats

Sorting:

Allow ascending and descending sorting over multiple fields.

GET /cars?sort=-manufactorer,+model

This returns a list of cars sorted by descending manufacturers and ascending models.

Field selection

Mobile clients display just a few attributes in a list. They don’t need all attributes of a resource. Give the API consumer the ability to choose returned fields. This will also reduce the network traffic and speed up the usage of the API.

GET /cars?fields=manufacturer,model,id,color

Paging

Use limit and offset. It is flexible for the user and common in leading databases. The default should be limit=20 and offset=0

GET /cars?offset=10&limit=5

To send the total entries back to the user use the custom HTTP header: X-Total-Count.

Range

GET /cars?createAt=[2000-10-11, 2010-10-12]

Links to the next or previous page should be provided in the HTTP header link as well. It is important to follow this link header values instead of constructing your own URLs.

Link: <https://blog.mwaysolutions.com/sample/api/v1/cars?offset=15&limit=5>; rel="next",
<https://blog.mwaysolutions.com/sample/api/v1/cars?offset=50&limit=3>; rel="last",
<https://blog.mwaysolutions.com/sample/api/v1/cars?offset=0&limit=5>; rel="first",
<https://blog.mwaysolutions.com/sample/api/v1/cars?offset=5&limit=5>; rel="prev",

Links

Links to other resources often need to be represented in responses. There is already
a well established format for this representation in JSON Hyper-Schema: Hypertext definitions for JSON Schema. This is already the prevailing representation in use by a number of prominent OpenStack projects and also in use by the Errors guideline.

{
“links”: [
{
“rel”: “help”,
“href”: “http://developer.openstack.org/api-ref/compute/#create-server
}
]
}

Version your API

Make the API Version mandatory and do not release an unversioned API. Use a simple ordinal number and avoid dot notation such as 2.5.

We are using the url for the API versioning starting with the letter „v“

/blog/api/v1

When an API has multiple versions, we recommend:

Two versions should be supported at the same time.
If two or more versions are available, applications may provide a shortcut to the latest version using the latest key-word.

For example, if versions 1 and 2 of the ‘blog’ API are available, the following two URIs would point to the same resources:

/blog/api/v2
/blog/api/lastest

REST API supports two types of versioning

"major versions", which have dedicated urls.
"microversions", which can be requested through the use of the X-OpenStack-Ironic-API-Version header.

There is only one major version supported currently, “v1”. As such, most URLs in this documentation are written with the “/v1/” prefix.

a version is defined as a string of 2 integers separated by a dot: X.Y. Here X is a major version, always equal to 1,
and Y is a minor version. Server minor version is increased every time the API behavior is changed

The server indicates its minimum and maximum supported API versions in the X-OpenStack-Ironic-API-Minimum-Version
and X-OpenStack-Ironic-API-Maximum-Version headers respectively, returned with every response. Client may request
a specific API version by providing X-OpenStack-Ironic-API-Version header with request.

The requested microversion determines both the allowable requests and the response format for all requests.
A resource may be represented differently based on the requested microversion.

If no version is requested by the client, the minimum supported version will be assumed. In this way, a client is
only exposed to those API features that are supported in the requested (explicitly or implicitly) API version.

We recommend clients that require a stable API to always request a specific version of API that they have been
tested against.

When to Change the Version

A version number indicates a certain level of backwards-compatibility the client can expect, and as such, extra care should be taken to maintain this trust. The following lists which types of changes necessitate a new version and which don’t:
Changes That Don’t Require a New Version

New resources
New HTTP methods on existing resources
New data formats
New attributes or elements on existing data types

Change That Require a New Version

Removed or renamed URIs
Different data returned for same URI
Removal of support for HTTP methods on existing URIs

Handle Errors with HTTP status codes

It is hard to work with an API that ignores error handling. Pure returning of a HTTP 500 with a stacktrace is not very helpful.

Use HTTP status codes

The HTTP standard provides over 70 status codes to describe the return values. We don’t need them all, but there should be used at least a mount of 10.

200 – OK – Eyerything is working
201 – OK – New resource has been created
204 – OK – The resource was successfully deleted

304 – Not Modified – The client can use cached data

400 – Bad Request – The request was invalid or cannot be served. The exact error should be explained in the error payload. E.g. „The JSON is not valid“
401 – Unauthorized – The request requires an user authentication
403 – Forbidden – The server understood the request, but is refusing it or the access is not allowed.
404 – Not found – There is no resource behind the URI.
422 – Unprocessable Entity – Should be used if the server cannot process the enitity, e.g. if an image cannot be formatted or mandatory fields are missing in the payload.

500 – Internal Server Error – API developers should avoid this error. If an error occurs in the global catch blog, the stracktrace should be logged and not returned as response.

Use error payloads

All exceptions should be mapped in an error payload. Here is an example how a JSON payload should look like.

{
  "errors": [
   {
    "userMessage": "Sorry, the requested resource does not exist",
    "internalMessage": "No car found in the database",
    "code": 34,
    "more info": "http://dev.mwaysolutions.com/blog/api/v1/errors/12345"
  "type": ""
  "trace_id": ""
   }
  ]
}

Allow overriding HTTP method

Some proxies support only POST and GET methods. To support a RESTful API with these limitations, the API needs a way to override the HTTP method.

Use the custom HTTP Header X-HTTP-Method-Override to overrider the POST Method.

Errors

Errors are a crucial part of the developer experience when using an API. As
developers learn the API they inevitably run into errors. The quality and
consistency of the error messages returned to them will play a large part
in how quickly they can learn the API, how they can be more effective with
the API, and how much they enjoy using the API.

{
  "errors": [
    {
      "request_id": "1dc92f06-8ede-4fb4-8921-b507601fb59d",
      "code": "orchestration.create-failed",
      "status": 418,
      "title": "The Stack could not be created",
      "detail": "The Stack could not be created because of error(s) in other parts of the system.",
      "links": [
        {
          "rel": "help",
          "href": "TODO(sdague): example href"
        }
      ]
    },
    {
      "request_id": "d413ea12-dfcd-4009-8fad-229b475709f2",
      "code": "compute.scheduler.no-valid-host-found",
      "status": 403,
      "title": "No valid host found",
      "detail": "No valid host found for flavor m1.xlarge.",
      "links": [
        {
          "rel": "help",
          "href": "TODO(sdague): example href"
        }
      ]
    }
  ]
}

Advanced

Security

Authentication

Every REST API MUST at least accept basic authentication.

Other authentication options are optional, such as Trusted Apps, OS username and
password as query parameters, or ‘remember me’ cookies.

By default, access to all resources (using any method) requires the client to be
authenticated. Resources that should be available anonymously MUST be marked as
such. The default implementation SHOULD use the AnonymousAllowed annotation.

Authorisation

Authorisation is NOT handled by the REST APIs themselves. It is the responsibility
of the service layer to make sure the authenticated client (user) has the correct
permissions to access the given resource.

Caching

REST APIs SHOULD support conditional GET requests. This will allow the client to
cache resource representations. For conditional GET requests to work, the client
MUST send the ETag value when requesting resources using the If-None-Match request
header. The ETag is the one provided by a previous request to that same URI. See
the section on version control for entities below.

Server implementations should acknowledge the If-None-Match header and check
whether the response should be a 304 (Not Modified). See the section on response
codes below.

Concurrency

PUT or DELETE API requests SHOULD provide an If-Match header, and SHOULD react
meaningfully to 412 (Precondition Failed) responses. See the section on response
codes below.

The ETag is the one provided by a previous GET request to that same URI. See the
section on version control for entities below.

Etag

http://specs.openstack.org/openstack/api-wg/guidelines/etags.html

HTTP Response Codes

HTTP defines a set of standard response codes on requests, they are
largely grouped as follows:

  • 1xx: compatibility with older HTTP, typically of no concern
  • 2xx: success
  • 3xx: redirection (the resource is at another location, or is
    unchanged since last requested)
  • 4xx: client errors (the client did something wrong)
  • 5xx: server errors (the server failed in a way that was unexpected)

2xx Success Codes


* Synchronous resource creation

 * Response status code must be ``201 Created``
 * Must return a Location header with the URI of the created resource
 * Should return a representation of the resource in the body

* Asynchronous resource creation

  * Response status code must be ``202 Accepted``
  * Must return a Location header set to one of the following:
      * the URI of the resource to be created, if known.
      * the URI of a status resource that the client can use to query the
        progress of the asynchronous operation.

* Synchronous resource deletion

 * Response status code must be ``204 No Content``

* For all other successful requests, the return code should be **200 OK**.

* If a request attempts to put a resource into a state which it is
  already in (for example, locking an instance which is already locked), the
  return code should be in the **2xx Successful** range (usually matching the
  return code which would be given if the state had changed). It is not
  appropriate to use **409 Conflict** when the resulting state of the resource
  is as the user requested.

5xx Server Error Codes

These codes represent that the server, or gateway, has encountered an error
or is incapable of performing the requested method. They indicate to a
client that the request has resulted in an error that exists on the
server side and not with the client.

They should be used to indicate that errors have occurred during the
request process which cannot be resolved by the client alone. The nature
of each code in the 5xx series carries a specific meaning and they should
be fully researched before deploying.

The server must not return server-side stacktraces/traceback output to the
end user. Tracebacks and stacktraces belong in server-side logs, not returned
via the HTTP API to an end user.

2xx Success Codes

Synchronous resource creation

    Response status code must be 201 Created
    Must return a Location header with the URI of the created resource
    Should return a representation of the resource in the body

Asynchronous resource creation
    Response status code must be 202 Accepted

    Must return a Location header set to one of the following:
            the URI of the resource to be created, if known.
            the URI of a status resource that the client can use to query the progress of the asynchronous operation.

Synchronous resource deletion

    Response status code must be 204 No Content

For all other successful requests, the return code should be 200 OK.
If a request attempts to put a resource into a state which it is already in (for example, locking an instance which is already locked), the return code should be in the 2xx Successful range (usually matching the return code which would be given if the state had changed). It is not appropriate to use 409 Conflict when the resulting state of the resource is as the user requested.

Failure Code Clarifications


* If the request results in the OpenStack user exceeding his or her quota, the
  return code should be **403 Forbidden**. Do **not** use **413 Request
  Entity Too Large**.

* For badly formatted requests, the return code should be **400 Bad Request**.
  Do **not** use **422 Unprocessable Entity**.

  * If the API limits the length of a property that is a collection, the return
    code should be **400 Bad Request** when the request exceeds the length
    limit. The client should adjust requests to achieve success, and shouldn't
    expect to repeat the request and have it work. Do **not** use
    **403 Forbidden** for this case, because this is different than exceeding
    quota -- for a subsequent request to succeed when quotas are exceeded the
    server environment must change.

* If a request contains a reference to a nonexistent resource in the body
  (not URI), the code should be **400 Bad Request**. Do **not** use **404
  NotFound** because :rfc:`7231#section-6.5.4` (section 6.5.4) mentions **the
  origin server did not find a current representation for the target resource**
  for 404 and **representation for the target resource** means a URI. A good
  example of this case would be when requesting to resize a server to a
  non-existent flavor. The server is the resource in the URI, and as long as it
  exists, 404 would never be the proper response. **422 Unprocessable Entity**
  is also an option for this situation but do **not** use 422 because the code
  is not defined in :rfc:`7231` and not standard. Since the 400 response code
  can mean a wide range of things, it is extremely important that the error
  message returned clearly indicates that the resource referenced in the body
  does not exist, so that the consumer has a clear understanding of what they
  need to do to correct the problem.

* If a request contains an unexpected attribute in the body, the server should
  return a **400 Bad Request** response. Do **not** handle the request as
  normal by ignoring the bad attribute. Returning an error allows the client
  side to know which attribute is wrong and have the potential to fix a bad
  request or bad code. (For example, `additionalProperties` should be `false`
  on JSON-Schema definition)

* Similarly, if the API supports query parameters and a request contains an
  unknown or unsupported parameter, the server should return a **400 Bad
  Request** response. Invalid values in the request URL should never be
  silently ignored, as the response may not match the client's expectation. For
  example, consider the case where an API allows filtering on name by
  specifying '?name=foo' in the query string, and in one such request there is
  a typo, such as '?nmae=foo'. If this error were silently ignored, the user
  would get back all resources instead of just the ones named 'foo', which
  would not be correct.  The error message that is returned should clearly
  indicate the problem so that the user could correct it and re-submit.

* If a request is made to a known resource URI, but the HTTP method used for
  the request is not supported for that resource, the return code should be
  **405 Method Not Allowed**. The response should include the `Allow` header
  with the list of accepted request methods for the resource.

* If a request is made which attempts to perform an action on a resource which
  is already performing that action and therefore the request cannot be
  fulfilled (for example, snapshotting an instance which is already in the
  process of snapshotting), the return code should be **409 Conflict**.

* A **500 Internal Server Error** should **not** be returned to the user for
  failures due to user error that can be fixed by changing the request on the
  client side.  500 failures should be returned for any error state that cannot
  be fixed by a client, and requires the operator of the service to perform
  some action to fix. It is also possible that this error can be raised
  deliberately in case of some detected but unrecoverable error such as a
  MessageQueueTimeout from a failure to communicate with another service
  component, an IOError caused by a full disk, or similar error.

.. note:: If an error response body is returned, it must conform to the
   :ref:`errors` guideline.

HTTP Methods
------------

HTTP defines a concept of METHODS on a resource uri.

..

 +-------------+--------------+--------------------+--------------------+
 | METHOD      | URI          | ACTION             | HAS BODY?          |
 +-------------+--------------+--------------------+--------------------+
 | HEAD        | /foo/ID      | EXISTS             | NO                 |
 +-------------+--------------+--------------------+--------------------+
 | GET         | /foo/ID      | READ               | NO                 |
 +-------------+--------------+--------------------+--------------------+
 | POST        | /foo         | CREATE             | YES                |
 +-------------+--------------+--------------------+--------------------+
 | PUT         | /foo/ID      | UPDATE             | YES                |
 +-------------+--------------+--------------------+--------------------+
 | PATCH       | /foo/ID      | UPDATE (partial)   | YES                |
 +-------------+--------------+--------------------+--------------------+
 | DELETE      | /foo/ID      | DELETE             | NO                 |
 +-------------+--------------+--------------------+--------------------+

The mapping of HTTP requests method to the Create, Read, Update, Delete
(`CRUD
<https://en.wikipedia.org/wiki/Create,_read,_update_and_delete>`_) model
is one of convenience that can be considered a useful, but incomplete,
memory aid. Specifically it misrepresents the meaning and purpose
of POST. According to :rfc:`7231#section-4.3.3` POST "requests that
the target resource process the representation enclosed in the request
according to the resource's own specific semantics". This can, and
often does, mean create but it can mean many other things, based on
the resource's requirements.

More generally, CRUD models the four basic functions of persistent
storage. An HTTP API is not solely a proxy for persistent storage.
It can provide access to such storage, but it can do much more.

**TODO**: HEAD is weird in a bunch of our wsgi frameworks and you
don't have access to it. Figure out if there is anything useful
there.

**TODO**: Provide guidance on what HTTP methods (PUT/POST/PATCH/DELETE, etc)
should always be supported, and which should be preferred.

* When choosing how to update a stored resource, **PUT** and **PATCH** imply
  different semantics. **PUT** sends a full resource representation (including
  unchanged fields) which will replace the resource stored on the server. In
  contrast, **PATCH** accepts partial representation which will modify the
  server's stored resource. :rfc:`5789` does not specify a partial
  representation format. JSON-patch in :rfc:`6902` specifies a way to send a
  series of changes represented as JSON. One unstandardized alternative is to
  accept missing resource fields as unchanged from the server's saved state of
  the resource. :rfc:`5789` doesn't forbid using PUT in this way, but this
  method makes it possible to lose updates when dealing with lists or sets.

* There can also be confusion on when to use **POST** or **PUT** in the
  specific instance of creating new resources. **POST** should be used when
  the URI of the resulting resource is different from the URI to which the
  request was made and results in the resource having an identifier (the URI)
  that the server generated. In the OpenStack environment this is the common
  case. **PUT** should be used for resource creation when the URI to which the
  request is made and the URI of the resulting resource is the same.

  That is, if the id of the resource being created is known, use **PUT** and
  **PUT** to the correct URI of the resource. Otherwise, use **POST** and
  **POST** to a more generic URI which will respond with the new URI of the
  resource.

* The **GET** method should only be used for retrieving representations of
  resources. It should never change the state of the resource identified by
  the URI nor the state of the server in general. :rfc:`7231#section-4.3.1`
  states **GET is the primary mechanism of information retrieval and the
  focus of almost all performance optimizations.**.

HTTP request bodies are theoretically allowed for all methods except TRACE,
however they are not commonly used except in PUT, POST and PATCH. Because of
this, they may not be supported properly by some client frameworks, and you
should not allow request bodies for GET, DELETE, TRACE, OPTIONS and HEAD
methods.


#### PUT

idempotene

** create **

identiter known by client

PUT /applications/clientSpecifiedId

{
    "name" : "Best App",
    "description": "Awesomeness"
}


** update **

* full replacement

PUT /applications/existingId

{
    "name" : "Best App",
    "description": "Awesomeness"
}


#### POST

**create**

POST /applications

{
    "name" : "Best App"
}

    201 Created
    Locations : https://api.test.com/applications/12334

**update**

POST /applications/12334

{
    "name" : "Best User"
}

200 OK


Common Mistakes
---------------

There are many common mistakes that have been made in the
implementations of RESTful APIs in OpenStack. This section attempts to
enumerate them with reasons why they were wrong, and propose future
alternatives.

Use of 501 - Not Implemented

Some time in the Folsom era projects started using 501 for “Feature
Not Implemented” - Discussion on openstack-dev <http://lists.openstack.org/pipermail/openstack-dev/2012-December/003759.html>_

This is a completely incorrect reading of HTTP. “Method” means
something very specific in HTTP, it means an HTTP Method. One of GET /
HEAD / POST / PUT / PATCH / OPTIONS / TRACE.

The purpose of the 501 error was to indicate to the client that POST
is not now, and never will be an appropriate method to call on any
resource on the server. An appropriate client action is to blacklist
POST and ensure no code attempts to use this. This comes from the
early days of HTTP where there were hundreds of commercial HTTP server
implementations, and the assumption that all HTTP methods would be
handled by a server was not something the vendors could agree on. This
usage was clarified in RFC :rfc:7231#section-6.6.2 (section 6.6.2).

If we assume the following rfc statement to be true: “This is the
appropriate response when the server does not recognize the request
method and is not capable of supporting it for any resource.” that is
irreconcilable with a narrower reading, because we’ve said all clients
are correct in implementing “never send another POST again to any
resource”. It’s as if saying the “closed” sign on a business means
both, closed for today, as well as closed permanently and ok for the
city to demolish the building tomorrow. Stating that either is a valid
reading so both should be allowed only causes tears and confusion.

We live in a very different world today, dominated by Apache and
Nginx. As such 501 is something you’d be unlikely to see in the
wild. However that doesn’t mean we can replace it’s definition with
our own.

Going forward projects should use a 400 ‘BadRequest’ response for this
condition, plus a more specific error message back to the user that
the feature was not implemented in that cloud. 404 ‘NotFound’ may also
be appropriate in some situations when the URI will never
exist. However one of the most common places where we would return
“Feature Not Implemented” is when we POST an operation to a URI of the
form /resource/{id}/action. Clearly that URI is found, however some
operations on it were not supported. Returning a 404 (which is by
default cachable) would make the client believe /resource/{id}/action
did not exist at all on the server.

Example

GET /v3/auth/tokens

{
    "token": {
        "expires_at": "2013-02-27T18:30:59.999999Z",
        "issued_at": "2013-02-27T16:30:59.999999Z",
        "methods": [
            "password"
        ],
        "user": {
            "domain": {
                "id": "1789d1",
                "links": {
                    "self": "http://identity:35357/v3/domains/1789d1"
                },
                "name": "example.com"
            },
            "id": "0ca8f6",
            "links": {
                "self": "http://identity:35357/v3/users/0ca8f6"
            },
            "name": "Joe"
        }
    }
}

参考

http://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/
https://developer.atlassian.com/docs/atlassian-platform-common-components/rest-api-development/atlassian-rest-api-design-guidelines-version-1
https://docs.openstack.org/developer/ironic/dev/webapi.html
https://docs.openstack.org/developer/nova/api_microversion_dev.html
http://json-schema.org/latest/json-schema-hypermedia.html
http://specs.openstack.org/openstack/api-wg/guidelines/errors.html
http://specs.openstack.org/openstack/api-wg/guidelines/links.html
https://git.openstack.org/cgit/openstack/nova/tree/api-guide/source
https://www.twilio.com/docs/api/rest/
https://developers.facebook.com/docs/
https://developers.google.com/maps/documentation/
https://www.heavybit.com/library/video/stormpath-cto-on-designing-a-beautiful-rest-json-api/ TODO
https://stormpath.com/blog/spring-mvc-rest-exception-handling-best-practices-part-1
https://stormpath.com/blog/spring-mvc-rest-exception-handling-best-practices-part-2

猜你喜欢

转载自blog.csdn.net/wenxueliu/article/details/83962182