使用Flask框架构建服务器端

使用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/', 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

你可能感兴趣的:(server)