Flask备忘录

此文仅仅作为学习本人Flask途中的学习笔记以及备忘录!!!!!

代码格式粘贴复制可能格式出错emm,自己知道就好了!!

无图,慢慢看!!挺枯燥的学习笔记,做个网络备份!!!

一.服务架构:

二.Flask是什么?

01. 微框架

02. 特点

03. 组成

04.常用库:

三.Flask环境搭建:

01.Windows虚拟环境直接上:

02.Conda环境:

03.Linux环境:

四.Flask工程实践:

01.万物之源Hello World:

02.请求处理:

03.JinJa2模板:

04.错误自定义:

05.静态文件:

06.Web表单:

07.数据库

08.电子邮件

09. 工厂函数:

10. 蓝图

11. 大型项目结构:

12. 项目部署:

五. 前后端分离:

01.为什么要前后端分离:

02.什么是前后端分离:

六.快速博客搭建:

01.安装bt

02.bt功能:

03.访问宝塔页面

七.实际项目:

八.项目实践:

九.Flask漏洞:

01.XSS(跨站脚本攻击):

02.CSRF(跨站请求伪造):

03.Secret key

04.SSTI

十.参考文章:

大家好,又见面了,经过前段时间的前端三剑客的学习,大家是否对Web有更深入的了解呢?是否自己能做一个美观大方的静态网站?或者有没有自己尝试着用JS做一些小玩意,诸如小时钟啥的,hh,没有也无所谓,这节课过后你们可以继续去学。不过这节课我们就来讲一些后端的东西吧!!!

由于你们之前学过Python,那我就直接以Python作为入门的后端语言,以它的轻量级框架——Flask作为大家伙们的后端入门框架!!!

来一起看看吧!!

important:需要注意的是,我们需要先下载一款软件叫做Postman,这个在你们以后学习乃至测试,实验都会用到!

比邻网盘记得带个*号!!

尤其是前后端接口的测试,可以使用诸如postman之类的软件进行测试以及纠错!

一.服务架构:

先给大家科普一下一些常识:

- B/S:浏览器/服务器架构(客户端需要更新才行)
- C/S:客户端/服务器架构(刷新页面即可更新)(主流)(尤其是H5时代的到来)

CS即Client/Server(客户机/服务器)结构,C/S结构在技术上很成熟,它的主要特点是交互性强、具有安全的存取模式、网络通信量低、响应速度快、利于处理大量数据。但是该结构的程序是针对性开发,变更不够灵活,维护和管理的难度较大。通常只局限于小型局域网,不利于扩展。并且,由于该结构的每台客户机都需要安装相应的客户端程序,分布功能弱且兼容性差,不能实现快速部署安装和配置,因此缺少通用性,具有较大的局限性。要求具有一定专业水准的技术人员去完成。

BS即Browser/Server(浏览器/服务器)结构,就是只安装维护一个服务器(Server),而客户端采用浏览器(Browse)运行软件。B/S结构应用程序相对于传统的C/S结构应用程序是一个非常大的进步。 B/S结构的主要特点是分布性强、维护方便、开发简单且共享性强、总体拥有成本低。但数据安全性问题、对服务器要求过高、数据传输速度慢、软件的个性化特点明显降低,这些缺点是有目共睹的,难以实现传统模式下的特殊功能要求。例如通过浏览器进行大量的数据输入或进行报表的应答、专用性打印输出都比较困难和不便。此外,实现复杂的应用构造有较大的困难。

二.Flask是什么?

Flask是一个微框架,什么叫做微框架呢?它并不是去掉了啥Web功能,或是叫你把所有Web应用放在一个py文件里面去执行,(可以但没必要hh),本身Flask提供了最基础最核心的功能,其余的拓展功能并不直接替你决策。如使用哪种数据库之类的,这些都是由开发者掌握,简而言之,Flask框架是一个DIY的框架,你可以在上面玩出各种花样!flask框架小,不代表着它的功能少!

并且它也是可以在复杂的生产环境中投入使用的!

Flask更多是和优秀的轮子结合,使得它更加灵活以及强大,可玩性更强!配置选项众多,但都设置了一些合理的默认值,使你不用过于担心安全问题!

  • Flask确实很”轻”,不愧是Micro Framework,从Django转向Flask的开发者一定会如此感慨,除非二者均为深入使用过
  • Flask自由、灵活,可扩展性强,第三方库的选择面广,开发时可以结合自己最喜欢用的轮子,也能结合最流行最强大的Python库
  • 入门简单,即便没有多少web开发经验,也能很快做出网站
  • 非常适用于小型网站
  • 非常适用于开发web服务的API
  • 开发大型网站无压力,但代码架构需要自己设计,开发成本取决于开发者的能力和经验
  • 各方面性能均等于或优于Django
  • Django自带的或第三方的好评如潮的功能,Flask上总会找到与之类似第三方库
  • Flask与关系型数据库的配合使用不弱于Django,而其与NoSQL数据库的配合远远优于Django
  • Flask比Django更加Pythonic,与Python的philosophy更加吻合

Flask核心有三个主要依赖:路由、调试和Web服务器网关接口(WSGI,Web server gateway interface),子系统是由Werkzeug提供,模板系统由Jinja2提供,命令行集成是由Click提供。

原生flask不支持数据库访问、Web表单验证和用户身份验证等高级功能。这些绝大多数web应用需要的核心服务都以拓展的形式实现,然后再与核心包集成。

开发者可以自由挑选符合项目需求的拓展,甚至可以自行开发。

单单只有Flask还是远远不够的!这时候你就要把目光放在那些强大的Flask库上,下面就是我们常用的库的列表,内容不完整,仅供参考:

  • flask-script 为Flask提供强大的命令行操作,与Django shell类似。
  • flask-login Flask user session 管理,提供诸如 login_user, logout_user, login_required, current_user等功能,也是其他很多Flask库的基础。
  • flask-admin 为Flask应用提供操作简单且易于扩展的数据库管理界面,与Django自带的数据库管理app类似。
  • Flask-WTF Flask与WTForms的集成,提供强大的Form安全和校验机制,与Django内置的Form功能类似。
  • flask-principal Flask强大的权限管理机制,灵活性强,提供了一个权限管理的基础框架,是很多Flask权限相关扩展的基础。
  • flask-restful 一个强大的Flask RESTful框架,简单好用。
  • flask-api 相当于Django REST Framework的Flask版,是另一个强大的Flask RESTful框架。
  • Flask-Mail Flask-Mail 为Flask应用添加了SMTP 邮件发送功能
  • Flask-User Flask-User集成了用户管理相关功能,并允许对功能做定制性修改,其相关功能包括Register, Confirm email, Login, Change username, Change password, Forgot password等。 Flask-User 基于 Flask-SQLAlchemy,NoSQL数据库无法使用。
  • flask-security Flask-Security让开发者能够很快的为应用添加常用的安全机制,其整合了 Flask-Login, Flask-Mail, Flask-Principal, Flask-Script等应用。
  • flask-babel Flask国际化和本地化扩展,基于Babel
  • flask-locale 为Flask应用添加语言包,与 flask-babel类似。

三.Flask环境搭建:

额,实质上Flask就是python写的一个拓展包,直接下载它就是了!!

我们现在Windows完成开发,再将代码打包至服务器上进行生产环境的实现。

好,现在打开我们的Pycharm

然后点击终端,进行Flask的下载

pip install flask

直接下载,好了,环境就搭好了,快不快?

一般来说,我们都是在Windows环境下进行开发,然后项目打包至云服务器上进行部署。

如果你之前装了conda,那也可以直接输入下面的命令:

conda create --name flask python=3.9

使用PyCharm创建一个名为flask-demo的项目,并选择我们刚才创建的python环境。

然后Flask实质上是python的一个包,和你们之前用的pandas,numpy之类的都差不多!

  1. 虚拟环境:

  2. 在标准库中没有venv这个包,需要执行以下命令安装python3-venv

sudo apt-get install python3-venv
  • 创建虚拟环境的命令模式如下:
python3 -m venv virtual-environment-name
-m venv 选项的作用是以独立的脚本运行标准库中的venv包,后面的参数是虚拟环境的名称。
确保当前目录是在flasky
python3 -m venv venv

执行上面的命令之后,会出现一个名为venv的子目录,这里就是全新的虚拟环境,包含这个项目专用的Python解释器。

  • 使用虚拟环境 如果要想使用虚拟环境,首先要先激活虚拟环境,可以使用下面的命令激活虚拟环境
source venv/bin/activate

当然,你聪明的大脑应该可以相出退出虚拟环境的命令:

source venv/bin/deactivate

如果使用的是为微软windows系统,激活命令是:

venv\Scripts\activate
  • 使用pip安装Python包 Python通过包管理器pip安装,所有的虚拟环境都是使用这个工具。

若想要在虚拟环境中安装flask,要确保venv虚拟环境已经激活,然后执行下面命令

(venv) $ pip install flask

四.Flask工程实践:

然后我们直接新建一个py文件,命名为app.py

然后将下面这一段代码直接给输进去

先不要管什么含义,先输进去再说!!

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Hello World</h1>'

if __name__ == "__main__":
    app.run(port=80)

输入,然后运行,

运行你可以直接正常运行 Shift+Ctrl+F10或者是在终端里面进行输入:

flask run

值得注意的是,你首先要指定环境变量:

export FLASK_APP=app.py
flask run

更多详情可以在终端输入:查看

flask run --help
(venv) C:\Users\jiang'xiao\PycharmProjects\pythonProject1213>flask run --help
Usage: flask run [OPTIONS]
  Run a local development server.

  This server is for development purposes only. It does not provide the
  stability, security, or performance of production WSGI servers.

  The reloader and debugger are enabled by default if FLASK_ENV=development or
  FLASK_DEBUG=1.

