HTTP视频上传(断点续传)
更新时间:2021-03-03
本文档详细介绍了使用“HTTP断点续传”的方式实现文件上传的对接说明,并在本小节最后给出前端HTML5实现demo,以及若干服务端实现demo。
前端H5实现上传demo地址: https://hdgit.bokecc.com/ccvideo/VOD_H5_UploadDemo/-/releases
服务端Java实现的上传demo地址: https://hdgit.bokecc.com/ccvideo/VOD_JAVA_UploadDemo/-/releases
1. 创建视频上传信息
接口描述
根据本地待上传文件的各种属性请求该接口,获取系统分配的视频id(videoid), 上传路径(metaurl,chunkurl)等上传信息,以便进行后续上传。本接口不支持跨域访问。
请求地址
https://spark.bokecc.com/api/video/create/v2
请求方式
GET
请求参数
以下请求参数需要进行THQS权限认证, 权限认证方式请参考THQS认证方式。
名称 | 类型 | 必填 | 描述 |
---|---|---|---|
userid | String | 是 | 用户id。 |
title | String | 是 | 视频标题。 |
tag | String | 否 | 视频标签。 |
description | String | 否 | 视频描述。 |
categoryid | String | 否 | 分类id,默认上传到用户的默认分类。 |
filename | String | 是 | 视频文件名(必须带后缀名,如不带默认为视频文件)。 |
filesize | String | 是 | 视频文件大小,单位:Byte。 |
notify_url | String | 否 | 视频处理完毕的通知地址。 |
delete_callback_url | String | 否 | 视频审核结果的通知地址。 |
httpsflag | String | 否 | 值为1时,metaurl,chunkurl返回https协议的上传域名。 |
请求示例
https://spark.bokecc.com/api/video/create/v2?filename=111.mp4&format=json&filesize=1024&time=1670830808305&title=111&userid=215445&hash=C8ACA25E14EC4686C6A0C87BF4751193
返回数据
名称 | 类型 | 说明 |
---|---|---|
uploadinfo | Object | 上传信息。 |
uploadinfo说明
名称 | 类型 | 说明 |
---|---|---|
videoid | String | 视频id。 |
userid | String | 用户id。 |
servicetype | String | 服务类型。 |
metaurl | String | 系统分配的文件状态以及断点位置查询接口。 |
chunkurl | String | 系统分配的文件内容块上传接口。 |
返回示例
{
"uploadinfo": {
"videoid": "3B35ED608474B8DB2BBA",
"userid": "40EF004A6F",
"servicetype": "240593F089",
"metaurl": "http://abc.com/servlet/uploadmeta/v2",
"chunkurl": "http://abc.com/servlet/uploadchunk/v2"
}
}
异常示例
{
"error": "SERVICE_EXPIRED"
}
错误码
错误码 | 说明 |
---|---|
INVALID_REQUEST | 用户输入参数错误。 |
SPACE_NOT_ENOUGH | 用户剩余空间不足。 |
SERVICE_EXPIRED | 用户服务终止。 |
PROCESS_FAIL | 服务器处理失败。 |
TOO_MANY_REQUEST | 访问过于频繁。 |
PERMISSION_DENY | 用户服务无权限。 |
2. 查询文件上传状态及断点位置
接口描述
可以通过该接口查询文件的上传状态及“断点位置”(上传服务器已经接收的文件大小), 处于上传状态中的文件记录,可以调用servlet/uploadchunk/v2 接口(即chunkurl, 后续有详细介绍) 从“断点位置”处发送文件块内容,实现“断点续传”。需要说明的是,在发送第一个文件块之前,必须要调用本接口一次,检查该条文件记录是否处于上传状态中。文件状态及“断点”介绍请见本接口的响应说明。
本接口支持跨域访问,不需要进行THQS参数加密校验。
请求地址
步骤1返回上传信息中的metaurl
请求方式
GET
请求参数
名称 | 类型 | 必填 | 描述 |
---|---|---|---|
uid | String | 是 | 用户id。 |
ccvid | String | 是 | 视频文件vid。 |
filename | String | 是 | 视频名称。 |
md5 | String | 否 | 视频文件md5 32位md5串;如果请求时填写了md5的话,会在文件接收完成之后,校验文件的完整性;否则,不做完整性校验。 计算并校验md5可能会比较耗时,我们建议在后端程序实现的上传对接中校验md5,前端flash或H5实现时可以不做md5校验。 |
filesize | String | 是 | 视频文件大小,单位Byte。 |
servicetype | String | 是 | 服务类型。 |
请求示例
https://xxx?uid=xxx&ccvid=3437F658D1491BAF3F35109F9F77F2A8&filename=111.mp4&filesize=1024&servicetype=240593F08992E06B
返回数据
名称 | 类型 | 说明 |
---|---|---|
result | Integer | 响应状态码:(1,0,-1,-2,-3),响应状态码代表了当前的文件状态 1 文件已全部接收,上传成功; 0 文件仍在上传状态中,成功返回“断点位置”; -1 上传失败,可以放弃“本次”上传,不要重试了; -2 服务器内部错误,详见msg信息,可以续传重试; -3 请求参数错误,详见msg信息,请修正错误后重试 |
msg | String | 错误提示,出错时给出异常信息。 |
received | Long | 单位Byte,上传服务器已接收文件长度,亦即“断点位置”。 |
返回示例
{
"result": 0,
"msg": "ok",
"received": 1048576
}
异常示例
{
"result": -3,
"msg": "Null or invalid filesize",
"received": -1
}
上面的返回代表“请求参数错误,参数filesize非法或者为空”。
当返回result=1,此时received=文件长度filesize,代表整个文件已经完全接受完毕,上传操作可以结束了。
3. 上传视频文件块CHUNK
接口描述
通过该接口,向服务器分块发送文件数据。最大文件块chunksize=4MB。在上传具体的文件内容之前,要根据当前的“断点位置”对文件进行分块,然后依次调用此接口顺序上传文件块(即文件块的起始索引要和服务器已接收的长度相吻合)。如果传输过程中发生异常中断,需要再次获取”断点位置“并重新分块上传”。
本接口支持跨域,不需要进行THQS参数校验。
请求地址
步骤1返回上传信息中的chunkurl
请求方式
POST
请求参数
名称 | 类型 | 必填 | 描述 |
---|---|---|---|
ccvid | String | 是 | 视频id。 |
请求示例
请看步骤4
返回数据
名称 | 类型 | 说明 |
---|---|---|
result | Integer | 响应状态码:(1,0,-1,-2,-3),响应状态码代表了当前的文件状态 1 文件已全部接收,上传成功; 0 成功接收文件块,并返回当前的“断点位置”; -1 上传失败,可以放弃“本次”上传,不要重试了; -2 服务器内部错误,详见msg信息,可以续传重试; -3 请求参数错误,详见msg信息,请修正错误后重试 |
msg | String | 错误提示,出错时给出异常信息。 |
received | Long | 单位Byte,上传服务器已接收文件长度,亦即“断点位置”。 |
返回示例
{
"result": 1,
"msg": "Receive success",
"received": 128141777
}
上面的返回表示原始文件大小是 128141777 Bytes,服务器端接收了 128141777 Bytes,文件上传成功。
异常示例
{
"result": -2,
"msg": "Internal IO error",
"received": -1
}
上面的返回代表”服务器内部IO错误“,当发生服务器内部错误的时候,可实现重试续传逻辑,以提高上传成功率。 当返回result=1,此时received=文件长度filesize,代表整个文件已经完全接受完毕,上传操作可以结束了。
4. 上传过程中相关问题说明
1. “HTTP断点续传”实现机制
本接口通过HTTP POST方式将本地文件的指定部分(一段二进制数据)发送到服务器,服务器端会返回已经接收的文件长度,即”断点位置“。将要发送文件块的起止位置和”断点位置“的对应关系要符合HTTP/1.1协议(RFC 7233, section 4.2 :
https://tools.ietf.org/html/rfc7233#section-4.2的Content-Range约定。
POST报文格式
POST /servlet/resumechunk?ccvid=3B35ED608474B8DB2BB HTTP/1.1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4)
Charsert: UTF-8
Content-Type: multipart/form-data;
boundary=---------CCHTTPAPIFormBoundaryEEXX33781
Content-Range: bytes 0-1048575/128141777
Cache-Control: no-cache
Host: abc.bokecc.com
Accept: text/*
Connection: keep-alive
Content-Length: 1048773
-----------CCHTTPAPIFormBoundaryEEXX33781
Content-Disposition: form-data;name="file";filename="local.mp4"
Content-Type: application/octet-strea
(... binary block …)
-----------CCHTTPAPIFormBoundaryEEXX33781--
2. Content-Range与断点说明
用户在发送文件块的时候要根据返回的“断点位置”来设置HTTP header中的Content-Range内容。
Content-Range: bytes x-y/z
x 代表该块数据在整个文件中的开始索引== 上次请求返回的“断点位置”(received)。
y 代表该块数据在整个文件中的结束索引。
z代表文件总长度,即z = filesize 。
X和y都表示文件索引的下标位置,下标从0开始,到z-1止。即0 = x =y=z-1 ,y-x+1=本次文件块的大小。
再举例说明一下:像上面z=filesize=128141777字节,大概是122.2M,每次发送1M内容的话,需要连续发送123次。
第一次:Content-Range: bytes 0-1048575/128141777
第二次:Content-Range: bytes 1048576-2097151/128141777
第三次:Content-Range: bytes 2097152-3145727/128141777
...
假设发送第n块的时候由于网络原因产生中断,再次GET servlet/uploadmeta/v2返回的断点位置是127083985,那么
第n+1次:Content-Range: bytes 127083985-128132560/128141777
第n+2次:Content-Range: bytes 128132561-128141776/128141777
发送完毕,最后一块发送了128141776- 128132561+1=9216字节。
3. Content-Type与boundary说明
在POST请求中上传文件内容,Content-Type 格式如下:
Content-Type: multipart/form-data; boundary=boundaryText
body体内容格式如下:
--boundaryText\r\n
Content-Disposition: form-data;name="file";filename="local.mp4"\r\n
Content-Type: application/octet-stream\r\n
\r\n
(... binary block ...)\r\n
--boundaryText--\r\n
其中,(... binary block …) 部分为文件块二进制数据内容。详细说明请参看rfc1867( Form-based File Upload: https://www.ietf.org/rfc/rfc1867.txt
发送文件块POST请求java代码示例:
/**
* url为http//abc.bokecc.com/servlet/uploadchunk/v2?ccvid=videoid
* <p>
* chunkStart为本次文件块的开始索引
* <p>
* chunkEnd为本次文件块的结束索引
* <p>
* file为本地待上传文件
* <p>
* bufferOut为本次文件块内容的二进制数组,bufferOut.length=chunkEnd-chunkStart+1
*/
public String uploadchunk(String url, long chunkStart, long chunkEnd, File file, byte[] bufferOut) {
HttpURLConnection conn = null;
try {
// 定义数据分隔线
String BOUNDARY = "---------CCHTTPAPIFormBoundaryEEXX" + newRandom().nextInt(65536);
URL openUrl = newURL(url);
conn = (HttpURLConnection) openUrl.openConnection();
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4)");
conn.setRequestProperty("Charsert", "UTF-8");
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
// content-range
conn.setRequestProperty("Content-Range", "bytes " + chunkStart + "-" + chunkEnd + "/" + file.length());
OutputStream out = newDataOutputStream(conn.getOutputStream());
StringBuilder sb = newStringBuilder();
sb.append("--").append(BOUNDARY).append("\r\n");
sb.append("Content-Disposition: form-data;name=\"file\";filename=\"" + file.getName() + "\"\r\n");
sb.append("Content-Type: application/octet-stream\r\n");
sb.append("\r\n");
byte[] data = sb.toString().getBytes("UTF-8");
out.write(data);
out.write(bufferOut);
// 定义最后数据分隔线
byte[] end_data = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
out.write(end_data);
out.flush();
out.close();
// 定义BufferedReader输入流来读取URL的响应
BufferedReader reader = newBufferedReader(newInputStreamReader(conn.getInputStream()));
StringBuffer resultBuf = newStringBuffer("");
String line = null;
while ((line = reader.readLine()) != null) {
resultBuf.append(line);
}
reader.close();
conn.disconnect();
returnresultBuf.toString();
} catch (Exception e) {
System.out.println("发送POST请求出现异常!" + e);
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
5 上传步骤说明(流程图):
1、调用api/video/create/v2, 获取视频上传信息(videoid,metaurl,chunkurl等)。
2、调用servlet/uploadmeta/v2 (metaurl), 查询该条文件的上传状态及断点位置。
3、根据断点位置对本地文件进行分块,循环调用servlet/uploadchunk/v2 (chunkurl) 发送文件块,直到文件数据全部发送。
4、如果在发送文件块的过程中出现中断,可再次调用servlet/uploadmeta/v2 (metaurl) 以查询当前的文件状态及断点位置,判断并执行后续动作,直到文件上传成功或失败。
5、应当实现适宜的重试循环逻辑,以提高上传成功率(当网络环境较差时,可能会多次出现上传中断或拿不到响应的情况,或者遇到服务器内部错误的时候,应该重试续传,具体可参看流程图)