本篇所有操作均在基于 Ubuntu 16.04 LTS 的虚拟机下完成,且使用 Vagrant 来操作虚拟机系统,虚拟机系统 VirtualBox Version: 7.0
一、跳转和重定向
环境准备:
Python 2.7.11+
pip==9.0.3
flask==0.11.1
werkzeug==0.11.10
在 Flask,跳转和重定向是通过 flask.redirect 实现的。
1、跳转
跳转(状态码 301)多用于旧网址在废弃前转向新网址以保证用户的访问,有页面被永久性移走的概念。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: utf-8 -*-#
from flask import Flask, redirect
app = Flask(__name__)
@app.route('/help2')
def help2():
return 'Help2 Page'
@app.route('/help')
def help():
"""跳转"""
return redirect('help2', code=301)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000, debug=True)
使用 httpie 命令行工具访问结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
> http GET http://127.0.0.1:9000/help2
HTTP/1.0 200 OK
Content-Length: 10
Content-Type: text/html; charset=utf-8
Date: Tue, 13 Dec 2022 15:01:02 GMT
Server: Werkzeug/1.0.1 Python/2.7.18
Help2 Page
> http GET http://127.0.0.1:9000/help
HTTP/1.0 301 MOVED PERMANENTLY
Content-Length: 217
Content-Type: text/html; charset=utf-8
Date: Tue, 13 Dec 2022 15:01:06 GMT
Location: http://127.0.0.1:9000/help2
Server: Werkzeug/1.0.1 Python/2.7.18
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="help2">help2</a>. If not click the link.
使用 Postman 客户端执行结果如下:
访问 GET http://127.0.0.1:9000/help
后页面会自动跳转至新的页面 GET http://127.0.0.1:9000/help2
。
2、重定向
重定向(状态码 302)表示页面是暂时性的转移,但是也不建议经常性使用重定向。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: utf-8 -*-#
from flask import Flask, url_for, redirect
app = Flask(__name__)
@app.route('/index2/')
def index2():
return 'Index2 Page'
@app.route('/index/')
def index():
"""重定向"""
return redirect(url_for('index2', id=2)) # redirect 的状态码 code 默认是 302,即重定向
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000, debug=True)
使用 httpie 命令行工具访问结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
> http GET http://127.0.0.1:9000/index2/
HTTP/1.0 200 OK
Content-Length: 11
Content-Type: text/html; charset=utf-8
Date: Tue, 13 Dec 2022 15:00:40 GMT
Server: Werkzeug/1.0.1 Python/2.7.18
Index2 Page
> http GET http://127.0.0.1:9000/index/
HTTP/1.0 302 FOUND
Content-Length: 233
Content-Type: text/html; charset=utf-8
Date: Tue, 13 Dec 2022 15:00:48 GMT
Location: http://127.0.0.1:9000/index2/?id=2
Server: Werkzeug/1.0.1 Python/2.7.18
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="/index2/?id=2">/index2/?id=2</a>. If not click the link.
使用 Postman 客户端执行结果如下:
访问 GET http://127.0.0.1:9000/index
后页面会自动跳转至新的页面 GET http://127.0.0.1:9000/index2/?id=2
。
相关链接:
二、301 302 308 状态码
重定向还是有需要深入探讨的地方,返回码不仅有经常使用的 301、303 还有 302、307、308。它们之间有什么区别呢?可以按照 是否缓存 和 重定向方法 这两个维度去拆分。
缓存(永久重定向) | 不缓存(临时重定向) | |
---|---|---|
转GET | 301 | 302、303 |
方法保持 | 308 | 307 |
如果是永久重定向那么浏览器客户端就会缓存此次重定向结果,下次如果有请求则直接从缓存读取(除非清除浏览器缓存)。譬如我们切换域名,将所有老域名的流量转入新域名,可以使用永久重定向。
如果只是临时重定向那么浏览器则不会缓存。譬如我们的服务临时升级,会使用临时重定向。
方法保持的意思是原请求和重定向的请求是否使用相同的方法。譬如原请求是 POST 提交一个表单,如果是 301 重定向的话,重定向的请求会转为 GET 重新提交,如果是 308 则会保持原来 POST 请求不变。
相关链接:
浅析 http 状态码 301、302、303、307、308 区别及对 SEO 优化网址 URL 劫持的影响
三、应用代码分析
1、配置文件
config.py
配置文件内容:
1
2
3
4
5
6
7
8
# coding=utf-8
# file: config.py
DEBUG = False
try:
from local_settings import *
except ImportError:
pass
代码分析:
(1)
1
from local_settings import *
local_settings.py 文件时可选存在的,它不进入版本库。若在 local_settings.py 文件中添加了设置项,则这些设置项会全部被添加进配置文件 config.py 中。注意:这是常用的通过本地配置文件重载版本库配置的方法!
2、应用文件
simple.py
文件内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# coding=utf-8
# file: simple.py
from flask import Flask, request, abort, redirect, url_for
app = Flask(__name__)
app.config.from_object('config')
@app.route('/people/')
def people():
name = request.args.get('name') # request.args.get() 获取查询参数 name 的值
if not name:
return redirect(url_for('login'))
user_agent = request.headers.get('User-Agent') # request.headers.get() 获取消息头 user_agent 的值
return 'Name: {0}; UA: {1}'.format(name, user_agent)
@app.route('/login/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user_id = request.headers.get('user_id')
return 'User: {} login'.format(user_id)
else:
return 'Open Login page'
@app.route('/secret/')
def secret():
abort(401) # 表示禁止访问
print 'This is never executed'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000, debug=app.debug)
代码分析:
(1)
1
app.config.from_object('config')
向 app.config 加载配置文件 config.py 中的全部内容。
(2)
1
@app.route('/people/')
访问 http://127.0.0.1/people
的请求会被 308 跳转至 http://127.0.0.1/people/
,保证了 URL 的唯一性。
(3)
1
user_agent = request.headers.get('User-Agent')
request.headers 存放了请求头的头部信息,通过它可以获取 UA 值。
(4)
1
@app.route('/login/', methods=['GET', 'POST'])
request.methods 的值是请求的类型,表示可以使用 POST 请求。
(5)
1
abort(401)
执行 abort(401) 会放弃请求并返回错误代码 401,表示禁止访问。之后的语句永远不会被执行。如下图所示:
(6)
1
app.run(host='0.0.0.0', port=9000, debug=app.debug)
能使用 debug=app.debug
是因为 flask.config.ConfigAttribute 在 app 中做了配置的代理,app.debug 其实就是 app.config['DEBUG']
且默认值是 False(可以通过打印 print app.debug
进行验证)。目前的配置代理项有:
1
2
3
4
5
6
7
app.debug -> DEBUG
app.testing -> TESTING
app.secret_key -> SECRET_KEY
app.session_cookie_name -> SESSION_COOKIE_NAME
app.permanent_session_lifetime -> PERMANENT_SESSION_LIFETIME
app.use_x_sendfile -> USE_X_SENDFILE
app.logger_name -> LOGGER_NAME
3、flask.config.ConfigAttribute
安装了 flask 之后,进入 flask\app.py 文件中可看到以下配置键且均设置了默认值(在 default_config 中设置默认值):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Flask(_PackageBoundObject):
......
config_class = Config
debug = ConfigAttribute('DEBUG')
testing = ConfigAttribute('TESTING')
secret_key = ConfigAttribute('SECRET_KEY')
session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME')
permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME',
get_converter=_make_timedelta)
send_file_max_age_default = ConfigAttribute('SEND_FILE_MAX_AGE_DEFAULT',
get_converter=_make_timedelta)
use_x_sendfile = ConfigAttribute('USE_X_SENDFILE')
logger_name = ConfigAttribute('LOGGER_NAME')
......
default_config = ImmutableDict({
'DEBUG': get_debug_flag(default=False),
'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,
})
再转到 flask\config.py 文件中可看到 ConfigAttribute 类的作用其实就是将属性转发到配置。所以上述 app.run(host='0.0.0.0', port=9000, debug=app.debug)
中的 debug=app.debug
也就等同于 debug=app.config['DEBUG']
,由于 app.config['DEBUG']
默认值是 False ,所以 debug=app.debug
最终等于 debug=False
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ConfigAttribute(object):
"""Makes an attribute forward to the config"""
def __init__(self, name, get_converter=None):
self.__name__ = name
self.get_converter = get_converter
def __get__(self, obj, type=None):
if obj is None:
return self
rv = obj.config[self.__name__]
if self.get_converter is not None:
rv = self.get_converter(rv)
return rv
def __set__(self, obj, value):
obj.config[self.__name__] = value