Options:
  -h, --host TEXT                 The interface to bind to.

  -p, --port INTEGER              The port to bind to.

  --cert PATH                     Specify a certificate file to use HTTPS.

  --key FILE                      The key file to use when specifying a
                                  certificate.

  --reload / --no-reload          Enable or disable the reloader. By default
                                  the reloader is active if debug is enabled.

  --debugger / --no-debugger      Enable or disable the debugger. By default
                                  the debugger is active if debug is enabled.

  --eager-loading / --lazy-loading
                                  Enable or disable eager loading. By default
                                  eager loading is enabled if the reloader is
                                  disabled.

  --with-threads / --without-threads
                                  Enable or disable multithreading.

  --extra-files PATH              Extra files that trigger a reload on change.

                                  Multiple paths are separated by ';'.

  --help                          Show this message and exit.

出现这个内容,点击蓝色的链接,进行访问:( Ctrl + C 可以停止这个服务,你也可以点击关闭按钮!!)

进行访问:

看,你就看到了一个大大的Hello World!!!

尝试修改一下里面的东西,然后来看看有什么变动!

Hello World

这里要注意:run的方法参数:

你可以这样子加上去!

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Hello World</h1>'

if __name__ == "__main__":
    app.run(port=80,debug=True)  #&#x770B;&#x8FD9;&#x91CC;&#xFF01;&#xFF01;&#xFF01;&#xFF01;

以逗号隔开即可!!!

我们来解读一下源码的结构:

1.初始化:

所有 Flask 应用都必须创建一个应用实例。Web 服务器使用一种名为 Web 服务器网关接口(WSGI,Web server gateway interface,读作”wiz-ghee”)的协议,把接收自客户端的所有请求都转交给这个对象处理。应用实例是 Flask 类的对象,通常由下述代码创建:

from flask import Flask

app = Flask(__name__)

Flask 类的构造函数只有一个必须指定的参数,即应用主模块或包的名称。在大多数应用

中,Python 的 name 变量就是所需的值。

2.路由初始化:

客户端(例如 Web 浏览器)把请求发送给 Web 服务器,Web 服务器再把请求发送给 Flask应用实例。应用实例需要知道对每个 URL 的请求要运行哪些代码,所以保存了一个 URL到 Python 函数的映射关系。处理 URL 和函数之间关系的程序称为路由。 在 Flask 应用中定义路由的最简便方式,是使用应用实例提供的 app.route 装饰器。

下面的例子说明了如何使用这个装饰器声明路由:

@app.route('/')
def index():
    return '<h1>Hello World!</h1>'

下面这种方法了解即可,实际上我们并不常用:

前例把 index() 函数注册为应用根地址的处理程序。使用 app.route 装饰器注册视图函数是首选方法,但不是唯一的方法。Flask 还支持一种更传统的方式:使用 app.add_url_rule() 方法。这个方法最简单的形式接受 3 个参数:URL、端点名和视图函数。下述示例使用 app.add_url_rule() 方法注册 index() 函数,其作用与前例相同:

def index():
    return '<h1>Hello World!</h1>'
app.add_url_rule('/', 'index', index)

index():

这个叫做视图函数,这个函数的返回值就是响应,是客户端接收到的内容,可以是HTML的字符串或者是表单!!

3.可变参数(动态路由):

只需要在app.route装饰器中使用特殊的句法即可!!

@app.route('/user/<name>')
def user(name):
    return '<h1>Hello, {}!</h1>'.format(name)</name>

路由 URL 中放在尖括号里的内容就是动态部分,任何能匹配静态部分的 URL 都会映射到这个路由上。调用视图函数时,Flask 会将动态部分作为参数传入函数。在这个视图函数中,name 参数用于生成个性化的欢迎消息。

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Hello World</h1>'

@app.route('/<name>')
def user(name):
    return '<h1>Hello, {}!</h1>'.format(name)

if __name__ == "__main__":
    app.run(port=5000)</name>

没有骂人!没有骂人!没有骂人!求生欲极强!!!

路由中的动态部分默认使用字符串,不过也可以是其他类型。例如,路由 /user/

上面这个十分简单,但又是后面一切一切的基础,要知道, 在Web服务里有个关键的地方就是对请求的处理!

如果你对HTTP有所学习,有所了解,那你应该知道常见的请求类型方法有如下几种:

其实我们也是可以自定义方法进行访问的,比如说以helloworld的方法进行访问,只不过现在要统一访问的方法!!

Flask允许我们指定某个请求可以通过哪些类型进行访问。可以在路由装饰器中传入methods需要的参数,该参数是一个列表,如只允许GET请求,可以设置methods=[‘GET’],如允许GET和POST,可以设置methods=[‘GET’,’POST’]。如设置下面/methods这个路由只允GET请求类型的访问。

这时候你就可以用 Postman来进行基本的测试操作了!什么?你还没有装postman??

@app.route("/methods", methods=["GET"])
def methods():
    return "Only allow GET request types"

1.请求与相应循环:

客户端向Flask发送请求时,要让视图函数访问一些对象,这样才能处理请求。并且在Flask中它使用上下文临时把某些对象变为全局可访问。

from flask import Flask
from flask import request

app = Flask(__name__)

@app.route('/')
def index():
    user_agent = request.headers.get('User-Agent')
    return '<p>Your browser is {}</p>'.format(user_agent)

if __name__ == "__main__":
    app.run(port=80)

你的User-Agent就这样打印在html页面上了!!

如果你有学习过Http报文的基本格式,你就知道这个是什么了!!

详情请见:

  1. 模板位置:

模板是包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为渲染。为了渲染模板,Flask 使用一个名为 Jinja2 的强大模板引擎。

示例:

<h1>Hello, {{ name }}!</h1>

任何模板,在Flask里面,它都是默认从templates里面取,要是你想要自定义,你就得另外加代码!!为了简便,我们就直接在这里新建个templates文件夹,然后创建一个index.html。

然后我们记得引入render_template这个包

from flask import Flask, render_template
import requests

app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Hello World!!</h1>'

@app.route('/<name>')
def user(name):
    return render_template('index.html', name=name)

if __name__ == "__main__":
    app.run(port=80)</name>

简而易见,Jinja2模板里面表示变量的格式为 {{}}

  1. 过滤器:

还可以通过 过滤器进行修改变量值比如:

Hello, {{ name|capitalize }}

将name这个变量的值变成首字母大写的形式

中间用那个熟悉的管道符 | 来隔开,虽然这里并不是管道符的作用hh

safe &#x6E32;&#x67D3;&#x503C;&#x65F6;&#x4E0D;&#x8F6C;&#x4E49;
capitalize &#x628A;&#x503C;&#x7684;&#x9996;&#x5B57;&#x6BCD;&#x8F6C;&#x6362;&#x6210;&#x5927;&#x5199;&#xFF0C;&#x5176;&#x4ED6;&#x5B57;&#x6BCD;&#x8F6C;&#x6362;&#x6210;&#x5C0F;&#x5199;
lower &#x628A;&#x503C;&#x8F6C;&#x6362;&#x6210;&#x5C0F;&#x5199;&#x5F62;&#x5F0F;
upper &#x628A;&#x503C;&#x8F6C;&#x6362;&#x6210;&#x5927;&#x5199;&#x5F62;&#x5F0F;
title &#x628A;&#x503C;&#x4E2D;&#x6BCF;&#x4E2A;&#x5355;&#x8BCD;&#x7684;&#x9996;&#x5B57;&#x6BCD;&#x90FD;&#x8F6C;&#x6362;&#x6210;&#x5927;&#x5199;
trim &#x628A;&#x503C;&#x7684;&#x9996;&#x5C3E;&#x7A7A;&#x683C;&#x5220;&#x6389;
striptags &#x6E32;&#x67D3;&#x4E4B;&#x524D;&#x628A;&#x503C;&#x4E2D;&#x6240;&#x6709;&#x7684; HTML &#x6807;&#x7B7E;&#x90FD;&#x5220;&#x6389;

注意一点,以后写Flask项目,乃至写任何项目,或是你工作,要记住一点,千万不要相信用户写的值!!!这里的safe过滤器要慎用!!

过滤器详情,请访问:

  1. 控制结构:

下面这个例子展示如何在模板中使用条件判断语句:

{% if user %}

 Hello, {{ user }}!

{% else %} Hello, Stranger!

{% endif %}

另一种常见需求是在模板中渲染一组元素。下例展示了如何使用 for 循环实现这一需求:

<ul>

 {% for comment in comments %}

 <li>{{ comment }}</li>

 {% endfor %}

</ul>

Jinja2 还支持宏。宏类似于 Python 代码中的函数。例如:

{% macro render_comment(comment) %}

 <li>{{ comment }}</li>

{% endmacro %}

<ul>

 {% for comment in comments %}

 {{ render_comment(comment) }}

 {% endfor %}

</ul>

为了重复使用宏,可以把宏保存在单独的文件中,然后在需要使用的模板中导入:

{% import 'macros.html' as macros %}

<ul>

 {% for comment in comments %}

 {{ macros.render_comment(comment) }}

 {% endfor %}

</ul>

需要在多处重复使用的模板代码片段可以写入单独的文件,再引入所有模板中,以避免重复:

{% include 'common.html' %}

另一种重复使用代码的强大方式是模板继承,这类似于 Python 代码中的类继承。首先,创建一个名为 base.html 的基模板:

<html>

<head>

 {% block head %}

 <title>{% block title %}{% endblock %} - My Application</title>

 {% endblock %}

</head>

<body>

 {% block body %}

 {% endblock %}

</body>

</html>

基模板中定义的区块可在衍生模板中覆盖。Jinja2 使用 block 和 endblock 指令在基模板中定义内容区块。在本例中,我们定义了名为 head、title 和 body 的区块。注意,title 包含在 head 中。下面这个示例是基模板的衍生模板:

{% extends "base.html" %}

{% block title %}Index{% endblock %}

{% block head %}

 {{ super() }}

 <style>

 </style>

{% endblock %}

{% block body %}

<h1>Hello, World!</h1>

{% endblock %}

