HTTP视频上传(断点续传)

优质
小牛编辑
120浏览
2023-12-01

本文档详细介绍了使用“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。)

需要传递以下参数:

参数含义
ccvidvid,视频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的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://github.com/CCVideo/VOD_H5_UploadDemo

服务端Java实现的上传demo地址:https://github.com/CCVideo/VOD_JAVA_UploadDemo