您的位置:首页 > 运维架构 > 网站架构

使用Flask框架构建服务器端

2016-05-24 15:07 411 查看

使用Flask框架构建服务器端

使用Flask构建RESTful风格的API服务器

#

构建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安装

Here we go

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/<int:star_id>', 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: