python vue flask_Python构建RESTful网络服务[Flask篇:用Flask+Vue.js打造全栈单页面应用]...

前言

本来在着手写关于使用Flask构建RESTful网络服务的文章,正好看到一篇相关文章,提供了一个Flask+vue构建全栈单页面应用的例子,觉得不错,就在此分享给大家。

作者Oleg Agapov ,似乎是俄语使用者,不过这篇文章是用英文写的,文风细致友好,可以一读。考虑到一些朋友不方便查看外网,特此翻译并转载到知乎专栏。以下是全文及几条网友问答翻译。

注意:阅读本文需要一点点的Flask知识和一点点的Vue知识(只要你看过任何一点点新建一个Flask应用且看过任何一点点新建一个Vue应用的文章,就满足了本条要求)

另外:Python构建RESTful网络服务[Django篇]没有完结,仍在连载中,见专栏更新。

用Flask+Vue.js打造全栈单页面应用

在本教程中,我将向您展示如何将Vue.js单页面应用程序与Flask后端整合起来。

基本上,如果你只想在Flask模板中使用Vue.js库,那么问题不大。唯一值得注意是Jinja(模板引擎)使用双花括号来渲染变量,而Vue插值也使用双花括号,解决这问题可以见这里https://github.com/yymm/flask-vuejs。

而本文要说的是另一种情况:我需要一个使用Vue.js构建的单页面应用程序(使用单页面组件、history模式的vue-router和其他优秀特性),并通过Flask服务器提供服务。简而言之,应该是这样工作的:Flask伺服一个index.html,这是我的Vue.js应用入口文件

使用Webpack进行前端工程化开发

可以从SPA访问Flask的API端点

使用Node.js运行前端项目时,也可以访问Flask的API端点

这种情况该怎么办呢?听起来是不是很有趣,说干就干吧!

客户端

我使用vue-cli生成基本的Vue.js应用程序。如果你还没有安装Vue.js,只要运行:

$ npm install -g vue-cli

客户端和后端代码将被分别放到不同的文件夹。初始化前端部分运行如下:

$ mkdir flaskvue

$ cd flaskvue

$ vue init webpack frontend

通过安装向导,按如下设置是:Vue build — Runtime only #仅在运行时构建

Install vue-router? — Yes # 使用vue-router提供的前端路由

Use ESLint to lint your code? — Yes # 使用ESLint检查代码

Pick an ESLint preset — Standard # 设置ESLint为标准模式

Setup unit tests with Karma + Mocha? — No # 不使用Karma + Mocha进行单元测试

Setup e2e tests with Nightwatch? — No # 不使用Nightwatch进行端对端测试

下一步:

$ cd frontend

$ npm install

# after installation

$ npm run dev

启动Vue.js应用开发服务器。让我们先来添加一些页面。

添加 Home.vue 和 About.vue 到 frontend/src/components 文件夹。简单填写一下,就像这样:

// Home.vue

Home page

// About.vue

About

现在要构建前端路由,前端应用要知道怎么处理我们我们在浏览器地址栏中填写地址。在frontend/src/router/index.js文件中填写如下内容来渲染我们的新组件:

import Vue from 'vue'

import Router from 'vue-router'

const routerOptions = [

{ path: '/', component: 'Home' },

{ path: '/about', component: 'About' }

]

const routes = routerOptions.map(route => {

return {

...route,

component: () => import(`@/components/${route.component}.vue`)

}

})

Vue.use(Router)

export default new Router({

routes,

mode: 'history'

})

使用浏览器访问 localhost:8080 和 localhost:8080/about 可以看到相应的页面。

我们现在几乎已经可以构建包含一系列静态文件的前端应用了,不过在此之前,我们还要重设前端应用文件的路径。在frontend/config/index.js 进行下一步设置,把如下代码

index: path.resolve(__dirname, '../dist/index.html'),

assetsRoot: path.resolve(__dirname, '../dist'),

改成

index: path.resolve(__dirname, '../../dist/index.html'),

assetsRoot: path.resolve(__dirname, '../../dist'),

现在包含了项目所需的html/css/js文件的 /dist 文件夹与 /frontend处于同级了。现在使用

$ npm run build

来构建项目。

后端

Flask 服务使用的Python版本为3.6。在 /flaskvue 文件夹中,为后端代码创建新的子文件夹,并在其中初始化虚拟环境:

$ mkdir backend

$ cd backend

$ virtualenv -p python3 venv

启用虚拟环境运行(在macOs上):

$ source venv/bin/activate

在虚拟环境下安装Flask

(venv) pip install Flask

现在让我们为Flask server编写代码。在根目录中创建run.py文件:

(venv) cd ..

(venv) touch run.py

添加如下代码到这个文件:

from flask import Flask, render_template

