By @notsobad
url中附加一个key
/api/posts/?key=aasd2323423asdfasdf
应用场景:
- google map api
- 简单的s -> s认证
缺点:
- key容易泄漏
- 不可靠,不适合做强认证
用双方都知道的密钥对api参数进行签名,可以防止参数伪造,如 http://shot.scanv.com/api/thumb/?blur=0&client=scanv&sign=0881307c9f1e440f05e4cc58075c9485&size=L&url=http%3A%2F%2Fwww.douban.com
应用场景:
- 网银交易的第三方接口常用
- 资源地址加密,如图片、视频,可以防止直接抓取
- b -> s, s -> s均可
api利用basic auth来做认证,可以用basic auth用户名来取代api key
curl https://api.stripe.com/v1/charges \
-u sk_test_mkGsLqEW6SLnZa487HYfJVLf:
# or
curl https://sk_test_mkGsLqEW6SLnZa487HYfJVLf:@api.stripe.com/v1/charges
优点:
- 方便调试,浏览器中访问认证一次,后续不需要再次输入
- 调用方便,程序访问可以直接把认证信息写入url中
- 用于s -> s
在tornado中实现basic auth:
# -*- coding= utf-8 -*-
import datetime
import os
import json
import tornado.ioloop
import tornado.web
import tornado
import tornado.httpclient
import traceback
import urllib2
import base64
import functools
import hashlib,base64,random
API_KEYS = {
'rjtzWc674hDxTSWulgETRqHrVVQoI3T8f9RoMlO6zsQ': 'test'
}
def api_auth(username, password):
if username in API_KEYS:
return True
return False
def basic_auth(auth):
def decore(f):
def _request_auth(handler):
handler.set_header('WWW-Authenticate', 'Basic realm=JSL')
handler.set_status(401)
handler.finish()
return False
@functools.wraps(f)
def new_f(*args):
handler = args[0]
auth_header = handler.request.headers.get('Authorization')
if auth_header is None:
return _request_auth(handler)
if not auth_header.startswith('Basic '):
return _request_auth(handler)
auth_decoded = base64.decodestring(auth_header[6:])
username, password = auth_decoded.split(':', 2)
if (auth(username, password)):
f(*args)
else:
_request_auth(handler)
return new_f
return decore
class ResHandler(tornado.web.RequestHandler):
@basic_auth(api_auth)
def get(self):
self.write("hello")
app = tornado.web.Application([
(r'/api/res/', ResHandler),
], **settings)
if __name__ == '__main__':
import tornado.options
tornado.options.parse_command_line()
app.listen(9527)
tornado.ioloop.IOLoop.instance().start()
代码在:https://gist.github.com/notsobad/5771635
## 数据结构 返回json数据结构,所有信息要在同一个wraper下。
- 要返回dict而不是list (为什么?)
- 要有操作时间字段 (为什么?)
- 异常情况要给出正确的错误提示,也要返回正确的json结构
如果你的api是给其他用户使用的,非内部api,最好在api中带上版本号,如果数据结构由大的变动,升级版本号即可,如可以这样设计
/api/v1/list_user/
参考 stripe api
推荐轻量级框架
- web.py
- tornado
- flask
- ....
推荐使用tornado
优点:
- 高效, 部署时可以无需uwsgi
- 异步,将一些db操作,第三方api访问放入异步,可以实现高并发
下面的代码实现了一个异步的api proxy
def getJSON(url, callback):
http_client = tornado.httpclient.HTTPClient()
response = http_client.fetch(url)
cont = response.body
http_client.close()
ret = None
try:
ret = json.loads(cont)
except:
traceback.print_exc()
return callback(ret)
class ProxyHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
url = 'http://www.xxx.com/abc.json'
getJSON(url, callback=self.on_response)
def on_response(self, ret):
if not ret:
raise tornado.web.httperror(500)
self.write(ret)
self.finish()
api要有缓存,要根据api的具体情况定制缓存策略,下面给一种简单的缓存架构
在api server端,对于不同的接口,声明合适的http头,如Cache-control, expires等,缓存本身由中间层来做,如nginx、varnish等来负责。
last_modified = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(time.time()))
web.header('Last-Modified',last_modified)
web.header('Cache-Control', 'max-age=86400')
如果你的应用使用nginx + uwsgi形式,可以使用uwsgi_cache
uwsgi_cache_path /home/data/cache levels=1:2 keys_zone=api:10m inactive=7d max_size=600m;
server {
listen 80;
server_name xx-api;
location / {
uwsgi_cache api;
uwsgi_cache_use_stale error timeout invalid_header http_500;
uwsgi_cache_key api$request_uri;
include uwsgi_params;
uwsgi_param UWSGI_CHDIR /var/www/api/;
uwsgi_param UWSGI_SCRIPT wsgi_app;
uwsgi_pass unix:////var/run/uwsgi_api/api.sock;
}
}
类似的,如果你用nginx+proxy_pass的形式,也可以用proxy_cache模块
nginx HttpLimitReqModule
参考链接: