HTTP视频上传(断点续传)

本文档详细介绍了使用“HTTP断点续传”的方式实现文件上传的对接说明,并在本小节最后给出前端HTML5实现demo,以及若干服务端实现demo。

1. 创建视频上传信息

根据本地待上传文件的各种属性请求该接口,获取系统分配的视频id(videoid), 上传路径(metaurl,chunkurl)等上传信息,以便进行后续上传。本接口需要使用THQS方式进行请求参数校验(关于THQS算法的细节请参见Spark API附录I), 本接口不支持跨域访问。

请求地址

https://spark.bokecc.com/api/video/create/v2

接口类型

GET

请求参数

名称 类型 必填 描述
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协议的上传域名

返回数据

名称 类型 说明
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参数加密校验。

请求地址

https://abc.bokecc.com/servlet/uploadmeta/v2

(接口路径即上一步请求api/video/create/v2返回上传信息中的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 服务类型,必选

返回数据

名称 类型 说明
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
}

上面的返回代表“查询状态及断点成功,已上传1048576字节,请继续上传”。

异常示例

{
  "result": -3,
  "msg": "Null or invalid filesize",
  "received": -1
}

上面的返回代表“请求参数错误,参数filesize非法或者为空”。

当返回result=1,此时received=文件长度filesize,代表整个文件已经完全接受完毕,上传操作可以结束了。

3. 上传视频文件块CHUNK

通过该接口,向服务器分块发送文件数据。最大文件块chunksize=4MB。在上传具体的文件内容之前,要根据当前的”断点位置“对文件进行分块,然后依次调用此接口顺序上传文件块(即文件块的起始索引要和服务器已接收的长度相吻合)。如果传输过程中发生异常中断,需要再次获取”断点位置“并重新分块上传。

本接口支持跨域,不需要进行THQS参数校验。

请求地址

https://abc.bokecc.com/servlet/uploadchunk/v2

接口类型

POST

(接口路径即请求api/video/create/v2返回上传信息中的chunkurl。)

需要传递以下参数:

名称 类型 必填 描述
ccvid String 视频id
注:参数ccvid直接附带在chunkurl之后即可,例如:http://abc.bokecc.com/servlet/uploadchunk/v2?ccvid=3B35ED608474B8DB2BB

返回数据

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

前端H5实现上传demo地址: https://hdgit.bokecc.com/ccvideo/VOD_H5_UploadDemo/-/releases

服务端Java实现的上传demo地址: https://hdgit.bokecc.com/ccvideo/VOD_JAVA_UploadDemo/-/releases

results matching ""

    No results matching ""