新手问题 客户端 API 开发总结

i5ting · 2013年10月31日 · 最后由 maclinuxp 回复于 2016年10月31日 · 13037 次阅读
本帖已被设为精华帖!

既然有人看,那咱就分享一下

API 标准写法

摘抄:http://www.startupcto.com/backend-tech/building-an-api-best-practices

You'll generally want to wrap all your API responses in an 'envelope' which specifies metadata about the APIcall.

// sample JSON envelope
{
  "status": {
    "code": 10000,
    "message": 'Success'
  },
  "response": {
     ...results...
  }
}

Doing this allows for client handler code to behave the same way for all API calls, since it gets a responses back in a universal format.

语义上再好一点,推荐:

// sample JSON envelope
{
  "status": {
    "code": 10000,
    "message": 'Success'
  },
  "data": {
     ...results...
  }
}

可视化编辑校验: http://jsoneditoronline.org/

注意事项

  • json一定要规范,不然ios的json库无法读
  • 如果支持jsonp,自己再加上一个callback就可以了。
  • 状态码说明:code:10000类似的,要以业务或者功能模块来分类,以便debug的时候快速定位
  • 尽量遵守rest

解释一个问题

http://ruby-china.org/topics/15164#reply5

xiaogui 7楼 , 19小时前  喜欢 
#5楼 @lytsingsun 
先说几点个人建议吧:
1、返回 json 数据,最好外面包一层校验,能让客户端快速知道,这数据合不合法、是否有错;
2、代码复用方面,主要看你项目的实际情况;
3、最好是能给客户端进行充分的沟通,怎么让双方都正规还方便;
4、性能方面,不外乎负载均衡、上缓存、数据库读写分离、代码优化;

第一点:就是说上面返回的status,要有code和message,根据code来判断问题,一般会直接alert出来code:message或者在日志里

这是xiaogui说的外面包一层校验的意思

其他的就没啥需要特殊说明的了

关于http的状态码和api里的status.code说明

http的状态码是直接在request里的,这个一般指服务器的状态,api是假设每一个请求都是200的,在客户端非200的请求统一处理异常,而200的请求,首先解读status获取code是否为0(假设0是请求正确返回),如果是0就读取data,继而完成相应业务逻辑处理。

  • 职责单一,该是server的就是server,该是业务逻辑的就是业务逻辑的
  • 少用http状态码,把http状态码统一封装到一个类,用于处理异常情况(ajax也好,ios的asi或afnetworking也好)
  • 同样可以表示状态的,优先自定的状态码

为什么在response json里没有必要加http status code

每一个request都可以获得状态码,而上面我给出的json是在response里,那么在在http请求完成的时候我们仍然可以获得此request的所有信息

如果仍然在response里加入request里的东西,这样做是重复的,真的是没有必要的,not DRY

换个角度讲,一般我们使用http库的时候要自己封装处理http status code的,尽力减少每个请求的代码,是不可能每个request都去

判断code==200.。。。。。然后。。。。。

技术选项

开发最佳实践

  • api的code和msg可以写一个gem,直接继承到api里
  • api测试和mock,最好是可以先mock出静态的,可以api和mobile端同时开发
  • 根据测试生成api文档

不过我还没有发现好的实现,还请各位指点

api的最佳实践

还是看open api吧


当。。。。

关联优先

比如获取新闻接口返回数据如下:

{
    "data": [
        {
            "nid": 2,
            "mid": 1,
            "text": "this is a test.",
            "images": [
                "http://121.199.40.172:8086/images/news/12.png",
                "http://121.199.40.172:8086/images/news/13.png",
                ...
            ],
            "videos": [
                "http://121.199.40.172:8086/videos/news/12.amr",
                "http://121.199.40.172:8086/videos/news/13.amr",
                ...
            ],
            "like": 0,
            "comment": 0,
            "publish_time": "2013-01-01 12:30:10"
        },
        ...
    ],
    "status": {
        "code": "0",
        "msg": "success"
    }
}