app = Flask(

__name__,

static_folder = "./dist/static",

template_folder = "./dist"

)

@app.route('/')

def index():

return render_template("index.html")

我们的代码与Flask starter的“Hello world”代码略有不同。主要的不同之处在于,我们指定了静态文件和模板文件目录为/dist文件夹,就是前文构建的前端应用所在文件夹。在Flask应用所在文件夹运行Flask服务器:

(venv)

$ FLASK_APP=run.py

$ FLASK_DEBUG=1

$ flask run

这将在localhost:5000上启动一个web服务器。FLASK_APP指向服务器启动文件,FLASK_DEBUG=1将在调试模式下运行。如果一切正常可以看到我们熟悉的界面,就是之前构建的前面项目一样的页面。

但是,如果试图进入/about页面,页面就会报错。Flask抛出一个错误,说请求的URL没有找到。这是因为,我们在前端路由中使用了HTML5 history模式,当我们访问Flask的/about页面时,后端路由并不知道要去哪里找我们想要的页面。我们需要配置web服务器将所有路由重定向到index.html。在Flask里很容易做到。将现有路由修改为:

@app.route('/', defaults={'path': ''})

@app.route('/')

def catch_all(path):

return render_template("index.html")

现在URL localhost:5000/about将被重定向到index.html,vue-router将自己处理它。

添加 404 页面

现在,无论对Flask服务器发送什么样的请求,它都不会返回404页面了,因为即使找不到资源,它也只会把url交给前端应用,而不会返回404状态。所以404状态的响应,要由Vue.js完成。

在 frontend/src/router/index.js 加上如下代码行:

const routerOptions = [

{ path: '/', component: 'Home' },

{ path: '/about', component: 'About' },

{ path: '*', component: 'NotFound' }

]

这里的 path '*' 是 vue-router 的通配符,表示除以上url以外的路由,都交给NotFound。现在在 /components 创建 NotFound.vue 文件,就简单写一下 好了。

// NotFound.vue

404 - Not Found

现在再次使用npm运行dev前端服务器,并尝试输入一些无意义的地址,比如localhost:8080/gljhewrgoh。应该看到我们的“Not Found”信息。添加API端点

使用Flask伺服所有服务

这是本教程的最后一步,我们在服务器端创建API并在客户端使用它。我将创建一个简单的端点,它将返回一个从1到100的随机数。

打开run.py并添加:

from flask import Flask, render_template, jsonify

from random import *

app = Flask(__name__,

static_folder = "./dist/static",

template_folder = "./dist")

@app.route('/api/random')

def random_number():

response = {

'randomNumber': randint(1, 100)

}

return jsonify(response)

@app.route('/', defaults={'path': ''})

@app.route('/')

def catch_all(path):

return render_template("index.html")

首先导入 random 库和 jsonify 函数。然后创建一个 /api/random 对应的视图返回一个JSON 响应,如:

{

"randomNumber": 36

}

现在访问 localhost:5000/api/random可以得到正确的Json响应。

服务器端工作已经完成了,是时候在客户端展示了。我们来改写Home.vue。

Home page

Random number from backend: {{ randomNumber }}

New random number

export default {

data () {

return {

randomNumber: 0

}

},

methods: {

getRandomInt (min, max) {

max = Math.floor(max)

return Math.floor(Math.random() * (max - min + 1)) + min

},

getRandom () {

this.randomNumber = this.getRandomInt(1, 100)

}

},

created () {

this.getRandom()

}

}

在目前,我只是在客户端模拟随机数生成过程。所以,这个组件是这样工作的:初始化变量随机数为0

在method部分,我们有getRandomInt(min, max)函数,它将返回一个指定范围内的数字,getRandom函数将调度之前的函数并将其值赋给randomNumber

创建组件方法后,将调用getRandom初始化randomNumber

在按钮点击事件上,我们将发送getRandom方法来获取新号码

现在在首页你应该看到我们的随机数。但它是前端自己产生的,不是后端传过来的,现在我们来改写代码,从后端获取数据。

为此,我将使用axios库。它允许我们发出HTTP请求并返回带有JSON answer的JavaScript Promise。安装:

(venv) cd frontend

(venv) npm install --save axios

打开Home.vue,修改script,

import axios from 'axios'

methods: {

getRandom () {

// this.randomNumber = this.getRandomInt(1, 100) this.randomNumber = this.getRandomFromBackend()

},

getRandomFromBackend () {

const path = 'http://localhost:5000/api/random';

axios.get(path)

.then(response => {

this.randomNumber = response.data.randomNumber

})

.catch(error => {

console.log(error)

})

}

}

首先我们要导入axios库。然后有一个新的方法getRandomFromBackend,它将使用axios异步地从API获取结果。最后,getRandom方法现在应该使用getRandomFromBackend函数来获得一个随机值。

