文章目录
1.回顾http请求/响应
模拟浏览器上传文件的请求
<html>
<body>
<form action="http://ip:port/upload" method="post" enctype="multipart/form-data">
<div>
<input type="file" name="file">
</div>
<div><input type="submit" value="上传"></div>
</form>
</body>
</html>
<!--
enctype=“multipart/form-data” 是 HTML 表单()元素的一个属性,用于指定表单数据在发送到服务器时应该采用的编码类型。当表单需要上传文件时,这个属性是必需的。
默认情况下,表单数据以 application/x-www-form-urlencoded 编码类型发送,这种编码类型适用于大多数简单的表单数据,如文本字段。但是,当表单中包含文件上传控件()时,就需要使用 multipart/form-data 编码类型,因为它允许表单数据以多部分消息的形式发送,每一部分都可以有自己的 MIME 类型,从而支持文件上传。
使用 enctype=“multipart/form-data” 的示例:
<form action="/upload" method="post" enctype="multipart/form-data">
<label for="file">选择文件:</label>
<input type="file" id="file" name="file">
<input type="submit" value="上传">
</form>
在这个例子中,当用户选择一个文件并点击“上传”按钮时,表单数据(包括文件)将以 multipart/form-data 编码类型发送到 /upload 路径。服务器端的脚本(如 PHP、Python Flask 或 Django 等)需要能够处理这种编码类型的请求,以便接收并处理上传的文件。
请求
POST /upload HTTP/1.1
Host: 120.46.25.211:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypm29gJBnevravOEW
#boundary=----WebKitFormBoundary+16字节随机字符
空行
------WebKitFormBoundarypm29gJBnevravOEW
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
hello word!
------WebKitFormBoundarypm29gJBnevravOEW--
当服务器收到了一个POST方法的/upload请求,我们则认为这是一个文件上传请求
解析请求,得到文件数据,将数据写入到文件中
响应
2.思路
云备份项目中,业务处理模块是针对客户端的业务请求进行处理,并最终给与响应。而整个过程中包含以下要实现的功能:
借助网络通信模块httplib库搭建http服务器与客户端进行网络通信
针对收到的请求进行对应的业务处理并进行响应(文件上传,列表查看,文件下载(包含断点续传))
业务处理模块要对客⼾端的请求进⾏处理,那么我们就需要提前定义好客⼾端与服务端的通信,明确客⼾端发送什么样的请求,服务端处理后应该给与什么样的响应,⽽这就是⽹络通信接⼝的设计。
文件上传请求与响应
POST /upload HTTP/1.1
Content-Length:11
Content-Type:multipart/form-data;boundary=----WebKitFormBoundary+16字节随机字符
\r\n
------WebKitFormBoundary
Content-Disposition:form-data;filename="a.txt";
\r\n
hello world
------WebKitFormBoundary--
HTTP/1.1 200 OK
Content-Length:0
\r\n
响应体
查看云端文件信息
GET /showfile HTTP/1.1
content-Length:0
\r\n
请求体
HTTP/1.1 200 OK
Content-Length:
Content-Type:text/html
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Page of Download</title></head>
<body>
<h1>Download</h1>
<table>
<tr>
<td> a.txt </td>
<td align="right"> 1994-07-08 03:00 </td>
<td align="right">27K </td>
</tr>
</table>
</body>
</html>
文件下载:重要字段
GET /download/a.txt http/1.1
Content-Length:0
\r\n
请求体
HTTP/1.1 200 OK
Content-Length:1024
ETags:"filename-size-mtime一个能够唯一标识文件的数据"
Accept-Ranges: bytes文件数据
\r\n
响应体:响应给客户端的文件数据
http的ETag头部字段: 存储了一个资源的唯一标识。
客户端第一次下载文件的时候,会收到这个响应信息。
第二次再次向服务端发起下载请求时,就会将这个字段加入到http请求中,服务器接收到http-request后根据这个唯一标识判断本次客户端请求的这个资源有没有被修改过,如果没有被修改过,直接使用原先缓存的数据发给客户端,不用重新从服务端备份的文件中下载到缓存。如果修改过,则重新从服务端备份的文件中下载到缓存发给客户端。
http协议本身对于etag中是什么数据并不关心,只要服务端能够唯一标识就行。因此我们etag就用"文件名-文件大小-最后一次修改时间"组成而etag字段。不仅仅是缓存用到,还有就是后边的断点续传的实现也会用到
etag字段为断点续传提供支持
客户端第一次下载文件的时候,会收到这个响应信息。第二次再次向服务端发起下载请求时,就会将这个字段的内容以If-Range
字段加入到http请求中,服务器接收到http-request后,根据这个唯一标识判断本次客户端请求的这个资源是否是上次请求的资源并且判断有没有被修改过,如果没有被修改过,则可以从上次记录的断点开始,发之后的数据给客户端。如果该数据被修改过,则需要从头发送数据。
http协议的Accept-Ranges: bytes 字段
用于告诉客户端我支持断点续传,并且数据单位以字节作为单位
Content-Type字段的重要性:
决定了浏览器如何处理响应正文
3.断点续传
GET /download/a.txt http/1.1
Content-Length: 0
If-Range: "⽂件唯⼀标识"
Range: bytes=89-999
HTTP/1.1 206 Partial Content
Content-Length:
Content-Range: bytes 89-999/100000
Content-Type: application/octet-stream
ETag: "inode-size-mtime⼀个能够唯⼀标识⽂件的数据"
Accept-Ranges: bytes对应⽂件从89到999字节的数据。
功能:
当文件下载过程中,因为某种异常而中断,如果再次进行从头下载,效率较低,因为需要将之前已经传输过的数据再次传输一遍。因此断点续传就是从上次下载断开的位置,重新下载即可,之前已经传输过的数据将不需要在重新传输。
目的:
提高文件重新传输效率
实现思想:
客户端在下载文件的时候,每次接收到数据写入本地文件后,记录自己当前下载的数据量;当异常下载中断时,下次断点续传的时候,将 要重新下载的数据区间(下载起始位置,结束位置)发送给服务器,服务器收到后,仅仅回传客户端需要的区间数据即可。
需要考虑的问题: 如果上次下载文件之后,这个文件在服务器上被修改了,则这时候将不能重新断点续传,而是应该重新进行文件下载操作。
Ehttp协议中断点续传的实现:
- 告诉服务器下载区间范围
- 服务器上要能够检测上一次下载之后这个文件是否被修改过
整体流程
第一次客户端向服务端发起下载文件请求,服务返回响应:Accept-Ranges: bytes【告知客户端,我支持断点续传】; etag【你当前下载文件的唯一标识】;文件数据【客户端每次下载到本地一定数据就记录一下当前下载位置】。客户端收到该响应后会保存etag。
第二次客户端向服务端发起下载文件请求【包含if-range字段(内容==上次的etag);包含range字段(告知服务器 我要的数据区间范围)】。服务端收到该请求,将if-range字段和上次下载的etag字段作比较(是否相等且文件未被修改,如果是,开始传range间的数据;不是,重传)。
构建http-reponse
HTTP/1.1 206 Partial Content
ETag: "xxx-xxx-xxxx’
Content-Range: bytes 100-10000/文件大小
GetEtag,通过什么来构建etag
场景:浏览器发来下载文件请求,服务端收到该请求,发现该文件已被压缩,此后服务端解压放到backupDir,这时该文件的mtime就会改变,所以,如果etag是通过解压后的文件的mtime来构建的,那么这是不合理的,不能因为服务端压缩/解压缩文件,就认定其“内容修改”。那么怎么构建etag?通过首次文件存储属性信息的backupAInfo.dat文件,这个文件里的mtime存储的mtime不会因为压缩/解压缩而改变。
业务处理总结
服务端启动 业务处理模块即监听客户端连接/请求;即对不同的请求设置不同的函数处理。
upload:
- req是否包含name为file的字段
- 根据name字段获取整个MultipartFormData结构体 构建存放文件的备份路径
- 将文件数据写入到云端
- 构建BackupAInfo插入到内存map+磁盘文件。
showFile:
- 从内存map中获取所有文件属性信息
- 根据所有属性信息 组织html文件数据
【设置动态html时 需要考虑】:文件属性是从内存中的map来的 如果服务端在云端删除了该文件 此时html网页就不应该显示该文件 需要我们根据文件属性信息构建原路径和压缩路径 如果这两个文件都不存在 则不显示 - 组织http响应
download:
- 根据客户端请求的资源路径req.path 获取文件属性备份信息
- 判断文件是否被压缩 如果被压缩需要解压缩
- 将文件解压到备份目录下
- 删除压缩包 修改备份信息
- 支持断点续传【请求包含If-Range字段(上次请求文件的etag)+该字段值与客户端本次请求的文件etag相同】
- 读取文件数据 放入rsp.body中
- 设置响应头部字段: ETag Accept-Ranges: bytes