RESTful API 基础应用

本文将通过nodejs+python+FastAPI来初步实现一个简单的RESTful API的应用。

1 准备工作

  1. 安装nodejs和python3
  2. 安装FastAPI
1
pip install fastapi

2 API服务器

首先使用FastAPI框架创建的简单Web应用程序。下面是详细过程:

  1. 导入所需的库和模块:

    • FastAPI:FastAPI框架的主要库。
    • FileUploadFile:FastAPI库中的请求文件相关类。
    • CORSMiddleware:FastAPI库中的CORS中间件类。
    • jsonos:Python的标准库,用于处理JSON数据和操作系统交互。
  2. 创建一个FastAPI应用实例:

    1
    app = FastAPI()
  3. 配置CORS中间件,允许指定的源访问该应用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    origins = {"http://192.168.31.31:8000", "http://localhost:8000", "http://127.0.0.1:8000"}

    app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
    )
  4. 指定保存上传文件的目录,并确保该目录存在:

    1
    2
    3
    SAVE_DIRECTORY = "C:\\Users\\.PORJECT\\PYTHON\\WebPort\\Data"

    os.makedirs(SAVE_DIRECTORY, exist_ok=True)
  5. 定义一个POST请求的路由,路径为’/upload’,用于处理文件上传请求:

    1
    2
    3
    @app.post('/upload')
    async def upload_file(file: UploadFile = File(...)):
    # ...
  6. 定义一个POST请求的路由,路径为’/add’,用于处理数字相加请求:

    1
    2
    3
    4
    5
    6
    7
    8
    class Numbers(BaseModel):
    a: int
    b: int

    @app.post('/add')
    def add_numbers(numbers: Numbers):
    result = numbers.a + numbers.b
    return {"result": result}
  7. 定义一个GET请求的路由,路径为’/config’,用于返回JSON配置数据:

    1
    2
    3
    @app.get('/config')
    def config():
    return {'name': 'Frankie', 'age': 20}
  8. 定义一个GET请求的路由,路径为’/Data/{a}/{b}’,用于计算两个整数的和:

    1
    2
    3
    4
    @app.get('/Data/{a}/{b}')
    def calculate(a: int, b: int):
    c = a + b
    return {"result": c}
  9. 定义一个GET请求的路由,路径为’/‘,用于处理根路径的请求:

    1
    2
    3
    @app.get('/')
    def root():
    return {"message": "Welcome to the root path!"}
  10. 运行FastAPI应用,监听所有网络接口,端口为8080,使用1个工作线程:

    1
    2
    3
    if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app=app, host="0.0.0.0", port=8080, workers=1)

这个简单的Web应用程序允许用户上传文件、计算两个整数的和以及获取一些配置信息。

2.1 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from fastapi import FastAPI, File, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import json
import os

app = FastAPI()

# 配置CORS中间件
origins = {"http://192.168.31.31:8000", "http://localhost:8000", "http://127.0.0.1:8000"}

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# 指定保存文件的目录
SAVE_DIRECTORY = "C:\\Users\\.PORJECT\\PYTHON\\WebPort\\Data"

# 确保保存目录存在
os.makedirs(SAVE_DIRECTORY, exist_ok=True)

@app.post('/upload')
async def upload_file(file: UploadFile = File(...)):
# 创建文件保存路径
file_path = os.path.join(SAVE_DIRECTORY, file.filename)

# 保存文件
with open(file_path, "wb") as buffer:
buffer.write(await file.read())

return {"message": f"文件 '{file.filename}' 上传成功,保存路径为: {file_path}"}

class Numbers(BaseModel):
a: int
b: int

@app.post('/add')
def add_numbers(numbers: Numbers):
result = numbers.a + numbers.b
return {"result": result}

# 定义一个GET请求的路由,路径为'/config',用于返回JSON数据
@app.get('/config')
def config():
return {'name': 'Frankie', 'age': 20}

# 定义一个GET请求的路由,路径为'/Data/{a}/{b}',用于计算两个整数的和
@app.get('/Data/{a}/{b}')
def calculate(a: int, b: int):
c = a + b
return {"result": c}

# 定义一个GET请求的路由,路径为'/',用于处理根路径的请求
@app.get('/')
def root():
return {"message": "Welcome to the root path!"}

if __name__ == '__main__':
import uvicorn
# 运行FastAPI应用,监听所有网络接口,端口为8080,使用1个工作线程
uvicorn.run(app=app, host="0.0.0.0", port=8080, workers=1)

3 启动API服务器

1
python3 main.py

4 测试API

  1. 测试文件上传功能,使用Postman或其他HTTP客户端发送POST请求到http://localhost:8080/upload,并在请求体中选择文件进行上传。

  2. 测试数字相加功能,使用Postman或其他HTTP客户端发送POST请求到http://localhost:8080/add,并在请求体中包含JSON数据{"a": 2, "b": 3}

  3. 测试获取配置信息功能,使用Postman或其他HTTP客户端发送GET请求到http://localhost:8080/config

5 web客户端及服务器

5.1 web客户端

