赞
踩
render_template模板页面传参
@app.route('/index')
def index():
user = {'username':'duke'}
#将需要展示的数据传递给模板进行显示
return render_template('index.html',title='我的',user=user)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{ title }} - 博客</title>
</head>
<body>
<h1> Hello ,{{ user.username }} !</h1>
</body>
</html>
session机制的介绍:
RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret.
运行错误:会话不可用,因为没有设置密钥。将应用程序上的secret_key设置为一些独特且秘密的内容。
from flask import Flask, request, session
app = Flask(__name__)
app.secret_key = "wristwaking"
@app.route("/")
def index():
session["login_user"] = "唤醒手腕"
print(session.get("login_user"))
return "index"
request的基本用途介绍
TypeError: The view function for 'index' did not return a valid response. The function either returned None or ended without a return statement.
错误类型:“索引”的视图函数没有返回有效的响应。函数返回None或没有rerun语句。
这是因为没有在URL参数中找到infor
,所以request.args.get('infor')
返回Python内置的None,而Flask不允许返回None。
解决方法很简单,我们先判断下它是不是None:
@app.route('/')
def hello_world():
data = request.args.get('infor')
if data==None:
# do something
return ''
return data
# 第二种方案,就是设置初始值
# data = request.args.get("infor") # 设置默认值
还记得上面有一次请求是这样的吗? http://127.0.0.1:5000?infor=1&infor=2
,仔细看下,infor有两个值。
@app.route('/')
def hello_world():
data = request.args.getlist('infor') # 返回一个list
return str(data)
在request请求中,解析POST数据
# username = wristwaking password = 5201314
@app.route("/form", methods=["POST"])
def form():
print(request.form)
# Immutable Multi Dict 不可改变的多个字典
# ImmutableMultiDict([('username', 'wristwaking'), ('password', '5201314')])
print(request.stream)
# <werkzeug.wsgi.LimitedStream object at 0x000002A9AF2CE4F0>
print(request.args)
# ImmutableMultiDict([])
return "success"
我们要想办法把我们要的username、password提取出来,怎么做呢?自己写?不用,Flask已经内置了解析器request.form
@app.route("/form", methods=["POST"])
def form():
print(request.stream.read())
# b'username=wristwaking&password=5201314'
print(request.stream.read())
# b'' 说明流只读取到一遍了
return "success"
解释原因:request.stream.read()
读取到的是字节流,我们进行解码,规则是utf-8
,结果如下所示:
@app.route("/form", methods=["POST"])
def form():
data = request.stream.read().decode(encoding="utf-8")
print(data)
# username=wristwaking&password=5201314
return "success"
@app.route('/register', methods=['POST'])
def register():
print(request.headers)
# print(request.stream.read()) # 不要用,否则下面的form取不到数据
print(request.form)
print(request.form['name'])
print(request.form.get('name'))
print(request.form.getlist('name'))
print(request.form.get('nickname', default='唤醒手腕'))
return 'welcome'
响应JSON时,除了要把响应体改成JSON格式,响应头的Content-Type
也要设置为 application/json
@app.route('/add', methods=['POST'])
def add():
result = {'sum': request.json['a'] + request.json['b']}
return Response(json.dumps(result), mimetype='application/json')
# 使用 jsonify 工具函数即可
@app.route('/add', methods=['POST'])
def add():
result = {'sum': request.json['a'] + request.json['b']}
return jsonify(result)
Flask中app
和current_app
的理解
在Flask内部维护者两个线程隔离的栈,current_app
指向了AppContext
(应用上下文)中的栈顶。
线程有个叫做ThreadLocal
的类,也就是通常实现线程隔离的类。而werkzeug
自己实现了它的线程隔离类:werkzeug.local.Local,LocalStack就是用Local实现的。
LocalStack是flask定义的线程隔离的栈存储对象,分别用来保存应用和请求上下文。它是线程隔离的意思就是说,对于不同的线程,它们访问这两个对象看到的结果是不一样的、完全隔离的。这是根据pid
的不同实现的,类似于门牌号。
而每个传给flask对象的请求,都是在不同的线程中处理,而且同一时刻每个线程只处理一个请求。所以对于每个请求来说,它们完全不用担心自己上下文中的数据被别的请求所修改。
request
指向了RequestContext
(请求上下文)栈顶,当请求进入的时候,Request对象被压入栈,从而request有了指向处理请求,接下来会判断AppContext栈顶是否为空,若为空则向栈中压入一个AppContext对象,即app
从而current_app
就有了指向,所以我们在项目中使用是没有报错的,而我们上面的代码不是在请求中实现的,所以AppContext栈顶为空,current_app并没有指向一个AppContext
对象。
from flask import Flask, request from werkzeug.utils import secure_filename import os app = Flask(__name__) # 文件上传目录 app.config['UPLOAD_FOLDER'] = 'static/uploads/' # 支持的文件格式 app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'} # 集合类型 # 判断文件名是否是我们支持的格式 def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS'] @app.route('/') def hello_world(): return 'hello world' @app.route('/upload', methods=['POST']) def upload(): upload_file = request.files['image'] if upload_file and allowed_file(upload_file.filename): filename = secure_filename(upload_file.filename) # 将文件保存到 static/uploads 目录,文件名同上传时使用的文件名 upload_file.save(os.path.join(app.root_path, app.config['UPLOAD_FOLDER'], filename)) return 'info is '+request.form.get('info', '')+'. success' else: return 'failed' if __name__ == '__main__': app.run(port=5000, debug=True)
app.config
中的config是字典的子类,可以用来设置自有的配置信息,也可以设置自己的配置信息。函数allowed_file(filename)
用来判断filename是否有后缀以及后缀是否在app.config['ALLOWED_EXTENSIONS']
中。
客户端上传的图片必须以image
标识。upload_file是上传文件对应的对象。
app.root_path
获取server.py所在目录在文件系统中的绝对路径。
upload_file.save(path)
用来将upload_file保存在服务器的文件系统中,参数最好是绝对路径,否则会报错(网上很多代码都是使用相对路径,但是笔者在使用相对路径时总是报错,说找不到路径)
os.path.join()
用来将使用合适的路径分隔符将路径组合起来。
我们用python客户端测试下:
import requests
file_data = {'image': open('Lenna.jpg', 'rb')}
user_info = {'info': 'Lenna'}
resp = requests.post("http://127.0.0.1:5000/upload", data=user_info, files=file_data)
print(resp.text)
要控制上产文件的大小,可以设置请求实体的大小,例如:
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB
不过,在处理上传文件时候,需要使用try:... except:...
如果要获取上传文件的内容可以:
file_content = request.files['image'].stream.read()
Cookie是存储在客户端的记录访问者状态的数据。具体原理,请见 http://zh.wikipedia.org/wiki/Cookie
,常用的用于记录用户登录状态的session大多是基于cookie实现的。
@app.route('/add')
def login():
resp = Response('add cookies')
resp.set_cookie(key='name', value='唤醒手腕', expires=time.time()+6*60)
return resp
@app.route('/show')
def show():
return request.cookies.__str__()
@app.route('/delete')
def del_cookie():
resp = Response('delete cookies')
resp.set_cookie('name', '', expires=0)
return resp
由上可以看到,可以使用Response.set_cookie
添加和删除cookie,expires参数用来设置cookie有效时间,它的值可以是datetime对象或者unix时间戳,笔者使用的是unix时间戳。
res.set_cookie(key='name', value='letian', expires=time.time()+6*60)
# 上面的expire参数的值表示cookie在从现在开始的6分钟内都是有效的。
当我们对某些网站进行爬取的时候,我们经常会换IP来避免爬虫程序被封锁。这些代理IP地址是如何获取的呢?其实很简单,目前网络上有很多IP代理商,例如神龙,天启,芝麻等等,这些代理商一般都会提供透明代理,匿名代理,高匿代理。本文就讲讲各种代理 IP 背后的原理
代理IP的介绍
代理实际上指的就是代理服务器,英文叫作 proxy server,它的功能是代理网络用户去取得网络信息。
形象地说,它是网络信息的中转站。在我们正常请求一个网站时,是发送了请求给 Web 服务器,Web 服务器把响应传回给我们。
如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥,此时本机不是直接向 Web 服务器发起请求,而是向代理服务器发出请求,请求会发送给代理服务器,然后由代理服务器再发送给 Web 服务器,接着由代理服务器再把 Web 服务器返回的响应转发给本机。
这样我们同样可以正常访问网页,但这个过程中 Web 服务器识别出的真实 IP 就不再是我们本机的 IP 了,就成功实现了 IP 伪装,这就是代理的基本原理。
代理类型:高匿 > 混淆 > 匿名 > 透明
代理IP一共可以分成4种类型,除了前面提到过的透明代理IP,匿名代理IP,高匿名代理IP,还有一种就是混淆代理IP。从基础的安全程度来说呢,他们的排列顺序是高匿 > 混淆 > 匿名 > 透明。
代理原理
代理类型主要取决于代理服务器端的配置,不同配置会形成不同的代理类型。在配置中,这三个变量REMOTE_ADDR,HTTP_VIA,HTTP_X_FORWARDED_FOR 是决定性因素。
(一)REMOTE_ADDR
REMOTE_ADDR 表示客户端的 IP,但是它的值不是由客户端提供的,而是服务器根据客户端的 IP 指定的。
如果使用浏览器直接访问某个网站,那么网站的 web 服务器(Nginx、Apache等)就会把 REMOTE_ADDR 设为客户端的 IP 地址。
如果我们给浏览器设置代理,我们访问目标网站的请求会先经过代理服务器,然后由代理服务器将请求转化到目标网站。那么网站的 web 代理服务器就会把 REMOTE_ADDR 设为代理服务器的 IP。
(二)X-Forwarded-For(XFF)
X-Forwarded-For 是一个 HTTP 扩展头部,用来表示 HTTP 请求端真实 IP。当客户端使用了代理时,web 代理服务器就不知道客户端的真实 IP 地址。为了避免这个情况,代理服务器通常会增加一个 X-Forwarded-For 的头信息,把客户端的 IP 添加到头信息里面。
X-Forwarded-For 请求头格式如下:
X-Forwarded-For: client, proxy1, proxy2
client 表示客户端的 IP 地址;proxy1 是离服务端最远的设备 IP; proxy2 是次级代理设备的 IP;从格式中,可以看出从 client 到 server 是可以有多层代理的。
如果一个 HTTP 请求到达服务器之前,经过了三个代理 Proxy1、Proxy2、Proxy3,IP 分别为 IP1、IP2、IP3,用户真实 IP 为 IP0,那么按照 XFF 标准,服务端最终会收到以下信息:
X-Forwarded-For: IP0, IP1, IP2
Proxy3 直连服务器,它会给 XFF 追加 IP2,表示它是在帮 Proxy2 转发请求。列表中并没有 IP3,IP3 可以在服务端通过 Remote Address 字段获得。我们知道 HTTP 连接基于 TCP 连接,HTTP 协议中没有 IP 的概念,Remote Address 来自 TCP 连接,表示与服务端建立 TCP 连接的设备 IP,在这个例子里就是 IP3。
(三)HTTP_VIA
via 是 HTTP 协议里面的一个header,记录了一次 HTTP 请求所经过的代理和网关,经过1个代理服务器,就添加一个代理服务器的信息,经过2个就添加2个。
正向代理 和 反向代理: 原理介绍
反向代理
1、用户发送请求到服务器(访问的其实是反向代理服务器,但用户不知道)
2、反向代理服务器发送请求到真正的服务器
3、真正的服务器将数据返回给反向代理服务器
4、反向代理服务器再将数据返回给用户
反向代理有什么用为什么要这么做:
作用:用户请求过多,服务器会有一个处理的极限。所以使用反向代理服务器接受请求,再用均衡负载将请求分布给多个真实的服务器。既能提高效率还有一定的安全性。
用途:如果不采用代理,用户的IP、端口号直接暴露在Internet(尽管地址转换NAT),外部主机依然可以根据IP、端口号来开采主机安全漏洞,所以在企业网,一般都是采用代理服务器访问互联网。
正向代理与反向代理最简单的区别:
正向代理隐藏的是用户,反向代理隐藏的是服务器
vue proxy 原理介绍
浏览器会因为同源策略跨域,但服务端不禁止,npm run dev 本来就是运行了服务器,所有利用服务器发送请求即可(将所有请求转发到自己的node服务器然后发送请求,即 代理
)
使用Node开发http正向代理服务器:
var http = require("http"); var url = require("url"); // 会建立一个http服务器,并监听 8080 端口: http.createServer(function(req,res){ console.log("start request:",req.url); var option = url.parse(req.url); option.headers = req.headers; // 当接收到请求信息时,从请求头发获取信息并进行转发: var proxyRequest = http.request(options, function(proxyResponse){ // 其余就是对信息输出,以方便我们了解到代理是否生效、代理内容如何等: proxyResponse.on("data", function(chunk){ console.log("proxyResponse length",chunk.length); }); proxyResponse.on("end", function(){ console.log("proxyed request ended"); res.end(); }) res.writeHead(proxyResponse.statusCode,proxyResponse.headers); }); req.on("data",function(chunk){ console.log("in request length:",chunk.length); proxyRequest.write(chunk,"binary"); }) req.on("end",function(){ console.log("original request ended"); proxyRequest.end(); }) }).listen(8080);
个人总结吧,以前学东西总是不喜欢看源码,主要是英语不行,后来才知道源码的重要性,学会看源码才能真正的去探究底层的实现,况且现在很多第三方库都把源码的注释写的很清楚。
Flask.py文件相关底层的介绍
源码中Flask类进行继承了Scaffold类(脚手架),关于Scaffold类的__init__
展示:
def __init__(
self,
import_name: str,
#: The name of the package or module that this object belongs
static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
static_url_path: t.Optional[str] = None,
template_folder: t.Optional[str] = None,
#: The path to the templates folder, relative to ···
root_path: t.Optional[str] = None,
):
:attr:root_path
, to add to the template loader.
Flask对象实现了一个WSGI应用程序,并作为中心对象。它被传递给应用程序的模块或软件包的名称。一旦创建了它,它将作为视图函数、URL规则、模板配置等的中心注册表。
装饰器@route,装饰视图函数:
底层原理:@app.route("/") def index():
-> app.add_url_rule("/", view_func=index)
项目启动app.run(),源码介绍
def run(
self,
host: t.Optional[str] = None,
# host: the hostname to listen on. Set this to ``'0.0.0.0'`` to have the server available externally as well.
# 要收听的主机名。将其设置为``‘0.0.0.0’``,以使服务器在外部也可用。
port: t.Optional[int] = None,
# the port of the webserver. Defaults to ``5000`` or the port defined in the ``SERVER_NAME`` config variable if present.
# webserver的端口。默认为``5000``或在``SERVER_NAME``配置变量中定义的端口。
debug: t.Optional[bool] = None,
# ebug: if given, enable or disable debug mode. See :attr:`debug`.
load_dotenv: bool = True,
# Load the nearest :file:`.env` and :file:`.flaskenv` files to set environment variables. Will also change the working directory to the directory containing the first file found.
# 加载最接近的:file:`.env`和:file:`.flaskenv`文件,以设置环境变量。还将将该工作目录更改为包含找到的第一个文件的目录。
**options: t.Any,
) -> None:
Note that if you are testing for assertions or exceptions in your application code, you must set app.testing = True
in order for the exceptions to propagate to the test client.
请注意,如果您正在测试应用程序代码中的断言或异常,则必须设置app.testing = True
,以便将异常传播到测试客户端。
Otherwise, the exception will be handled by the application (not visible to the test client) and the only indication of an AssertionError or other exception will be a 500 status code response to the test client. See the :attr:testing
attribute. For example::
app.testing = True
client = app.test_client()
The test client can be used in a with
block to defer the closing down of the context until the end of the with
block. This is useful if you want to access the context locals for testing::
测试客户端可以在带有with
中使用,以将上下文的关闭推迟到带有with
结束为止。如果您想访问上下文本地语言进行测试,这一点非常有用::
with app.test_client() as c:
rv = c.get('/?vodka=42')
assert request.args['vodka'] == '42'
Additionally, you may pass optional keyword arguments that will then be passed to the application’s :attr:test_client_class
constructor. For example::
from flask.testing import FlaskClient
class CustomClient(FlaskClient):
def __init__(self, *args, **kwargs):
self._authentication = kwargs.pop("authentication")
super(CustomClient,self).__init__( *args, **kwargs)
app.test_client_class = CustomClient
client = app.test_client(authentication='Basic ....')
os.getenv(key, default=None) 函数介绍:
如果环境变量的字典集没有存在对应的KEY,会读取默认值:“唤醒手腕”
import os
print(os.getenv("CONF", default="唤醒手腕"))
Flask 默认是单进程,单线程阻塞的任务模式,在项目上线的时候可以通过nginx+gunicorn 的方式部署flask任务
但是在开发的过程中如果想通过延迟的方式测试高并发怎么实现呢,其实非常简单:
app.run()中可以接受两个参数,分别是threaded和processes,用于开启线程支持和进程支持。
Flask 单线程阻塞的任务模式 测试:同时打开三个浏览器访问
from datetime import datetime from flask import Flask @app.route("/index") def index(): print('start : ', datetime.now()) time.sleep(10) # 模拟阻塞 """ Flask 默认是单进程,单线程阻塞的任务模式 """ print('end: : ', datetime.now()) return "hello world" if __name__ == "__main__": app.run(load_dotenv=True)
运行结果:总共需要的时间是30多秒,说明是单线程阻塞的模式
start : 2022-02-15 14:57:42.714942
end: : 2022-02-15 14:57:52.728294
start : 2022-02-15 14:57:52.730206
127.0.0.1 - - [15/Feb/2022 14:57:52] "GET /index HTTP/1.1" 200 -
end: : 2022-02-15 14:58:02.730972
start : 2022-02-15 14:58:02.733863
127.0.0.1 - - [15/Feb/2022 14:58:02] "GET /index HTTP/1.1" 200 -
end: : 2022-02-15 14:58:12.745196
127.0.0.1 - - [15/Feb/2022 14:58:12] "GET /index HTTP/1.1" 200 -
源码写的太明白了,原来是ThreadingMixIn的实例以多线程的方式去处理每一个请求,这样对开发者来说,只有在启动app时将threaded参数设定为True,flask才会真正以多线程的方式去处理每一个请求。
开启方式:
if __name__ == "__main__":
app.run(load_dotenv=True, threaded=True)
这个案例是进行与数据库的连接,启动Flask服务器应用,构建视图函数映射到响应的地址目录,并且建立pymysql的连接对象,进行数据库的操作,这边就是添加单词的相应的操作。
from datetime import datetime import threading import time from flask import Flask, request, redirect app = Flask(__name__) import pymysql app.config['mysql'] = pymysql.connect( host='localhost', # 连接名称,默认127.0.0.1 user='pythondb', # 用户名 passwd='root', # 密码 port=3306, # 端口,默认为3306 db='pythondb', # 数据库名称 charset='utf8', # 字符编码 ) @app.route('/') def Login(): with open('index.html', 'r', encoding='utf-8') as f: html = f.read() return html @app.route('/data') def ShowData(): english = request.args['english'].strip().replace("\n", "") chinese = request.args['chinese'].strip().replace("\n", "") sentence_eng = request.args['sentence_eng'].strip().replace("\n", "") sentence_chi = request.args['sentence_chi'].strip().replace("\n", "") cursor = app.config['mysql'].cursor() sql = "SELECT * FROM word ORDER BY id DESC limit 1" cursor.execute(sql) id = cursor.fetchone()[0] + 1 sql = "insert into word values(%s,%s,%s,%s,%s)" print(cursor.execute(sql, (id, english, chinese, sentence_eng, sentence_chi))) app.config['mysql'].commit() return redirect("http://localhost:8080/") if __name__ == "__main__": app.run(host='localhost', port=8080, load_dotenv=True)
对应的前端页面代码展示,其实就是简单构造一个表单进行提交的操作:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="http://localhost:8080/data"> <p><input name="english" placeholder="Word:"></p> <p><input name="chinese" placeholder="Chinese:"></p> <p><input name="sentence_eng" placeholder="sentence_eng:"></p> <p><input name="sentence_chi" placeholder="sentence_chi:"></p> <input type="submit" value="Submit"> </form> <style> input{ width: 300px; height: 40px } </style> </body> </html>
Gunicorn 是一个被广泛使用的高性能的 Python WSGI UNIX HTTP 服务组件 (WSGI: Web Server Gateway Interface),移植自 Ruby 的独角兽(Unicorn )项目,具有使用非常简单、轻量级的资源消耗、高性能等特点。
在项目中 Gunicorn 和 Flask 框架一同使用, 能够开启服务,处理请求,因其高性能的特点能够有效减少服务丢包率。
使用 pip 安装 gunicorn
pip install gunicorn
启动 Flask 项目
(base) [root@ server]# gunicorn -w 1 -b 0.0.0.0:5000 app:app
[2021-03-22 11:43:15 +0800] [6266] [INFO] Starting gunicorn 20.0.4
[2021-03-22 11:43:15 +0800] [6266] [INFO] Listening at: http://0.0.0.0:5000 (6266)
[2021-03-22 11:43:15 +0800] [6266] [INFO] Using worker: sync
[2021-03-22 11:43:15 +0800] [6269] [INFO] Booting worker with pid: 6269
启动 Flask 项目 + SSL 证书
if __name__ == '__main__':
context = ('./word.willwaking.com_bundle.crt', './word.willwaking.com.key')
app.run(host="0.0.0.0", port=5000, ssl_context=context)
gunicorn --certfile='/home/word_willwaking_com_2024/word.willwaking.com_bundle.crt' --keyfile='/home/word_willwaking_com_2024/word.willwaking.com.key' -w 4 -b 0.0.0.0:5000 app:app
gunicorn 如何实现高并发的?
对于 gunicorn 而言,当启动时就已经把 worker 进程预先 fork 出来了。当多个请求到来的时候,会轮流复用这些 worker 进程,从而能提高服务器的并发负载能力。对于 worker 数的配置,一般推荐2CPU数 + 1。这样一来,在任何时间,都有大概一般的 worker 是在做 I/O,剩下一般才是需要 CPU 的。
如果在开多进程的同时,也开多线程(也就是选择 gthread 类型的 worker),那么配置总的并发数(worker 进程数线程数),仍然建议 2CPU 数 + 1。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。