# 路由和配置
# 主流Web框架的介绍
# Django
- 优点:组件和功能齐全、教科书式框架、
- 缺点:占资源、创建复杂度较高、
# Flask:
- 优点:轻、快,一切从简能省则省
- 缺点:先天不足(原生组件少)、第三方组件稳定性较差(一旦不向下兼容,组件失效)
- Flask的session中的机制是:交由客户端保存机制
# Tornado:
- 优点:异步非阻塞框架,单线程执行多任务
# Flask安装
# 创建虚拟环境
#方式一:使用命令安装 1、安装virtualenv:若要使用python虚拟环境进行开发,首先需要下载virtualenv 命令:pip3 install virtualenv 2、创建虚拟环境: 命令:virtualenv venv(文件名) #方式二:使用pycharm安装 1、在一个窗口点击编译器,新建编译器,选择新建虚拟环境,选择好位置,点击生成编译的语言,最后生成即可 2、安装flask:pip3 install flask
# 安装flask时携带的三大组件
1、jinjia2:模板渲染语法,是Django的渲染语法template的爸爸 2、MarkupSafe:markupsafe是jinja2的依赖项,通过它来向浏览器返回合格的转义的字节流 3、Werkzeug:是Python的WSGI规范的实用函数库
# Werkzeug与其他满足WSGI库的比较
#Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器 #Werkzeug版本的消息收发 from werkzeug.wrappers import Request,Response @Request.application def hello(request): return Response('Hello World') if __name__ == '__main__': from werkzeug.serving import run_simple run_simple('localhost',4000,hello) ----------------------------------------------- #wsgiref版本的消息收发 from wsgiref.simple_server import make_server def runserver(environ, start_response): start_response('200 OK',[('Content-Type','text/html')]) return [byte('<h1>Hello world</h1>',encoding='utf-8')] if __name__ == "__main__"": httped = make_server('',8000, runserver) httped.serve_forever() ----------------------------------------------------- #本质的本质还是socket import socket from threading import Thread sk = socket.socket() sk.bind(('127.0.0.1',8001)) #默认浏览器端口8001 sk.listen() def func(conn): from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(b'hello') while 1: conn,addr = sk.accept() t = Thread(target=func,args=(conn,)) #实现并发 t.start()
# 启动flask项目
# 8行代码
from flask import Flask app = Flask(__name__) #实例化Flask对象,__name__本地执行是__main__ @app.route('/') #装饰器执行v = app.route('/') ,将函数名传入v中最后返回封装了hello的函数,以备后面调用 def hello(): name = request.args.get("name", "World") return f'Hello, {escape(name)}!' if __name__ == '__main__': app.run()
# 分析
1、通过Flask实例化:查看Flask的源码 def __init__( self, import_name, # 导入的名字 #远程静态文件所用的Host地址,默认为空 static_url_path=None, #静态文件路径默认当前项目中的static目录 static_folder="static", #远程静态文件所用的Host地址,默认为空 static_host=None, # host_matching是否开启host主机位匹配,是要与static_host一起使用,如果配置了static_host, 则必须赋值为True; # 这里要说明一下,@app.route("/",host="localhost:5000") 就必须要这样写 host_matching=False, # 理论上来说是用来限制SERVER_NAME子域名的,但是目前还没有感觉出来区别在哪里 subdomain_matching=False, template_folder="templates", #模板文件 # 指向另一个Flask实例的路径 instance_path=None, instance_relative_config=False, # 主模块所在的目录的绝对路径,默认项目目录 root_path=None, ): 2、@app.route('/') :将'/'和函数index的对应关系添加到路由中,类似个字典 { '/':index } 3、app.run():监听请求,如果有用户请求到来,就执行app的__call__方法 3.1、点击run函数跳转源码: def run(self, host=None, port=None, debug=None,...): ... run_simple(host, port, self, **options) # 在这里的第三个参数self也就是我们所说的实例化app,由上面的werkzeug实例我们知道,第三个参数会加括号执行,也就是触发了对象的__call__()方法,Django的wsgi中也是执行的__call__方法
# 配置Flask的静态文件
app = Flask(__name__, #导入本模块 template_folder='templates', #指定模板文件目录 static_url_path='/static/', #指定静态文件别名,相当于Djagno的STATIC_URL static_folder='static') # 指定静态文件位置 #使用静态文件 <img src="{{ url_for('static',filename='1.png') }}"/> #方式一:反向解析 <img src= "/static/1.png"/> # 方式二
# 应用的Demo
简单的登陆验证
from flask import Flask, escape,render_template,request,redirect app = Flask(__name__) @app.route('/login',methods=['GET','POST']) def login(): if request.method=='GET': print(request.query_string) # url中的数据都在此 return render_template('login.html') else: user = request.form.get('user') pwd = request.form.get('pwd') if user == 'alex' and pwd == '123': return redirect('https://www.baidu.com') else: return render_template('login.html',error='用户或密码错误') if __name__ == '__main__': app.run()
# 添加验证登陆的案列
# 主逻辑的代码
from flask import Flask, escape, render_template, request, redirect, session, url_for app = Flask(__name__) app.debug = True # 可以自动重启,可以生成浏览器的报错信息 app.secret_key="adfaew@##$@" # 配置服务器的密钥,通过它和存入客户端的session进行位运算后序列胡=化,可以得到相应的session字典,没有它,将无法生成session USERS = { 1: {'name': '张三', 'age': 23, 'gender': '男', 'text': '本人性格开朗、稳重、有活力,待人热情、真诚;工作认真负责,积极主动,能吃苦耐劳,'}, 2: {'name': '李四', 'age': 23, 'gender': '女', 'text': '本人性格开朗、稳重、有活力,待人热情、真诚;工作认真负责,积极主动,能吃苦耐劳,'}, 3: {'name': '王五', 'age': 23, 'gender': '男', 'text': '本人性格开朗、稳重、有活力,待人热情、真诚;工作认真负责,积极主动,能吃苦耐劳,'}, 4: {'name': '续七', 'age': 23, 'gender': '女', 'text': '本人性格开朗、稳重、有活力,待人热情、真诚;工作认真负责,积极主动,能吃苦耐劳,'}, } import functools def wapper(func): @functools.wraps(func) #在没有起别名的情况下使用它 def inner(*args,**kwargs): user = session.get('user_info') if not user: return redirect('/login') return func(*args, **kwargs) return inner @app.route('/index', methods=['GET']) # 必须写method,否则路径毫无意义 @wapper # 通过装饰器实现验证 def index(): user = session.get('user_info') # session中获取相应的参数 if not user: url = url_for('login') # 使用url_for反向生成路径 return redirect(url) # 跳转页面 return render_template('index.html', user_dict=USERS) # render_template相当于django中的render,用户渲染模板 @app.route('/detail/<int:nid>', methods=['GET']) # 使用<int:int> 指定传入参数的类型 def detail(nid): # 当使用反向时,可以传参数url_for('别名',nid=123} 必须时关键字参数 user = session.get('user_info') if not user: url = url_for('login') return redirect(url) info = USERS.get(nid) return render_template('detail.html', info=info) @app.route('/login', methods=['GET', 'POST'], endpoint='login') # endpoint 相当于取别名 def login(): if request.method == 'GET': print(request.query_string) # url中的数据都在此 return render_template('login.html') else: user = request.form.get('user') pwd = request.form.get('pwd') if user == 'alex' and pwd == '123': session['user_info'] = user # 设置session return redirect('/index') else: return render_template('login.html', error='用户或密码错误') if __name__ == '__main__': app.run()
# 相应的模板(模板语法)
#index.html文件中 <body> <h1>用户列表</h1> <table border="1px"> <!-- for循环中支持加括号,调用方法 --> {% for k, v in user_dict.items() %} <tr> <td>{{k}}</td> <!-- 字典支持python语法 --> <td>{{v.name}}</td> <td>{{v['name']}}</td> <td>{{v.get('name')}}</td> <td>{{v.gender}}</td> <td><a href="/detail/{{k}}">查看详细</a></td> </tr> {%endfor%} </table> </body> -------------------------------------------- #detail.html文件 <body> <h1>详细信息</h1> {{info.name}} {{info.text}} </body>
# Flask 配置文件
Flask的默认配置
flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为: #: Default configuration parameters. default_config = ImmutableDict({ 'ENV': None, 'DEBUG': None, #编辑环境 'TESTING': False,#上线前测试,日志打印 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, # 密钥设置,Flask使用这个密钥来对cookies和别的东西进行签名。 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), # session过期时间,默认设置31天,注意默认是化为秒,必须设置day 'USE_X_SENDFILE': False, 'SERVER_NAME': None,#当前所出的域名 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session',#就是交给客户端存储缓存的名字,可以通过浏览器的F12中Aplication中cookies中查看 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, # 每登陆一次都要刷新session 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json',#配置返回的相应头的Content-Type,如果浏览器识别不了,就会下载响应体 'TEMPLATES_AUTO_RELOAD': None, # 模板改完后会自动加载 'MAX_COOKIE_SIZE': 4093, })
通过上面的案例我们知道如secret_key、app.debug = True... 当配置文件一多,代码看上去就不整洁,所以我们需要统一配置
方式一:字典的添加键值对
app = Flask(__name__) app.config['DEBUG'] = True PS: 1、由于Config对象本质上是字典,所以还可以使用app.config.update(...) 2、上面的app.debug=True的当时不适用于其他参数配置,因为它是通过方法可以设置的,最常用的还是app.config['DEBUG'] = True
方式二:添加settings.py
# 现在项目目录下创建一个settings.py文件,在settings.py文件中 DEBUG = True SECRET_KEY = "ASDFSASDF" # 在app.py文件中导入settings.py的配置 app = Flask(__name__) app.config.from_pyfile('settings.py') .....
# from_pyfile源码
1、先通过传入的字符串,拼接路径,找到相应的文件 2、读取文件中的内容,进行编译,添加到config文件中 exec(compile(config_file.read(), filename, "exec"), d.__dict__)
方式三:在settings.py中写类,通过继承实现
# 在settings.py文件中 class Config(object): SECRET_KEY = "SDFFDEEEF" 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 # 在app中通过.from_object实现启用 app = Flask(__name__) app.config.from_object('settings.TestingConfig') # 从sys.path开始往下写路径
# from_object源码
1、点击from_object查看源码 def from_object(self, obj): if isinstance(obj, string_types): #如果传入的额是字符串 obj = import_string(obj) for key in dir(obj): if key.isupper(): self[key] = getattr(obj, key) # 反射 2、查看import_string ,django也使用过import_string 在函数中先进行切割 import_name = str(import_name).replace(":", ".") 最后返回相应的函数 return getattr(module, obj_name)
# 其他导入方式
1、 app.config.from_envvar("环境变量名称") 环境变量的值为python文件名称名称,内部调用from_pyfile方法 2、 app.config.from_json("json文件名称") JSON文件名称,必须是json格式,因为内部会执行json.loads 3、 app.config.from_mapping({'DEBUG':True}) 字典格式 PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录 #还记得Flask类中的init函数中的这两个参数就是用来配置root目录的
# 路由系统
# 路由的写法
@app.route('/login', methods=['GET', 'POST'], endpoint='login') def login(): if request.method == 'GET': print(request.query_string) # url中的数据都在此 return render_template('login.html') else:pass
# 源码分析
1、点击route函数查看源码 def route(self, rule, **options): #rule是路径规则’/‘,其他的都在option中 def decorator(f): #f为被装饰的函数 endpoint = options.pop("endpoint", None) #删除并获得别名 self.add_url_rule(rule, endpoint, f, **options) #添加匹配规则 return f #self还是app return decorator #返回了个函数,装饰器,闭包 # 总结: 1、因为给route函数传了参数,且加了括号,也是说直接就返回了decorator函数,此后将被装饰的login当参数f传递给decorator 2、此时就相当于: @decorator def login():pass 3、分别在装饰器中获取别名,执行app中的add_url_rule方法,主要还是这里添加路由的对应关系:源码中的一句话 Basically this example:: @app.route('/') def index():pass Is equivalent to the following:: def index():pass app.add_url_rule('/', 'index', index) If the view_func is not provided you will need to connect the endpoint to a view function like so: app.view_functions['index'] = index 3.2:如果没有传入别名: if endpoint is None: # view_func是我们的视图函数名 endpoint = _endpoint_from_view_func(view_func) #帮我们生成别名 3.3:打开_endpoint_from_view_func函数 def _endpoint_from_view_func(view_func): ... return view_func.__name__ #函数名,也就是说,默认的别名就是函数名,如果重名会报错overwriting #self.view_functions = {} 此字典用户存储视图函数和对应的别名,也是通过它来添加存储对应关系的 4、最后返回函数原函数的名字f=login
# 补习装饰器
def warpper(a,b): def inner(f): print('sdfasdf',f) return f return inner @warpper(1,3) # 执行后返回inner,此时将func函数当参数传入f def func(): print('1234234') func() #sdfasdf <function func at 0x000001F1828B2AE8> #1234234
通过研究源码我们可以理解了,最主要的一个函数是add_url_rule(),那么我们可不可以这样写函数?可以的
def test(): return '测试' app.add_url_rule('/test','n2',test,methods=['GET']) #也是可以的
# 路由系统之CBV
# 简单实例CBV的写法
from flask import Flask, request, url_for,views app = Flask(__name__) app.config.from_object('settings.TestingConfig') 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 get(self): return 'Index.GET' def post(self): return 'Index.POST' app.add_url_rule('/index1', view_func=IndexView.as_view(name='index1')) # name=endpoint,这里的name相当于别名,和django是有区别的 if __name__ == '__main__': app.run() ------------------------------------------------------- #带参数的CBV class DetailView(views.MethodView): methods = ['POST','GET'] def get(self,nid): #将具体的参数写在响应的函数参数中 info = USERS.get(nid) return render_template('detail.html',info=info) b_detail.add_url_rule('/detail/<int:nid>',view_func=DetailView.as_view(name='detail'))
# CBV源码分析
1、进入as_view(),进入后加载一个view函数:知识加载,as_view结束即返回此函数 def view(*args, **kwargs): self = view.view_class(*class_args, **class_kwargs) #view_class就是当前写的类 return self.dispatch_request(*args, **kwargs) #同样有dispatch,也就是我们的自定义类中,如果没有,就去继承的MethodView中找,也是使用的反射 2、紧接着给view函数赋值__name__,相当别名,第一个参数name if cls.decorators: #有装饰器 view.__name__ = name #赋值别名 view.__module__ = cls.__module__ for decorator in cls.decorators: view = decorator(view) #将我们写的装饰器一层一层的套在view上 3、接着给view添加属性 view.view_class = cls #将我们写的类赋值给view_class view.__name__ = name #别名,传入的 view.__doc__ = cls.__doc__ view.__module__ = cls.__module__ view.methods = cls.methods #去自己写的类中取methods view.provide_automatic_options = cls.provide_automatic_options return view #最后将view函数返回
函数知识补充:
#函数也可以.属性,一起皆对象 def func(): print('ok') func.xxx =2 print(func.xxx) #2
# 一般不继承View,一般继承MethodView
from flask import Flask,views,redirect,url_for # 导入views 基础类 app = Flask(__name__) # 定义装饰器 def auth_decorator(func): def inner(*args,**kwargs): print('装饰器') return func(*args,**kwargs) return inner CBV 添加 装饰器 和 指定 请求方法 class IndexView(views.MethodView): methods = ['POST','GET'] # 指定允许的 请求方法 decorators = [auth_decorator] # 所有方法都加上 装饰器 def get(self): return redirect(url_for('home')) def post(self): return 'POST' class HomeView(views.MethodView): def get(self): return 'Home' app.add_url_rule('/index',view_func=IndexView.as_view(name='index')) app.add_url_rule('/home',view_func=HomeView.as_view(name='home')) # CBV 只能通过这种方式 # view_func = IndexView.as_view(name='index') # 指定 反向解析的name if __name__ == '__main__': app.run()
# @app.route和app.add_url_rule的参数
# 常见参数总览
def route(self, rule, **options):pass #options还有很多
1、rule, URL规则
2、view_func, 视图函数名称
3、defaults=None, 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数
4、endpoint=None, 名称,用于反向生成URL,即: url_for('名称')
5、methods=None, 允许的请求方式,如:["GET","POST"]
6、strict_slashes=None, 对URL最后的 / 符号是否严格要求,
7、redirect_to=None, 重定向到指定地址如@app.route('/index/<int:nid>',redirect_to='/home/<nid>')
8、subdomain=None, 子域名访问
小提示:
使用了redirect_to后,浏览器接受的308,永久重定向,也就是根本不会进入视图就进行了重定向,区别于redirect会进入到视图函数
使用default设置默认参数
from flask import Flask app = Flask(__name__) app.DEBUG = True app.secret_key = '@WFSFFSdf' @app.route('/',methods=['GET'],endpoint='n1',defaults={'nid':88}) def index(nid): return str(nid) #页面返回88 if __name__=='__main__': app.run()
将老页面指向新页面使用redirect_to
@app.route('/index',methods=['GET'],endpoint='n1',redirect_to='/index2') def index(nid): return '公司老页面' @app.route('/index2',methods=['GET'],endpoint='n2') def index2(): return '公司新页面' if __name__=='__main__': app.run()
配置子域名
# 当域名需要解析的时候,会先在本地找域名解析,也就是我们电脑的C:\Windows\System32\drivers\etc\hosts的文件下查找对应关系,要是没有才通过网上的域名解析找对应的ip,下面我们在hosts中添加两条记录,linux的相关配置文件在etc/hosts/ 127.0.0.1 www.wanglixing.com 127.0.0.1 admin.wanglixing.com 127.0.0.1 buy.wanglixing.com from flask import Flask, views, url_for app = Flask(import_name=__name__) app.config['SERVER_NAME'] = 'wanglixing.com:9000' # 配置域名 @app.route("/", subdomain="admin") def static_index(): """Flask supports static subdomains This is available at static.your-domain.tld""" return "static.your-domain.tld" @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()
strict_slashes=None 对URL最后的/符号是否严格要求
@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
# 使用装饰器进行校验的方法
**方法一:**通过装饰器
def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner @app.route('/index.html',methods=['GET','POST'],endpoint='index') @auth def index(): return 'Index'
**方法二:**通过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!' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint
# 路由系统、扩展
# 常用的路径参数传递
@app.route('/user/<变量名字>') # 表示传入的参数是字符串,默认string,字符串可以接受一切参数,如数字、 @app.route('/post/<int:post_id>') # 表示传入的参数是int @app.route('/post/<float:post_id>') #表示传入的参数是float @app.route('/post/<path:path>') #传入的参数是路径 @app.route('/login', methods=['GET', 'POST'])
常用路由系统有以上五种,所有的路由系统都是基于一下对应关系来处理:
DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
如果没有指定具体的数据类型,那么默认就是使用
string
数据类型。<字符串>1、
int
数据类型只能传递int
类型。@app.route('/article/<int:id>/') # 注意<类型:id>这个后面的id受冒号前面类型的约束 def article(id): print(id) return 'ok'
2、
float
数据类型只能传递float
类型。3、
path
数据类型和string
有点类似,都是可以接收任意的字符串,但是path
可以接收路径,也就是说可以包含斜杠。@app.route('/p/<path:path>') def path(path): print(path) # 输入网址http://127.0.0.1:5000/p/path/aaaa 打印path/aaaa 是把path/aaaa整个看成是一个整体了 return 'path ok'
4、
uuid
数据类型只能接收符合uuid
的字符串。uuid
是一个全宇宙都唯一的字符串,一般可以用来作为表的主键。@app.route('/u/<uuid:user_id>/') # http://127.0.0.1:5000/u/046d7d41-c809-488e-a517-dd87291e8c4e/ def uu(user_id): print(user_id) # 046d7d41-c809-488e-a517-dd87291e8c4e return 'uuid ok' import uuid print(uuid.uuid4())
6、
any
数据类型可以在一个url
中指定多个路径。例如:# 如果有两个网址:/blog/<id>/ /article/<id>怎么实现一个app.route访问两个url的问题? @app.route('/<any(blog,article):url_path>/<id>/') def detail(url_path,id): if url_path == 'blog': return '博客详情:%s' % id else: return '博客详情:%s' % id
# 自定义转换器类
from flask import Flask, views, url_for from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) # 必须继承BaseConverter class RegexConverter(BaseConverter): """ 自定义URL匹配正则表达式 """ def __init__(self, map, regex): #map表示传入的第一个参数,regex表示我们的规则 super(RegexConverter, self).__init__(map) self.regex = regex def to_python(self, value): """ 路由匹配时,匹配成功后传递给视图函数中参数的值 :param value: :return: """ return int(value) def to_url(self, value): """ 使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数 :param value: :return: """ 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()
自定义URL转换器的方式总结
实现一个类,继承自
BaseConverter
。在自定义的类中,重写
regex
,也就是这个变量的正则表达式。将自定义的类,映射到
app.url_map.converters
上。比如:app.url_map.converters['tel'] = TelephoneConverter
to_python 和 to_url的作用
to_python的作用:这个方法的返回值,将会传递到view函数中作为参数。 to_url的作用:这个方法的返回值,将会在调用url_for函数的时候生成符合要求的URL形式。
# 举例说明
from flask import Flask,url_for from werkzeug.routing import BaseConverter app = Flask(__name__) # 一个url中,含有手机号码的变量,必须限定这个变量的字符串格式满足手机号码的格式 class TelephoneConveter(BaseConverter): regex = r'1[85734]\d{9}' # 用户在访问/posts/a+b/ 分别提取a和b两个版块 class ListConverter(BaseConverter): def to_python(self, value): # 这个地方的value就是请求地址根据参数类型传进来的值 return value.split('+') def to_url(self, value): return "+".join(value) # return "hello" app.url_map.converters['tel'] = TelephoneConveter app.url_map.converters['list'] = ListConverter @app.route('/') def hello_world(): print('='*30) print(url_for('posts',boards=['a','b'])) # /posts/a+b print('='*30) return 'Hello World!' @app.route('/user/<string:user_id>/') def user_profile(user_id): return '您输入的user_id为:%s' % user_id @app.route('/telephone/<tel:my_tel>/') def my_tel(my_tel): return '您的手机号码是:%s' % my_tel # http://127.0.0.1:5000/posts/a+b @app.route('/posts/<list:boards>/') def posts(boards): print(boards) # ['a', 'b'] return "您提交的板块是:%s" % boards if __name__ == '__main__': app.run(debug=True)
# 接收用户传递的参数
第一种:使用path的形式(将参数嵌入到路径中),就是上面讲的。
第二种:使用查询字符串的方式,就是通过
?key=value
的形式传递的。# 请求网址:http://127.0.0.1:5000/d/?wd=python @app.route('/d/') def d(): wd = request.args.get('wd') return '您通过查询字符串的方式传递的参数是:%s' % wd
如果你的这个页面的想要做
SEO
优化,就是被搜索引擎搜索到,那么推荐使用第一种形式(path的形式)。如果不在乎搜索引擎优化,那么就可以使用第二种(查询字符串的形式)。
# 反向解析
#
url_for
的基本使用url_for
的理解:通常是通过/
来找到视图函数hello_word
,url_for
的作用就是通过视图函数hello_word
来反向的找到/
。url_for
第一个参数,应该是视图函数的名字的字符串。后面的参数就是传递给url
。 如果传递的参数之前在url
中已经定义了,那么这个参数就会被当成path
的形式给url
。如果这个参数之前没有在url
中定义,那么将变成查询字符串的形式放到url
中。form flask import Flask from flask import url_for app = Flask(__name__) # http://127.0.0.1:5000/ @app.route('/') def hello_word(): print(url_for('list', page=1)) # /list/1/ return 'Hello World' # http://127.0.0.1:5000/list/ @app.route('/list/<page>/') def list(page): print(url_for('my_list', page=3, count=6)) # 构建出来的url:/post/list/2/?count=6 return 'list' @app.route('/post/list/<page>/') def my_list(page): return 'my list' -------------------------------------------------------- 无参数 app.route('/login',method=['GET','POST'],endpoint='login') 视图 return redirect(url_for('login')) 模板 <form action='{{url_for("login")}}'></form> 有参数 app.route('/index/<int:nid>',method=['GET','POST'],endpoint='index') 视图 return redirect(url_for('index',nid=111)) 模板 <form action='{{url_for("index",nid=111)}}'></form>
# 为什么需要
url_for
将来如果修改了
URL
,但没有修改该URL对应的函数名,就不用到处去替换URL了。url_for
会自动的处理那些特殊的字符,不需要手动去处理。url = url_for('login',next='/') # 会自动的将/编码,不需要手动去处理。 # url=/login/?next=%2F
强烈建议以后在使用url的时候,使用url_for
来反转url。
# URL中需要注意的点
在局域网中让其他电脑访问我的网站
如果想在同一个局域网下的其他电脑访问自己电脑上的Flask网站,那么可以设置
host='0.0.0.0'
才能访问得到。app.run(debug=True, host='0.0.0.0') 指定端口号: Flask项目,默认使用`5000`端口。如果想更换端口,那么可以设置`port=9000`。
url唯一:在定义url的时候,一定要记得在最后加一个斜杠。
- 如果不加斜杠,那么在浏览器中访问这个url的时候,浏览器会自动加斜杠,那么就访问不到。这样用户体验不太好。
- 搜索引擎会将不加斜杠的和加斜杠的视为两个不同的url。而其实加和不加斜杠的都是同一个url,那么就会给搜索引擎造成一个误解。加了斜杠,就不会出现没有斜杠的情况。
# GET请求和POST请求
在网络请求中有许多请求方式,比如:GET、POST、DELETE、PUT请求等。那么最常用的就是
GET
和POST
请求了。1. GET请求:只会在服务器上获取资源,不会更改服务器的状态。这种请求方式推荐使用GET请求。 2. `POST`请求:会给服务器提交一些数据或者文件。一般POST请求是会对服务器的状态产生影响,那么这种请求推荐使用POST请求。 3. 关于参数传递: GET请求:把参数放到url中,通过`?xx=xxx`的形式传递的。因为会把参数放到url中,所以如果视力好,一眼就能看到你传递给服务器的参数。这样不太安全。 POST请求:把参数放到`Form Data`中。会把参数放到`Form Data`中,避免了被偷瞄的风险,但是如果别人想要偷看你的密码,那么其实可以通过抓包的形式。因为POST请求可以提交一些数据给服务器,比如可以发送文件,那么这就增加了很大的风险。所以POST请求,对于那些有经验的黑客来讲,其实是更不安全的。 4. 在`Flask`中,`route`方法,默认将只能使用`GET`的方式请求这个url,如果想要设置自己的请求方式,那么应该传递一个`methods`参数。
# 装饰器报错/别名重复原因即解决办法
# 常见装饰器报错实例
from flask import Flask,render_template app = Flask(__name__) def war(func): def inner(*args, **kwargs): ret = func(*args, **kwargs) return ret return inner @app.route('/') @war def home(): return '家目录' @app.route('/index') @war def a(): #不仅只有在home视图函数名时,装饰器返回的时inner,在没有配置函数别名的时候,默认以函数名为别名,所以报错同名inner return '家目录' if __name__ == '__main__': app.run() #AssertionError: View function mapping is overwriting an existing endpoint function: inner
视图函数名重复报错
@app.route('/') def home(): return '家目录' @app.route('/index') def home(): return '家目录' #AssertionError: View function mapping is overwriting an existing endpoint function: home
# 两种方法解决
1、在装饰器中使用wrap,使在源码中取__name__时,依旧指向原本函数的名字 from functools import wraps def war(func): @wraps(func) def inner(*args, **kwargs): ret = func(*args, **kwargs) return ret return inner @app.route('/') @war def home(): return '家目录' @app.route('/index') @war def a(): return '家目录' ------------------------------------------------- 2、使用别名,在route中添加endpoint @app.route('/',endpoint='n1') @war def home(): return '家目录' @app.route('/index',endpoint='n2') @war def a(): #不仅只有在home视图函数名时,装饰器返回的时inner,在没有配置函数别名的时候,默认以函数名为别名,所以报错同名inner return '家目录'
← Werkzeug 讲解 模板、请求与响应 →