先写一个简单的web客户端,用于上传文件和获取配置信息。

  1. 声明文档类型:

    1
    <!DOCTYPE html>
  2. 创建一个HTML文档,设置语言为中文:

    1
    <html lang="zh">
  3. 设置文档的编码为UTF-8,以支持中文字符:

    1
    2
    <head>
    <meta charset="UTF-8">
  4. 设置页面适应不同设备屏幕大小:

    1
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  5. 设置页面标题:

    1
    <title>文件上传示例</title>
  6. <head>标签内添加<script>标签,定义一个uploadFile函数,用于处理文件上传:

    1
    2
    3

    async function uploadFile()

  7. 获取文件输入框元素,并设置上传文件的URL(根据实际情况修改为你的服务器地址):

    1
    2
    const fileInput = document.getElementById('fileInput');
    const url = 'http://192.168.31.31:8080/upload';
  8. 检查文件输入框是否为空,如果为空则弹出警告:

    1
    2
    3
    4
    if (fileInput.files.length === 0) {
    alert('请先选择一个文件!');
    return;
    }
  9. 创建一个FormData对象,用于存储上传的文件:

    1
    2
    const formData = new FormData();
    formData.append('file', fileInput.files[0]);
  10. 使用fetch函数发送POST请求,将文件上传到服务器:

    1
    2
    3
    4
    5
    6
    try {
    const response = await fetch(url, {
    method: 'POST',
    body: formData,
    credentials: 'include'
    });
  11. 检查响应是否正常,如果不正常则抛出错误:

    1
    2
    3
    if (!response.ok) {
    throw new Error('网络响应不正常');
    }
  12. 解析响应的JSON数据,并将其显示在页面上:

    1
    2
    3
    4
    5
    6
    7
    const data = await response.json();
    document.getElementById('result').innerText = `上传结果: ${data.message}`;
    } catch (error) {
    console.error('获取数据时发生错误:', error);
    document.getElementById('result').innerText = '发生错误,请重试。';
    }

  13. 创建一个<body>标签,设置页面内容:

    1
    2
    3
    4
    5
    6
    7
    8
    <body>
    <h1>文件上传示例</h1>
    <label for="fileInput">选择文件: </label>
    <input type="file" id="fileInput" required>
    <br>
    <button onclick="uploadFile()">上传文件</button>
    <p id="result"></p>
    </body>

这个示例页面允许用户选择一个文件并上传到服务器。上传成功后,页面上会显示上传的文件保存路径。如果上传过程中出现错误,页面上会显示错误信息。

完整的HTML代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传示例</title>
<script>
async function uploadFile() {
const fileInput = document.getElementById('fileInput');
const url = 'http://192.168.31.31:8080/upload'; // 根据实际服务地址修改

if (fileInput.files.length === 0) {
alert('请先选择一个文件!');
return;
}

const formData = new FormData();
formData.append('file', fileInput.files[0]); // 添加文件到FormData对象

try {
const response = await fetch(url, {
method: 'POST', // 指定为POST请求
body: formData, // 直接发送FormData对象
credentials: 'include' // 带上凭据
});

if (!response.ok) {
throw new Error('网络响应不正常');
}

const data = await response.json();
document.getElementById('result').innerText = `上传结果: ${data.message}`;
} catch (error) {
console.error('获取数据时发生错误:', error);
document.getElementById('result').innerText = '发生错误,请重试。';
}
}
</script>
</head>
<body>
<h1>文件上传示例</h1>
<label for="fileInput">选择文件: </label>
<input type="file" id="fileInput" required>
<br>
<button onclick="uploadFile()">上传文件</button>
<p id="result"></p>
</body>
</html>

5.2 web服务器

web服务器采用nodejs搭建,详细步骤如下:

  1. 导入所需的库和模块:

    • http:Node.js的HTTP库,用于创建HTTP服务器。
    • fs:Node.js的文件系统库,用于读取文件。
    • path:Node.js的路径库,用于处理文件路径。
  2. 设置服务器端口和主机名:

    1
    2
    const PORT = 8000;
    const hostname = '0.0.0.0';
  3. 创建一个HTTP服务器实例:

    1
    const server = http.createServer();
  4. 定义服务器请求处理函数,当接收到请求时执行:

    1
    2
    3
    server.on('request', (req, res) => {
    // ...
    });
  5. 设置服务器监听的主机名和端口:

    1
    2
    3
    server.listen(PORT, hostname, () => {
    console.log(`Server running at http://${hostname}:${PORT}/`);
    });

这个简单的Web服务器允许用户访问一个名为PythonWebPortD.html的HTML文件。当用户访问服务器根路径时,服务器将返回该HTML文件。如果请求的文件不存在,服务器将返回404错误。

完整的Node.js代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const http = require('http');
const fs = require('fs');
const path = require('path');

const PORT = 8000;
// const hostname = '127.0.0.1';
const hostname = '0.0.0.0';

// 创建HTTP服务器
const server = http.createServer((req, res) => {
let filePath = './PythonWebPortD.html'; // 假定HTML文件位于同一目录下
if (req.url === '/') {
filePath = path.join(__dirname, filePath); // 获取绝对路径
}

fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end(JSON.stringify(err));
return;
}

res.writeHead(200, {'Content-Type': 'text/html'});
res.end(data);
});
});

// 启动服务器
server.listen(PORT, hostname, () => {
console.log(`Server running at http://${hostname}:${PORT}/`);
});

6 注意:

现代浏览器出于安全考虑,不允许跨域请求,因此需要使用代理服务器或者将前端和后端部署在同一域名下。故而,首先需要在api服务器配置CORS中间件(上文已经介绍不再赘述)并在HTML文件中配置请求凭证。

例如,在使用fetch函数发送跨域请求时,可以设置credentials: 'include'credentials: 'include'是一个HTTP请求头,用于指定是否在跨域请求中包含凭据(如cookies)。当credentials: 'include'设置在HTTP请求中时,浏览器会发送请求中的凭据(如cookies)到跨域服务器。这可以用于跨域请求中的身份验证和授权。

1
2
3
4
5
6
fetch(url, {
method: 'POST',
body: formData,
credentials: 'include'
})

在这个例子中,浏览器会发送请求中的凭据(如cookies)到跨域服务器。