- 浏览: 3552629 次
- 性别:
- 来自: 大连
博客专栏
-
使用Titanium Mo...
浏览量:38217
-
Cordova 3.x入门...
浏览量:607603
-
常用Java开源Libra...
浏览量:682783
-
搭建 CentOS 6 服...
浏览量:89534
-
Spring Boot 入...
浏览量:402149
-
基于Spring Secu...
浏览量:69760
-
MQTT入门
浏览量:91835
文章分类
最新评论
-
afateg:
阿里云的图是怎么画出来的?用什么工具?
各云服务平台的架构图 -
cbn_1992:
博主,采用jdbctoken也就是数据库形式之后,反复点击获取 ...
Spring Security OAuth2 Provider 之 数据库存储 -
ipodao:
写的很是清楚了,我找到一份中文协议:https://mcxia ...
MQTT入门(6)- 主题Topics -
Cavani_cc:
还行
MQTT入门(6)- 主题Topics -
fexiong:
博主,能否提供完整源码用于学习?邮箱:2199611997@q ...
TensorFlow 之 构建人物识别系统
根据 HTTP/1.1 协议,客户端可以获取 response 资源的一部分,以便由于在通信中断后还能够继续前一次的请求,常见的场景包括大文件下载、视频播放的前进/后退等。
以下是一个Byte-Range请求的具体HTTP信息:
详细说明可以参考HTTP/1.1(RFC 2616)的以下部分:
(0)Client Request -> (1)Web Server -> (2)Servlet Container -> (3)Web Framework -> (4) Your Code
可以通过 Spring MVC 的 XmlViewResolver中注册一个自定义的View,在Controller中返回该View来实现。
这样具体的Byte-Range请求处理都将会在ByteRangeViewRender中进行。ByteRangeViewRender的具体实现可以参考Tomcat的DefaultServlet.java和Spring的ResourceHttpRequestHandler.java。
以下是一个写好的View:
通过ProgressListener实现大文件上传时进度条的显示
1)application-context.xml
2)CustomMultipartResolver.java
3)CustomProgressListener.java
4)ProgressInfo.java
5)Controller
6)JSP
参考:
How to Implement HTTP byte-range requests in Spring MVC
Implementing HTTP byte-range requests in Spring MVC
FileServlet supporting resume and caching and GZIP
以下是一个Byte-Range请求的具体HTTP信息:
引用
【Status Code】
206 Partial Content (出错时416)
【Request Headers】
Range:bytes=19448183-
【Response Headers】
Accept-Ranges:bytes
Content-Length:58
Content-Range:bytes 19448183-19448240/19448241
Content-Type:video/mp4
206 Partial Content (出错时416)
【Request Headers】
Range:bytes=19448183-
【Response Headers】
Accept-Ranges:bytes
Content-Length:58
Content-Range:bytes 19448183-19448240/19448241
Content-Type:video/mp4
详细说明可以参考HTTP/1.1(RFC 2616)的以下部分:
- 3.12 Range Units
- 14.5 Accept-Ranges
- 14.16 Content-Range
- 14.27 If-Range
- 14.35 Range
(0)Client Request -> (1)Web Server -> (2)Servlet Container -> (3)Web Framework -> (4) Your Code
- Web Server: 一般遵循HTTP/1.1 协议都支持Byte-Range请求,比如Apache、Ngnix
- Servlet Container:大部分也支持,比如Tomcat的DefaultServlet.java
- Web Framework: Spring4.2开始支持,具体可以查看ResourceHttpRequestHandler.java
可以通过 Spring MVC 的 XmlViewResolver中注册一个自定义的View,在Controller中返回该View来实现。
ModelAndView mv = new ModelAndView("byteRangeViewRender"); mv.addObject("file", new File("C:\\RenSanNing\\xxx.mp4")); mv.addObject("contentType", "video/mp4"); return mv;
这样具体的Byte-Range请求处理都将会在ByteRangeViewRender中进行。ByteRangeViewRender的具体实现可以参考Tomcat的DefaultServlet.java和Spring的ResourceHttpRequestHandler.java。
以下是一个写好的View:
public class ByteRangeViewRender extends AbstractView { // Constants ---------------------------------------------------------------------------------- private static final int DEFAULT_BUFFER_SIZE = 20480; // ..bytes = 20KB. private static final long DEFAULT_EXPIRE_TIME = 604800000L; // ..ms = 1 week. private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES"; @Override protected void renderMergedOutputModel(Map<String, Object> objectMap, HttpServletRequest request, HttpServletResponse response) throws Exception { File file = (File) objectMap.get("file"); if (file == null || !file.exists()) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } String contentType = (String) objectMap.get("contentType"); String fileName = file.getName(); long length = file.length(); long lastModified = file.lastModified(); String eTag = fileName + "_" + length + "_" + lastModified; long expires = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME; // Validate request headers for caching --------------------------------------------------- // If-None-Match header should contain "*" or ETag. If so, then return 304. String ifNoneMatch = request.getHeader("If-None-Match"); if (ifNoneMatch != null && matches(ifNoneMatch, fileName)) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setHeader("ETag", eTag); // Required in 304. response.setDateHeader("Expires", expires); // Postpone cache with 1 week. return; } // If-Modified-Since header should be greater than LastModified. If so, then return 304. // This header is ignored if any If-None-Match header is specified. long ifModifiedSince = request.getDateHeader("If-Modified-Since"); if (ifNoneMatch == null && ifModifiedSince != -1 && ifModifiedSince + 1000 > lastModified) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setHeader("ETag", eTag); // Required in 304. response.setDateHeader("Expires", expires); // Postpone cache with 1 week. return; } // Validate request headers for resume ---------------------------------------------------- // If-Match header should contain "*" or ETag. If not, then return 412. String ifMatch = request.getHeader("If-Match"); if (ifMatch != null && !matches(ifMatch, fileName)) { response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); return; } // If-Unmodified-Since header should be greater than LastModified. If not, then return 412. long ifUnmodifiedSince = request.getDateHeader("If-Unmodified-Since"); if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + 1000 <= lastModified) { response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); return; } // Validate and process range ------------------------------------------------------------- // Prepare some variables. The full Range represents the complete file. Range full = new Range(0, length - 1, length); List<Range> ranges = new ArrayList<Range>(); // Validate and process Range and If-Range headers. String range = request.getHeader("Range"); if (range != null) { // Range header should match format "bytes=n-n,n-n,n-n...". If not, then return 416. if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) { response.setHeader("Content-Range", "bytes */" + length); // Required in 416. response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); return; } String ifRange = request.getHeader("If-Range"); if (ifRange != null && !ifRange.equals(eTag)) { try { long ifRangeTime = request.getDateHeader("If-Range"); // Throws IAE if invalid. if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) { ranges.add(full); } } catch (IllegalArgumentException ignore) { ranges.add(full); } } // If any valid If-Range header, then process each part of byte range. if (ranges.isEmpty()) { for (String part : range.substring(6).split(",")) { // Assuming a file with length of 100, the following examples returns bytes at: // 50-80 (50 to 80), 40- (40 to length=100), -20 (length-20=80 to length=100). long start = sublong(part, 0, part.indexOf("-")); long end = sublong(part, part.indexOf("-") + 1, part.length()); if (start == -1) { start = length - end; end = length - 1; } else if (end == -1 || end > length - 1) { end = length - 1; } // Check if Range is syntactically valid. If not, then return 416. if (start > end) { response.setHeader("Content-Range", "bytes */" + length); // Required in 416. response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); return; } // Add range. ranges.add(new Range(start, end, length)); } } } // Prepare and initialize response -------------------------------------------------------- // Get content type by file name and set content disposition. String disposition = "inline"; // If content type is unknown, then set the default value. // For all content types, see: http://www.w3schools.com/media/media_mimeref.asp // To add new content types, add new mime-mapping entry in web.xml. if (contentType == null) { contentType = "application/octet-stream"; } else if (!contentType.startsWith("image")) { // Else, expect for images, determine content disposition. If content type is supported by // the browser, then set to inline, else attachment which will pop a 'save as' dialogue. String accept = request.getHeader("Accept"); disposition = accept != null && accepts(accept, contentType) ? "inline" : "attachment"; } // Initialize response. response.reset(); response.setBufferSize(DEFAULT_BUFFER_SIZE); response.setHeader("Content-Disposition", disposition + ";filename=\"" + fileName + "\""); response.setHeader("Accept-Ranges", "bytes"); response.setHeader("ETag", eTag); response.setDateHeader("Last-Modified", lastModified); response.setDateHeader("Expires", expires); // Send requested file (part(s)) to client ------------------------------------------------ // Prepare streams. RandomAccessFile input = null; OutputStream output = null; try { // Open streams. input = new RandomAccessFile(file, "r"); output = response.getOutputStream(); if (ranges.isEmpty() || ranges.get(0) == full) { // Return full file. Range r = full; response.setContentType(contentType); response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total); response.setHeader("Content-Length", String.valueOf(r.length)); copy(input, output, r.start, r.length); } else if (ranges.size() == 1) { // Return single part of file. Range r = ranges.get(0); response.setContentType(contentType); response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total); response.setHeader("Content-Length", String.valueOf(r.length)); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206. // Copy single part range. copy(input, output, r.start, r.length); } else { // Return multiple parts of file. response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206. // Cast back to ServletOutputStream to get the easy println methods. ServletOutputStream sos = (ServletOutputStream) output; // Copy multi part range. for (Range r : ranges) { // Add multipart boundary and header fields for every range. sos.println(); sos.println("--" + MULTIPART_BOUNDARY); sos.println("Content-Type: " + contentType); sos.println("Content-Range: bytes " + r.start + "-" + r.end + "/" + r.total); // Copy single part range of multi part range. copy(input, output, r.start, r.length); } // End with multipart boundary. sos.println(); sos.println("--" + MULTIPART_BOUNDARY + "--"); } } finally { close(output); close(input); } } // Helpers (can be refactored to public utility class) ---------------------------------------- private void close(Closeable resource) { if (resource != null) { try { resource.close(); } catch (IOException ignore) { } } } private void copy(RandomAccessFile input, OutputStream output, long start, long length) throws IOException { byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; int read; try { if (input.length() == length) { // Write full range. while ((read = input.read(buffer)) > 0) { output.write(buffer, 0, read); } } else { input.seek(start); long toRead = length; while ((read = input.read(buffer)) > 0) { if ((toRead -= read) > 0) { output.write(buffer, 0, read); } else { output.write(buffer, 0, (int) toRead + read); break; } } } } catch (IOException ignore) { } } private long sublong(String value, int beginIndex, int endIndex) { String substring = value.substring(beginIndex, endIndex); return (substring.length() > 0) ? Long.parseLong(substring) : -1; } private boolean accepts(String acceptHeader, String toAccept) { String[] acceptValues = acceptHeader.split("\\s*(,|;)\\s*"); Arrays.sort(acceptValues); return Arrays.binarySearch(acceptValues, toAccept) > -1 || Arrays.binarySearch(acceptValues, toAccept.replaceAll("/.*$", "/*")) > -1 || Arrays.binarySearch(acceptValues, "*/*") > -1; } private boolean matches(String matchHeader, String toMatch) { String[] matchValues = matchHeader.split("\\s*,\\s*"); Arrays.sort(matchValues); return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1; } // Inner classes ------------------------------------------------------------------------------ protected class Range { long start; long end; long length; long total; public Range(long start, long end, long total) { this.start = start; this.end = end; this.length = end - start + 1; this.total = total; } } }
通过ProgressListener实现大文件上传时进度条的显示
1)application-context.xml
<bean id="multipartResolver" class="com.rensanning.test.core.fileupload.CustomMultipartResolver"> <property name="defaultEncoding" value="UTF-8"/> <property name="fileSizeMax" value="20971520"/><!-- 20M : Maximum size of a single uploaded file--> <property name="maxUploadSize" value="52428800"/><!-- 50M : The maximum allowed size of a complete request--> </bean>
2)CustomMultipartResolver.java
public class CustomMultipartResolver extends CommonsMultipartResolver { @Autowired private CustomProgressListener progressListener; public void setFileUploadProgressListener(CustomProgressListener progressListener){ this.progressListener = progressListener; } public void setFileSizeMax(long fileSizeMax) { getFileUpload().setFileSizeMax(fileSizeMax); } @Override public MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { String encoding = determineEncoding(request); FileUpload fileUpload = prepareFileUpload(encoding); progressListener.setSession(request.getSession()); fileUpload.setProgressListener(progressListener); try { List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); return parseFileItems(fileItems, encoding); } catch (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadBase.FileSizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex); } catch (FileUploadException ex) { throw new MultipartException("Could not parse multipart servlet request", ex); } } }
3)CustomProgressListener.java
@Component public class CustomProgressListener implements ProgressListener { private HttpSession session; public void setSession(HttpSession session){ this.session = session; ProgressInfo ps = new ProgressInfo(); this.session.setAttribute(Constants.SESSION_KEY_UPLOAD_PROGRESS_INFO, ps); } @Override public void update(long pBytesRead, long pContentLength, int pItems) { ProgressInfo ps = (ProgressInfo) session.getAttribute(Constants.SESSION_KEY_UPLOAD_PROGRESS_INFO); ps.setBytesRead(pBytesRead); ps.setContentLength(pContentLength); ps.setItemSeq(pItems); } }
4)ProgressInfo.java
public class ProgressInfo { private long bytesRead; private long contentLength; private int itemSeq; public long getBytesRead() { return bytesRead; } public void setBytesRead(long bytesRead) { this.bytesRead = bytesRead; } public long getContentLength() { return contentLength; } public void setContentLength(long contentLength) { this.contentLength = contentLength; } public int getItemSeq() { return itemSeq; } public void setItemSeq(int itemSeq) { this.itemSeq = itemSeq; } }
5)Controller
@ResponseBody @RequestMapping(value = "admin/common/getProgress.do", method = RequestMethod.GET) public String getProgress(HttpServletRequest request, HttpServletResponse response) { if (request.getSession().getAttribute(Constants.SESSION_KEY_UPLOAD_PROGRESS_INFO) == null) { return ""; } ProgressInfo ps = (ProgressInfo) request.getSession().getAttribute(Constants.SESSION_KEY_UPLOAD_PROGRESS_INFO); Double percent = 0d; if (ps.getContentLength() != 0L) { percent = (double) ps.getBytesRead() / (double) ps.getContentLength() * 1.0d; if (percent != 0d) { DecimalFormat df = new DecimalFormat("0.00"); percent = Double.parseDouble(df.format(percent)); } } int pp = (int)(percent * 100); return String.valueOf(pp); }
6)JSP
<div class="control-group" id="progressbar" style="display:none;"> <div class="progress progress-striped active"> <div class="bar" id="progressbardata" style="width: 0;"></div> </div> </div> <script language="javascript"> function upload() { $('#uploadForm').submit(); var interval = setInterval(function() { $.ajax({ dataType : "json", url : "<%=request.getContextPath()%>/admin/common/getProgress.do", contentType : "application/json; charset=utf-8", type : "GET", success : function(data, stats) { if(data) { $('#progressbar').show(); console.log(data); if (data == '100') { clearInterval(interval); } else { $('#progressbardata').width(data+'%'); } } } }); }, 100); } </script>
参考:
How to Implement HTTP byte-range requests in Spring MVC
Implementing HTTP byte-range requests in Spring MVC
FileServlet supporting resume and caching and GZIP
- ByteRangeViewRender.zip (3.4 KB)
- 下载次数: 357
评论
2 楼
ausit
2015-12-13
请问,函数在getFileUpload().setFileSizeMax(fileSizeMax);
在哪里 ?能分享个能跑的demo么,谢谢
在哪里 ?能分享个能跑的demo么,谢谢
1 楼
lis1314
2015-10-28
mark!
相关推荐
通过以上步骤,我们就能在Spring Boot应用中实现大文件的断点续传功能。这种功能的实现不仅可以提高用户满意度,还能减少服务器带宽和存储资源的浪费。在实际项目中,还需要考虑安全性、性能优化等方面的问题,例如...
综上所述,通过Spring Boot和plupload的结合,我们可以实现大文件的断点续传功能,提高文件上传的稳定性和效率。在实际项目中,还需要考虑错误处理、安全性(如防止重复上传、文件覆盖)以及性能优化等问题。这个名...
以上就是“webuploader MVC 切片上传-断点续传-秒传Demo”的核心知识点,这种技术不仅提高了大文件上传的效率,还增强了系统的稳定性和用户体验。在实际项目中,结合WebUploader与其他MVC框架,如ASP.NET MVC、...
断点续传技术是现代网络下载中非常关键的一项功能,尤其在大文件传输时更为重要。这个技术允许用户中断下载后,在同一位置继续下载,而无需重新开始,从而节省了大量的时间和带宽资源。在安卓系统中,实现断点续传...
曾经没有上传过:后台返回前端,表示从未上传过此文件,前端通过技术,将大文件分隔成无数个小文件,一一上传 曾经上传一部分:前端在上传每个模块之前,先请求后台,判断此模块是否已经上传过,如果已经上传过,...
Java实现断点续传是一项在文件传输中非常实用的技术,特别是在大文件传输或者网络不稳定的情况下。断点续传允许用户在文件传输中断后从上次中断的位置继续,而不是重新开始整个传输过程,极大地提高了效率和用户体验...
本项目"springboot+vue 大文件上传 包括断点续传 秒传 分片上传.zip"提供了一套完整的解决方案,针对大文件上传进行了优化,确保了上传的高效性和可靠性。 首先,我们来看SpringBoot的部分。SpringBoot是基于Spring...
在本文中,我们将深入探讨如何使用SpringBoot框架与Plupload工具进行集成,以实现文件的批量上传、断点续传和秒传功能。这个项目基于SpringBoot 2和Plupload 2.3.6,提供了直观的上传进度条,并且是在IntelliJ IDEA...
基于SpringBoot的文件上传系统,前后端分离,单文件上传,多文件上传,大文件上传,断点续传,文件秒传,图片上传 项目经过严格测试,确保可以运行! 采用前后端分离的方式进行开发,实现了几种常用的文件上传功能...
在IT行业中,大文件上传和断点续传是常见的需求,尤其在云存储、文件分享以及数据备份等场景。SpringBoot作为一个轻量级的Java框架,因其简洁的配置和强大的功能,被广泛应用在各种项目中。本项目"大文件上传支持...
Spring Boot结合MinIO实现的大文件分片上传技术,通过将大文件分割成小片段并并行上传,有效提升了文件上传速度和稳定性。该技术利用了Spring Boot框架的强大功能和MinIO对象存储的高效性,使得大文件的上传过程更加...
前端Excel大文件file slice分片,md5校验文件完整性并作文件标识记录写入数据库,支持断点续传。文件上传完毕后,使用EasyExcel读取文件流,处理Excel数据写入数据库中,可处理百万级数据。项目完整,连接数据库即可...
在IT行业中,大文件上传和断点续传是常见的需求,尤其是在处理用户生成内容或大数据交换的场景。本文将深入探讨如何使用Webupload与SpringBoot框架实现大文件上传及断点续传功能。 首先,SpringBoot是Java领域的一...
本项目"java实现大文件上传分片上传断点续传.zip"提供了一个基于SpringBoot框架的解决方案,它实现了大文件的分片上传和断点续传功能。以下是关于这个项目的关键知识点的详细说明: 1. **大文件上传**: - 大文件...
在本文中,我们将深入探讨如何使用Spring MVC框架与Ajax技术结合来实现文件上传的功能。Spring MVC是Spring框架的一部分,提供了一种模型-视图-控制器(MVC)架构模式,用于构建Web应用程序。Ajax(Asynchronous ...
断点续传功能则进一步提高了文件传输的效率和用户体验,尤其是在处理大文件时。本项目提供了两种实现方式,一种是基于Web端的上传下载,另一种是通过Java接口实现的断点续传上传。 1. **Web端上传下载**: - 这种...
本书共计10章,分别介绍了快速搭建Spring Web应用、精通MVC结构、URL映射、文件上传与错误处理、创建Restful应用、保护应用、单元测试与验收测试、优化请求、将Web应用部署到云等内容,循序渐进地讲解了Spring MVC4...
这个场景通常涉及到前端的JavaScript或jQuery库(如jQuery File Upload)与后端的Spring MVC控制器之间的交互,以及可能的多线程处理来跟踪文件上传的进度。接下来,我们将深入探讨如何在Spring MVC中实现这一功能。...
在Spring MVC框架中,plUpload是一个非常实用的前端文件上传插件,尤其适用于处理大文件,如视频和图片的断点续传。该插件支持多浏览器兼容,提供了丰富的功能和自定义选项,使得文件上传体验更加友好。下面将详细...