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、应当实现适宜的重试循环逻辑,以提高上传成功率(当网络环境较差时,可能会多次出现上传中断或拿不到响应的情况,或者遇到服务器内部错误的时候,应该重试续传,具体可参看流程图)

results matching ""

    No results matching ""