在积累了必定的工做与项目实战经验后,愈来愈意识到验参的重要性。 前几天又重读总结了一下【程序员修炼之道】,书中提到,卓有成效的程序员从不相信任何人,包括本身。 关于这句话的一个很重要的实践便是在本身编写的程序中,作好验参工做,使对字段的限制与文档一致。这点能够显著得加强系统的稳定性,保护系统的健壮与数据的一致。html
在实际工做中,发现验参环节并非那么容易作好,微服务系统有多层结构,每一个服务内代码的组织又是分层的, 在哪些场景与环节进行验参比较实用是本身须要考虑清楚的点。 这篇总结一下工做中见到的各Python项目对于验参的各类处理,以及经常使用的验参的库。前端
假设项目为一个简单的两层结构,gateway接受浏览器http请求,经过thrift rpc协议调下层service,service将数据写到DB中。java
这样一个简单的调用链能够抽出几处须要验参的地方:python
以前在写一个Java项目的时候,问到组内的一个较有经验的Java开发在Java中验参一般怎么作,怎样是比较地道的写法。 他开玩笑说,if else不就行了嘛。(固然,以后仍是告诉了我能够用javax.validation中的注解很天然轻易地完成这个任务)git
诚然,if else能够撸出一切。并且的确我也在一些项目中包括刚参加工做时的小公司的代码里见到过这样的作法,不用引入第三方库,直接进行判断,某字段是否存在,某字段是否为None,字段长度是否超长等。 这的确能够完成工做,但真的不够clean,每次在函数的前部分都要处理这些东西,代码写出来很丑。若要把这种if抽出来,粒度又太细,同时又要使用好比装饰器这种技术将它和函数本体编织起来,并且不一样函数要验证的条件每每又是不同的,以前写的验证方法可能还要再改以达到通用,这就又要再改引用了这个验证方法的方法,等等状况会出现不少问题,因此并非一个长久的组织方法。程序员
下面介绍几个我见过的用于验参的第三方包,可接避免上面那样的重复造轮子。github
marshmallow自己是Python中的一个出色的用于作序列化的库,同时也提供了在验参功能。 它容许开发者定义一些schema,schema中能够以各类方式(allow_none, required, lambda)来表述一个字段的规则,同时在反序列化(将外部参数转化为领域对象)时,会自动进行验参工做,并将不符合规则的参数统一组织起来,并且容许开发者本身提供个性化的相应报错信息。web
上面说到反序列化,那这位又问了,序列化时它不作验参吗?的确如此,marshmallow的做者认为,序列化是指将本身系统内的数据给提供出去(至关于to_json()),对于它们的质量与来源是咱们能够保证的,故不须要在这个时候进行反序列化。 但实际状况也并不每每如此,好比我最近接触到的项目,因为以前没有作好验参工做,且表结构较复杂,会有一些存在库中的历史数据实际上是少字段的或者是不符合规则的,在这种状况下,能够先将数据取出来,进行序列化生成dict对象,再用dict对象来调用schema.validate(dict)来专门进行验证,从而搜集信息,修补数据。数据库
它的schema能够按照以下方式定义:json
from marshmallow import Schema, fields, validate, validates
class UserSchema(Schema):
name = fields.Str(required=True, validate=lambda n: n)
age = fields.Decimal(required=True, validate=lambda n: n > 18)
location = fields.Str(required=True)
@validates('location')
def validate_location(self, value):
valid_locations = [u'SHANGHAI', u'TOKYO', u'NEWYORK']
if value not in valid_locations:
raise ValidationError('Unknown location.')
复制代码
此外,marshmallow还提供了一些常见规则的验证,好比Email,URL来验证字段是否符合规则,不用再去硬写一些让人头疼的正则。它们都继承于Validator类,你也能够继承它来编写本身的验证规则类来扩展marshmallow的能力,使得一切都很地道、好用。
在gateway层面,面对http请求,可使用webargs包来进行参数提取与校验。 咱们知道,http请求传参有多种可能,好比在url中的?key=value&key2=value2
这种格式,此外,post方法传参的可能就更多了,有plan text,application/json等。
而webargs包即是用来简化这一拿参验参的过程,它让开发者能够经过定义一个schema或是dict的结构来表示本身指望从http中获得哪些参数,以及使用哪些规则。查一下源码的话,很容易发现,它内部也是使用了marshmallow,调用了marshmallow的load函数,最后返回一个dict。 这种方式使用起来对开发仍是很友好的,举例以下(抄自项目readme):
from flask import Flask
from webargs import fields
from webargs.flaskparser import use_args
app = Flask(__name__)
hello_args = {"name": fields.Str(required=True)}
# or use schema
# HelloArgs(Schema):
# name = fields.Str(required=True)
@app.route("/")
@use_args(hello_args)
# 对应上面
# @use_args(HelloArgs(strict=True))
def index(args):
return "Hello " + args["name"]
if __name__ == "__main__":
app.run()
# curl http://localhost:5000/\?name\='World'
# Hello World
复制代码
说了上面,哪位又问了,marshmallow与webargs所作的验参都不够专注,前者是为序列化服务,后者更多地是为取参同时进行,有些状况下只有两个参数,不想去定义那些schema,感受好麻烦,并且只想用单一的验参功能,要怎么作呢?
那么schema就是你想要的。Schema validation just got Pythonic
它的用法与api比较pythonic,很语义化并且够函数式,写起来仍是比较好玩的,不过要注意正确性。 在工做中我见过一些同事拿它在service中的handler代码层验参。 抄袭项目readme示例代码以下,诸位能够感觉下,仍是蛮有意思的:
必定不要小看验参这件事,在大型项目的开发中,可能有不少历史遗留问题,兼容性问题,甚至是来自脚本的恶意请求等。你根本没法肯定你编写的代码会被怎样调用,若是这些环节失去了这些保障,线上的复杂状况,会让你的代码在一些匪夷所思的地方报出经典的NoneType
error,甚至有些数据库的字段会被莫名其妙地被写为空,却根本不知道它是在何处发生的。这些错误的发生会让开发人员不知所措,措手不及,由于明明在测试时根本没出现过,极度难以调试。 等到时候再要加校验,已经很困难了。
在经历了一些莫明其妙的问题后,我不得不开始重视验参环节,毕竟没吃过亏仍是不知道疼。 我认为验参环节是一种运行时的assert技术,marshmallow与webargs提供的序列化、取参数的同时进行验参我认为是比较好的方案,它不会让开发者在代码中多写一行专门去调验参函数又能把这件事给作得很棒。
作好参数验证,是一次请求,一个函数运行,一次持久化成功的第一道保障,client环境太复杂,咱们这些写service的仍是要保护好本身啊!