extends 指令声明这个模板衍生自 base.html。在 extends 指令之后,基模板中的 3 个区块被重新定义,模板引擎会将其插入适当的位置。如果基模板和衍生模板中的同名区块中都有内容,衍生模板中的内容将显示出来。在衍生模板的区块里可以调用 super(),引用基模板中同名区块里的内容。

上述JinJa2模板了解即可,实际的开发环境中我们采用的是前后端分离的做法,举个最简单的例子,前端用Vue.js,后端用Flask,之间沟通交流,就采用api的形式来进行处理。

  1. Flask-Bootstrap

如果你真的想尝试一下用JinJa2模板进行项目的开发,你可以去试一下Flask-Bootstrap。

① 安装Flask-Bootstrap

我们直接安装:

(venv) $ pip install flask-bootstrap

② 使用Flask-Bootstrap

老规矩,还是得进行应用实例初始化:

from flask_bootstrap import Bootstrap
...

bootstrap = Bootstrap(app)

然后在templates里面新建一个文件,叫做user.html,然后CV代码:

{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
 <div class="container">
 <div class="navbar-header">
 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
 <span class="sr-only">Toggle navigation</span>

 </button>
 <a class="navbar-brand" href="/">Flasky</a>
 </div>
 <div class="navbar-collapse collapse">
 <ul class="nav navbar-nav">
 <li><a href="/">Home</a></li>
 </ul>
 </div>
 </div>
</div>
{% endblock %}
{% block content %}
<div class="container">
 <div class="page-header">
 <h1>Hello, {{ name }}!</h1>
 </div>
</div>
{% endblock %}

然后app.py这样子操作:

from flask import Flask, render_template
from flask_bootstrap import Bootstrap

app = Flask(__name__)
bootstrap=Bootstrap(app)

@app.route('/')
def index():
    return '<h1>Hello World!!</h1>'

@app.route('/index/<name>')
def hello(name):
    return render_template('user.html',name=name)

if __name__ == "__main__":
    app.run(port=80)</name>

然后你就看到了它:

然后下面一些官方说明,应该有点基础的都可以知道吧!那我就不用讲了:

如果你想自定义一些scripts文件,那么你需要这样做:

{% block scripts %}
{{ super() }}
<script type="text/javascript" src="my-script.js"></script>
{% endblock %}
base.html&#xFF1A;

{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
{% endblock %}

看,自定义的优点又来了,当用户遇到你熟悉的404或者500页面时,我们可以自定义它的HTML页面:

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500

这时候,你只需要在templates里面新建两个文件,一个404.html和500.html并且进行编辑优化,就可以呈现给用户看了!!

这里分叉一下!

你可以使用abort来主动抛出异常:

from flask import Flask
app = Flask(__name__)
@app.route('/login/')
def login():
    # &#x6B64;&#x5904;&#x4F7F;&#x7528;abort&#x53EF;&#x4EE5;&#x4E3B;&#x52A8;&#x629B;&#x51FA;&#x5F02;&#x5E38;
    abort(404)
if __name__ == '__main__':
    app.run()

这里就是主动抛出404的错误报告!

当然,你也可以使用模板继承,用bootstrap的风格页面进行404以及500的提示!!

  1. 链接:

Flask 提供了 url_for() 辅助函数,它使用应用的 URL 映射中保存的信息生成 URL。url_for() 函数最简单的用法是以视图函数名(或者 app.add_url_route() 定义路由时使用的端点名)作为参数,返回对应的 URL。

例如,在当前版本的 hello.py 应用中调用模板url_for(‘index’) 得到的结果是 /,即应用的根 URL。调用 url_for(‘index’, _external=True)返回的则是绝对地址,在这个示例中是 http://localhost:5000/

生成连接应用内不同路由的链接时,使用相对地址就足够了。如果要生成在浏览器之外使用的链接,则必须使用绝对地址,例如在电子邮件中发送的链接。

使用 url_for() 生成动态 URL 时,将动态部分作为关键字参数传入。例如,url_for(‘user’, name=’john’, _external=True) 的返回结果是 http://localhost:5000/user/john。传给 url_for() 的关键字参数不仅限于动态路由中的参数,非动态的参数也会添加到查询字符串中。例如,url_for(‘user’, name=’john’, page=2, version=1) 的返回结果是 /user/john?page=2&version=1。

实际上你可以通过这个功能实现路由跳转!!!!

这个极其重要,但是实质上要注意的点也没多少emm

让我们来看一下吧!!!

就比如你有一个外部的jpg,或者外部的css文件,js文件要引入到模板中,那么此时,我们应该怎样引入呢?

保存到 static文件夹里,它和templates同级!

然后分类好即可,其实你不分类也是可以的emm

然后在模板中使用应该如下:

{{ url_for('static', filename='css/12.css') }}

你就在原本html里面要写的东西,把它改成这种模样就可以了!!

Web表单这个极其重要的东西,让我们来看一下吧!

什么是Web表单,举一个最好的例子,就是你们登录的时候,填写的账号密码,需要提交到后台,这就是Web表单。

一般,表单数据都是由浏览器通过POST请求发送给后端的,而在FLask中,用户填写的信息可以通过request.from。

Flask-WTF 扩展可以把处理 Web 表单的过程变成一种愉悦的体验。这个扩展对独立的WTForms 包进行了包装,方便集成到 Flask 应用中。

安装它也很方便:

(venv) $ pip install flask-wtf
  1. 配置

Flask-WTF不用像之前提到的那几个一样要初始化应用,它要求开发者给他提供一个密钥,这个密钥会被用来加密或者以签名的形式用在表单传输以及识别中!

app.config['SECRET_KEY']='woquzheshishenme'

一般这个密钥都不会在源码中写入,因为这可能带来巨大的风险!一般都是用一个文件写着,再导入。或者直接干脆放在系统的环境变量里面,这样可以让黑客减少攻击网站成功的概率。

密钥的出现,为Flask对抗CSRF攻击提供一个强大的工具。CSRF简单来说就是跨站请求伪造攻击,感兴趣的可以去了解一下。

  1. 表单类:

每个Web表单都由一个继承自FlaskForm的类来表示。

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class NameForm(FlaskForm):
 name = StringField('What is your name?', validators=[DataRequired()])
 submit = SubmitField('Submit')
  1. 表单Jinja2
<form method="POST">
 {{ form.hidden_tag() }}
 {{ form.name.label }} {{ form.name() }}
 {{ form.submit() }}
</form>

from表单自动生成一个隐藏字段,用来防御CSRF

当然这样子的样式极其简陋,你可以用css样式来美化:

<form method="POST">
 {{ form.hidden_tag() }}
 {{ form.name.label }} {{ form.name(id='my-text-field') }}
 {{ form.submit() }}
</form>

你也可以应用Bootstrap预定义的表单样式渲染整个Flask-WTF表单。

{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}

完整示范代码:

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
 <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
  1. 处理表单:

那我们怎样在视图函数中处理Web表单呢?

@app.route('/', methods=['GET', 'POST'])
def index():
 name = None
 form = NameForm()
 if form.validate_on_submit():
 name = form.name.data
 form.name.data = ''
 return render_template('index.html', form=form, name=name)

那么我们现在完整的项目就是:

app.py:

from flask import Flask, render_template
from flask_bootstrap import Bootstrap
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired

class NameForm(FlaskForm):
    name = StringField('What is your name?', validators=[DataRequired()])
    submit = SubmitField('Submit')

app = Flask(__name__)
bootstrap = Bootstrap(app)
app.config['SECRET_KEY'] = 'woquzheshishenme'

@app.route('/', methods=['GET', 'POST'])
def fromb():
    name = None
    form = NameForm()
    if form.validate_on_submit():
        name = form.name.data
    form.name.data = ''
    return render_template('fromb.html', form=form, name=name)

if __name__ == "__main__":
    app.run(debug=True,port=80)

fromb.html:

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
 <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}

base.html

{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
 <div class="container">
 <div class="navbar-header">
 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
 <span class="sr-only">Toggle navigation</span>

 </button>
 <a class="navbar-brand" href="/">Flasky</a>
 </div>
 <div class="navbar-collapse collapse">
 <ul class="nav navbar-nav">
 <li><a href="/">Home</a></li>
 </ul>
 </div>
 </div>
</div>
{% endblock %}
{% block content %}
<div class="container">
 {% block page_content %}{% endblock %}
</div>
{% endblock %}

然后就成功得做到了表单提交这个功能了!!!

后面更复杂的功能大家后面去了解一下!

  1. 路由跳转(重定向)

然后现在我们想着,你表单登录成功之后,像淘宝,京东啥的,是不是有个到另一个页面的跳转功能!

那我们这里也模仿实现,填入账号密码跳转到个人页面!!!

这就要用到Flask的路由跳转功能了!!

我们注意到上面的代码,如果你刷新返回的话,你填入的东西竟然没有保存到会话中,这怎么可以!!这就好像你每一次逛淘宝都要去填写一遍账号密码,一不小心从页面退出来又要填写账号密码!!

这时候就要引出我们的用户会话了

① 用户会话:

from flask import Flask, render_template, session, redirect, url_for
@app.route('/', methods=['GET', 'POST'])
def index():
 form = NameForm()
 if form.validate_on_submit():
 session['name'] = form.name.data
 return redirect(url_for('index'))
 return render_template('index.html', form=form, name=session.get('name'))

采用这种方式,局部变量name就可以保存在用户会话中,即session[‘name’],所以应用会记得你曾经输入过的值!

然后redirect实现重定向功能,就是重定向到index这个视图函数里面的逻辑去执行!

当然你也可以用路由重定向来实现:

return redirect(url_for('index'))
return redirect('/')

上述两种方法都是一样的,只不过你要保证’/’路由下的视图函数是index。

Ⅰ 会话控制cookie和session

用户会话可以通过time来控制会话cookie

&#x53D1;&#x9001;&#x5230;response&#x7684;headers&#x91CC;&#x9762;&#xFF08;&#x5BA2;&#x6237;&#x7AEF;&#xFF09;
from flask import Flask
import time
app = Flask(__name__)
@app.route('/set_cookie/')
def set_cookie():
    resp = make_response('&#x8BBE;&#x7F6E;cookie')
    # &#x6307;&#x5B9A;&#x8FC7;&#x671F;&#x65F6;&#x95F4;
    expires = time.time + 10
    resp.set_cookie('name','xiaoming',expires=expires)
    return resp
if __name__ == '__main__':
    app.run()

② 闪现消息:

用户需要知道自己的状态,就比如你输入账号登录,是否成功之类的,这时候就要闪现消息的存在了!!

from flask import Flask, render_template, session, redirect, url_for, flash
@app.route('/', methods=['GET', 'POST'])
def index():
 form = NameForm()
 if form.validate_on_submit():
     old_name = session.get('name')
     if old_name is not None and old_name != form.name.data:
         flash('Looks like you have changed your name!')
     session['name'] = form.name.data
     return redirect(url_for('index'))
 return render_template('index.html',
     form = form, name = session.get('name'))

看,如下结果

只不过你的base.html应该改成这样:

{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
 <div class="container">
 <div class="navbar-header">
 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
 <span class="sr-only">Toggle navigation</span>

 </button>
 <a class="navbar-brand" href="/">Flasky</a>
 </div>
 <div class="navbar-collapse collapse">
 <ul class="nav navbar-nav">
 <li><a href="/">Home</a></li>
 </ul>
 </div>
 </div>
</div>
{% endblock %}
{% block content %}
<div class="container">
 {% for message in get_flashed_messages() %}
 <div class="alert alert-warning">
 <button type="button" class="close" data-dismiss="alert">&#xD7;</button>
 {{ message }}
 </div>
 {% endfor %}
 {% block page_content %}{% endblock %}
</div>
{% endblock %}
  1. 文件上传

当你设计一个基于Flask的Yolov5的检测系统,要求实现的功能是打开Web网页,上传一张照片,然后识别出来返回给Web页面,那么这套系统必不可少的环节就是文件上传!

① form标签:

首先HTML的知识,先要设置表单属性为:

enctype="multipart/form-data"

并且form标签内要包含

<input type="file">

② 文件上传流程

思路就是POST方法,用户上传文件。服务端应用通过请求对象上的files字典访问文件,然后使用文件的save()方法将文件永久地保存在文件系统上的某处。

③ 案例基础代码:

import os
from flask import Flask, request, redirect, url_for
from werkzeug import secure_filename

UPLOAD_FOLDER = '/path/to/the/uploads'
#&#x5B58;&#x50A8;&#x4E0A;&#x4F20;&#x7684;&#x6587;&#x4EF6;&#x7684;&#x5730;&#x65B9;
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
#&#x5141;&#x8BB8;&#x4E0A;&#x4F20;&#x7684;&#x6587;&#x4EF6;&#x7684;&#x7C7B;&#x578B;&#x96C6;&#x5408;&#xFF0C;&#x767D;&#x540D;&#x5355;&#x65B9;&#x6CD5;

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

最基础,啥都不用防护就这样干:

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')

但是未经过滤,很容易发生一些诸如经典的PHP文件上传漏洞那样的无语。不要完全信任用户的值!这时候就要让Werkzeug提供的secure_filename()的方法出马了!

④ 上传优化

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        file = request.files['file']
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            return redirect(url_for('uploaded_file',
                                    filename=filename))
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form action method="post" enctype="multipart/form-data">
      <p><input type="file" name="file">
         <input type="submit" value="Upload">
    </p></form>
    '''

如果没有使用secure_filename( ),那么很容易使用../路径绕过,从而导致一些致命的东西发生!

那么上传了文件,如何进行读取访问,甚至进行下一步的操作呢!?

from flask import send_from_directory

@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'],filename)</filename>

你还可以优化它的上传功能,比如限制文件大小之类的!!

app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024

来到了一个关键的环节!

由于大家之前学过MySQL,所以我们直接以Flask链接MySQL来进行开发!

当然Flask管理数据库还是有专门的拓展包,你也可以使用python的pymysql进行管理开发!我们这里使用Flask-SQLAlchemy

安装也一样:

(venv) $ pip install flask-sqlalchemy

官方URL如下:

我们这里使用MySQL,那就直接记住:

mysql://username:password@hostname/database

1.配置:

直接在app.py里面进行配置:

import os
from flask_sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

db 对象是 SQLAlchemy 类的实例,表示应用使用的数据库,通过它可获得 Flask-SQLAlchemy提供的所有功能。

2.定义模型:

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)

    def __repr__(self):
        return '<role %r>' % self.name

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)

    def __repr__(self):
       return '<user %r>' % self.username</user></role>

类变量 tablename 定义在数据库中使用的表名。如果没有定义 tablename,Flask-SQLAlchemy 会使用一个默认名称,但默认的表名没有遵守流行的使用复数命名的约定,所以最好由我们自己来指定表名。其余的类变量都是该模型的属性,定义为 db.Column 类的实例。db.Column 类构造函数的第一个参数是数据库列和模型属性的类型。表 5-2 列出了一些可用的列类型以及在模型中使用的 Python 类型。

列类型如下,如果像知道更为详细的,请上Flask官网!!

3.定义关系:

class Role(db.Model):
    # ...

    users = db.relationship('User', backref='role')
class User(db.Model):
    # ...

    role_id = db.Column(db.Integer,db.ForeignKey('roles.id'))

定义好模型以及关系之后,我们来看一下基本的操作:

4.数据库操作:

from app import db

① 创建表:

db.create_all()

② 删除旧表:

(会把所有数据都删掉!!!)

db.drop_all()

③ 插入行:

from app import Role, User
admin_role = Role(name='Admin')
mod_role = Role(name='Moderator')
user_role = Role(name='User')
user_john = User(username='john', role=admin_role)
user_susan = User(username='susan', role=user_role)
user_david = User(username='david', role=user_role)

但是此时id还未写入数据库中,未被赋值!

数据库的改动通过数据库会话管理,在 Flask-SQLAlchemy 中,会话由 db.session 表示。准备把对象写入数据库之前,要先将其添加到会话中:

db.session.add(admin_role)
db.session.add(mod_role)
db.session.add(user_role)
db.session.add(user_john)
db.session.add(user_susan)
db.session.add(user_david)

你也可以合在一起,创建个数组用逗号隔开!

 db.session.add_all([admin_role, mod_role, user_role,user_john, user_susan, user_david])

然后提交会话到数据库!

db.session.commit()

④ 修改行:

⑤ 删除行:

db.session.delete(mod_role)
db.session.commit()

⑥ 查询行:

 Role.query.all()
 User.query.all()

其实这个拓展包就是把sql封装一层,把各个接口api放在外面,方便我们使用,也可以大程度的防止SQL注入的发生!

你可以类比数据库中的where语句,sqlalchemy也有相应的过滤器进行精确定位:

 User.query.filter_by(role=user_role).all()

甚至你可以查看它对应的SQL语句是什么!

str(User.query.filter_by(role=user_role))

5.视图函数中操作数据库:

案例:

@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username=form.name.data)
            db.session.add(user)
            db.session.commit()
            session['known'] = False
        else:
            session['known'] = True
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('index'))
 return render_template('index.html',
        form=form, name=session.get('name'),
        known=session.get('known', False))

当然你也可以尝试使用MySQLdb或是PyMySQL来进行数据库的连接与操作,pymysql挺好玩的,大家可以去尝试一下!!!!

当你可能遇到过注册一个账号时,叫你邮箱验证。或者你也可以实现一个功能,就是网站一旦发现404错误,就立马发邮件给你,提醒开发者上站查看信息!!!

Flask里面用Flask-Mail拓展来实现这个功能。

(venv) $ pip install flask-mail

Flask-Mail 连接到简单邮件传输协议(SMTP,simple mail transfer protocol)服务器,把邮件交给这个服务器发送。如果不进行配置,则 Flask-Mail 连接 localhost 上的 25 端口,无须验证身份即可发送电子邮件。

  1. 配置:
import os
...

app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')

当然还是和上面的老样子,这些信息推荐保存在环境变量里面。

什么,到这里你还是不知道环境变量怎么弄?

export&#xFF01;&#xFF01;&#xFF01;&#xFF01;
  1. 邮件逻辑:
from flask_mail import Message

app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <flasky@example.com>'

def send_email(to, subject, template, **kwargs):
    msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
                  sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
    msg.body = render_template(template + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    mail.send(msg)</flasky@example.com>
  1. smtplib

当然这边你也可以使用python的smtplib拓展包进行邮件的发送,os调用即可!!

import smtplib
from email.mime.text import MIMEText

&#x8BBE;&#x7F6E;&#x670D;&#x52A1;&#x5668;&#x6240;&#x9700;&#x4FE1;&#x606F;
163&#x90AE;&#x7BB1;&#x670D;&#x52A1;&#x5668;&#x5730;&#x5740;
mail_host = 'smtp.qq.com'
163&#x7528;&#x6237;&#x540D;
mail_user = 'xxxxxxxx'
&#x5BC6;&#x7801;(&#x90E8;&#x5206;&#x90AE;&#x7BB1;&#x4E3A;&#x6388;&#x6743;&#x7801;)
mail_pass = 'xxxxxxxx'
&#x90AE;&#x4EF6;&#x53D1;&#x9001;&#x65B9;&#x90AE;&#x7BB1;&#x5730;&#x5740;
sender = 'xxxxxxxxxxxxx'
&#x90AE;&#x4EF6;&#x63A5;&#x53D7;&#x65B9;&#x90AE;&#x7BB1;&#x5730;&#x5740;&#xFF0C;&#x6CE8;&#x610F;&#x9700;&#x8981;[]&#x5305;&#x88F9;&#xFF0C;&#x8FD9;&#x610F;&#x5473;&#x7740;&#x4F60;&#x53EF;&#x4EE5;&#x5199;&#x591A;&#x4E2A;&#x90AE;&#x4EF6;&#x5730;&#x5740;&#x7FA4;&#x53D1;
receivers = ['xxxxxxxxxxxxxxx']

&#x8BBE;&#x7F6E;email&#x4FE1;&#x606F;
&#x90AE;&#x4EF6;&#x5185;&#x5BB9;&#x8BBE;&#x7F6E;
message = MIMEText('&#x7F51;&#x7AD9;404&#x9519;&#x8BEF;&#x5566;&#xFF01;&#xFF01;&#x5FEB;&#x70B9;&#x767B;&#x4E0A;&#x7F51;&#x7AD9;&#x770B;&#x770B;&#xFF01;&#xFF01;', 'plain', 'utf-8')
&#x90AE;&#x4EF6;&#x4E3B;&#x9898;
message['Subject'] = '&#x7F51;&#x7AD9;404&#x9519;&#x8BEF;&#x5566;&#xFF01;&#xFF01;&#x5FEB;&#x70B9;&#x767B;&#x4E0A;&#x7F51;&#x7AD9;&#x770B;&#x770B;&#xFF01;&#xFF01;'
&#x53D1;&#x9001;&#x65B9;&#x4FE1;&#x606F;
message['From'] = sender
&#x63A5;&#x53D7;&#x65B9;&#x4FE1;&#x606F;
message['To'] = receivers[0]

&#x767B;&#x5F55;&#x5E76;&#x53D1;&#x9001;&#x90AE;&#x4EF6;

try:
    smtpObj = smtplib.SMTP()
    # &#x8FDE;&#x63A5;&#x5230;&#x670D;&#x52A1;&#x5668;
    smtpObj.connect(mail_host, 25)
    # &#x767B;&#x5F55;&#x5230;&#x670D;&#x52A1;&#x5668;
    smtpObj.login(mail_user, mail_pass)
    # &#x53D1;&#x9001;
    smtpObj.sendmail(
        sender, receivers, message.as_string())
    # &#x9000;&#x51FA;
    smtpObj.quit()
    print('success')
except smtplib.SMTPException as e:
    print('error', e)  # &#x6253;&#x5370;&#x9519;&#x8BEF;

在单个文件中开发应用是很方便,但却有个很大的缺点:应用在全局作用域中创建,无法动态修改配置。运行脚本时,应用实例已经创建,再修改配置为时已晚。这一点对单元测试尤其重要,因为有时为了提高测试覆盖度,必须在不同的配置下运行应用。

这个问题的解决方法是延迟创建应用实例,把创建过程移到可显式调用的工厂函数中。这种方法不仅可以给脚本留出配置应用的时间,还能够创建多个应用实例,为测试提供便利。

web/init.py:

from flask import Flask, render_template
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_moment import Moment
from flask_sqlalchemy import SQLAlchemy
from config import config

bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()

def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)
    bootstrap.init_app(app)
    mail.init_app(app)
    moment.init_app(app)
    db.init_app(app)
    # &#x6DFB;&#x52A0;&#x8DEF;&#x7531;&#x548C;&#x81EA;&#x5B9A;&#x4E49;&#x7684;&#x9519;&#x8BEF;&#x9875;&#x9762;
    return app

虽然应用工厂函数解决了修改动态配置的问题,但是让定义路由变得更为复杂了!还记得上面的app.py吗?我们通过app.route路由装饰器定义路由,但在工厂函数中只有调用create_app()才可以使用装饰器,那应用的反应过慢会导致错误发生!

Flask就此采用了蓝图这一解决方法来解决这一问题!

这块东西,年代久远,我也忘得差不多了,hh,后面一定补。

Flask 用 蓝图(blueprints) 的概念来在一个应用中或跨应用制作应用组件和支 持通用的模式。蓝图很好地简化了大型应用工作的方式,并提供给 Flask 扩展在应用 上注册操作的核心方法。一个 Blueprint 对象与 Flask 应用对 象的工作方式很像,但它确实不是一个应用,而是一个描述如何构建或扩展应用的 蓝图

蓝图的基本设想是当它们注册到应用上时,它们记录将会被执行的操作。 当分派请求和生成从一个端点到另一个的 URL 时,Flask 会关联蓝图中的视图函数。

在蓝图中定义的路由和错误处理程序处于休眠状态,直到蓝本注册到应用上之后,它们才真正成为应用的一部分。使用位于全局作用域中的蓝图时,定义路由和错误处理程序的方法几乎与单脚本应用一样。

01.教程

1.第一个蓝图:

from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound

simple_page = Blueprint('simple_page', __name__,
                        template_folder='templates')

@simple_page.route('/', defaults={'page': 'index'})
@simple_page.route('/<page>')
def show(page):
    try:
        return render_template('pages/%s.html' % page)
    except TemplateNotFound:
        abort(404)</page>

当我们使用 @simple_page.route 装饰器绑定函数时,在蓝图之后被注册时它 会记录把 show 函数注册到应用上的意图。此外,它会给函数的端点加上 由 Blueprint 的构造函数中给出的蓝图的名称作为前缀(在此例 中是 simple_page )。

  1. 案例:

web/main/init.py

from flask import Blueprint

main = Blueprint('main', __name__)

from . import views, errors

蓝本通过实例化一个 Blueprint 类对象创建。这个构造函数有两个必须指定的参数:蓝本的名称和蓝本所在的包或模块。与应用一样,多数情况下第二个参数使用 Python 的 name 变量即可。

应用的路由保存在包里的 app/main/views.py 模块中,而错误处理程序保存在 app/main/errors.py 模块中。导入这两个模块就能把路由和错误处理程序与蓝本关联起来。注意,这些模块在 app/main/ init.py 脚本的末尾导入,这是为了避免循环导入依赖,因为在 app/main/views.py 和 app/main/errors.py 中还要导入 main 蓝本,所以除非循环引用出现在定义main 之后,否则会致使导入出错。

web/init.py

def create_app(config_name):
    # ...

    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint)
    return app

web/main/errors.py

from flask import render_template
from . import main

@main.app_errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

@main.app_errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500

web/main/views.py

from datetime import datetime
from flask import render_template, session, redirect, url_for
from . import main
from .forms import NameForm
from .. import db
from ..models import User

@main.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        # ...

        return redirect(url_for('.index'))
    return render_template('index.html', form=form, name=session.get('name'), known=session.get('known', False),current_time=datetime.utcnow())

不知道当你发现没有,现在介绍的几个功能就把app.py这个py文件搞得有点臃肿了!更何况我们的业务往往要更加复杂。

这时候要求我们编写代码时要规范!

1.生成需求文件:

当我们准备将项目放在云服务器上的时候,往往要手动安装很多拓展包,那这样子极度的麻烦,不知道你们时候还记起freeze!!通过pip freeze 我们可以很轻松的生成需求文件。

pip freeze > requirements.txt

一键安装只需要:

pip install -r requirements.txt

2.基础结构(项目布局):

当然上面的案例仅供参考,你们有自己的方案就可以了!要稍微规范一点!要实现维护方便的目的!

  • requirements.txt 列出了所有依赖包,便于在其他计算机中重新生成相同的虚拟环境;
  • config.py 存储配置;

config.py里面放的是配置选项,你可以将配置分为开发和生产,通过config.py进行快速应用!

案例:

import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
    MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.googlemail.com')
    MAIL_PORT = int(os.environ.get('MAIL_PORT', '587'))
    MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in \
        ['true', 'on', '1']
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
    FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
    FLASKY_MAIL_SENDER = 'Flasky Admin <flasky@example.com>'
    FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    @staticmethod
    def init_app(app):
        pass

class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
         'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')

class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
        'sqlite://'

class ProductionConfig(Config):
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'sqlite:///' + os.path.join(basedir, 'data.sqlite')

config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig,

    'default': DevelopmentConfig
}</flasky@example.com>

还是那句话,实际生产中,请把重要敏感信息放在环境变量里头,不要放在源代码里!!!

你还可以在里面加入更多的配置,这都看你自己的选择了!

部署是一个大的问题。

说实话,你就算直接运行python脚本都可以进行项目部署。

nohup python3 app.py

但毕竟是测试用,所以这样肯定是不可以的!

我们可以用gunicorn部署,或者用docker容器进行部署!

1.gunicorn

这个比较简单!

① 安装:

在Linux:

pip3 install python3-gunicorn

这个命令可以省掉你很多麻烦,真的!

然后按照惯例进入虚拟环境,要记得项目主程序内的应用实例的名称!

② 运行:

gunicorn -w 2 -b 0.0.0.0:80 app:app

当然你可以加上日志监查之类的:

gunicorn -w 2 -b 0.0.0.0:80 app:app --error-logfile=/logs/error.log --access-logfile=/logs/access.log -D
  • -b IP及端口
  • -w 线程数
  • –error-logfile 错误日志
  • -D 后台运行,也可以使用nohup命令
  • -K 同步并发设置

部署之后可能会遇到的问题:

解决方法:

③ 停止运行:

杀掉gunicorn服务的方法也挺简单:

pstree -ap | grep gunicorn

当然你可能没装pstree,其实使用ps也是差不多的,只需要找到这个gunicorn进程号即可!

找到gunicorn这个服务的进程号PID。

然后:

kill -9 PID

就可以直接删掉了!!!

2.docker

① 安装:

由于apt官方库里的docker版本可能比较旧,所以先卸载可能存在的旧版本:

$ sudo apt-get remove docker docker-engine docker-ce docker.io

更新apt包索引:

$ sudo apt-get update

安装以下包以使apt可以通过HTTPS使用存储库(repository):

$ sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common

添加Docker官方的GPG密钥:

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

使用下面的命令来设置stable存储库:

$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \$(lsb_release -cs) stable"

再更新一下apt包索引:

$ sudo apt-get update

安装最新版本的Docker CE:

$ sudo apt-get install -y docker-ce

验证docker

查看docker服务是否启动:

$ systemctl status docker

若未启动,则启动docker服务:

$ sudo systemctl start docker

经典的hello world:

$ sudo docker run hello-world

有以上输出,表示docker安装成功.

② 详情:

你可以通过以下命令查看docker版本

docker version

③ 挂载:

有许多方法:

docker run -p 80:80 -v "D:\xxx\xxx:/var/www/html" -d php:7.4-apache

④ Dockerfile:

假设我们是以当前项目为例,来创建docker,那我们dockerfile文件应该是:

FROM python:3.6-alpine
ENV FLASK_APP app.py
ENV FLASK_CONFIG docker
RUN adduser -D flasky
USER flasky
WORKDIR /home/flasky
COPY requirements requirements
RUN python -m venv venv
RUN venv/bin/pip install -r requirements/docker.txt
COPY app app
COPY migrations migrations
COPY flasky.py config.py boot.sh ./
&#x8FD0;&#x884C;&#x65F6;&#x914D;&#x7F6E;
EXPOSE 5000
ENTRYPOINT ["./boot.sh"]

所有 Dockerfile 文件中都要有 FROM 命令,其作用是指定一个基容器映像,在其基础上构建当前映像。多数情况下都使用 Docker Hub(Docker 容器映像仓库)中公开可用的映像。

某案例解释:

FROM python:2.7-slim
&#x4F7F;&#x7528;&#x5B98;&#x65B9;&#x63D0;&#x4F9B;&#x7684; Python &#x5F00;&#x53D1;&#x955C;&#x50CF;&#x4F5C;&#x4E3A;&#x57FA;&#x7840;&#x955C;&#x50CF;
&#x6307;&#x5B9A;"python:2.7-slim"&#x8FD9;&#x4E2A;&#x5B98;&#x65B9;&#x7EF4;&#x62A4;&#x7684;&#x57FA;&#x7840;&#x955C;&#x50CF;&#xFF0C;&#x4ECE;&#x800C;&#x514D;&#x53BB;&#x5B89;&#x88C5; Python &#x7B49;&#x8BED;&#x8A00;&#x73AF;&#x5883;&#x7684;&#x64CD;&#x4F5C;&#x3002;&#x5426;&#x5219;&#xFF0C;&#x8FD9;&#x4E00;&#x6BB5;&#x5C31;&#x5F97;&#x8FD9;&#x4E48;&#x5199;&#x4E86;&#xFF1A;
##FROM ubuntu:latest
##RUN apt-get update -yRUN apt-get install -y python-pip python-dev build-essential

WORKDIR /app
&#x5C06;&#x5DE5;&#x4F5C;&#x76EE;&#x5F55;&#x5207;&#x6362;&#x4E3A; /app
&#x610F;&#x601D;&#x662F;&#x5728;&#x8FD9;&#x4E00;&#x53E5;&#x4E4B;&#x540E;&#xFF0C;Dockerfile &#x540E;&#x9762;&#x7684;&#x64CD;&#x4F5C;&#x90FD;&#x4EE5;&#x8FD9;&#x4E00;&#x53E5;&#x6307;&#x5B9A;&#x7684; /app &#x76EE;&#x5F55;&#x4F5C;&#x4E3A;&#x5F53;&#x524D;&#x76EE;&#x5F55;&#x3002;

ADD . /app
&#x5C06;&#x5F53;&#x524D;&#x76EE;&#x5F55;&#x4E0B;&#x7684;&#x6240;&#x6709;&#x5185;&#x5BB9;&#x590D;&#x5236;&#x5230; /app &#x4E0B;
Dockerfile &#x91CC;&#x7684;&#x539F;&#x8BED;&#x5E76;&#x4E0D;&#x90FD;&#x662F;&#x6307;&#x5BF9;&#x5BB9;&#x5668;&#x5185;&#x90E8;&#x7684;&#x64CD;&#x4F5C;&#x3002;&#x6BD4;&#x5982; ADD&#xFF0C;&#x6307;&#x7684;&#x662F;&#x628A;&#x5F53;&#x524D;&#x76EE;&#x5F55;&#xFF08;&#x5373; Dockerfile &#x6240;&#x5728;&#x7684;&#x76EE;&#x5F55;&#xFF09;&#x91CC;&#x7684;&#x6587;&#x4EF6;&#xFF0C;&#x590D;&#x5236;&#x5230;&#x6307;&#x5B9A;&#x5BB9;&#x5668;&#x5185;&#x7684;&#x76EE;&#x5F55;&#x5F53;&#x4E2D;&#x3002;

RUN pip install --trusted-host pypi.python.org -r requirements.txt
&#x4F7F;&#x7528; pip &#x547D;&#x4EE4;&#x5B89;&#x88C5;&#x8FD9;&#x4E2A;&#x5E94;&#x7528;&#x6240;&#x9700;&#x8981;&#x7684;&#x4F9D;&#x8D56;

EXPOSE 80
&#x5141;&#x8BB8;&#x5916;&#x754C;&#x8BBF;&#x95EE;&#x5BB9;&#x5668;&#x7684; 80 &#x7AEF;&#x53E3;

ENV NAME World
&#x8BBE;&#x7F6E;&#x73AF;&#x5883;&#x53D8;&#x91CF;

CMD ["python", "app.py"]
&#x8BBE;&#x7F6E;&#x5BB9;&#x5668;&#x8FDB;&#x7A0B;&#x4E3A;&#xFF1A;python app.py&#xFF0C;&#x5373;&#xFF1A;&#x8FD9;&#x4E2A; Python &#x5E94;&#x7528;&#x7684;&#x542F;&#x52A8;&#x547D;&#x4EE4;
&#x8FD9;&#x91CC;app.py &#x7684;&#x5B9E;&#x9645;&#x8DEF;&#x5F84;&#x662F; /app/app.py&#x3002;CMD ["python", "app.py"] &#x7B49;&#x4EF7;&#x4E8E; "docker run python app.py"&#x3002;
&#x5728;&#x4F7F;&#x7528; Dockerfile &#x65F6;&#xFF0C;&#x53EF;&#x80FD;&#x8FD8;&#x4F1A;&#x770B;&#x5230;&#x4E00;&#x4E2A;&#x53EB;&#x4F5C; ENTRYPOINT &#x7684;&#x539F;&#x8BED;&#x3002;&#x5B83;&#x548C; CMD &#x90FD;&#x662F; Docker &#x5BB9;&#x5668;&#x8FDB;&#x7A0B;&#x542F;&#x52A8;&#x6240;&#x5FC5;&#x9700;&#x7684;&#x53C2;&#x6570;&#xFF0C;&#x5B8C;&#x6574;&#x6267;&#x884C;&#x683C;&#x5F0F;&#x662F;&#xFF1A;"ENTRYPOINT CMD"&#x3002;
&#x4F46;&#x662F;&#xFF0C;&#x9ED8;&#x8BA4;&#xFF0C;Docker &#x4F1A;&#x63D0;&#x4F9B;&#x4E00;&#x4E2A;&#x9690;&#x542B;&#x7684; ENTRYPOINT&#xFF0C;&#x5373;&#xFF1A;/bin/sh -c&#x3002;&#x6240;&#x4EE5;&#xFF0C;&#x5728;&#x4E0D;&#x6307;&#x5B9A; ENTRYPOINT &#x65F6;&#xFF0C;&#x6BD4;&#x5982;&#x5728;&#x8FD9;&#x4E2A;&#x4F8B;&#x5B50;&#x91CC;&#xFF0C;&#x5B9E;&#x9645;&#x4E0A;&#x8FD0;&#x884C;&#x5728;&#x5BB9;&#x5668;&#x91CC;&#x7684;&#x5B8C;&#x6574;&#x8FDB;&#x7A0B;&#x662F;&#xFF1A;/bin/sh -c "python app.py"&#xFF0C;&#x5373; CMD &#x7684;&#x5185;&#x5BB9;&#x5C31;&#x662F; ENTRYPOINT &#x7684;&#x53C2;&#x6570;&#x3002;
&#x57FA;&#x4E8E;&#x4EE5;&#x4E0A;&#x539F;&#x56E0;&#xFF0C;&#x540E;&#x9762;&#x4F1A;&#x7EDF;&#x4E00;&#x79F0; Docker &#x5BB9;&#x5668;&#x7684;&#x542F;&#x52A8;&#x8FDB;&#x7A0B;&#x4E3A; ENTRYPOINT&#xFF0C;&#x800C;&#x4E0D;&#x662F; CMD&#x3002;

Ⅰ.docker file细节命令解读:

  • FROM:定制的镜像都是基于 FROM 的镜像。
  • RUN:用于执行后面跟着的命令行命令。
  • shell格式
  • exec格式
  • COPY:复制指令,从上下文目录中复制文件或者目录到容器里指定路径。
  • ADD 指令和 COPY 的使用格类似(同样需求下,官方推荐使用 COPY)。功能也类似
  • CMD:类似于 RUN 指令,用于运行程序,但二者运行的时间点不同。
  • CMD 在docker run 时运行。
  • RUN 是在 docker build。
  • ENTRYPOINT:类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。
  • 如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
  • ENV:设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。
  • ARG:构建参数,与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。
  • VOLUME:定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。
  • EXPOSE:仅仅只是声明端口。
  • WORKDIR:指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作目录,必须是提前创建好的)。docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在。
  • USER:用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。
  • HEALTHCHECK:用于指定某个程序或者指令来监控 docker 容器服务的运行状态。
  • ONBUILD:用于延迟构建命令的执行。
  • LABEL:LABEL 指令用来给镜像添加一些元数据(metadata),以键值对的形式。

具体详情请参考docker官方文档。

config.py配置:

class DockerConfig(ProductionConfig):
    @classmethod
    def init_app(cls, app):
        ProductionConfig.init_app(app)

        # &#x628A;&#x65E5;&#x5FD7;&#x8F93;&#x51FA;&#x5230;stderr
        import logging
        from logging import StreamHandler
        file_handler = StreamHandler()
        file_handler.setLevel(logging.INFO)
        app.logger.addHandler(file_handler)

config = {
    # ...

    'docker': DockerConfig,
    # ...

}

RUN 命令在容器映像的上下文中执行指定的命令。在示例 17-9 中,第一个 RUN 命令在容器中创建一个名为 flasky 的用户。adduser 命令由 Alpine Linux 提供,在 FROM 命令指定的基映像中可用。adduser 命令的 -D 参数禁止命令提示用户输入密码。USER 命令选择以哪个用户的身份运行容器,以及 Dockerfile 文件中后续的命令。Docker 默认使用 root 用户,但是如无必要,一般建议切换为常规用户。WORKDIR 命令定义应用所在的顶层目录。这里使用的是前面创建的 flasky 用户的家目录(home directory)。Dockerfile 文件中余下的命令都将在这个目录中执行。

COPY 命令从本地文件系统中把文件复制到容器的文件系统中。这里复制了 requirements、app 和 migrations 这 3 个完整的目录,以及应用顶层目录中的 flasky.py、config.py 和新出现的 boot.sh 文件(稍后讨论)。后面两个 RUN 命令创建虚拟环境,并在里面安装所需的包。我们为 Docker 部署方式专门准备了一个需求文件,即 requirements/docker.txt。这个文件从 requirements/common.txt 中导入全部依赖,在此基础上又添加了 Gunicorn,在 Heroku 部署方式中用作 Web 服务器。EXPOSE 命令定义服务器安装在容器的哪个端口上。启动容器后,Docker 会把这个端口映射到宿主设备的真实端口上,以便容器接收外部世界发来的请求。最后一个命令 ENTRYPOINT 指定启动容器时如何运行应用。我们把前面复制到容器中的boot.sh 当作启动脚本。

boot.sh

shell script脚本,dddd

#!/bin/sh
source venv/bin/activate
flask deploy
exec gunicorn -b 0.0.0.0:5000 --access-logfile - --error-logfile - flasky:app

这个脚本先激活构建容器的过程中创建的 venv 虚拟环境,然后执行本章前面为应用定义的 deploy 命令(部署到 Heroku 中也用到了)。deploy 命令创建一个新数据库,将其更新到最新版本,然后插入默认用户角色。我们没有设定 DATABASE_URL 环境变量,因此这里使用的是 SQLite 数据库。最后,启动 Gunicorn 服务器,监听 5000 端口。Docker 会捕获应用的所有输出,将其写入日志,因此我们配置 Gunicorn,把访问日志和错误日志文件都写入标准输出。使用 exec 命令启动 Gunicorn 后,Gunicorn 的进程便取代了运行 boot.sh 文件的进程。这是因为 Docker 会特别留意启动容器的进程,希望整个生命周期内它都是主进程。如果这个进程停止运行了,容器也就停止了。

⑤ 启动:

$ docker build -t flasky:latest

在你的项目目录的上一级输入这个命令,-t参数指定容器映像的名称和标签,二者之间以冒号分隔。标签经常使用 latest,即使用容器映像的最新版本。build 命令最后那个点号把当前目录设为构建过程中的顶级目录。Docker 将在这个目录中寻找 Dockerfile 文件,而且容器映像可以从这个目录及其全部子目录中复制所需的文件。

docker build 命令成功运行结束后,本地映像仓库中将多出一个容器映像。本地系统中映像仓库的内容可使用 docker images 命令查看:

docker images

⑥ 运行:

$ docker run --name flasky -d -p 8000:5000 -e SECRET_KEY=57d40f677aff4d8d96df97223c74d217 -e MAIL_USERNAME=<your-gmail-username> -e MAIL_PASSWORD=<your-gmail-password> flasky:latest</your-gmail-password></your-gmail-username>

额,这就是各种设置参数,也就是直接把相关敏感参数设置到环境变量里头,并且启动docker容器!!!

–name 选项为容器指定一个名称。名称可以不指定,如果未指定,Docker 将使用随机的词生成一个。

-d 选项指定以孤立模式启动容器,即在系统的后台作业中运行容器。非孤立模式下的容器作为前台任务执行,依附在当前控制台会话上。

-p 选项把宿主系统的 8000 端口映射到容器的 5000 端口上。Docker 给了我们充分的自由,允许我们把容器端口映射到宿主系统的任何端口上。映射后,同一个容器映像将在宿主的不同端口上运行两个或多个容器映像实例,而各实例都使用自己的虚拟化 5000 端口。

-e 选项定义在容器的上下文中存在的环境变量,与 Dockerfile 文件中使用 ENV 命令定义的环境变量共存。SECRET_KEY 变量的值确保使用唯一且极难猜到的密钥签署用户会话和令牌。你要为这个变量生成唯一的密钥。MAIL_USERNAME 和 MAIL_PASSWORD 变量配置发送电子邮件的 Gmail 服务。如果你在生产环境中使用其他电子邮件服务,可能还要定义 MAIL_SERVER、MAIL_PORT 和 MAIL_USE_TLS 变量。

docker run 命令的最后一个参数是要运行的容器映像的名称和标签。这个参数的值应该与执行 docker build 命令时提供给 -t 选项的值一致。

容器在后台启动后,docker run 命令会在控制台打印容器的 ID。这是一个 256 位的唯一标识符,以十六进制表示。需要引用容器的命令都可以使用这个 ID(其实只需提供 ID 的前几个字符,这样就足以唯一标识容器了)。

⑦ 关闭容器:

docker ps 查看docker容器启动情况!!

然后你会看到很多信息,然后注意那个CONTAINER ID,后面关闭某个容器时,就会用到这个ID号!!!

docker stop CONTAINER ID

但是stop命令只是停止这个容器的运行,但是并不会从系统中删除这个容器,如果你想彻底删除这个容器,那就用以下命令:

 docker rm CONTAINER ID

当然你可以综合一下:

 docker rm -f CONTAINER ID

你要是想知道docker里面发生了什么,你可以:

docker exec -it CONTAINER ID sh

⑧ 编排容器:

容器化应用通常由多个容器组成。前一节我们看到,主应用和数据库服务器分别运行在单独的容器中。应用变复杂后,难免要用到多个容器。有些应用可能需要使用额外的服务,例如消息队列或缓存。另一些应用可能采用微服务架构,以分布式结构部署多个小型子应用,分别运行在单独的容器中。需要处理高负载或者需要高容错能力的应用可能想进行伸 缩,在负载均衡程序背后运行多个实例。

随着应用所需的容器数量不断增长,如果只使用 Docker,那么管理和协调容器的任务将变得难上加难。这种情况下,使用构建在 Docker 基础上的编排框架能助你一臂之力。

随 Docker 一起安装的 Compose 工具集提供了基本的编排功能。使用 Compose 时,构成应用的各容器在一个配置文件中描述,这个文件通常命名为 docker-compose.yml。这里定义的所有容器,可以使用 docker-compose 命令一次性全部启动。

docker-compose.yml:Compose配置文件

version: '3'
services:
  flasky:
    build: .
    ports:
      - "8000:5000"
    env_file: .env
    links:
      - mysql:dbserver
    restart: always
  mysql:
    image: "mysql/mysql-server:5.7"
    env_file: .env-mysql
    restart: always

这个文件的内容使用 YAML 格式编写。YAML 是一种简洁的格式,通过键-值映射和列表表示层次结构。version 键指定使用哪个版本的 Compose,services 键在子元素中定义应用的各个容器。

Flasky 应用使用两个服务,分别名为 flasky 和 mysql。

flasky 服务是应用的一部分,名下的子键指定传给 docker build 和 docker run 命令的参数。

build 键指定构建目录,即 Dockerfile 文件所在的目录。ports 键指定网络端口映射。

env_file 键是为容器定义多个环境变量的便利方式。

links 键连接 MySQL 容器,对外的主机名为 dbserver。

restart 键设为 always,这样一旦容器意外退出,Docker 便会自动重启容器。

此次部署的 .env 文件中要定义下述变量:

FLASK_APP=flasky.py
FLASK_CONFIG=docker
SECRET_KEY=3128b4588e7f4305b5501025c13ceca5
MAIL_USERNAME=<your-gmail-username>
MAIL_PASSWORD=<your-gmail-password>
DATABASE_URL=mysql+pymysql://flasky:<database-password>@dbserver/flasky</database-password></your-gmail-password></your-gmail-username>

mysql 服务的结构较简单,因为这个服务直接使用现有的映像启动,无须构建。image 键指定这个服务所用容器映像的名称和标签。与 docker run 命令一样,Docker 会从容器映像注册处下载指定的映像。env_file 和 restart 键的作用与 flasky 容器中的那些键相仿。注意,MySQL 容器的环境变量存储在另一个文件中,名为 .env-mysql。你可能会想把所有容器的环境变量都放在 .env 文件中,但是这样做不好, 最好禁止一个容器访问另一个容器的机密信息

.env-mysql 文件中要定义下述环境变量:

MYSQL_RANDOM_ROOT_PASSWORD=yes
MYSQL_DATABASE=flasky
MYSQL_USER=flasky
MYSQL_PASSWORD=<database-password></database-password>

boot.sh脚本优化:

#!/bin/sh
source venv/bin/activate

while true; do
    flask deploy
    if [[ "$?" == "0" ]]; then
        break
    fi
    echo Deploy command failed, retrying in 5 secs...

    sleep 5
done

exec gunicorn -b :5000 --access-logfile - --error-logfile - flasky:app

启动应用:

docker-compose up -d --build

那么如果你前面设置好了,这里你应该可以访问此Web应用!

⑨ 查看信息:

Compose日志:

docker-compose logs

监控日志流:

docker-compose logs -f

输出每个容器的概况:

docker-compose ps

列出所有容器,包括停止了的但仍在系统中的容器:

docker ps -a

彻底从系统中删除容器:

docker rm -f ID

五. 前后端分离:

我们实际开发的时候可以采用前后端分离的办法,比如前端采用Vue.js,后端采用Flask,后端主要处理业务逻辑层,简单处理各跳转路由以及身份验证和数据库之间的信息交流之类即可,不再强调前端页面的渲染!我们前端页面有Vue.js来进行渲染。

最好采用Flask后端只能内网访问!而Vue.js的前端框架可以外网访问,之间用接口来连接!

但是!千万不要直接理解为前后端分离就是flask+vue,或者springboot+vue。我们回归到本质,什么是前后端分离,以及为什么要前后端分离!

不像互联网初期那样的前端页面都是静态,后端也只是简单处理一下账号密码的验证,现在随着软件技术以及业务发展,前端功能越来越复杂,各种页面更加花里胡哨,也变得越来越重要,所以随之发展的还有各种前端技术!

可以说前端技术的更新迭代还是比较快的!所以开发的问题也越来越多,以前的一体化维护起来也越来越困难,这是程序员提出前端应该像后端那样实现工程系统化!

现在很多互联网公司都采取前后端分离的解决方案,说到底就是前端后端分开来写!

前端只需要独立编写客户端的代码,后端只需要独立编写服务器代码以及提供数据接口即可!前端应用负责数据展示和数据交互,后端应用负责提供数据处理接口!

接口文档在其中扮演着重要的角色!所以你们以后开发记得规范好接口,并且写好文档!他们之间一般以JSON格式进行数据交互!

前后端分离还有一个好处,那就是一旦一方没有按照计划完成开发,那任意一方都可以不用理另一方,直接根据接口文档进行假数据的测试,这样极大提升开发效率。

当然小项目,你一人即是一个团队!!

六.快速博客搭建:

这个大家知道就好了,我们采用WordPress这个开源项目来进行实际项目的搭建,环境安装我们采用bt页面进行安装。

需要注意的是,如果你的云服务器之前是已经安装过nginx,阿帕奇环境等,需要删除先,否则你要找到bt安装的环境在哪个目录,相信你们做不到的!hh

首先登录你的云服务器进行安装:

乌班图选手:

wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh

Centos选手:

yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh

然后冒出东西就填y或者yes,这个没得说。可能时间要长点,慢慢等!

然后安装完毕就可以输入命令:

sudo /etc/init.d/bt dedfault

然后不出意外的话,你会看见你的各种信息,包括外网地址,内网地址,账号密码等。这时候你就要拿一个小本本记住这些东西,当然后面你也可以修改这些设置的!

其实bt功能十分的强!我们只不过用它的一个小功能来一键安装wordpress,它还有运维,文件传输,Web站点,数据库管理,监控,计划任务等设置!

我们直接在你的主机上点开浏览器,输入你那个外网访问地址,如果访问不到请检查你的云服务器的防火墙的设置,包括平台商服务器和你云服务器的防火墙,要开放对应端口!

然后输入你刚刚记的账号和密码即可!!

然后安装LNMP就可以了!这个过程花费的时间就看你的服务器的性能了!

然后安装完毕点开,软件商城进行搜索Wordpress,一键安装即可!!

正常的话是新建站点,然后把站点弄到安装目录下,这些你们自己上网搜就行了,很简单的,然后你就得到了自己的个人博客!!!

然后你就发现了一个震惊的真相,什么!有个主题和官网一模一样?没错,我们的官网就是Power by WordPress,哈哈

但是Wordpress后端是用PHP语言来写的,而我们学的是Python,虽然后端逻辑差不多,但是推荐WordPress是想让大伙能快速有一个自己的网站!其实2021年来说,wordpress建站仍然是一个很好的选择,像什么个人博客,企业官网,社会组织官网,电商介绍官网啥的,都是可以用这个来进行懒人建站!

到这里,相信你们已经完成了自己的一个小梦想:建成了自己的个人博客网站!

当然也可以用Flask复刻出来!

七.实际项目:

实际项目你们肯定是一波找模板,然后调参数之类的hh,或者你们做小程序,那也是找模板找框架的,都1202年了,自己写原生太慢了:

下面推荐几个模板框架网站:

慢慢找点有用的网站收藏吧!!!!

网站不是很多,你们自己去找吧!

八.项目实践:

暂无,分开来讲!!

九.Flask漏洞:

还是那个准则,尽量走官方API,然后要做好过滤,不要过度相信用户输入的值!

跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets,CSS)的缩写混淆,故将跨站脚本攻击缩写为 XSS。恶意攻击者往 WEB 页面里插入恶意 HTML 代码,当用户浏览该页之时,嵌入其中 Web 里面的 HTML 代码会被执行,从而达到恶意攻击用户的特殊目的。

1.原理:

函数类,漏洞操作对应层,浏览器内核版本,有内容提交的地方可能会有xss攻击

2.分类:

  • 反射型,直接攻击,攻击并不存储在数据库中,x.php–>回包 发包x
  • 存储型,持续攻击,x.php–>数据库–>x.php–>回显,后端代码
  • DOM型,静态前端代码–>x.php,前端代码

3.危害:

  • 盗取各类用户账号,如机器登录账号、用户网银账号、各类管理员账号
  • 控制企业数据,包括读取、篡改、添加、删除企业敏感数据的能力
  • 盗窃企业重要的具有商业价值的资料
  • 非法转账
  • 强制发送电子邮件
  • 网站挂马
  • 控制受害者机器向其他网站发起攻击

4.特征:

  • 搜索框:反射型,主要链接式攻击
  • 留言板:存储型,主要拿取别人cookie,
  • DOM型:

5.Flask跨站:

其实Flask用Jinja2模板是会自动转义 所有值,但一旦我们采用前后端分离,就要小心一点!

  • 生成 HTML 而不使用 Jinja2
  • 在用户提交的数据上调用了 Markup
  • 发送上传的 HTML 文件,永远不要这么做,使用 Content-Disposition: attachment 标头来避免这个问题
  • 发送上传的文本文件。一些浏览器使用基于开头几个字节的 content-type 猜测,所以用户可能欺骗浏览器执行 HTML

属性注入的XSS是不受Jinja2保护的,我们应该在属性中就是用jinja2表达式,始终用单引号或者双引号进行包裹:

<a href="{{ href }}">the text</a>

1.原理:

跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种使已登录用户在不知情的情况下执行某种动作的攻击。因为攻击者看不到伪造请求的响应结果,所以 CSRF 攻击主要用来执行动作,而非窃取用户数据。当受害者是一个普通用户时,CSRF 可以实现在其不知情的情况下转移用户资金、发送邮件等操作;但是如果受害者是一个具有管理员权限的用户时 CSRF 则可能威胁到整个 WEB 系统的安全。

2.案例:

如果你的验证信息存储在 cookie 中,你有隐式的状态管理。”已登入”状态由一个 cookie 控制,并且这个 cookie 在每个页面的请求中都会发送。不幸的是,在第三方站点触发的请求中也会发送这个 cookie 。如果你不注意这点,一些人可能会通过社会工程学来诱导你应用的用户在他们不知道的情况下做一些蠢事。

比如你有一个指定的 URL ,当你发送 POST 请求时会删除一个用户的资料(比如 http://example.com/user/delete 。如果一个攻击者现在创造一个页面来用 JavaScript 发送这个 post 请求,他们只是诱骗一些用户加载那个页面,而他们的资料最终会被删除。

3.防御:

基本上,对于每个修改服务器上内容的请求,你应该使用一次性令牌,并存储在 cookie 里, 并且 在发送表单数据的同时附上它。 在服务器再次接收数据之后,你要比较两个令牌,并确保它们相等。

flask内不存在原生的表单验证,所以需要表单验证框架!!

我们的Flask-WTF插件就是一个抵御CSRF很好的工具!

1.原理及其介绍:

具体详情请查看这篇文章!

2.防御措施:

你设置的secret key除了保存到环境变量里头,还要设置一个足够强的密钥,最好是随机化的,但这样对用户体验感不好,每次重启服务,用户会话都会过期。

最实用的解决方案就是生成一个UUID值。我们可以在类Unix系统上使用 uuid或者 uuidgen命令来完成该任务,或者在安装了Python环境的主机上执行如下命令:

$ python -c 'import uuid; print(uuid.uuid4());'

可以使用服务端会话,加强会话的鲁棒性

计算机软件在输入错误、磁盘故障、网络过载或有意攻击情况下,能否不死机、不崩溃,就是该软件的鲁棒性。

import os
from flask import Flask, session
from flask_session import Session

app = Flask(__name__)

app.config['SECRET_KEY'] = os.urandom(64)
app.config['SESSION_TYPE'] = 'filesystem'

Session(app)

@app.route('/')
def index():
    if 'logged_in' not in session:
        session['logged_in'] = False

    if session['logged_in']:
        return '<h1>You are logged in!</h1>'
    else:
        return '<h1>Access Denied</h1>', 403

if __name__ == '__main__':
    app.run()

当用户输入被串联到模板中而不是作为数据传递时,服务器端模板注入漏洞就会出现,简单来说也就是不正确的使用 flask 中的render_template_string 方法会引发SSTI。

这个属于程序员本身的过失,这是人为问题,可以避免的,下面就是经典的漏洞代码:

from flask import Flask,render_template_string,request

app = Flask(__name__)
@app.route('/test/')
def test():
    code = request.args.get('id')   //get&#x65B9;&#x5F0F;&#x83B7;&#x53D6;id
    html = '''
        <h3>%s</h3>
    '''%(code)
    return render_template_string(html)
#&#x4EA7;&#x751F;&#x539F;&#x56E0;&#x5C31;&#x662F;&#x5148;&#x8FDB;&#x884C;&#x4E86;&#x62FC;&#x63A5;&#xFF0C;&#x5728;&#x6E32;&#x67D3;
app.run()

上述的代码就是可以恶意嵌入script代码进行XSS攻击等操作!

可以更改为:

from flask import Flask, request
from jinja2 import Template

app = Flask(__name__)

@app.route("/safe")
def safe():
    name = request.args.get('name', 'guest')

    t = Template("Hello {{n}}")
    return t.render(n=name)

if __name__ == "__main__":
    app.run()

感兴趣的可以查看:

十.参考文章:

Original: https://blog.csdn.net/weixin_51334923/article/details/121110555
Author: 逼疯了的代码
Title: Flask备忘录

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/750237/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球