创建博客-使用REST Web服务
来源:互联网 发布:freebsd 查看安装软件 编辑:程序博客网 时间:2024/05/23 23:47
使用Flask创建REST Web服务很简单,使用熟悉的route()
装饰器及其methods
可选参数可以声明服务所提供资源URL的路由,处理JSON数据同样简单,因为请求中包含的JSON数据可通过request.json
这个Python字典获取,并且需要包含JSON的响应可以使用Flask提供的辅助函数jsonify()
从Python字典中生成
创建API蓝本
REST API相关的路由是一个自成一体的程序子集,所以为了更好的组织代码,我们最好把这些路由放到独立的蓝本中,这个程序API蓝本的基本结构如下:
|-flasky |-app/ |-api_1_0 |-__init__.py |-users.py |-posts.py |-comments.py |-authentication.py |-errors.py |-decorators.py
注意,API包的名字中有个版本号,如果需要创建一个向前兼容的API版本,可以添加一个版本号不同的包,让程序同时支持两个版本的API
在这个API蓝本中,各资源分别在不同的模块中实现,蓝本中还包含处理认证、错误以及提供自定义装饰器的模块,蓝本的构造文件如下所示:
# app/api_1_0/__init__.pyfrom flask import Blueprintapi = Blueprint('api', __name__)from . import authentication, posts, users, comments, errors#...
注册API蓝本的代码如下:
# app/__init__.pydef create_app(config_name): #... from .api_1_0 import api as api_1_0_blueprint app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0') return app
错误处理
REST Web服务将请求的状态告知客户端时,会在响应中发送适当的HTTP状态码,并将额外信息放入响应主体,客户端能从Web服务得到的常见状态码如下表
处理404和500状态码时会有点小麻烦,因为这两个错误是由Flask自己生成的,而且一般会返回HTML响应,这很可能会让API客户端困惑
为所有客户端生成适当相应的一种方式是,在错误处理程序中根据客户端请求的格式改写响应,这种技术成为内容协商, 下例是改进后的404错误处理程序,它向Web服务客户端发送JSON格式响应,除此之外都发送HTML格式响应,500错误处理程序的写法类似
app/main/errors.py@main.app_errorhandler(404)def page_not_found(e): if request.accept_mimetypes.accept_json and \ not request.accept_mimetypes.accept_html: response = jsonify({'error': 'not found'}) response.status_code = 404 return response return render_template('404.html'), 404
这个新版错误处理程序检查Accept请求首部(Werkzeug将其编码为request.accept_mimetypes
),根据首部的值决定客户端期望接受的响应格式,浏览器一般不限制响应的格式,所以只为接受JSON格式而不接受HTML格式的客户端生成JSON响应
其他状态码都是由Web服务生成,因此可在蓝本的errors.py
模块作为辅助函数实现,下例是403错误的处理程序,其他错误处理程序的写法类似
# app/api_1_0/errors.pydef forbidden(message): response = jsonify({'error':'forbidden', 'message': message}) response.status_code = 403 return response
现在,Web服务的视图函数可以调用这些辅助函数生成错误响应了
使用Flask-HTTPAuth认证用户
和普通的Web程序一样,Web服务也需要保护信息,确保未经授权的用户无法访问,为此RIA必须询问用户的登录密令,并将其传给服务器进行验证
REST Web服务的特征之一是无状态,即在服务器在两次请求之间不能“记住”客户端的任何信息,客户端必须在发出的请求中包含所有必要信息,因此所有请求都必须包含用户密令
程序当前的登录功能是在Flask-Login的帮助下实现的,可以把数据存储在用户会话中,默认情况下,Flask把会话保存在客户端cookie中,因此服务器没有保存任何用户相关的信息,都转交给客户端保存,这种实现方式看起来遵守了REST架构的无状态要求,但在REST Web服务中使用cookie有点不现实,因为Web浏览器之外的客户端很难提供对cookie的支持,鉴于此,使用cookie并不是一个很好的设计选择
REST架构的无状态看起来似乎过于严格,但这并是不随意提出的要求,无状态的服务器伸缩起来更加简单,如果服务器保存了客户端的相关信息,就必须提供一个所有服务器都能访问的共享缓存,这样才能保证一直使用同一台服务器处理特定客户端的请求,这样的需求很难实现
因为REST架构基于HTTP协议,所以发送密令的最佳方式是使用HTTP认证,基本认证和摘要认证都可以,在HTTP认证中,用户密令包含在请求的Authorization首部中
HTTP认证协议很简单,可以直接实现,不过Flask-HTTPAuth拓展提供了一个便利的包装,可以把协议的细节隐藏在装饰器之中,类似于Flask-Login提供的login_required装饰器
Flask-HTTPAuth使用pip安装,在将HTTP基本认证的扩展进行初始化之前,我们先要创建一个HTTPBasicAuth类对象,和Flask-Login一样,Flask-HTTPAuth不对验证用户命令所需的步骤做任何假设,因此所需的信息在回调函数中提供,下例展示了如何初始化Flask-HTTPAuth扩展,以及如何在回调函数中验证密令
# app/api_1_0/authentication.pyfrom flask_httpauth import HTTPBasicAuthauth = HTTPBasicAuth()@auth.verify_passworddef verify_password(email, password): if email == '': g.current_user = AnonymousUser() return True user = User.query.filter_by(email = email).first() if not user: return False g.current_user = user return user.verify_password(password)
由于这种用户认证方法只在API蓝本中使用,所以Flask-HTTPAuth扩展只能在蓝本包中初始化,而不像其他扩展那样要在程序包中初始化
电子邮件和密码使用User模型中现有的方法验证,如果登录密令正确,这个验证回调函数就返回True,否则返回False,API蓝本也支持匿名用户访问,此时客户端发送的电子邮件字段必须为空
验证回调函数把通过认证的用户保存在Flask的全局对象g中,这样一来,视图函数便能进行访问,注意在匿名登录时,这个函数返回True并把Flask-Login提供的AnonymousUser类实例赋值给g.current_user
由于每次请求时都要传送用户密令,所以API路由最好通过安全的HTTP提供,加密所有的请求和响应
如果认证密令不正确,服务器向客户端返回401错误,默认情况下,Flask-HTTPAuth自动生成这个状态码,但为了和API返回的其他错误保持一致,我们可以自定义这个错误响应:
#app/api_1_0/authentication.py#...@auth.error_headlerdef auth_error(): return unauthorized('Invalid credentials')
为了保护路由,可使用装饰器auth.login_required
@api.route('/posts')@auth.login_requireddef get_posts(): pass
不过,这个蓝本中的所有路由都要使用相同的方式进行保护,所以我们可以在before_request
处理程序中使用一次login_required
装饰器,应用到整个蓝本,如下例所示:
#app/api_1_0/authentication.pyfrom .errors import forbidden@api.before_request@auth.login_requireddef before_request(): if not g.current_user.is_anonymous and \ not g.current_user.comfirmed: return forbidden('Uncofirmed account')
现在,API蓝本中的所有路由都能进行自动认证,而且作为附加认证,before_request
处理程序还会拒绝已通过认证但没有确认账户的用户
基于令牌的认证
每次请求时,客户端都要发送认证密令,为了避免总是发送敏感信息,我们可以提供一种基于令牌的认证方案
使用基于令牌的认证方案时,客户端要先把登录密令发送给一个特殊的URL,从而生成认证令牌,一旦客户端获得令牌,就可用令牌代替登录密令认证请求,处于安全考虑,令牌有过期时间,令牌过期后,客户端必须重新发送登陆密令以生成新令牌,令牌落入他人之手所带来的安全隐患受限于令牌的短暂使用期限,为了生成和验证认证令牌,我们要在User模型中定义两个新方法,这两个新方法用到了itsdangerous
包,如下
# app/models.pyclass User(db.Model): #.... def generate_auth_token(self, expiration): s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration) return s.dumps({'id': self.id}) @staticmethod def verify_auth_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return None return User.query.get(data['id'])
generate_auth_token()
方法使用编码后的用户id字段值生成一个签名令牌,还指定了以秒为单位的过期时间,verify_auth_token()
方法接受的参数是一个令牌,如果令牌可用就返回对应的对象,verify_auth_token()
是静态方法,因为只有解码令牌后才能知道用户是谁
为了能够认证包含令牌的请求,我们必须修改Flask-HTTPAuth提供的verify_password
回调,除了普通的密令之外,还要接受令牌,修改后的回调函数如下:
# app/api_1_0/authentication.py@auth.verify_passworddef verify_password(email_or_token, password): if email_or_token == '': g.current_user = AnonymousUser() return True if password == '': g.current_user = User.verify_auth_token(email_or_token) g.token_used = True return g.current_user is not None user = User.query.filter_by(email = email_or_token).first() if not user: return False g.current_user = user g.token_used = False return user.verify_password(password)
在这个新版本中,第一个认证参数可以是电子邮件地址或认证令牌,如果这个参数为空,那就和之前一样,假定是匿名用户,如果密码为空,那就假定email_or_token
参数提供的是令牌,按照令牌的方式进行认证,如果两个参数都不为空,假定使用常规的邮件地址和密码进行认证,在这种实现方式中,基于令牌的认证是可选的,由客户端决定是否使用,为了让视图函数能区分这两种认证方式,我们添加了g.token_used
变量
把认证令牌发送给客户端的路由也要添加到API蓝本中,具体实现如下:
# app/api_1_0/authentication.py#...@api.route('/token')def get_token(): if g.current_user.is_anonymous() or g.token_used: return unauthorized("Invalid credentials") return jsonify({'token': g.current_user.generate_auth_token( expiration=3600), 'expiration': 3600})
这个路由也在蓝本中,所以添加到before_request
处理程序上的认证机制也会用在这个路由上,为了避免客户端使用旧令牌申请新令牌,要在视图函数中检查g.token_used
变量的值,如果使用令牌进行认证就拒绝请求,这个视图函数返回JSON格式的响应,其中包含了过期时间为1小时的令牌,JSON格式的响应也包含过期时间
- 创建博客-使用REST Web服务
- 使用ASP.NET web API创建REST服务(二)
- 创建博客-使用HTTPie测试Web服务
- 使用Spring MVC创建REST服务
- 使用 WSDL 2.0 描述 REST Web 服务
- 使用 WSDL 2.0 描述 REST Web 服务
- 使用 WSDL 2.0 描述 REST Web 服务
- 创建博客-初识REST
- REST Web 服务介绍
- REST风格Web服务
- REST Web 服务介绍
- rest基于web服务
- REST,Web 服务,REST-ful 服务
- REST,Web 服务,REST-ful 服务
- REST,Web 服务,REST-ful 服务
- 使用WCF创建Web服务
- 使用WCF创建Web服务
- NodeJs使用json web token验证REST服务
- Exchange Server 2013部署 windows server 2008 r2
- POI中找不到WorkbookFactory类
- SimpleDateFormat转换时间,12,24时间格式
- hdu1717小数化分数2
- Bitmap recycle() 源码解析
- 创建博客-使用REST Web服务
- HDU1690-Bus System
- 引用CSS文件到html网页里的四种方法
- Spinner 下拉列表
- BZOJ 1500, 维修数列
- static方法能否被重写
- 关注了王石的新闻vs没关注万科的股票
- java 字节流与字符流的区别
- 实现一个简单的菜单程序,运行时显示"Menu:A(dd) D(elete) S(ort) Q(uit),Select one: "提示用户输入。输入A、D、S时分别提示"数据已经增加、删除、排序"