# 路由和配置

# 主流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转换器的方式总结

  1. 实现一个类,继承自BaseConverter

  2. 在自定义的类中,重写regex,也就是这个变量的正则表达式。

  3. 将自定义的类,映射到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)
    

# 接收用户传递的参数

  1. 第一种:使用path的形式(将参数嵌入到路径中),就是上面讲的。

  2. 第二种:使用查询字符串的方式,就是通过?key=value的形式传递的。

    # 请求网址:http://127.0.0.1:5000/d/?wd=python
    @app.route('/d/')
    def d():
        wd = request.args.get('wd')
        return '您通过查询字符串的方式传递的参数是:%s' % wd
    
  3. 如果你的这个页面的想要做SEO优化,就是被搜索引擎搜索到,那么推荐使用第一种形式(path的形式)。如果不在乎搜索引擎优化,那么就可以使用第二种(查询字符串的形式)。

# 反向解析

  • # url_for的基本使用

    url_for的理解:通常是通过 /来找到视图函数hello_wordurl_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请求等。那么最常用的就是GETPOST请求了。

    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 '家目录'