不管是进行app开发还是web开发,server端的开发都是必不可少的部分,最近打算尝试一下服务端开发;考虑到开发效率以及目前主流的前后端API标准,我打算采用python的flask框架来搭建一个简单server端作为移动app的Web Service来使用,API格式采用RESTful标准,本文将讲述RESTful架构服务端的大致实现过程。
RESTful架构
什么是RESTful架构:
- 每一个URI代表一种资源;
- 客户端和服务器之间,传递这种资源的某种表现层
- 客户端通过四个HTTP动词,对服务器端资源进行操作,实现”表现层状态转化”。
Representational State Transfer 表现层状态转化
- Resoure:资源—-所谓”资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务
- URL:统一资源定位符—-要获取资源,访问它的URL就可以了 * URI:统一资源标识符—-每种资源对应一个特定的URI
- Representation:表现层 —- 资源呈现形式,如json,text,html
- State Trasfer:状态转移 —- http无状态协议中的GET、POST、PUT、DELETE
REST架构设计六原则
- 前后端统一接口
- 无状态 —- 每次客户端请求必需包含完整的信息
- 可缓存 —- 服务器端必需指定哪些请求是可以缓存的
- 服务端客户端业务分离
- 分层系统
- 按需编码
优点
- 可以利用缓存Cache来提高响应速度
- 通讯本身的无状态性可以让不同的服务器的处理一系列请求中的不同请求,提高服务器的扩展性
- 浏览器即可作为客户端,简化软件需求
- 相对于其他叠加在HTTP协议之上的机制,REST的软件依赖性更小
*不需要额外的资源发现机制,在软件技术演进中的长期的兼容性更好
API的设计实现
- 注册登录的Token实现
- Token是什么:是服务端生成的一串字符串,作为客户端进行请求的一个令牌;当客户端第一次登录时,服务端会去数据库查询用户名和密码并进行对比是否正确,如果正确服务器就会生成一个Token返回给客户端,并将该token存到服务器的session里边,以后客户端只需带上这个Token前来请求数据即可,通过比对session就可以验证登录,无需再次带上用户名和密码,也不需要再查询数据库。
- Token有什么好处:可以减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮
- Token的生成方式:服务端通过把客户端第一次登录时传输的部分参数(例如deviceId,MAC地址)+时间戳+随机salt进行拼接之后然后生成一个散列值,返回给客户端使用sharepreference保存,以后请求数据都要带上token
- 传输数据的加密算法: 对用户名,密码,deviceID等重要隐私信息进行拼接然后RSA加密
- RSA算法: RSA是一种公钥加密算法(非对称加密),它的加密和解密使用不同密钥。具体做法是这样,客户端在登录前先向服务器请求一个公钥密钥,然后用这个公钥密钥加密需要传输的数据(数据是指根据密码,用户名等生成的散列值),然后发送给服务器。服务器使用私钥密钥解密,然后与根据数据库中的用户名和密码计算出来的散列值进行比较,一致的话,登录成功。然后服务端生成token返还给客户端,该token也需要进行加密比较安全,采用AES对称加密,采用同一个密钥可以同时用作信息的加密和解密。
- 摘要算法主要有MD5和SHA-1,又叫作Hash算法或散列算法,是一种将任意长度的输入浓缩成固定长度的字符串的算法,这个过程是不可逆的; 保证不同内容的文件生成的散列值一定不同,相同内容的文件生成的散列值一定相同
- 数字签名主要经过以下几个过程: 信息发送者使用一单向散列函数(HASH函数)对信息生成信息摘要; 信息发送者使用自己的私钥签名信息摘要; 信息发送者把信息本身和已签名的信息摘要一起发送出去; 信息接收者通过使用与信息发送者使用的同一个单向散列函数(HASH函数)对接收的信息本身生成新的信息摘要,再使用信息发送者的公钥对信息摘要进行验证,以确认信息发送者的身份和信息是否被修改过。
- 数字加密主要经过以下几个过程: 当信息发送者需要发送信息时,首先生成一个对称密钥,用该对称密钥加密要发送的报文; 信息发送者用信息接收者的公钥加密上述对称密钥; 信息发送者将第一步和第二步的结果结合在一起传给信息接收者,称为数字信封; 信息接收者使用自己的私钥解密被加密的对称密钥,再用此对称密钥解密被发送方加密的密文,得到真正的原文。
- 区别数字签名和数字加密的过程虽然都使用公开密钥体系,但实现的过程正好相反,使用的密钥对也不同。数字签名使用的是发送方的密钥对,发送方用自己的私有密钥进行加密,接收方用发送方的公开密钥进行解密,这是一个一对多的关系,任何拥有发送方公开密钥的人都可以验证数字签名的正确性。数字加密则使用的是接收方的密钥对,这是多对一的关系,任何知道接收方公开密钥的人都可以向接收方发送加密信息,只有唯一拥有接收方私有密钥的人才能对信息解密。另外,数字签名只采用了非对称密钥加密算法,它能保证发送信息的完整性、身份认证和不可否认性,而数字加密采用了对称密钥加密算法和非对称密钥加密算法相结合的方法,它能保证发送信息保密性。
Flask框架
Flask框架是一个Python编写的基于Werkzeug WSGI工具箱和Jinja2模板引擎的轻量级web开发框架。Flask只保留了简单的核心,但是有着丰富的extension来实现其他功能模块以达到类似于Django这种全栈式框架的功能效果。例如ORM需要使用SQLAlchemy扩展、Oauth需要使用Oauthlib扩展, 此外还有大量的扩展用以支持数据库整合、表单验证、上传处理、缓存、生成后台和各种开放验证等等。
代码实现
下面我们将通过具体代码来运用Flask框架实现一个RESTful Web Service返回json数据以及template前端页面。该项目中有两个路由,一个用Flask-RESTful的路由形式返回json数据,另一个用Flask原生路由形式返回一个基于Jinja2模板的Flask-Bootstrap风格的注册登录页面;其中json数据通过SQLAlchemy插件从MySQL数据库中获取,然后显示到浏览器中。
配置Flask环境
新建一个Flask项目,步骤如下
- 打开PyCharm编辑器(安装教程请Google),点击File按钮,选择new project
- 选择左侧列表中Flask选项,修改Location中的”untitled”为你想要的项目名称,选择合适的interpreter,一般默认即可,Pycharm会自动下载安装Flask框架
- 点击Create,可以看到项目结构:static文件夹、templates文件夹、py文件;Flask项目创建成功
Mac安装Flask框架遇到的小bug
Pycharm自动安装Flask框架时使用的是”pip install Flask”命令,但是在mac中会因为没有sudo权限导致无法安装成功,Pycharm会提示我们去terminal中输入命令行安装;然而,当我们在命令行中输入”sudo pip install Flask”时却会发现下载安装过程中依然会出现“bad md5 hash mismatch”的报错,解决步骤如下:- Step1:更新pip到1.3以上,输入命令”sudo pip install —upgrade pip”
- Step2:安装Flask不使用缓存,输入命令”sudo pip install —no-cache-dir Flask”
部分需要用到的Flask插件pip安装命令
# Flask-RESTful插件: 用于快速开发REST API sudo pip install flask-restful # Flask-Bootstrap插件: 用于构建Bootsrap风格的网页;也可以直接下载引用Bootstrap sudo pip install flask-bootstrap # Flask-Pymysql插件: 一个类似于mysqldb的Python数据库驱动插件 sudo pip install flask-pymysql # Flask-SQLAlchemy插件: 一个用于操作SQL数据库的ORM sudo pip install flask-sqlalchemy
配置MySQL数据库
在该项目中,我们连接MySQL中的test数据库(如果该数据库不存在请自行创建),然后新建一个user表,包含id和name两个属性,后面要显示到浏览器中的json数据就是从这个表中提取。我们可以通过直接执行MySQL命令来操作数据库,也可以在Python文件中通过SQLAlchemy来执行SQL语句操作数据库;当然这一切的前提是在你的计算机上安装MySQL并开启服务,我们推荐使用xampp集成安装环境,非常方便,而且同时适用于win和mac。下面列出常用的MySQL命令:
# 创建表user,包含id(主键)、name两个字段
create table user (
id int(4) not null primary key, name varchar(20)
) engine=innodb default charset=utf8;
# 创建表book,包含id(主键)、name、user_id三个字段,其中user_id字段为book表的外键字段名
对应user表中的id字段,一个user可能有一本或多本book
create table book (
id int(4) not null primary key, name varchar(20), user_id varchar(20)
) engine=innodb default charset=utf8;
# 给book表增加一个category字段
alter table book add category varchar(20) primary key;
# 列出表book的结构
desc book;
# 列出表book所有数据
select * from book
# 清空表book所有数据
truncate table book
# 添加外键
alter table book add constraint FK_ID foreign key(user_id) REFERENCES user(id);
# 删除外键, FK_ID为外键名
alter TABLE book DROP FOREIGN KEY FK_ID;
app.py
在app.py中:
一方面: 我们通过SQLAlchemy连接数据库并查询user表中的用户数据信息,作为json数据返回给浏览器;这一部分采用RESTful插件实现, 通过访问localhost://5000/restful就可以看到返回的json数据
另一方面: 我们在flaskTest函数中加载login.html页面,该页面通过采用bootstrap框架实现.
# coding=utf-8
__author__ = 'xiaoyong'
from flask import Flask, request, render_template
from flask.ext.bootstrap import Bootstrap
from flask_restful import fields, marshal_with, Api, Resource
from sqlalchemy import Column, String, create_engine, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
import json
from datetime import datetime
app = Flask(__name__)
api = Api(app)
bootstrap = Bootstrap(app)
# 初始化MySQL数据库连接引擎
db_engine = create_engine('mysql+pymysql://root:@localhost:3306/test')
Base = declarative_base()
# 数据库表user对应的User对象
class User(Base):
__tablename__ = 'user'
id = Column(String(20), primary_key=True)
name = Column(String(20))
# 一对多外键关系
books = relationship('Book')
class Book(Base):
__tablename__ = 'book'
id = Column(String(20), primary_key=True)
name = Column(String(20))
user_id = Column(String(20), ForeignKey('user.id'))
# 创建DBSession类型
DBSession = sessionmaker(bind=db_engine)
# 创建DBsession对象
session = DBSession()
# Query user表中的所有行数据
db_users = session.query(User).all()
session.close()
@app.route('/flask/<user_name>', methods=['GET', 'POST'])
def flaskTest(user_name):
# 访问http://localhost:5000/flask/xy,进入登陆页
if user_name == 'xy' and request.method == 'GET':
return render_template('login.html')
# 点击login,返回用户json数据
if request.method == 'POST' and request.form['user']:
u = request.form['user']
p = request.form['pw']
dic = {'user': u, 'password': p}
return json.dumps(dic)
else:
return 'error url'
# 返回的json数据模型
class restfulModel(object):
def __init__(self, users, state):
self.users = users
self.state = state
# marshal-蒙版
resource_fileds = {
'users': fields.List(fields.String),
'state': fields.String(default=''),
'expires': fields.Float(default=0.0),
'date': fields.DateTime(default=str(datetime.now()))
}
# RESTful插件
class restfulTest(Resource):
@marshal_with(resource_fileds)
def get(self):
user_list = []
for user in db_users:
user_dic = {}
user_dic['name'] = user.name
user_dic['id'] = user.id
user_list.append(str(user_dic))
return restfulModel(user_list, 'OK')
api.add_resource(restfulTest, '/restful')
if __name__ == '__main__':
app.run(debug=True)
login.html
该注册登录页面是基于Jinja2模板和bootstrap框架实现
PS:实现该页面注册框居中的css代码:
form.form-signin {
width: 20%;
margin-left: auto;
margin-right: auto;
}
结语
上述即为通过Flask框架搭建资源服务器的大致过程,涉及到RESTful架构,SQLAlchemy ORM,Bootstrap,json处理等相关知识;我们可以看出利用Python框架搭建服务器的便捷性,很容易上手,如果需要深入学习相关知识请参考flask官网和相关extensions官网教程。