@AV 2015.04
Representational State Transfer
- 表现层
- 状态转化
表现层 = (资源的)表现层
- 资源是抽象概念,一个资源对应一个URI
- 表现层是资源的具体形式
状态转化: 客户端通过HTTP协议操作资源,让资源的状态发生变化
符合REST思想的架构
注意: RESTFul仅适用API设计
- Client–server
- Stateless
- Cacheable
- Layered system
- Code on demand (optional)
- Uniform interface
CURD
GET /tickets
- Retrieves a list of ticketsGET /tickets/12
- Retrieves a specific ticketPOST /tickets
- Creates a new ticketPUT /tickets/12
- Updates ticket #12PATCH /tickets/12
- Partially updates ticket #12DELETE /tickets/12
- Deletes ticket #12
- 用名词表示资源
- 使用Http Methods操作资源
- 保证GET, PUT, DELETE等操作幂等
Send A HTTP Request
telnet www.baidu.com 80
Trying 115.239.210.27...
Connected to www.a.shifen.com.
Escape character is '^]'.
GET / HTTP/1.1
Host: www.baidu.com
Receive A HTTP Response
HTTP/1.1 200 OK
Date: Mon, 18 Apr 2016 04:12:26 GMT
Content-Type: text/html
Content-Length: 14613
Last-Modified: Wed, 03 Sep 2014 02:48:32 GMT
...
<!DOCTYPE html><!--STATUS OK-->
...
Request
POST /v1/oauth2/token HTTP/1.1
Host: api.example.com
Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=takaaki&password=abcde&scope=api
Response
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"access_token": "b77yz37w7kzy8v5fuga6zz93",
"token_type": "bearer",
"expires_in": 2629743,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
}
- 公开哪些API
- Request (Endpoint) 设计
- Response设计
- 安全及访问限制
POST /create.do?from=1&to=2&transfer=500.00 👎
POST /transaction http/1.1
host: 127.0.0.1
from=1&to=2&amount=500.00 👍
http://api.example.com/service/api/users 👎
http://api.example.com/users 👍
http://api.example.com/data/
http://api.example.com/shangpin 👎
http://api.example.com/products/12345 👍
http://api.example.com/Users/12345
http://example.com/API/getUserName 👎
http://api.example.com/users/12345 👍
建议:大小写敏感,全部采用小写,大写返回404
http://api.example.com/friends?id=100
http://api.example.com/friend/100/message 👎
http://api.example.com/friends/100
http://api.example.com/friend/100/message 👍
- GET 获取资源
- POST 创建资源
- PUT 更新资源
- DELETE 删除资源
- PATCH 部分更新资源
- HEAD 获取资源概要信息
- OPTIONS 检查资源支持的Method
使用Header覆盖
POST /v1/users/123 HTTP/1.1
Host: api.example.com
X-HTTP-Method-Override: DELETE
使用POST Body覆盖
POST /v1/users/123 HTTP/1.1
Host: api.example.com
user=testuser&_method=PUT
统一使用名词复数
http://api.example.com/v1/users/12345
使用常用单词
search👍 find👎
photo👍 picture👎
仅使用英文+数字
http://api.example.com/v1/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC/123
多个单词使用连字符-
Twitter /statuses/user_timeline
YouTube /guideCategories
Facebook /me/books.quotes
LinkedIn /v1/people-search
Bit.ly /v3/user/popular_earned_by_clicks
优先使用资源的子集而非开辟新资源
popular-users 👎
users/popular 👍
路径 or 参数
- 是否能完全表示资源
- 是否可以省略
过滤
GET /tickets
GET /tickets?state=open
GET /tickets?state=open,closed
排序
GET /tickets?sort=-priority
GET /tickets?sort=-priority,created_at
提交数据
使用JSON
获取关联字段
GET /tickets/12?embed=customer.name,assigned_user
{
"id" : 12,
"subject" : "I have a question!",
"summary" : "Hi, ....",
"customer" : {
"name" : "Bob"
},
assigned_user: {
"id" : 42,
"name" : "Jim",
}
}
理论派
GET /customers/123 HTTP/1.1
Accept: application/vnd.company.myapp.customer-v3+xml
实践派
GET v3.0/customers/123 HTTP/1.1
Accept: application/xml
https://graph.facebook.com/v2.0/me
http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&Version=2011-08-01
可能好的解决方法
- Alias + Redirection
- Backwards-compatible API
Twitter JSON
Facebook JSON (部分支持XML)
Foursquare JSON
Github JSON
Amazon XML
Flickr XML / JSON
Yahoo XML / JSON / PHPserialize
URL参数
https://api.example.com/v1/users?format=xml
扩展名
https://api.example.com/v1/users.json
Header
GET /v1/users
Host: api.example.com
Accept: application/json
callback({"id":123,"name":"Saeed"})
Simple requests
GET /resources/public-data/ HTTP/1.1
Host: bar.other
Origin: http://foo.example
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Preflighted requests
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000
http://api.exmample.com/v1/users/12345?fields=name,age
{
"status": {
"code": 200,
"message": 'Success'
},
"response": {
...results...
}
}
{
"id": 23245,
"name": "Taro Yamada",
"profile": {
"birthday": 3456,
"gender": "male",
"languages": [ "ja", "en"]
}
}
{
"id": 23245,
"name": "Taro Yamada",
"birthday": 3456,
"gender": "male",
"languages": [ "ja", "en"]
}
[
{
"id": 234342,
"name": "Taro Tanaka",
"profileIcon": "http://image.example.com/profile/234342.png",
},
{
"id": 93734,
"name": "Hanako Yamada",
"profileIcon": "http://image.example.com/profile/93734.png",
},
:
]
{
"friends": [
{
"id": 234342,
"name": "Taro Tanaka",
"profileIcon": "http://image.example.com/profile/234342.png",
},
{
"id": 93734,
"name": "Hanako Yamada",
},
]
}
- page + per_page
- offset + limit
- count + since_id + max_id reference
Github
Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next",
<https://api.github.com/user/repos?page=50&per_page=100>; rel="last"
{
"nextPageToken": "CKaEL",
"items": [
...
]
}
{
"data": [
... Endpoint data is here
],
"paging": {
"cursors": {
"after": "MTAxNTExOTQ1MjAwNzI5NDE=",
"before": "NDMyNzQyODI3OTQw"
},
"previous": "https://graph.facebook.com/me/albums?limit=25&before=NDMyNzQyODI3OTQw"
"next": "https://graph.facebook.com/me/albums?limit=25&after=MTAxNTExOTQ1MjAwNzI5NDE="
}
}
HTTP Status Code
1XX 信息
2XX 成功
3XX 重定向
4XX 客户端引起的错误
5XX 服务器端引起的错误
X-MYNAME-ERROR-CODE: 2013
X-MYNAME-ERROR-MESSAGE: Bad authentication token
X-MYNAME-ERROR-INFO: http://docs.example.com/api/v1/authentication
{
"error": {
"code": 2013,
"message": "Bad authentication token",
"info": "http://docs.example.com/api/v1/authentication"
}
}
{
"code" : 1024, //总的错误代码
"message" : "Validation Failed", //总的错误信息
"errors": [{
"developerMessage": "面向开发者的错误信息",
"userMessage": "面向用户的错误信息(i18n)",
"code": 2013, //错误代码
"field" : "first_name", //错误字段
"info": "http://docs.example.com/api/v1/authentication"
//参考资料
}]
}
基于OAuth Token的权限验证
GET /v1/users HTTP/1.1
Host: api.example.com
Authorization: Bearer b77yz37w7kzy8v5fuga6zz93
Token失效
HTTP/1.1 401 Unauthorized
{
"error":"invalid_token"
}
- 始终使用SSL
- HTTPs也需要防范中间人攻击
- XSS
- XSRF
- 大量访问
- More
速度限制
API形式
GET https://api.github.com/rate_limit
{
"resources": {
"core": {
"limit": 60,
"remaining": 60,
"reset": 1383704430
},
"search": {
"limit": 5,
"remaining": 5,
"reset": 1383700890
}
}, "rate": {
"limit": 60,
"remaining": 60,
"reset": 1383704430
}
}
HTTP Header形式
- X-RateLimit-Limit 单位时间的访问数上限
- X-RateLimit-Remaining 剩余访问数
- X-RateLimit-Reset 访问数重置的时间
超出访问数
HTTP/1.1 429 Too Many Requests
Content-Type: text/html
Retry-After: 3600
{
"errors": [
{
"code": 88,
"message": "Rate limit exceeded"
}
]
}
综合实例
GET https://api.github.com/users/AlloVince
HTTP/1.1 200 OK
Server: GitHub.com
Date: Mon, 16 Jun 2014 21:32:36 GMT
Content-Type: application/json; charset=utf-8
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 55
X-RateLimit-Reset: 1402957018
X-XSS-Protection: 1; mode=block
X-Frame-Options: deny
Content-Security-Policy: default-src 'none'
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit- Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Allow-Origin: *
X-GitHub-Request-Id: 719794F7:01FB:299F044:539F6273 Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
- REST LEVEL0 - 使用HTTP
- REST LEVEL1 - 引入资源的概念
- REST LEVEL2 - 引入HTTP动词(GET/POST/PUT/DELETE)
- REST LEVEL3 - 引入HATEOAS
Refer: Richardson Maturity Model
超媒体即引用状态引擎(Hypermedia As The Engine Of Application State)
[
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-6RV70583SB702805EKEYSZ6Y",
"rel": "self",
"method": "GET"
},
{
"href": "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&token=EC-60U79048BN7719609",
"rel": "approval_url",
"method": "REDIRECT"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-6RV70583SB702805EKEYSZ6Y/execute",
"rel": "execute",
"method": "POST"
}
]
可能关联的行为
- self
- parent_payment
- sale
- update
- authorization
- reauthorize
- capture
- void
- refund
- delete
- LSUDs (large set of unknown developers)
- SSKDs (small set of known developers)
REST并不能解决所有问题
- 并不是所有业务都能抽象为资源
- 性能 (1 Screen 1 Call)
需要反思的问题
- 这样设计是不是只为了保持Model字段的纯净
- 这样设计是不是只为了好维护
OData(Open Data Protocol), by Microsoft
GET http://services.odata.org/Northwind/Northwind.svc/Products?$format=json
GET http://services.odata.org/Northwind/Northwind.svc/Products?$format=json&$expand=Supplier
GET /authors?name=Tol*
GET /authors?name=Tol*&expand=books,books.reviews,books.sales,bioData
USE "http://myserver.com/mytables1.xml" as table1;
USE "http://myserver.com/mytables2.xml" as table2;
SELECT * FROM table1 WHERE id IN (select id FROM table2)
https://developer.yahoo.com/yql/
http://techblog.netflix.com/2013/01/optimizing-netflix-api.html
http://www.redotheweb.com/2012/08/09/how-to-design-rest-apis-for-mobile.html
- Best Practices for Designing a Pragmatic RESTful API
- Best practices for API versioning
- The future of API design: The orchestration layer
brew install npm
npm install -g reveal-md
wget https://gist.githubusercontent.com/AlloVince/ba8c33138adbdd39d757/raw/gistfile1.md
reveal-md gistfile1.md -s "^\n---" -v "^\n----" -t "league"