《中间件技术》实验1:远程文件管理
原 型 开 发
引言
实验目的:用你熟悉的语言,开发一个基于网络(比如Socket)的简单的互操作程序:实现A机器的程序,可以管理(增加、删除、改等)B机器上的某个文件夹或者目录。
问题分析
由于题目并未限制编程语言和技术,在此直接选用了HTTP协议,利用Python的Flask框架开发Web应用。
在服务器端,设计必要的RESTful API,用于前端jQuery发送Ajax请求与远程服务器连接。
用户可以直接访问服务器端的Web地址,对服务器端的文件夹进行文件管理。
也可在本机启动客户端Web服务,通过指定服务器端的URL,客户端访问服务器端API,实现远程的文件管理。
项目仓库地址:https://github.com/unsioer/file-manager-flask
环境
开发环境:
Visual Studio Code
Python 3.8.5
Flask 1.1.2
具体设计
概要
本实验的服务器端和客户端均采用了Python的Flask这一Web框架。
为了演示方便,服务器端和客户端均在本机测试。服务器端绑定0.0.0.0:5000
,操作的目录设置为test
;客户端代码置于client
文件夹下,绑定0.0.0.0:10000
,直接请求http://127.0.0.1:5000
的内容。这显然容易推广到“A机器管理B机器的指定文件夹”。
服务器端的路由设计中,
/api
用于RESTful API,/app
用于返回HTML界面。客户端的路由设计中,
/app
用于返回HTML界面(实际与服务器端的内容一致)。
下面具体说明使用到的技术。
客户端:
客户端直接利用了Flask的模板解析功能,实际用于交互的只有一个HTML页面。因为界面功能基本一致,为了减少耦合,与服务器端共用代码(提交的代码中,client/templates
文件夹实际是templates
文件夹的映射)。
HTML界面的各项交互功能主要靠jQuery和衍生插件jquery-confirm
(jQuery版的确认消息提示框)完成;
还用到了bootstrap
以及font-awesome
美化界面。
我们知道,客户端与服务器端应该使用的是不同的套接字,所以jQuery的Ajax请求的URL应该是不同的。所以,在共用的HTML代码中,设计了{{ host_link }}
这一字段。
客户端有一个字典remote_config = {'host_link':'http://127.0.0.1:5000'}
,直接填写服务器端的URL。
代码差异如下:
1 |
|
1 |
|
1 |
|
服务器端对应的Python代码未提供host_link
,则不会被解析;客户端提供了host_link
,在对应位置会被替换为指定的服务器端URL,由JavaScript的字符串替换功能改为正确的服务器端API的URL。
服务器端:
服务器端除了上文提及的HTML界面外,主要内容为一系列操作的API。
大体上的代码大致均如:
1 |
|
@app.route()
指定了路径和请求方法;
@cross_origin()
则是为了实现API的跨域访问,使得客户端Web界面得以操作服务器端的API。
服务器端路由
服务器端的路由设计中,/api
用于RESTful API,/app
用于返回HTML界面。
需要注意的几点:
- 以下的API设计中,格式均对齐为
/api/(file|folder)/[a-z]+/<path>?
,这是为了防止路径名称与API产生冲突。作为简易实现的功能,严格来说不是严谨的设计。 - 作为服务器端,如果要让客户端利用API,需要提供跨域访问支持。这里使用了Python的
flask_cors
包,并对每个API添加@cross_origin()
支持。 - 目前设计的API中,除发生未知的系统错误外,均返回JSON字串,HTTP状态码均为200(虽然大部分JSON字串有一个
state
属性,写法仿照HTTP返回状态码)。这只是简易实现功能,不是严谨的设计。在Flask中,返回错误状态码可以使用abort
方法,可以通过from flask import abort
引入。 - 如果是URL路径不存在,Flask默认返回404状态码。
GET /api/folder/basic/<path:folderPath>
获取指定目录下的文件(包含文件夹)信息。
返回JSON格式:
1 |
|
GET /api/folder/basic/
获取根目录下的文件信息。
返回JSON格式:同上。
GET /api/file/check/<path:filePath>
检查给出完整路径的文件(夹)是否存在。
注:由于下文一些API针对文件(夹)名冲突做了不同处理,HTML界面实际并未利用到此API。
返回JSON格式:
存在:
{"status": 200}
不存在:
{"status": 404}
POST /api/folder/basic/<path:folderPath>
在指定目录上传文件。
HTTP请求体:提交内容为表单数据,file
项为二进制(即上传的文件)内容。
冲突检测:如果该目录下已经有同名文件,则将上传的文件命名为[非后缀名] - 副本[.后缀名]
保存。如果仍然存在命名冲突,则尝试命名为[非后缀名] - 副本 (2)[.后缀名]
, [非后缀名] - 副本 (3)[.后缀名]
……直至避开冲突。
返回JSON格式:{"status": 200}
POST /api/folder/basic
在根目录上传文件。
HTTP请求体、冲突检测、返回JSON格式:同上。
PUT /api/folder/basic/<path:folderPath>
为指定目录下的指定文件(夹)重命名。
HTTP请求体:{"src":"原名称","dst":"新名称"}
。
文件(夹)名称合法性检测:拒绝文件(夹)名出现\
/
:
?
<
>
*
等特殊字符。
冲突检测:在这里,不允许新名称与当前目录下其他文件的名称有冲突。
返回JSON格式:
- 重命名成功,或原名称与新名称相同:
{"status": 200}
- 请求体不合法:
{"status": 400, "msg": "No data given"}
或{"status": 400, "msg": "No source file or destination file given"}
- 文件(夹)名称有特殊字符:
{"status": 403, "msg": "Illegal file or folder name"}
- 源文件不存在:
{"status": 404, "msg": "Source file does not exist"}
- 新名称与当前目录下其他文件的名称冲突:
{"status": 400, "msg": "Destination file exists"}
PUT /api/folder/basic/
为根目录下的指定文件(夹)重命名。
HTTP请求体、文件(夹)名称合法性检测、冲突检测、返回JSON格式:同上。
DELETE /api/folder/basic/<path:folderPath>
删除指定目录下的指定文件(夹)。
HTTP请求体:{"src":"要删除的文件(夹)名称"}
。
返回JSON格式:
- 删除成功,或原名称与新名称相同:
{"status": 200}
- 请求体不合法:
{"status": 400, "msg": "No data given"}
或{"status": 400, "msg": "No source file or destination file given"}
- 源文件不存在:
{"status": 404, "msg": "Source file does not exist"}
- 没有删除权限:
{"status": 400, "msg": "Permission refused"}
DELETE /api/folder/basic/
删除根目录下的指定文件(夹)。
HTTP请求体、返回JSON格式:同上。
POST /api/folder/new/<path:folderPath>
在指定目录下新建文件夹。
HTTP请求体:{"src":"文件夹名称"}
。
文件夹名称合法性检测:拒绝文件夹名出现\
/
:
?
<
>
*
等特殊字符。
冲突检测:在这里,不允许文件夹名称与当前目录下其他文件夹的名称有冲突。
返回JSON格式:
删除成功,或原名称与新名称相同:
{"status": 200}
请求体不合法:
{"status": 400, "msg": "No data given"}
或{"status": 400, "msg": "No folder name given"}
文件夹名称有特殊字符:
{"status": 403, "msg": "Illegal folder name"}
文件夹名称与当前目录下其他文件夹的名称冲突:
{"status": 400, "msg": "Destination folder exists"}
POST /api/folder/new/
在根目录下新建文件夹。
HTTP请求体、文件夹名称合法性检测、冲突检测、返回JSON格式:同上。
PUT /api/file/copy/<path:filePath>
复制指定路径下的文件(夹)到指定位置文件夹下。
指定位置文件夹如不存在则新建。
HTTP请求体:{"dst":"目标文件夹名称"}
。
路径名称合法性检测:目标文件夹路径不得出现 :
?
<
>
*
等特殊字符。
冲突检测:
以下内容也适用于目标文件夹名称就是当前文件(夹)所在文件夹的情况。
如果该目录下已经有同名文件,则将复制的文件命名为[非后缀名] - 副本[.后缀名]
保存。如果仍然存在命名冲突,则尝试命名为[非后缀名] - 副本 (2)[.后缀名]
, [非后缀名] - 副本 (3)[.后缀名]
……直至避开冲突。
如果该目录下已经有同名文件夹,则将复制的文件夹命名为[文件夹名] - 副本
保存。如果仍然存在命名冲突,则尝试命名为[文件夹名] - 副本 (2)
, [文件夹名] - 副本 (3)
……直至避开冲突。
返回JSON格式:
- 复制成功:
{"status": 200}
- 要复制的文件(夹)不存在:
{"status": 404, "msg": "File not found"}
- 请求体不合法:
{"status": 400, "msg": "No data given"}
或{"status": 400, "msg": "No destination folder given"}
- 目标文件夹路径名称不合法:
{"status": 403, "msg": "Illegal path"}
- 复制失败:
{"status": 400, "msg": "错误信息"}
PUT /api/file/move/<path:filePath>
移动指定路径下的文件(夹)到指定位置文件夹下。
指定位置文件夹如不存在则新建。
HTTP请求体:{"dst":"目标文件夹名称"}
。
路径名称合法性检测:目标文件夹路径不得出现 :
?
<
>
*
等特殊字符。
冲突检测:
当目标文件夹名称就是当前文件(夹)所在文件夹时,不做处理,返回{"status": 200}
。
如果该目录下已经有同名文件,则将移动的文件命名为[非后缀名] - 副本[.后缀名]
保存。如果仍然存在命名冲突,则尝试命名为[非后缀名] - 副本 (2)[.后缀名]
, [非后缀名] - 副本 (3)[.后缀名]
……直至避开冲突。
如果该目录下已经有同名文件夹,则将移动的文件夹命名为[文件夹名] - 副本
保存。如果仍然存在命名冲突,则尝试命名为[文件夹名] - 副本 (2)
, [文件夹名] - 副本 (3)
……直至避开冲突。
返回JSON格式:
- 移动成功,或目标文件夹名称就是当前文件(夹)所在文件夹:
{"status": 200}
- 要移动的文件(夹)不存在:
{"status": 404, "msg": "File not found"}
- 请求体不合法:
{"status": 400, "msg": "No data given"}
或{"status": 400, "msg": "No destination folder given"}
- 目标文件夹路径名称不合法:
{"status": 403, "msg": "Illegal path"}
- 移动失败:
{"status": 400, "msg": "错误信息"}
以下为HTML界面路由,服务器端与客户端基本一致。
客户端路由
GET /app/<path:folderPath>
及GET /app/
返回HTML界面,显示指定目录及根目录下的情况。界面效果详见下节。
Web界面展示
这里做了简要截图,也可详见演示视频。
基本界面
界面左上方为网页标题,下有四个按钮,分别为:根目录、刷新、新建文件夹、上传(文件)。
接着是文件列表。对于非根目录,还有一行..
的链接返回上一级目录。
每个文件(夹)根据文件类型配套不同图标。文件夹有链接可以访问;文件则显示具体大小。文件和文件夹均显示最近修改时间。
对于每个文件(夹),均提供四种操作,分别为:复制、移动、重命名、删除。
最下方显示作者和创作年份。
提示框
新建、重命名、复制、移动提示框(以重命名提示框为例):
删除提示框:
技术总结
本次实验实现了一个最简单的基于网络的远程文件管理应用,完成了实验所有要求并做了一些合法性检测,设计了相对简洁美观的界面。
由于只是简易实现功能,尚有以下不足之处:
API命名规范
请求和返回内容设计较为随意
未进行充分测试,难免bug(如文件操作权限不足在API中没有全部体现)
未运用Vue.js等前端框架,纯手写jQuery,代码较为繁琐
本站所有文章除特別聲明外,均採用 CC BY-SA 4.0 協議 ,轉載請註明出處!