返回的对象数组,乍看是合适的,但是我们发现1条新闻对应着多个评论,如果评论没有出现在此接口中,我就要发一个新的请求,这样问题就来了,该不该呢?

个人觉得,好的api,如果一次news的数据不是特别多,就应该最新评论,如5条,10条的带上

更新: 使用rack-cors解决跨域问题

使用rack-cors,解决跨域问题,实际中也是这样用,不过application.rb里配置的路由还需要更严谨

config.middleware.use Rack::Cors do
   allow do
     origins '*'
     resource '/*', headers: :any, methods: [:get, :post, :put, :delete, :destroy]
   end
 end

一般移动测试都要起一个server,然后访问api服务器的时候就要跨域,这样就可以在移动端浏览器里测试了。

而phonegap打包的时候是本地html所以不用跨域,但是为了测试方便,服务器还是建议加上rack-cors。

欢迎关注我的公众号【node全栈】 node全栈.png

共收到 52 条回复

#1楼 @hooopo 炮哥回复,荣幸之至啊

#2楼 @i5ting 最近也在搞这个 有时间总结一下去

#3楼 @hooopo 其实api的code和msg可以写一个gem,直接继承到api里,再有就是api测试和mock,最好是可以先mock出静态的,可以api和mobile端同时开发,不过目前还没有啥好的东西

客户端是异步的,服务端也应该返回这个json属于哪个请求的吧?不然客户端拿到JSON,都不知道是对应哪个请求的,怎么进行下一步的处理呢。难道是code:10000?呵呵。。感觉太难理解了这样做。除非有相当完善的文档,不然客户端和服务端都容易搞晕。

#5楼 @zfjoy520 这个不需要的,如果真有这样的需要,只要在客户端里处理就好了,曾见过有人在ios里把asi里request里把self做参数传入,其实没啥必要,http是无状态的,又有队列,何必一定要标示是哪个请求呢?

除了render html => render json 之外好像也没有什么区别

#6楼 @i5ting ^_^ 不明觉厉

#7楼 @dddd1919 json是目前最适合也是最常用的格式,主要是客户端里好解析,html和xml这类的非常麻烦,虽然sdk里也有

#8楼 @zfjoy520 其实request里也可以取到url信息的,施主多虑了,哇哈哈

新手求问, api client 应该都能直接收到 HTTP Status 吧? 为何需要把 status code 放在 response body 里?

#11楼 @larryzhao http的状态码是直接在request里的,这个一般指服务器的状态,api是假设每一个请求都是200的,在客户端非200的请求统一处理异常,而200的请求,首先解读status获取code是否为0(假设0是请求正确返回),如果是0就读取data,继而完成相应业务逻辑处理。

#11楼 @larryzhao 有些code是不希望用500 ==,最好是自己处理error code便于查找错误,http status 200保证api有足够的容错

第一点:就是说上面返回的status,要有code和message,根据code来判断问题,一般会直接alert出来code:message或者在日志里

这是xiaogui说的外面包一层校验的意思

其他的就没啥需要特殊说明的了

这是校验的意思?

#13楼 @dddd1919 嗯,而且乱用http状态码,比如登陆不成功你来个404,那服务器真404了怎么呢?这种有业务含义的还是自定一个比较好的

#14楼 @xstmjh 通过校验,才能返回status啊,这不是外面包一层校验的意思?

#13楼 @dddd1919

有些code是不希望用500 ==

Could I have an example for that?

返不返回状态码应该是在理论和现实之间妥协的结果吧,事实上 http 的状态还是挺丰富的。http://httpstatus.es/ 如果是全 RESTful ,应该能表示大多意思了。 无论哪种情况,都是需要维护一个关于返回状态的文档的。

#15楼 @i5ting 这个的确是不对的, 不过如果服务端是自己写的话, 这点应该要尽量保证,

