长视频响应过慢:如何优化HTTP GET返回视频的业务逻辑?
问题根源分析
你现在的问题核心在于一次性把整个长视频加载到内存中——IOUtils.toByteArray(url)会把远程视频的全部内容读取到字节数组里,只有当整个视频下载完成后,才会开始向客户端返回响应。这不仅会占用大量服务器内存,还会让客户端等待极长的时间才能收到第一个字节。
解决方案:流式分块传输
我们需要改成边下载边输出的流式逻辑,让服务器不用缓存整个视频,同时客户端能立刻开始接收数据。下面是具体的改写方案:
1. 重写videoAsStream方法,实现流式传输
把一次性读取全部内容改成逐块读取远程流,并直接写入响应输出流,用try-with-resources确保输入流自动关闭,避免资源泄漏:
@Override public StreamingOutput videoAsStream(final String videoUrl) { try { final URL url = new URL(videoUrl); return output -> { // 使用try-with-resources自动关闭输入流 try (InputStream inputStream = url.openStream()) { // 逐块复制流,默认缓冲区大小足够处理大多数场景 IOUtils.copy(inputStream, output); output.flush(); } }; } catch (final MalformedURLException e) { log.error("Url exception for url {}", videoUrl); throw new UncheckedIOException(e); } }
2. 增强REST响应的分块传输支持
在返回Response时,明确启用HTTP分块传输编码,让客户端知道可以逐步接收数据:
@GET @Path("/test") public Response test(@QueryParam("url") final String videoUrl) { final CrawlerResult result = this.crawlerService.crawl(videoUrl); if (result.isSuccess()) { final StreamingOutput fileStream = this.crawlerService.videoAsStream(result.getResult()); return Response.ok(fileStream, "video/mp4") // 更准确的媒体类型,比APPLICATION_OCTET_STREAM更友好 .header("content-disposition", "attachment; filename=\"movie.mp4\"") // 修正引号格式,避免潜在解析问题 .header("Transfer-Encoding", "chunked") .build(); } else { return Response.status(Response.Status.NOT_ACCEPTABLE) .entity(result) .build(); } }
额外优化建议
- 动态生成文件名:可以从原视频URL中提取真实文件名,替换固定的
movie.mp4,比如通过new URL(videoUrl).getPath().substring(new URL(videoUrl).getPath().lastIndexOf('/') + 1)来获取原文件名,提升用户体验。 - 添加超时控制:远程视频下载可能卡住,建议给URL连接设置超时时间,避免线程长时间阻塞:
final URL url = new URL(videoUrl); URLConnection connection = url.openConnection(); connection.setConnectTimeout(5000); // 连接超时5秒 connection.setReadTimeout(10000); // 读取超时10秒 InputStream inputStream = connection.getInputStream();
这样修改后,服务器会在读取到远程视频的第一块数据时就开始向客户端返回,用户不用等整个视频下载完成,同时服务器内存占用也会大幅降低,长视频的响应体验会立刻改善。
内容的提问来源于stack exchange,提问作者Almas Abdrazak
相关产品推荐
相关产品推荐

