HTTP视频上传(断点续传)
本文档详细介绍了使用“HTTP断点续传”的方式实现文件上传的对接说明,并在本小节最后给出前端HTML5实现demo,以及若干服务端实现demo。
1 创建视频上传信息
根据本地待上传文件的各种属性请求该接口,获取系统分配的视频id(videoid), 上传路径(metaurl,chunkurl)等上传信息,以便进行后续上传。本接口需要使用THQS方式进行请求参数校验(关于THQS算法的细节请参见Spark API附录I), 本接口不支持跨域访问。
接口地址http://spark.bokecc.com/api/video/create/v2
接口类型: GET 请求
需要传递以下参数:
参数 | 含义 |
---|---|
userid | 用户id,必选 |
title | 视频标题 |
tag | 视频标签 |
description | 视频描述 |
categoryid | 分类id(不选 默认上传到用户的默认分类) |
filename | 视频文件名(必须带后缀名,如不带 默认为视频文件), 必选 |
filesize | 视频文件大小(Byte), 必选 |
notify_url | 视频处理完毕的通知地址 |
返回数据uploadinfo包括如下字段:
字段名 | 含义 |
---|---|
videoid | 视频id |
userid | 用户id |
servicetype | 服务类型 |
metaurl | 系统分配的文件状态以及断点位置查询接口 |
chunkurl | 系统分配的文件内容块上传接口 |
返回json格式信息如下:
"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参数加密校验。
接口地址:http://abc.bokecc.com/servlet/uploadmeta/v2
接口类型: GET请求。
(接口路径即上一步请求api/video/create/v2返回上传信息中的metaurl。)
需要传递以下参数:
参数 | 含义 |
---|---|
uid | 用户id,必选 |
ccvid | 视频文件vid,必选 |
filename | 视频名称,必选 |
md5 | 视频文件md5 32位md5串;如果请求时填写了md5的话,会在文件接收完成之后,校验文件的完整性;否则,不做完整性校验。 计算并校验md5可能会比较耗时,我们建议在后端程序实现的上传对接中校验md5,前端flash或H5实现时可以不做md5校验。 |
filesize | 视频文件大小,单位Byte,必选 |
servicetype | 服务类型,必选 |
返回数据 包括如下字段:
字段名 | 含义 |
---|---|
result | 响应状态码:(1,0,-1,-2,-3),响应状态码代表了当前的文件状态 1 文件已全部接收,上传成功; 0 文件仍在上传状态中,成功返回“断点位置”; -1 上传失败,可以放弃“本次”上传,不要重试了; -2 服务器内部错误,详见msg信息,可以续传重试; -3 请求参数错误,详见msg信息,请修正错误后重试 |
msg | 错误提示,出错时给出异常信息。 |
received | 单位Byte,上传服务器已接收文件长度,亦即“断点位置”。 |
返回json格式信息如下:
{
"result": 0,
"msg":"ok",
"received":1048576
}
上面的返回代表“查询状态及断点成功,已上传1048576字节,请继续上传”。
异常返回格式
{
"result": -3,
"msg":“Null or invalid filesize”,
"received":-1
}
上面的返回代表“请求参数错误,参数filesize非法或者为空”。
当返回result=1,此时received=文件长度filesize,代表整个文件已经完全接受完毕,上传操作可以结束了。
3 上传视频文件块CHUNK
通过该接口,向服务器分块发送文件数据。最大文件块chunksize=4MB。在上传具体的文件内容之前,要根据当前的”断点位置“对文件进行分块,然后依次调用此接口顺序上传文件块(即文件块的起始索引要和服务器已接收的长度相吻合)。如果传输过程中发生异常中断,需要再次获取”断点位置“并重新分块上传。
本接口支持跨域,不需要进行THQS参数校验。
接口地址: http://abc.bokecc.com/servlet/uploadchunk/v2
接口类型: POST 请求。 (接口路径即请求api/video/create/v2返回上传信息中的chunkurl。)
需要传递以下参数:
|参数|含义| |ccvid|vid,视频id, 必选|
参数ccvid直接附带在chunkurl之后即可,例如:http://abc.bokecc.com/servlet/uploadchunk/v2?ccvid=3B35ED608474B8DB2BB
返回数据 包括如下字段:
字段 | 含义 |
---|---|
result | 响应状态码:(1,0,-1,-2,-3),响应状态码代表了当前的文件状态 1 文件已全部接收,上传成功; 0 成功接收文件块,并返回当前的“断点位置”; -1 上传失败,可以放弃“本次”上传,不要重试了; -2 服务器内部错误,详见msg信息,可以续传重试; -3 请求参数错误,详见msg信息,请修正错误后重试 |
msg | 错误提示,出错时给出异常信息。 |
received | 单位Byte,上传服务器已接收文件长度,亦即“断点位置”。 |
返回json格式信息如下:
{
"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*/
/* chunkStart为本次文件块的开始索引*/
/* chunkEnd为本次文件块的结束索引*/
/* file为本地待上传文件*/
/* 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、应当实现适宜的重试循环逻辑,以提高上传成功率(当网络环境较差时,可能会多次出现上传中断或拿不到响应的情况,或者遇到服务器内部错误的时候,应该重试续传,具体可参看流程图)
前端H5实现上传demo地址: https://github.com/CCVideo/VOD_H5_UploadDemo
服务端Java实现的上传demo地址:https://github.com/CCVideo/VOD_JAVA_UploadDemo