另外我看了你贴的 Github 的 API 的链接, 好像也是没有把 Status 放在里面, 而是在如果 Status 不是20x/30x 等正确结果的时候,放一条错误信息.

另外我自己一直非常非常喜欢 Twitter 的 API 设计, 他们应该也是没有把 Status Code 放到 Response Body 里的 https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline

由于我的确还没有真正设计过给移动端使用的 API,也没有在移动端做过开发,我大多时候是从我的 Ajax 经验角度来想的, 如果可以这样类比的话 (我不能完全确定),我觉得其实就是像下面这样来解决。

$.ajax({
  url: <some api url>,
}).done(function(data) {
  // process data
}).fail(function(responseText) {
  // process error
});

那么这样做的问题在哪儿呢? 由于最近开始做移动端的开发了,所以也就要遇到这方面的问题了,求教...

#19楼 @larryzhao 虽然不想说,但是有时你会发现做app的同学们没你想的那么高大上....

#20楼 @WolfLee 哈,不追求高大上哈,反而我很害怕追求高大上遇到实际的问题,因为我自己没有移动开发经验,所以想了解些可能会遇到的问题

#14楼 @xstmjh 其实我说的第一点“校验”的意思有两部分: 1、校验数据是否经过篡改 1)客户端利用特定唯一的 key(如请求时间+用户标示+固定码)生成对应校验 value; 2)客户端向服务器发送请求,并将请求时间、校验 value 一同放入 json ; 3)服务器在接收到客户端的请求时,同样拿 json 包体内部的请求时间、用户标示和事先约定的固定码生成校验 value,然后与 json 包体内部对比; 4)若校验 value,则抛弃该次请求,并做一些应对措施。

2、校验数据是否正确 这块就像楼主解释的那样。

是否使用“校验数据是否篡改”的措施,请根据你的客户端服务和实际情况酌情处理,这一块想了解更多,请参阅支付宝的部分 json 接口。

没有数据校验和安全加解密部分(application_controller覆盖render实现),可以考虑复写非200等状态的数据,将异常也转化成json等要求的格式。想使用同一个请求路径的话,api就设计成命令模式,根据cmd_str再去分发,避免客户端需要调试不同的api路径。

非常感谢分享的链接,好好读一下,THX。

#17楼 @larryzhao 比如数据库未找到数据后返回结果可以是

result = {
  "status": {
    "code": 1,
    "message": "Xxx data not found"
  }
}
render :json => result

为了快速找到请求出错的位置,把一些可预见的错误都写道返回信息里(像这种空结果或者写入错误的处理一般都是用status做处理了),这样发现问题调试非常方便。客户端处理api的数据首先也会判断status code是否是0(请求结果正确)再决定是否进入下一步处理。我理解的这就是把http的状态码变成自定义的code方便追踪,另外客户端也可以根据code和message做一些友好的错误处理(如果不处理而产生502了客户端也只好呆傻了)

#24楼 @dddd1919

首先我觉得, 数据库未找到数据简单来说的话, 我觉得应该从两个方面来看,

  • Model.find(id) 这样的情况应该是一个404, Record Not Found
  • Model.where(<condition>) 这样的情况应该是200, 并且返回一个空数组 (Most of the time)

另外我有疑问的是, 把这个 status code 变成自定义的话, HTTP Status Code 还是正常设置么? 对于上面这种情况, 你 HTTP status code 应该是啥比较好?

#25楼 @larryzhao 以前有用find,现在都是where了,不知道两者除了error之外有没有其他区别。

  • Http status code不用去管,404这号的不是我该控制的,所以以前用find客户端可能会接收到502,这样客户端就只能显示请求失败,502产生的原因多种多样,可能是find错误也可能是其他错误
  • 一般做api的请求状态无非200 404 302 502这类的,404是routes问题,自定义的error code主要是为了处理502 302等等异常的部分,让客户端也可以友好处理api的 “502”

顺带问个问题,假如项目想完全脱离rails view,前段框架用的是Marionette。那么在处理表单的时候,那些与一个model相关联的字段如何绑定呢?比如一个表单的select选项(这个是需要查询数据库的)。有没有一个通用的解决方案,因为要解决一类通过弹出层的表单。

与楼主不谋而合,关于这点

  • api测试和mock,最好是可以先mock出静态的,可以api和mobile端同时开发

最近正在开发维护一个项目:API Mock Server

同类的实现有:

Python:https://github.com/tomashanacek/mock-server Scala: https://github.com/studiodev/Mocky

#28楼 @zlx_star 非常好,有机会一起code

#22楼 @xiaogui 我怎么觉得你说的oauth2里的用户鉴权呢?每个token都有过期时间,如果传入的时间过期则重置token。。。。

#24楼 @dddd1919 说的挺好

我理解的这就是把http的状态码变成自定义的code方便追踪

这句是有问题的,每一个request都可以获得状态码,而不是在response里加入request里的东西,这样做是重复的,真的是没有必要的

用http库的时候都说要自己封装一层,这时候处理http status code 的

#31楼 @i5ting 差不多同样的意思,但这里主要是为了保证所传信息为未被篡改的原始信息。

#28楼 @zlx_star 果断收藏~正需要这个~给你32个攒

#32楼 @i5ting 我的rails是集api+web,所以这么干了,纯api应该是直接封装code了

#34楼 @cod7ce 很高兴能帮到你,欢迎提供反馈和贡献代码

#36楼 @zlx_star 鄙视一下楼主,push request了很久,都没反馈,更新不够及时啊,哈哈

#37楼 @i5ting 我看到了你的 pull request ,目前这个功能已经有了,所以就没有 merge。另外,我回复了你的 issue ,你看看是不是错过了?

#38楼 @zlx_star 我之前测试过,确实没有的,我是直接改代码,没有rackup,不起作用的,可能和你理解的不一样吧

#38楼 @zlx_star 我之前测试过,确实没有的,我是直接改代码,没有rackup,不起作用的,可能和你理解的不一样吧

#38楼 @zlx_star 我之前测试过,确实没有的,我是直接改代码,没有rackup,不起作用的,可能和你理解的不一样吧

#38楼 @zlx_star 我之前测试过,确实没有的,我是直接改代码,没有rackup,不起作用的,可能和你理解的不一样吧

#42楼 @i5ting 我们在 issue 上面讨论这个问题吧。

讨教一下...开发 API 之后, 认证咋整.

  • 当然不会上 oauth, 因为是内部应用, 认证不在我这里, 没这个条件
  • 希望可以防止 replay attack
  • 实现基于某个现有的标准, 或者事实上的标准

可供参考的例子不多

  • ruby-china 的 API, 直接用 Token 认证
  • gitlab API, 直接用 Token
  • api_auth, 满足需求, 可是在 header 里面加了太多东西(DATE & Content-MD5 & APIAuth), 感觉有点非主流. 这样实现的问题是文档不好写, 毕竟是给别人用的 API 的文档.

#44楼 @ruohanc 当然是使用token了。而且不止一个。通常客户端类型一个,认证的一个。

api请求的参数无效返回什么状态码?422吗?

#45楼 @jimrokliu 不太理解.......不止一个是啥意思..

#47楼 @ruohanc 有时候你需要一个独立的token表示这个是官方版的ios请求。有时候需要给第三方伙伴一个token,再加上你认证登陆的token,一般都是两个。

不错的分享。

更新: 使用rack-cors解决跨域问题

51楼 已删除
52楼 已删除
53楼 已删除

#19楼 @larryzhao 这个说法不错,http status的code已经足够了,code只是起分类的作用,如果需要sub-code来代替message,倒是可以加在response里。

既然有人看,就在推荐个配套工具(Postman,咱就不说了,国外的),这里推荐个过国内的,Apizza(http://apizza.cc?f=rb),更简单,还支持文档

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册