Python-用tornado进行API开发

只有通过努力才能得到你想要的!

最近由于项目需要用Python为一个项目开发API,用于提供服务,最终选择了tornado,其实还可以选择:flask或者sanic,都是相当不错的框架

下面展示一个简单的例子,用于记录,以便日后需要的时候又得重新看文档

项目结构

项目结构图

tornado-api/application.py

为整个简单示例的核心文件,声明了一个handler,并且实例化了一个application,为其注册了一个路由/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env /usr/local/bin/python3
# encoding: utf-8
import json

from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application
import tornado.options

from .handler.authenticate import authenticate


class MainHandler(RequestHandler):

@authenticate()
def get(self):
self.__set_response_header()
self.write("Hello, world")

@authenticate()
def post(self):
self.__set_response_header()
data = json.loads(self.request.body.decode("utf-8"))
print(data)
self.write({
"success": True
})

# 这里实现options方法是因为web中跨域请求分为两步,首先它会发送一个options请求
# 查看支持的http方法
def options(self):
self.__set_response_header()

# 这里的意思是在返回时附带允许请求的http response 头
def __set_response_header(self):

self.set_header("Access-Control-Allow-Origin", "*")
self.set_header("Access-Control-Allow-Credentials", "true")
self.set_header("Access-Control-Allow-Headers", "x-requested-with")
self.set_header('Access-Control-Allow-Methods',
'POST, GET, OPTIONS, HEAD')

if __name__ == "__main__":

tornado.options.parse_command_line()
handlers = [
(r"/", MainHandler),
]
application = Application(handlers=handlers,
static_path="./tornado-api/static/",
autoreload=True)
application.listen(9888, xheaders=True)
IOLoop.current().start()

tornado-api/__init__.py

整个包tornado-api的初始化模块,__init__.py文件为空其实就可以,但是我们在里面设置了后面要用到验证用的用户配置

1
2
3
4
5
6
7
8
#!/usr/bin/env /usr/local/bin/python3
# encoding: utf-8

__version__ = "0.1"

USERS = [
("admin", "admin")
]

tornado-api/static/

这个目录下存放了webservice的静态文件,这里包含了网站icon和robots文件,在启动应用的时候我们也制定了静态目录

application = Application(handlers=handlers, static_path="./tornado-api/static/")

tornado-api/handler/authenticate.py

这个文件里面生命了一个装饰器:authenticate,用于装饰需要认证的API,说到这里我们为什么不用cookie或者session,
因为我们在调用API的时候一般在程序终不会保存cookie,也不方便,所以我们在这里创建了一个机遇HTTP Basic认证的
装饰器,用于装饰需要认证的API,具体思路是这样的:

  • 尝试获取请求中的Authorization头部信息,如果没有,响应一个401状态码,提示用于输入用户名和密码
  • 如果有,则解析头部中包含的用户名和密码,与tornado-api/__init__.py声明的用户名密码进行匹配,如果匹配继续
    响应,否则任然响应401状态码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env /usr/local/bin/python3
# encoding: utf-8

"""
继承自 tornado.web.RequestHandler 用于对api请求做访问控制
@version: 0.1
@author: Gamelfie
@contact: fudenglong1417@gmail.com
@software: PyCharm
@file: authenticate.py
@time: 2017/3/31 11:51
"""
from functools import wraps
import base64

import tornado.web

from .. import USERS


def authenticate():

def decorate(api):

@wraps(api)
def wrapper(*args, **kwargs):
instance = args[0]
assert isinstance(instance, tornado.web.RequestHandler)
if "Authorization" in instance.request.headers:
signature = instance.request.headers["Authorization"].split(" ")[1]
user, pwd = base64.b64decode(signature.encode()).decode("utf-8").split(":")
for item in USERS:
if user == item[0] and pwd == item[1]:
return api(*args, **kwargs)
instance.clear()
instance.set_status(401)
instance.add_header("WWW-Authenticate", "Basic realm=\"user and password?\"")

return wrapper

return decorate

使用演示

启动:python3 -m tornado-api.application --port=8886

访问示例:

访问示例

输入用户名和密码之后:

访问示例

访问示例

终端显示

以上是GET方法的演示,下面我们演示一下POST方式请求:

POST示例

POST示例

Tornado中文文档,不过好像是Python2的,建议看源文档

使用jQuery进行异步请求

1
2
3
4
5
6
7
8
9
10
11
$.ajax({
type: "POST",
url: "http://127.0.0.1:9888/",
success: function(data){
console.log(data);
},
beforeSend: function(xhr){
xhr.withCredentials = true;
xhr.setRequestHeader("Authorization", "Basic YWRtaW46YWRtaW4=");
}
});

使用nginx进行负载均衡

为了提高服务的稳定性以及接口的吞吐量,我们可以启动多个进程,分别绑定多个端口,然后使用nginx进行负载均衡

可以使用supervisor进程管理工具管理进程,supervisor配置可以如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
; supervisor进程管理工具使用的配置文件
[group:tornado_service]
programs=tornado_service
[program:tornado_service]
command=/usr/bin/python3 -m tornado-api.application --port=888%(process_num)d --log-file-num-backups=1 --log-file-prefix=server.log --logging=info
process_name=%(program_name)s_%(process_num)02d
numprocs=4
numprocs_start=5
autostart=False
autorestart=True
startretries=3
user=root
stdout_logfile=NONE
directory=/root/dts-external-servic

启动4个进程:supervisorctl start tornado_service:*

配置nginx进行负载均衡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
upstream tornado_service {
server 127.0.0.1:8885;
server 127.0.0.1:8886;
server 127.0.0.1:8887;
server 127.0.0.1:8888;
}
server {
listen 8088;
# Allow file uploads
client_max_body_size 50M;
location / {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_pass http://tornado_service;
}
}

然后就可以通过http://admin:admin@server_ip:8088访问我们的接口