重构查分后台

背景

几个月前有写过一个简单的快速查成绩的网站,放在了我的 Github 里。

凑和能用,但是一直有一个疙瘩:查成绩的 API 太丑!

这是当时的 get_grade.php,点击查询按钮会向它发送 POST 请求,由它来返回结果。没错,get_grade.php 竟然是直接在服务器端执行命令获得输出!(我们就先不谈它的安全风险了,😫😶😫🤐😖😰🤢)

1
2
3
4
5
6
7
8
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$location = '/.../get_grade.py';
$command = 'python3 ' . $location . ' ' . $username . ' ' . $password;
$new_command = escapeshellcmd($command);
$output = shell_exec($new_command);
echo json_encode($output);

既然脚本是用 Python 写的,为什么当时不使用 Flask 做一个查成绩的 API 呢?

没有采用 Flask 搭建接收POST请求的后端服务,因为我自己的前端网站是 Https 加密的,它不允许从已加密的前端网站从未加密的后端接口获取信息,即不允许 Http/Https 混用。而在配置 Flask 的 Https 加密的过程中我遇到了困难,遂放弃。

重构

后来,我发现 Nginx 很适合做代理,可以把一些奇奇怪怪的 8000、8080 等等端口的服务转发到 80/443 端口上,这样的话,我可以尽管用 Node.js 或 Flask 搭建 API,再使用 Nginx 转发到 80/443 即可。这样还有一个好处:我只需要在 Nginx 里面配置 Https 加密,而不用在 Node.js 或 Flask 做,这样上面所说的问题就解决了。

代理配置示例被我放到了 Gist 上,如下。

我把查分 API 的地址选在了 https://yusanshi.com/api/get_grade ,查阅 Nginx 文档,关于 location 有如下表述:

using the “=” modifier it is possible to define an exact match of URI and location. If an exact match is found, the search terminates

因此我选用 = 这个 modifier 来精确匹配 API 地址,最终的 Nginx 配置文件如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
server_name yusanshi.com;

......

location = /api/get_grade {
proxy_pass http://127.0.0.1:8082;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}

}

它表示,如果请求 https://yusanshi.com/api/get_grade ,那么就将其转发到 http://127.0.0.1:8082 ,对应地,run.py 也要将 /api/get_grade 绑定到输出成绩的函数,并监听本地 8082 端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, request, jsonify
from get_grade import get_grade


app = Flask(__name__)


@app.route('/api/get_grade', methods=['GET', 'POST'])
def return_grade():
if request.method == 'GET':
return "Use POST method please."
elif request.method == 'POST':
username = request.form['username']
password = request.form['password']
output = get_grade(username, password)
return jsonify(output)


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

上传完所有的文件,用 service nginx restart重启 Nginx,测试一下。

看起来还好,基本上算是完工了。

呃,但是有红色的 WARNING。

WARNING: This is a development server. Do not use it in a production deployment.

文档里找到如下说明:

When running publicly rather than in development, you should not use the built-in development server (flask run). The development server is provided by Werkzeug for convenience, but is not designed to be particularly efficient, stable, or secure.

Instead, use a production WSGI server.

首先安装 waitress。

1
pip3 install waitress

再修改一下 run.py

1
2
3
4
5
6
7
8
9
......

import waitress

......


if __name__ == '__main__':
waitress.serve(app, host="127.0.0.1", port=8082)

这下没 WARNING 了。

更新:查成绩 API 地址改成了 https://yusanshi.com/api/get_grade/。(2020.3.18)