JFinal

登录 注册

输出流

做APP项目需要后台返回视频流而不是一个下载地址,要求在浏览器中访问就能播放

测试代码实现

public class VideoRender extends Render {

	@Override
	public void render() {
		int BUFFER_LENGTH = 1024 * 16;
		long EXPIRE_TIME = 1000 * 60 * 60 * 24;
		Pattern RANGE_PATTERN = Pattern.compile("bytes=(?<start>\\d*)-(?<end>\\d*)");
		String videoPath = "D:\\project\\mibo\\jfs\\video";
		String videoFilename = "7.mp4";
		Path video = Paths.get(videoPath, videoFilename);
		try {
			int length = (int) Files.size(video);
			int start = 0;
			int end = length - 1;
			String range = request.getHeader("Range");
			range = range == null ? "" : range;
			Matcher matcher = RANGE_PATTERN.matcher(range);
			if (matcher.matches()) {
				String startGroup = matcher.group("start");
				start = startGroup.isEmpty() ? start : Integer.valueOf(startGroup);
				start = start < 0 ? 0 : start;
				String endGroup = matcher.group("end");
				end = endGroup.isEmpty() ? end : Integer.valueOf(endGroup);
				end = end > length - 1 ? length - 1 : end;
			}
			int contentLength = end - start + 1;
			response.setBufferSize(BUFFER_LENGTH);
			response.setHeader("Content-Disposition", String.format("inline;filename=\"%s\"", videoFilename));
			response.setHeader("Accept-Ranges", "bytes");
			response.setDateHeader("Last-Modified", Files.getLastModifiedTime(video).toMillis());
			response.setDateHeader("Expires", System.currentTimeMillis() + EXPIRE_TIME);
			response.setContentType(Files.probeContentType(video));
			response.setHeader("Content-Range", String.format("bytes %s-%s/%s", start, end, length));
			response.setHeader("Content-Length", String.format("%s", contentLength));
			response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
			int bytesRead;
			int bytesLeft = contentLength;
			ByteBuffer buffer = ByteBuffer.allocate(BUFFER_LENGTH);
			try (SeekableByteChannel input = Files.newByteChannel(video, READ);
					OutputStream output = response.getOutputStream()) {
				input.position(start);
				while ((bytesRead = input.read(buffer)) != -1 && bytesLeft > 0) {
					buffer.clear();
					output.write(buffer.array(), 0, bytesLeft < bytesRead ? bytesLeft : bytesRead);
					bytesLeft -= bytesRead;
				}
				output.close();
				input.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

在浏览器中访问能播放了,但是控制台报IO异常

org.apache.catalina.connector.ClientAbortException: java.io.IOException: 您的主机中的软件中止了一个已建立的连接。
	at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:333)
	at org.apache.catalina.connector.OutputBuffer.appendByteArray(OutputBuffer.java:718)
	at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:647)
	at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:368)
	at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:346)
	at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:96)
	at com.mibo.common.base.VideoRender.render(VideoRender.java:70)
	at com.jfinal.core.ActionHandler.handle(ActionHandler.java:103)
	at com.jfinal.ext.handler.ContextPathHandler.handle(ContextPathHandler.java:48)
	at com.mibo.common.handler.NotFoundHandler.handle(NotFoundHandler.java:36)
	at com.jfinal.core.JFinalFilter.doFilter(JFinalFilter.java:73)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:651)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:417)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:754)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1376)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.IOException: 您的主机中的软件中止了一个已建立的连接。
	at sun.nio.ch.SocketDispatcher.write0(Native Method)
	at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51)
	at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
	at sun.nio.ch.IOUtil.write(IOUtil.java:65)
	at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
	at org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:134)
	at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101)
	at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:157)
	at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite(NioEndpoint.java:1184)
	at org.apache.tomcat.util.net.SocketWrapperBase.doWrite(SocketWrapperBase.java:691)
	at org.apache.tomcat.util.net.SocketWrapperBase.writeBlocking(SocketWrapperBase.java:471)
	at org.apache.tomcat.util.net.SocketWrapperBase.write(SocketWrapperBase.java:409)
	at org.apache.coyote.http11.Http11OutputBuffer$SocketOutputBuffer.doWrite(Http11OutputBuffer.java:530)
	at org.apache.coyote.http11.filters.IdentityOutputFilter.doWrite(IdentityOutputFilter.java:73)
	at org.apache.coyote.http11.Http11OutputBuffer.doWrite(Http11OutputBuffer.java:189)
	at org.apache.coyote.Response.doWrite(Response.java:538)
	at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:328)
	... 29 more

百思不得其解,求解决方案

评论

  • 06-14 11:47
    重点注意读写数据时的逻辑错误,这部分代码最好做个单元测试

    此外,response.setHeader("Content-Disposition",...) 这些要注意, 我记得 Content-Disposition 这个是文件下载要配置的
  • 发送