Flask(一)
Flask
0.Flask简介
Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合。
默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用
wsgiref
最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。
如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。
正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口协议来实现这样的服务器软件,让我们专心用Python编写Web业务。这个接口就是WSGI:Web Server Gateway Interface。而wsgiref模块就是python基于wsgi协议开发的服务模块
from wsgiref.simple_server import make_server
def mya(environ, start_response):
print(environ)
start_response('200 OK', [('Content-Type', 'text/html')])
if environ.get('PATH_INFO') == '/index':
with open('index.html','rb') as f:
data=f.read()
elif environ.get('PATH_INFO') == '/login':
with open('login.html', 'rb') as f:
data = f.read()
else:
data=b'<h1>Hello, web!</h1>'
return [data]
if __name__ == '__main__':
myserver = make_server('', 8011, mya)
print('监听8010')
myserver.serve_forever()
wsgiref简单应用
1.安装
pip3 install flask
2.werkzeug简介
Werkzeug是一个WSGI工具包,他可以作为一个Web框架的底层库。这里稍微说一下, werkzeug 不是一个web服务器,也不是一个web框架,而是一个工具包,官方的介绍说是一个 WSGI 工具包,它可以作为一个 Web 框架的底层库,因为它封装好了很多 Web 框架的东西,例如 Request,Response 等等
代码示例:
from werkzeug.wrappers import Request, Response
3.flask快速使用
from flask import Flask # 实例化产生一个Flask对象 app = Flask(__name__) # 将 '/'和视图函数hello_workd的对应关系添加到路由中 @app.route('/') # 1. v=app.route('/') 2. v(hello_world) def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run() # 最终调用了run_simple()
4.配置文件
flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为:
{ 'DEBUG': get_debug_flag(default=False), 是否开启Debug模式 'TESTING': False, 是否开启测试模式 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, 'APPLICATION_ROOT': None, 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, }
方式一
app.config['DEBUG'] = True PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...)
方式二
#通过py文件配置 app.config.from_pyfile("python文件名称") 如: settings.py DEBUG = True app.config.from_pyfile("settings.py") #通过环境变量配置 app.config.from_envvar("环境变量名称") #app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) 环境变量的值为python文件名称名称,内部调用from_pyfile方法 app.config.from_json("json文件名称") JSON文件名称,必须是json格式,因为内部会执行json.loads app.config.from_mapping({'DEBUG': True}) 字典格式 app.config.from_object("python类或类的路径") app.config.from_object('pro_flask.settings.TestingConfig') settings.py class Config(object): DEBUG = False TESTING = False DATABASE_URI = 'sqlite://:memory:' class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo' class DevelopmentConfig(Config): DEBUG = True class TestingConfig(Config): TESTING = True PS: 从sys.path中已经存在路径开始写 PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录(Flask对象init方法的参数)
5.路由系统
典型写法
@app.route('/detail/<int:nid>',methods=['GET'],endpoint='detail')
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
路由系统本质
""" 1. decorator = app.route('/',methods=['GET','POST'],endpoint='n1') def route(self, rule, **options): # app对象 # rule= / # options = {methods=['GET','POST'],endpoint='n1'} def decorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator 2. @decorator decorator(index) """ #同理 def login(): return '登录' app.add_url_rule('/login', 'n2', login, methods=['GET',"POST"]) #与django路由类似 #django与flask路由:flask路由基于装饰器,本质是基于:add_url_rule #add_url_rule 源码中,endpoint如果为空,endpoint = _endpoint_from_view_func(view_func),最终取view_func.__name__(函数名)
基本用法总结:
启动flask 第一步 from flask import Flask app=Flask(__name__) 第二步 @app.route("/") def index(): return "123" 第三步 app.run() 启动flask本质是执行 wsgi_app(ev,re) django四剑客 ''' redirect--->redirect HttpResponse---->"" render--->render_template 注意他的传值,必须字典打散 JsonResponse--->jsonify ''' 配置文件的四种方式 #第一种方式 # app.debug=True # app.secret_key="asdas" #第二种方式 # app.config["DEBUG"]=True #第三种方式 # app.config.from_pyfile("settings.py") #第四种方式(推荐) app.config.from_object('settingss.Test') flask路由本质 基于装饰器添加路由其实是执行: app.add_url_rule(self, rule, endpoint=None, view_func=None) rule--->路由 endpoint--->反向解析的别名 view_func---->当前的视图函数 methods ---->允许请求的方式["get","post"],如果不传默认允许的是get请求 怎样反向解析获取路由 url_for(别名)
严格模式:
from flask import Flask app=Flask(__name__) @app.route("/<int:nid>",strict_slashes=False) def index(nid): print(nid) return "ok" @app.route("/index",strict_slashes=True,redirect_to="/1") def index1(): return "ok1" if __name__ == '__main__': app.run()
CBV(源码分析)
'''
def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner '''
方式一: class IndexView(views.View): methods = ['GET'] # decorators = [auth, ] def dispatch_request(self): print('Index') return 'Index!' #如果不传name,这所有返回的都是view,这样就会报错,所有人家必须你要传递参数 #然后他传递给view_func的其实就是你视图类中的dispatch_request方法。这样我们没有办法,在一个视图类中写多种请求方式 app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint #或者,通常用此方式
方式二:
class IndexView(views.MethodView): methods = ['GET'] #cbv添加装饰,用这个,我们看as_view中就知道了,不写methods默认为GET请求 decorators = [auth, ] def get(self): return 'Index.GET' def post(self): return 'Index.POST' #如果我们继承了MethodView,他帮我们重写了,dispatch_request方法,他给我们做了一个分发,通过请求,来执行不同的函数 app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint
app.add_url_rule参数
@app.route和app.add_url_rule参数: rule, URL规则 view_func, 视图函数名称 defaults = None, 默认值, 当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'} 为函数提供参数 endpoint = None, 名称,用于反向生成URL,即: url_for('名称') methods = None, 允许的请求方式,如:["GET", "POST"] #对URL最后的 / 符号是否严格要求 strict_slashes = None ''' @app.route('/index', strict_slashes=False) #访问http://www.xx.com/index/ 或http://www.xx.com/index均可 @app.route('/index', strict_slashes=True) #仅访问http://www.xx.com/index ''' #重定向到指定地址 redirect_to = None, ''' @app.route('/index/<int:nid>', redirect_to='/home/<nid>') ''' #子域名访问 subdomain = None, ''' #C:\Windows\System32\drivers\etc\hosts 127.0.0.1 www.liuqingzheng.com 127.0.0.1 admin.liuqingzheng.com 127.0.0.1 buy.liuqingzheng.com from flask import Flask, views, url_for app = Flask(import_name=__name__) app.config['SERVER_NAME'] = 'liuqingzheng.com:5000' @app.route("/", subdomain="admin") def static_index(): """Flask supports static subdomains This is available at static.your-domain.tld""" return "static.your-domain.tld" #可以传入任意的字符串,如传入的字符串为aa,显示为 aa.liuqingzheng.com @app.route("/dynamic", subdomain="<username>") def username_index(username): """Dynamic subdomains are also supported Try going to user1.your-domain.tld/dynamic""" return username + ".your-domain.tld" if __name__ == '__main__': app.run() 访问: http://www.liuqingzheng.com:5000/dynamic http://admin.liuqingzheng.com:5000/dynamic http://buy.liuqingzheng.com:5000/dynamic '''
支持正则
#1 写类,继承BaseConverter #2 注册:app.url_map.converters['regex'] = RegexConverter # 3 使用:@app.route('/index/<regex("\d+"):nid>') 正则表达式会当作第二个参数传递到类中 from flask import Flask, views, url_for from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) class RegexConverter(BaseConverter): """ 自定义URL匹配正则表达式 """ def __init__(self, map, regex): super(RegexConverter, self).__init__(map) self.regex = regex def to_python(self, value): """ 路由匹配时,匹配成功后处理并传递给视图函数中参数的值 """ return int(value) def to_url(self, value): """ 使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数 """ val = super(RegexConverter, self).to_url(value) return val # 添加到flask中 app.url_map.converters['regex'] = RegexConverter @app.route('/index/<regex("\d+"):nid>') def index(nid): print(url_for('index', nid='888')) return 'Index' if __name__ == '__main__': app.run()
案例:登录,显示用户信息 (模板)
from flask import Flask,render_template,request,redirect,url_for,Markup app = Flask(__name__) app.debug = True # 模板渲染的时候 ''' 模板里面 渲染变量 {{}}-->和django一样 {% for k,v in dict.item()%} {{v.name}} {{v.get("name")}} {{v['name']}} {% endfor %} ''' ''' methods=["GET","POST] /detail/<int:nid> nid会当做参数传给我们的视图函数 我们给模板传值的必须是关键字传值 url_for()做反向解析,填的是endpoint的值,如果要跳转的视图没有指定endpoint,就用函数名 ''' USERS = { 1:{'name':'张三','age':18,'gender':'男','text':"道路千万条"}, 2:{'name':'李四','age':28,'gender':'男','text':"安全第一条"}, 3:{'name':'王五','age':18,'gender':'女','text':"行车不规范"}, } @app.route('/detail/<int:nid>',methods=['GET']) def detail(nid): info = USERS.get(nid) return render_template('detail.html',info=info) @app.route('/index',methods=['GET']) def index(): # return redirect('/login') url = url_for('l1') return redirect(url) #return render_template('index.html',user_dict=USERS) @app.route('/login',methods=['GET','POST'],endpoint='l1') def login(): if request.method == "GET": return render_template('login.html') else: # request.query_string user = request.form.get('user') pwd = request.form.get('pwd') if user == 'cxw' and pwd == '123': return redirect('http://www.baidu.com') return render_template('login.html',error='用户名或密码错误') def func1(a,b): return Markup(f"<h1>蔡徐坤{a},{b}</h1>") @app.route("/test") def test(): return render_template('test.html', error=func1) if __name__ == '__main__': app.run()
templates / detail.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>详细信息 {{info['name']}}...{{info.get('name')}}....{{info.name}}</h1> <div> {{info.text}} </div> </body> </html>
index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>用户列表</h1> <table> {% for k,v in user_dict.items() %} <tr> <td>{{k}}</td> <td>{{v.name}}</td> <td>{{v['name']}}</td> <td>{{v.get('name')}}</td> <td><a href="/detail/{{k}}">查看详细</a></td> </tr> {% endfor %} </table> </body> </html>
login.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>用户登录</h1> <form method="post"> <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="登录">{{error}} </form> </body> </html>
test.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> <!--{{error|safe}}--> <!--{{error("666","ttt")}}--> <!--<a>{{url_for("l1")}}</a>--> 1312 </body> </html>
六.请求与响应
from flask import Flask from flask import request from flask import render_template from flask import redirect from flask import make_response app = Flask(__name__) ''' 获取当前请求的内容 1 先要导入request 2 直接用request.方法,属性 返回的时候,如果需要设置额外的响应参数,比如cookie,heard 1 response=make_response(四剑客) 2 response.设置属性=“属性值” 3 return response ''' @app.route('/login.html', methods=['GET', "POST"]) def login(): # 请求相关信息 # request.method 提交的方法 print("request.method",request.method) # request.args get请求提及的数据 print("request.args", request.args) # request.form post请求提交的数据 # request.values post和get提交的数据总和 # request.cookies 客户端所带的cookie # request.headers 请求头 # request.path 不带域名,请求路径 # request.full_path 不带域名,带参数的请求路径 # request.script_root # request.url 带域名带参数的请求路径 # request.base_url 带域名请求路径 # request.url_root 域名 # request.host_url 域名 # request.host 127.0.0.1:500 # request.files # obj = request.files['the_file_name'] # obj.save('/var/www/uploads/' + secure_filename(f.filename)) # 响应相关信息 # return "字符串" # return render_template('html模板路径',**{}) # return redirect('/index.html') #return jsonify({'k1':'v1'}) # response = make_response(render_template('index.html')) # response是flask.wrappers.Response类型 # response.delete_cookie('key') # response.set_cookie('key', 'value') # response.headers['X-Something'] = 'A value' # return response response1=make_response(render_template('rr.html')) #response1.set_cookie('key_sss', 'valuessbbsd') 设置cookie # response1.delete_cookie('key_sss') 删除cookie response1.headers['sb'] = 'asdas' 设置响应头 return response1 if __name__ == '__main__': app.run()
rr.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> </head> <h1>qweqw</h1> <body> </body> </html>
七.session (cookie键可通过配置文件修改,默认是session;value值是一个加密的字典)
from flask import Flask,session app = Flask(__name__) app.debug=True app.secret_key="ajsdklas" app.config['SESSION_COOKIE_NAME']="session_key" # app.session_interface 查看源码 @app.route("/") def index(): session['name']="sb" return "ok" @app.route("/test") def test(): print(session['name']) return "ok1" if __name__ == '__main__': app.run()
app.session_interface中save_session的参数(设置cookie的参数)
key, 键 value='', 值 max_age=None, 超时时间 cookie需要延续的时间(以秒为单位)如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止 expires=None, 超时时间(IE requires expires, so set it if hasn't been already.) path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问,浏览器只会把cookie回传给带有该路径的页面,这样可以避免将cookie传给站点中的其他的应用。 domain=None, Cookie生效的域名 你可用这个参数来构造一个跨站cookie。如, domain=".example.com"所构造的cookie对下面这些站点都是可读的:www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取 secure=False, 浏览器将通过HTTPS来回传cookie httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
session源码的执行流程
-save_seesion -响应的时候,把session中的值加密序列化放大到了cookie中,返回到浏览器中 -open_session -请求来了,从cookie中取出值,反解,生成session对象,以后再视图函数中直接用sessoin就可以了。
八.闪现(message)
-设置:flash('aaa')
-取值:get_flashed_message()
-
-假设在a页面操作出错,跳转到b页面,在b页面显示a页面的错误信息
from flask import Flask,flash,get_flashed_messages,request,redirect app = Flask(__name__) app.debug=True app.secret_key = 'asdfasdf' ''' 1 设置flash 1.1 flash("要传递的值",category="分类的名称"),如果不传默认是message 本质:session['_flash'] 2取flash设置的值我们用get_flashed_messages 2.1 get_flashed_messages(with_categories=False, category_filter=()), 2.1.1如果不传递 category_filter,取出上面存储的所有分类传递的值 2.1.2如果不传with_categories就只取值,不取分类的名字,如果传,就获取 分类名和分类值 3 这个flash只能一个视图函数中取,只要有一个视图函数取过了,那其他视图函数就不能获取 本质:session.pop("_flash") 3.1 但是在同一个视图函数里面可以无限的取值 ''' @app.route('/index') def index(): # 从某个地方获取设置过的所有值,并清除。 #flash('超时错误',category="x1") flash("它过来了,你要小心") flash("我是第二个",category="ss") return "ssdsdsdfsd" # return redirect('/error') @app.route('/error') def error(): """ 展示错误信息 :return: 如果get_flashed_messages(with_category=True) """ #data = get_flashed_messages(category_filter=['x1']) data=get_flashed_messages(with_categories=True,category_filter=['ss']) data1 = get_flashed_messages(with_categories=True, category_filter=['ss']) print(type(data)) print(data1) return "错误信息:%s" %(data,) if __name__ == '__main__': app.run()
九.请求扩展
from flask import Flask,render_template app = Flask(__name__) ''' 1 before_request 请求之前 1.1可写多个befor_request函数 1.2而且是从上往下执行的 1.3 一旦有返回值,请求的视图函数不会执行,已经剩下的befor_request不会执行 2 after_request 请求之后 2.1可以写多个after_request函数 2.2 所有的after_request是从下往上执行,和befor_request相反 2.3 无论 befor_request有没有返回值,我的after_request都会执行 2.4 必须接受response,而且必须返回response 3 before_first_request 是我项目启动后,接受到的第一个请求,会执行该函数,后面就不会在执行 4 teardown_request(e) 4.1 这是e 是接收我服务器抛出的异常 4.2 无论我服务器有没有错误,都会执行该函数 4.3 虽然能接收异常,但是没有办法处理异常 5 errorhandler(500) 5.1 参数的中值为错误码 5.2 当服务器抛出对应状态码的异常,就会执行该函数 5.3 并且该函数可以处理异常,让用户无法感知,服务器错误 5.4 每一个错误码,都需要一个对应的函数进行处理 ''' # app.debug=True #基于它做用户登录认证 # @app.before_request # def process_request(): # print(request) # print("我是请求之前") # return "我回来了" # # @app.before_request # def process_request1(): # print("我是请求之前1") #请求之后 # @app.after_request # def process_response1(response): # print('process_response1 走了') # return response # @app.after_request # def afr(response): # print("23423") # return response # @app.after_request # def tt(response): # print("我是第一个") # return response #项目接收的第一个请求 # @app.before_first_request # def a(): # print("我的第一次") #如论有无异常都执行,如果没有异常这个e就是None # @app.teardown_request # def ter(e): # # if e: # #logingh # # return "wo si l" # print("我抛异常") # # #我不希望看到我的服务器错误,参数填写的是错误状态码 # @app.errorhandler(500) # def error_500(arg): # print(arg) # return "请等一下在试" # # @app.errorhandler(404) # def erros(arg): # print(arg) # return "你拨打的用户不存在" @app.template_global() def sb(a1, a2): return a1 + a2 @app.template_filter() def db(a1, a2, a3): print(a1) print(a2) print(a3) return a1 + a2 + a3 # @app.route('/') def index(): print("我是你要请求的函数") # a return render_template("gllo.html") if __name__ == '__main__': app.run()
gllo.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> {{sb(1,2)}} {{ 1|db(2,3)}} </body> </html>
十.中间件:
from flask import Flask app=Flask(__name__) class MyMiddleware: def __init__(self,wsgi_app): self.wsgi_app_old=wsgi_app def __call__(self,environ, start_response): print("我的开始之前") res=self.wsgi_app_old(environ, start_response) print("我是所有的结束之后") return res @app.before_request def a(): print("我是请求之前") @app.route("/") def index(): print("我是视图函数") return "ok" if __name__ == '__main__': app.__call__ ''' def __call__(self, environ, start_response): print(ww) return self.wsgi_app(environ, start_response) print(asdas) print(ww) return self.wsgi_app(environ, start_response) print(asdas) ''' app.wsgi_app = MyMiddleware(app.wsgi_app)#MyMiddleware的对象 #新的app.wsgi_app #app.wsgi_app是MyMiddleware的对象 #app.wsgi_app() #MyMiddleware的对象的__call__() #MyMiddleware.__call__ app.run()