保存文件,运行前端开发服务器(localhost:8080),访问首页,你应该会在控制台看到一个错误,没有随机值。但别担心,只是发生了CORS错误(跨站请求被拒绝),这意味着我们的Flask服务器API默认情况下是对其他web服务器关闭的(在我们的例子中,“其他web服务器”是指运行Vue应用程序的Node.js服务器)。我们可以使用npm的build命令构建一个应用,并打开localhost:5000 (Flask服务),就可以看到我们的成果。但是,每次对客户端应用程序进行一些更改时,都要重新构建,并不十分方便。

我们使用Flask的CORS 插件,它将允许我们为API访问创建一个规则。插件是flask-cors,安装:

(venv) pip install -U flask-cors

我将使用特定于资源的方法,并将{" origin ": " * "}应用于所有/api/*路由(这样每个人都可以使用我的/api端点)。更多配置见CORS相关文档。

from flask_cors import CORS

app = Flask(

__name__,

static_folder = "./dist/static",

template_folder = "./dist"

)

cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

更改后,可以直接从前端开发服务器调用Flask api。

实际上,如果通过Flask提供静态文件,那么就不需要CORS扩展。感谢Carson Gee(https://github.com/carsongee)的工作。在生产环境中提供静态文件,我们可以这样做:

import requests

@app.route('/', defaults={'path': ''})

@app.route('/')

def catch_all(path):

if app.debug:

return requests.get('http://localhost:8080/{}'.format(path)).text

return render_template("index.html")

简洁而优雅!魔法✨!

现在,你已经使用你喜欢的技术构建了一个全栈应用程序了。

后记:

最后,我想谈谈如何改进这个解决方案。

首先,只有在希望为外部服务器提供对API端点的访问权限时,才使用CORS扩展。否则,只需使用代理前端开发服务器的技巧。

另一个改进是避免在客户端使用硬编码的API路由。也许你需要考虑一些带有API端点的字典。当你改变你的API路由时,你只需要刷新一个字典。前端仍然有一个有效的端点。

通常在开发过程中,我们将至少有两个终端窗口:一个用于Flask,另一个用于Vue.js。在生产中,我们将避免为Vue运行单独的Node.js服务器。

译者摘选的网友问答

问:为什么不直接用SPA连到RESTful API,而用Flask代理呢?

答:因为我想在生产环境中,只用启用一个服务,而不是两个服务。用Flask代理,我就不用启动一个Node.js服务了。

问:你用什么IDE开发?

答:我用好用的VS Code,它对Python和Vue都有很好的支持。

问:我在处理登录逻辑时,使用Flask重定向到了登录视图,但是出现了错误?

答:后端只提供数据,你应当在Vue端设置这一重定向。

问:在开发环境,使用两个服务,是因为可以使用前面服务的热重载是吗?

答:基本是的。

问:我把项目部署到heroku上,需要对这个设置执行什么特殊的操作吗?我部署的似乎没能正常工作。我添加了一个Procfile并安装了gunicorn,复制了这个包到根文件夹,并添加了一个heroku/nodejs构建包 ……

答:我没有在heroku上部署过,好像是vue的路由问题。你看看是不是没有重定向到vue的index.html文件上。(译者注,看原提问者的描述,应该是使用Nginx之类的服务器时,没有正确伺服静态文件。)

本文为Flask+Vue整合开发提供了一个很好的模板,虽然只是一个很小很小的应用,但是却可以在这种模型思路下,搭建完整的前后端分离项目。文章作者写JS似乎不喜欢写分号,代码也写得极为简单(甚至不完整,完整代码见于其Github),但不妨碍正想使用Python结合前端框架Vue编写Web项目的读者汲取一些有用的思路。

Python构建RESTful网络服务[Django篇]仍在连载中,后续会讨论测试与API文档。希望在以的后的文章中,能继续讨论Flask、Tornado等其它Python Web框架的RESTful网络服务开发。

文章列表Python构建RESTful网络服务[Django篇:Vue+Django构建前后端分离项目]: https://zhuanlan.zhihu.com/p/54776124

Python构建RESTful网络服务[Django篇:基于函数视图的API]:https://zhuanlan.zhihu.com/p/55562891

Python构建RESTful网络服务[Django篇:使用PostgreSQL替代SQLite]:https://zhuanlan.zhihu.com/p/55903530

Python构建RESTful网络服务[Django篇:基于类视图的API]:https://zhuanlan.zhihu.com/p/57024322

Python构建RESTful网络服务[Django篇:基于视图集的API]:https://zhuanlan.zhihu.com/p/57791697

Python构建RESTful网络服务[Django篇:用户接入控制,认证与权限]:https://zhuanlan.zhihu.com/p/58426061

你可能感兴趣的:(python,vue,flask)