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

前言

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

 

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

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

基本上,如果你只想在Flask模板中使用Vue.js库,那么问题不大。唯一值得注意是Jinja(模板引擎)使用双花括号来渲染变量,而Vue插值也使用双花括号.

本文要说的是另一种情况:我需要一个使用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

<template>
<div>
  <p>Home page</p>
</div>
</template>


// About.vue

<template>
<div>
  <p>About</p>
</div>
</template>

现在要构建前端路由,前端应用要知道怎么处理我们我们在浏览器地址栏中填写地址。在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

要在Windows中激活。

在虚拟环境下安装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('/<path:path>')
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

<template>
<div>
  <p>404 - Not Found</p>
</div>
</template>

现在再次使用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('/<path:path>')
def catch_all(path):
    return render_template("index.html")

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

{
  "randomNumber": 36
}

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

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

<template>

<div>
  <p>Home page</p>
  <p>Random number from backend: {{ randomNumber }}</p>
  <button @click="getRandom">New random number</button>
</div>
</template>

<script>
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()
  }
}
</script>

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

  • 初始化变量随机数为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相关文档。

在run.py:

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的工作在生产环境中提供静态文件,我们可以这样做:

import requests


@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
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服务器。

猜你喜欢

转载自blog.csdn.net/c710473510/article/details/89021484