构建RESTful风格的服务器可以使用Flask-RestFul框架
这个框架将资源视作一个类,我们需要提供POST,GET,PUT,DELETE等方法的具体实现,然后再绑定端点(URL)
但是我认为这样的开发体验不够便捷,我用Flask的原因是因为Flask可以非常方便的构建端点到view function的映射,所以我依然使用Flask框架作为RESTful风格API服务器的开发
Flask是一个微框架,相对于Django来说,它不够完整,很多地方需要自己完成,同时它的易于扩展性可以允许我们很自由的选择第三方实现
我选择了一下第三部件:
Flask-HTTPAuth 用于简单验证
SQLAlchemy 用于SQL语言的ORM
Alembic 用于管理DB版本
Passlib 用于密码管理
以上都可以使用pip安装
RESTful风格的中心是资源,资源指的是某种实体(而不是某种动作),比如围绕着用户登陆,注册登出这样的动作应该归结为用户资源,使用POST GET DELETE PUT等方法来表达用户登陆,获取登陆状态,登出,更新登陆状态的操作。
现在我们就来实现这一部分
首先我们应该在数据库里拥有User资源,也就User表
我们使用SQLAlchemy来声明数据库
首先构造base.py
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:////tmp/iamstar.db', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
def init_db():
# remember import your models
from Model import xxxxxx
Base.metadata.create_all(bind=engine)
构造好base后,我们可以根据base来构建其他数据库模型
from sqlalchemy import Column, Integer, String, Text
from sqlalchemy.orm import relationship
from Model.base import Base
from passlib.apps import custom_app_context as pwd_context
class User(Base):
__tablename__ = 'User'
id = Column(Integer, primary_key=True)
username = Column(String(32), index=True)
password_hash = Column(String(128))
def __init__(self, username=None, password=None):
self.username = username
self.hash_password(password=password)
def __repr__(self):
return ''
def hash_password(self, password):
self.password_hash = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password_hash)
我们的User模型继承至Base
通过Column等方法来构建表
在我们的User模型中,我们引入了passlib库 并且添加了两个方法用于生成和验证hash密码。
有了模型之后我们可以开始着手view函数部分了
我们只实现用户的登陆和token验证部分,登出等不在本文中写
from flask import Flask, request, session, redirect, url_for, render_template, flash, abort, jsonify, g
from flask.ext.httpauth import HTTPBasicAuth, HTTPTokenAuth
auth = HTTPBasicAuth()
from Model.User import User
DEBUG = True
SECRET_KEY = 'iawue8rp8q2(U*&)(&^t^&T*&^t&T(EA*'
# achieve our dream
app = Flask(__name__)
app.config.from_object(__name__)
@app.teardown_appcontext
def shutdown_session(exception=None):
db_session.remove()
@app.route('/app/user/', methods=['GET', 'POST', 'DELETE', 'PUT'])
def user():
if request.method == 'GET':
return render_template('test.html', register=True)
# TODO return the public_info of user_id / or info_details of mine
if request.method == 'POST':
# TODO register a user
# username = request.json.get('username')
# password = request.json.get('password')
username = request.form['username']
password = request.form['password']
if username is None or password is None:
return jsonify(missing_arguments=True) # missing arguments
if User.query.filter_by(username=username).first() is not None:
return jsonify(existing_user=True) # existing user
u = User(username=username, password=password)
db_session.add(u)
db_session.commit()
return jsonify(register=True)
if request.method == 'DELETE':
pass
# TODO to delete a user,do not offer now
if request.method == 'PUT':
pass
# TODO upgrade a new user's info,need logged
以上是注册用户的方法,注册用户时,用户的密码会被hash然后存储
接下来是登陆部分,我将登陆作为另一种资源单独建立了表,表中只有用户名和用户token两个域所以不贴代码
我们使用HTTPBasicAuth来构建验证函数,HTTPAuth库为我们提供了一种很棒的开发体验,我们只需像下面这样构建一个验证函数,并用@auth.verify_password修饰器修饰
@auth.verify_password
def verify_password(username, password):
user = User.query.filter_by(username=username).first()
if not user or not user.verify_password(password):
g.user = None
return False
g.user = user
return True
之后我们只需要在要求登陆的view function上面添加@auth.login_required修饰器即可控制view functoin的权限
@auth.login_required
def login():
if request.method == 'GET':
return render_template('test.html', login=True)
# TODO return the stage of login
if request.method == 'POST':
# need a Authorization hear
username = g.user.username
if username:
l = Login(username=username)
now, token = l.generate_and_save_token()
db_session.add(l)
db_session.commit()
return jsonify(login=True, token=token, now=now)
return jsonify(login=False)
# TODO login and return the result,token within 600s life-circle and create time
if request.method == 'DELETE':
pass
# TODO logout and return the result
if request.method == 'PUT':
pass
# TODO re-login to upgrade some things
上面的代码段里,如果用户成功验证密码g.user里面就会有值否则为空
用户成功验证后,就会使用Login 数据模型中的一个方法generate_and_save_token(),给这个登陆用户生成一个token用于后续的交互。
在上面例子中,客户端发送账号密码的方式是:添加Authorization头信息
实例:
格式:Authorization: <scheme> <credentials>
Authorization: Basic username:password
Authorization: Token eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2NDA1NTg2OSwiaWF0IjoxNDY0MDU1MjY5fQ.eyJpZCI6bnVsbH0.4Oe60fyM8t6BvPvdZWlNqCO3ZKXW5HHsjVmoaUkp-1E
上面的验证方法,使用第一个例子的格式就行,同时,我们可以将username:password 进行加密,在verify_password函数中写上解密方法
接着后续的用户验证方法应该使用token方式
首先引入HTTPTokenAuth库
from flask.ext.httpauth import HTTPTokenAuth
token_auth = HTTPTokenAuth(scheme='Token')
#注意这里的构造器参数scheme
接着和HTTPBasicAuth类似的
@token_auth.verify_token
def verify_token(token):
g.user = None
l = Login.query.filter_by(token=token).first()
if l is not None:
g.user = User.query.filter_by(username=l.username)
return g.user is not None
if __name__ == '__main__':
app.run()
在需要token验证的函数上添加@token_auth.login_required
@app.route('/app/star/', methods=['GET', 'POST', 'DELETE', 'PUT'])
@token_auth.login_required
def star(star_id):
if request.method == 'GET':
return jsonify(tr=True)
# TODO return the list of star by default params / or a star's info
if request.method == 'POST':
pass
# TODO return the list of star by given params
if request.method == 'DELETE':
pass
# TODO to delete a star,do not offer now
if request.method == 'PUT':
pass
# TODO do not offer
客户端发送token的方式:
python
Authorization: Token eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2NDA1NTg2OSwiaWF0IjoxNDY0MDU1MjY5fQ.eyJpZCI6bnVsbH0.4Oe60fyM8t6BvPvdZWlNqCO3ZKXW5HHsjVmoaUkp-1E