netty文件添加断点续传

netty文件添加断点续传、netty文件服务器添加断点续传、netty返回mp4处理、netty播放视频、netty播放mp4、netty实现断点续传

如果请求的是媒体文件,需要进行断点续传的响应:断点续传百科:https://baike.baidu.com/item/%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0/764723
例如 /mp4.mp4

首先要在 pipeline 中添加大数据写处理,否则无法使用下面的断点功能:

  ChannelPipeline p = ...;
  p.addLast("streamer", new ChunkedWriteHandler());
  p.addLast("handler", new MyHandler());

响应代码

public void returnStaticFile(FinalServerContext context, ChannelHandlerContext ctx) throws Exception {
        File file = context.getResponse().getResponseFile().getFile();
        if (!file.exists()) {
            log.warn("文件不存在:{}", file.getAbsolutePath());
            FinalServerConfiguration.webExceptionHandler.notHandler(ctx);
            return;
        }
        HttpResponseStatus status = HttpResponseStatus.OK;
        HttpHeaders headers = new DefaultHttpHeaders();
        headers.set(HttpHeaderNames.ACCEPT_RANGES, HttpHeaderValues.BYTES);
        headers.set(HttpHeaderNames.CONTENT_LENGTH, file.length());
        headers.set(HttpHeaderNames.LAST_MODIFIED, file.lastModified());
        headers.set(HttpHeaderNames.CACHE_CONTROL, HttpHeaderValues.PUBLIC);
        // 设置文件请求头
        HttpUtils.setResponseHeadName(context.getResponse().getResponseFile(), headers);

        // 静态文件需要做到断点续传
        String range = context.getRequest().getHeaders().get(HttpHeaderNames.RANGE);
        long offset = 0L, length = file.length();
        if (StrUtil.isNotBlank(range)) {// Range: bytes=1900544-  Range: bytes=1900544-6666666
            range = range.substring(6);
            String[] split = range.split("-");
            try {
                offset = Long.parseLong(split[0]);
                if (split.length == 2) {
                    long end = Long.parseLong(split[1]);
                    if (end <= length) {
                        headers.set(HttpHeaderNames.CONTENT_RANGE, "bytes " + range + "/" + file.length());
                        length = end - offset + 1;
                    }
                } else {
                    headers.set(HttpHeaderNames.CONTENT_RANGE, "bytes " + offset + "-" + (length - 1) + "/" + file.length());
                    length = length - offset;
                }
                headers.set(HttpHeaderNames.CONTENT_LENGTH, length);// 重写响应长度
                // status = HttpResponseStatus.PARTIAL_CONTENT; // 206
            } catch (Exception e) {
                log.warn("断点续传解析错误", e);
            }
        }

        DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
        response.headers().set(headers);

        ctx.write(response);
        RandomAccessFile accessFile = new RandomAccessFile(file, "r");
        ctx.writeAndFlush(
        ctx.writeAndFlush(
                // new DefaultFileRegion(file, offset, length) // 零拷贝,部分系统可能不支持,低版本jdk6以下存在bug
                new ChunkedFile(
                        accessFile, offset, length, 1024 * 64), // 正常发送文件块
                ctx.newProgressivePromise()
        ).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) {
                future.channel().close();
            }

        });
    }

html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
hi 
<video width="320" height="240" controls src="/mp4.mp4"></video>
</body>
</html>

响应结果
image-1670553586721

image-1670553611285

访问230MB视频时

image-1670554333450

netty断点续传视频跳进度

GIF 2022-12-9 10-56-55

对比Nginx

image-1670554394945