mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-14 17:58:59 +00:00
ALF-17387: Support For HTTP Range Requests in Repository WebScripts
- Added HttpRangeProcessor.processRange which takes a WebScriptResponse parameter instead of HttpServletResponse - Changed HttpRangeProcessor.processSingeRange and HttpRangeProcessor.processMultiRange to accept a generic Object parameter then cast to the appropriate WebScriptResponse or HttpServletResponse - Added Javadoc to HttpRangeProcessor.processRange - Changed StreamContent.streamContentImpl to add code from BaseDownloadContentServlet which does the work of processing the range header from the request - Changed StreamContent.streamContentImpl method signature to accept nodeRef and propertyQName parameters needed for multi-range requests - Modified methods which override or call StreamContent.streamContentImpl for new method signature, passing in nodeRef and propertyQName or nulls where appropriate git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@45222 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -63,7 +63,9 @@ public class ContentInfo extends StreamContent
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void streamContentImpl(WebScriptRequest req, WebScriptResponse res, ContentReader reader, boolean attach, Date modified, String eTag, String attachFileName)
|
protected void streamContentImpl(WebScriptRequest req, WebScriptResponse res,
|
||||||
|
ContentReader reader, NodeRef nodeRef, QName propertyQName,
|
||||||
|
boolean attach, Date modified, String eTag, String attachFileName)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
setAttachment(res, attach, attachFileName);
|
setAttachment(res, attach, attachFileName);
|
||||||
|
@@ -36,6 +36,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.repo.content.MimetypeMap;
|
import org.alfresco.repo.content.MimetypeMap;
|
||||||
import org.alfresco.repo.content.filestore.FileContentReader;
|
import org.alfresco.repo.content.filestore.FileContentReader;
|
||||||
|
import org.alfresco.repo.web.util.HttpRangeProcessor;
|
||||||
import org.alfresco.repo.webdav.WebDAVHelper;
|
import org.alfresco.repo.webdav.WebDAVHelper;
|
||||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||||
import org.alfresco.service.cmr.repository.ContentReader;
|
import org.alfresco.service.cmr.repository.ContentReader;
|
||||||
@@ -76,6 +77,12 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw
|
|||||||
*/
|
*/
|
||||||
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z", Locale.US);
|
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z", Locale.US);
|
||||||
|
|
||||||
|
private static final String HEADER_CONTENT_RANGE = "Content-Range";
|
||||||
|
private static final String HEADER_CONTENT_LENGTH = "Content-Length";
|
||||||
|
private static final String HEADER_ACCEPT_RANGES = "Accept-Ranges";
|
||||||
|
private static final String HEADER_RANGE = "Range";
|
||||||
|
private static final String HEADER_USER_AGENT = "User-Agent";
|
||||||
|
|
||||||
/** Services */
|
/** Services */
|
||||||
protected PermissionService permissionService;
|
protected PermissionService permissionService;
|
||||||
protected NodeService nodeService;
|
protected NodeService nodeService;
|
||||||
@@ -430,7 +437,7 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stream the content
|
// Stream the content
|
||||||
streamContentImpl(req, res, reader, attach, modified, modified == null ? null : String.valueOf(modified.getTime()), attachFileName, model);
|
streamContentImpl(req, res, reader, nodeRef, propertyQName, attach, modified, modified == null ? null : String.valueOf(modified.getTime()), attachFileName, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -668,7 +675,7 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw
|
|||||||
long lastModified = modifiedTime == null ? file.lastModified() : modifiedTime;
|
long lastModified = modifiedTime == null ? file.lastModified() : modifiedTime;
|
||||||
Date lastModifiedDate = new Date(lastModified);
|
Date lastModifiedDate = new Date(lastModified);
|
||||||
|
|
||||||
streamContentImpl(req, res, reader, attach, lastModifiedDate, String.valueOf(lastModifiedDate.getTime()), attachFileName, model);
|
streamContentImpl(req, res, reader, null, null, attach, lastModifiedDate, String.valueOf(lastModifiedDate.getTime()), attachFileName, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -677,6 +684,8 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw
|
|||||||
* @param req The request
|
* @param req The request
|
||||||
* @param res The response
|
* @param res The response
|
||||||
* @param reader The reader
|
* @param reader The reader
|
||||||
|
* @param nodeRef The content nodeRef if applicable
|
||||||
|
* @param propertyQName The content property if applicable
|
||||||
* @param attach Indicates whether the content should be streamed as an attachment or not
|
* @param attach Indicates whether the content should be streamed as an attachment or not
|
||||||
* @param modified Modified date of content
|
* @param modified Modified date of content
|
||||||
* @param eTag ETag to use
|
* @param eTag ETag to use
|
||||||
@@ -686,12 +695,14 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw
|
|||||||
protected void streamContentImpl(WebScriptRequest req,
|
protected void streamContentImpl(WebScriptRequest req,
|
||||||
WebScriptResponse res,
|
WebScriptResponse res,
|
||||||
ContentReader reader,
|
ContentReader reader,
|
||||||
|
NodeRef nodeRef,
|
||||||
|
QName propertyQName,
|
||||||
boolean attach,
|
boolean attach,
|
||||||
Date modified,
|
Date modified,
|
||||||
String eTag,
|
String eTag,
|
||||||
String attachFileName) throws IOException
|
String attachFileName) throws IOException
|
||||||
{
|
{
|
||||||
streamContentImpl(req, res, reader, attach, modified, eTag, attachFileName, null);
|
streamContentImpl(req, res, reader, nodeRef, propertyQName, attach, modified, eTag, attachFileName, null);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Stream content implementation
|
* Stream content implementation
|
||||||
@@ -699,6 +710,8 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw
|
|||||||
* @param req The request
|
* @param req The request
|
||||||
* @param res The response
|
* @param res The response
|
||||||
* @param reader The reader
|
* @param reader The reader
|
||||||
|
* @param nodeRef The content nodeRef if applicable
|
||||||
|
* @param propertyQName The content property if applicable
|
||||||
* @param attach Indicates whether the content should be streamed as an attachment or not
|
* @param attach Indicates whether the content should be streamed as an attachment or not
|
||||||
* @param modified Modified date of content
|
* @param modified Modified date of content
|
||||||
* @param eTag ETag to use
|
* @param eTag ETag to use
|
||||||
@@ -708,6 +721,8 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw
|
|||||||
protected void streamContentImpl(WebScriptRequest req,
|
protected void streamContentImpl(WebScriptRequest req,
|
||||||
WebScriptResponse res,
|
WebScriptResponse res,
|
||||||
ContentReader reader,
|
ContentReader reader,
|
||||||
|
NodeRef nodeRef,
|
||||||
|
QName propertyQName,
|
||||||
boolean attach,
|
boolean attach,
|
||||||
Date modified,
|
Date modified,
|
||||||
String eTag,
|
String eTag,
|
||||||
@@ -730,20 +745,58 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set mimetype for the content and the character encoding + length for the stream
|
res.setHeader(HEADER_ACCEPT_RANGES, "bytes");
|
||||||
res.setContentType(mimetype);
|
|
||||||
res.setContentEncoding(reader.getEncoding());
|
|
||||||
res.setHeader("Content-Length", Long.toString(reader.getSize()));
|
|
||||||
|
|
||||||
// set caching
|
|
||||||
setResponseCache(res, modified, eTag, model);
|
|
||||||
|
|
||||||
// get the content and stream directly to the response output stream
|
|
||||||
// assuming the repository is capable of streaming in chunks, this should allow large files
|
|
||||||
// to be streamed directly to the browser response stream.
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
reader.getContent(res.getOutputStream());
|
boolean processedRange = false;
|
||||||
|
String range = req.getHeader(HEADER_CONTENT_RANGE);
|
||||||
|
if (range == null)
|
||||||
|
{
|
||||||
|
range = req.getHeader(HEADER_RANGE);
|
||||||
|
}
|
||||||
|
if (range != null)
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("Found content range header: " + range);
|
||||||
|
|
||||||
|
// ensure the range header is starts with "bytes=" and process the range(s)
|
||||||
|
if (range.length() > 6)
|
||||||
|
{
|
||||||
|
if (range.indexOf(',') != -1 && (nodeRef == null || propertyQName == null))
|
||||||
|
{
|
||||||
|
if (logger.isInfoEnabled())
|
||||||
|
logger.info("Multi-range only supported for nodeRefs");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
HttpRangeProcessor rangeProcessor = new HttpRangeProcessor(contentService);
|
||||||
|
processedRange = rangeProcessor.processRange(
|
||||||
|
res, reader, range.substring(6), nodeRef, propertyQName,
|
||||||
|
mimetype, req.getHeader(HEADER_USER_AGENT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (processedRange == false)
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("Sending complete file content...");
|
||||||
|
|
||||||
|
// set mimetype for the content and the character encoding for the stream
|
||||||
|
res.setContentType(mimetype);
|
||||||
|
res.setContentEncoding(reader.getEncoding());
|
||||||
|
|
||||||
|
// return the complete entity range
|
||||||
|
long size = reader.getSize();
|
||||||
|
res.setHeader(HEADER_CONTENT_RANGE, "bytes 0-" + Long.toString(size-1L) + "/" + Long.toString(size));
|
||||||
|
res.setHeader(HEADER_CONTENT_LENGTH, Long.toString(size));
|
||||||
|
|
||||||
|
// set caching
|
||||||
|
setResponseCache(res, modified, eTag, model);
|
||||||
|
|
||||||
|
// get the content and stream directly to the response output stream
|
||||||
|
// assuming the repository is capable of streaming in chunks, this should allow large files
|
||||||
|
// to be streamed directly to the browser response stream.
|
||||||
|
reader.getContent( res.getOutputStream() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (SocketException e1)
|
catch (SocketException e1)
|
||||||
{
|
{
|
||||||
|
@@ -227,7 +227,7 @@ public class NodeContentGet extends StreamContent
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
res.setStatus(HttpStatus.SC_OK);
|
res.setStatus(HttpStatus.SC_OK);
|
||||||
streamContentImpl(req, res, textReader, false, modified, String.valueOf(modified.getTime()), null);
|
streamContentImpl(req, res, textReader, null, null, false, modified, String.valueOf(modified.getTime()), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
@@ -20,6 +20,7 @@ package org.alfresco.repo.web.util;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -34,6 +35,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
|
|||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates HTTP response for "Range" scoped HTTP requests for content.
|
* Generates HTTP response for "Range" scoped HTTP requests for content.
|
||||||
@@ -64,7 +66,17 @@ public class HttpRangeProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a range header - handles single and multiple range requests.
|
* Process a range header for a HttpServletResponse - handles single and multiple range requests.
|
||||||
|
*
|
||||||
|
* @param res the HTTP servlet response
|
||||||
|
* @param reader the content reader
|
||||||
|
* @param range the byte range
|
||||||
|
* @param ref the content NodeRef
|
||||||
|
* @param property the content property
|
||||||
|
* @param mimetype the content mimetype
|
||||||
|
* @param userAgent the user agent string
|
||||||
|
* @return whether or not the range could be processed
|
||||||
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public boolean processRange(HttpServletResponse res, ContentReader reader, String range,
|
public boolean processRange(HttpServletResponse res, ContentReader reader, String range,
|
||||||
NodeRef ref, QName property, String mimetype, String userAgent)
|
NodeRef ref, QName property, String mimetype, String userAgent)
|
||||||
@@ -81,6 +93,34 @@ public class HttpRangeProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a range header for a WebScriptResponse - handles single and multiple range requests.
|
||||||
|
*
|
||||||
|
* @param res the webscript response
|
||||||
|
* @param reader the content reader
|
||||||
|
* @param range the byte range
|
||||||
|
* @param ref the content NodeRef
|
||||||
|
* @param property the content property
|
||||||
|
* @param mimetype the content mimetype
|
||||||
|
* @param userAgent the user agent string
|
||||||
|
* @return whether or not the range could be processed
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public boolean processRange(WebScriptResponse res, ContentReader reader, String range,
|
||||||
|
NodeRef ref, QName property, String mimetype, String userAgent)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
// test for multiple byte ranges present in header
|
||||||
|
if (range.indexOf(',') == -1)
|
||||||
|
{
|
||||||
|
return processSingleRange(res, reader, range, mimetype);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return processMultiRange(res, range, ref, property, mimetype, userAgent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a single range request.
|
* Process a single range request.
|
||||||
*
|
*
|
||||||
@@ -91,9 +131,26 @@ public class HttpRangeProcessor
|
|||||||
*
|
*
|
||||||
* @return true if processed range, false otherwise
|
* @return true if processed range, false otherwise
|
||||||
*/
|
*/
|
||||||
private boolean processSingleRange(HttpServletResponse res, ContentReader reader, String range, String mimetype)
|
private boolean processSingleRange(Object res, ContentReader reader, String range, String mimetype)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
|
// Handle either HttpServletResponse or WebScriptResponse
|
||||||
|
HttpServletResponse httpServletResponse = null;
|
||||||
|
WebScriptResponse webScriptResponse = null;
|
||||||
|
if (res instanceof HttpServletResponse)
|
||||||
|
{
|
||||||
|
httpServletResponse = (HttpServletResponse) res;
|
||||||
|
}
|
||||||
|
else if (res instanceof WebScriptResponse)
|
||||||
|
{
|
||||||
|
webScriptResponse = (WebScriptResponse) res;
|
||||||
|
}
|
||||||
|
if (httpServletResponse == null && webScriptResponse == null)
|
||||||
|
{
|
||||||
|
// Unknown response object type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// return the specific set of bytes as requested in the content-range header
|
// return the specific set of bytes as requested in the content-range header
|
||||||
|
|
||||||
/* Examples of byte-content-range-spec values, assuming that the entity contains total of 1234 bytes:
|
/* Examples of byte-content-range-spec values, assuming that the entity contains total of 1234 bytes:
|
||||||
@@ -117,18 +174,38 @@ public class HttpRangeProcessor
|
|||||||
if (getLogger().isDebugEnabled())
|
if (getLogger().isDebugEnabled())
|
||||||
getLogger().debug("Failed to parse range header - returning 416 status code: " + err.getMessage());
|
getLogger().debug("Failed to parse range header - returning 416 status code: " + err.getMessage());
|
||||||
|
|
||||||
res.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
if (httpServletResponse != null)
|
||||||
res.setHeader(HEADER_CONTENT_RANGE, "\"*\"");
|
{
|
||||||
res.getOutputStream().close();
|
httpServletResponse.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||||
|
httpServletResponse.setHeader(HEADER_CONTENT_RANGE, "\"*\"");
|
||||||
|
httpServletResponse.getOutputStream().close();
|
||||||
|
}
|
||||||
|
else if (webScriptResponse != null)
|
||||||
|
{
|
||||||
|
webScriptResponse.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||||
|
webScriptResponse.setHeader(HEADER_CONTENT_RANGE, "\"*\"");
|
||||||
|
webScriptResponse.getOutputStream().close();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set Partial Content status and range headers
|
// set Partial Content status and range headers
|
||||||
res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
String contentRange = "bytes " + Long.toString(r.start) +
|
||||||
res.setContentType(mimetype);
|
"-" + Long.toString(r.end) + "/" + Long.toString(reader.getSize());
|
||||||
String contentRange = "bytes " + Long.toString(r.start) + "-" + Long.toString(r.end) + "/" + Long.toString(reader.getSize());
|
if (httpServletResponse != null)
|
||||||
res.setHeader(HEADER_CONTENT_RANGE, contentRange);
|
{
|
||||||
res.setHeader(HEADER_CONTENT_LENGTH, Long.toString((r.end - r.start) + 1L));
|
httpServletResponse.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||||
|
httpServletResponse.setContentType(mimetype);
|
||||||
|
httpServletResponse.setHeader(HEADER_CONTENT_RANGE, contentRange);
|
||||||
|
httpServletResponse.setHeader(HEADER_CONTENT_LENGTH, Long.toString((r.end - r.start) + 1L));
|
||||||
|
}
|
||||||
|
else if (webScriptResponse != null)
|
||||||
|
{
|
||||||
|
webScriptResponse.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||||
|
webScriptResponse.setContentType(mimetype);
|
||||||
|
webScriptResponse.setHeader(HEADER_CONTENT_RANGE, contentRange);
|
||||||
|
webScriptResponse.setHeader(HEADER_CONTENT_LENGTH, Long.toString((r.end - r.start) + 1L));
|
||||||
|
}
|
||||||
|
|
||||||
if (getLogger().isDebugEnabled())
|
if (getLogger().isDebugEnabled())
|
||||||
getLogger().debug("Processing: Content-Range: " + contentRange);
|
getLogger().debug("Processing: Content-Range: " + contentRange);
|
||||||
@@ -137,7 +214,15 @@ public class HttpRangeProcessor
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// output the binary data for the range
|
// output the binary data for the range
|
||||||
ServletOutputStream os = res.getOutputStream();
|
OutputStream os = null;
|
||||||
|
if (httpServletResponse != null)
|
||||||
|
{
|
||||||
|
os = httpServletResponse.getOutputStream();
|
||||||
|
}
|
||||||
|
else if (webScriptResponse != null)
|
||||||
|
{
|
||||||
|
os = webScriptResponse.getOutputStream();
|
||||||
|
}
|
||||||
is = reader.getContentInputStream();
|
is = reader.getContentInputStream();
|
||||||
|
|
||||||
streamRangeBytes(r, is, os, 0L);
|
streamRangeBytes(r, is, os, 0L);
|
||||||
@@ -172,11 +257,28 @@ public class HttpRangeProcessor
|
|||||||
* @return true if processed range, false otherwise
|
* @return true if processed range, false otherwise
|
||||||
*/
|
*/
|
||||||
private boolean processMultiRange(
|
private boolean processMultiRange(
|
||||||
HttpServletResponse res, String range, NodeRef ref, QName property, String mimetype, String userAgent)
|
Object res, String range, NodeRef ref, QName property, String mimetype, String userAgent)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
final Log logger = getLogger();
|
final Log logger = getLogger();
|
||||||
|
|
||||||
|
// Handle either HttpServletResponse or WebScriptResponse
|
||||||
|
HttpServletResponse httpServletResponse = null;
|
||||||
|
WebScriptResponse webScriptResponse = null;
|
||||||
|
if (res instanceof HttpServletResponse)
|
||||||
|
{
|
||||||
|
httpServletResponse = (HttpServletResponse) res;
|
||||||
|
}
|
||||||
|
else if (res instanceof WebScriptResponse)
|
||||||
|
{
|
||||||
|
webScriptResponse = (WebScriptResponse) res;
|
||||||
|
}
|
||||||
|
if (httpServletResponse == null && webScriptResponse == null)
|
||||||
|
{
|
||||||
|
// Unknown response object type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// return the sets of bytes as requested in the content-range header
|
// return the sets of bytes as requested in the content-range header
|
||||||
// the response will be formatted as multipart/byteranges media type message
|
// the response will be formatted as multipart/byteranges media type message
|
||||||
|
|
||||||
@@ -211,9 +313,18 @@ public class HttpRangeProcessor
|
|||||||
if (getLogger().isDebugEnabled())
|
if (getLogger().isDebugEnabled())
|
||||||
getLogger().debug("Failed to parse range header - returning 416 status code: " + err.getMessage());
|
getLogger().debug("Failed to parse range header - returning 416 status code: " + err.getMessage());
|
||||||
|
|
||||||
res.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
if (httpServletResponse != null)
|
||||||
res.setHeader(HEADER_CONTENT_RANGE, "\"*\"");
|
{
|
||||||
res.getOutputStream().close();
|
httpServletResponse.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||||
|
httpServletResponse.setHeader(HEADER_CONTENT_RANGE, "\"*\"");
|
||||||
|
httpServletResponse.getOutputStream().close();
|
||||||
|
}
|
||||||
|
else if (webScriptResponse != null)
|
||||||
|
{
|
||||||
|
webScriptResponse.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||||
|
webScriptResponse.setHeader(HEADER_CONTENT_RANGE, "\"*\"");
|
||||||
|
webScriptResponse.getOutputStream().close();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,11 +368,21 @@ public class HttpRangeProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// output headers as we have at least one range to process
|
// output headers as we have at least one range to process
|
||||||
res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
OutputStream os = null;
|
||||||
res.setHeader(HEADER_CONTENT_TYPE, MULTIPART_BYTERANGES_HEADER);
|
if (httpServletResponse != null)
|
||||||
res.setHeader(HEADER_CONTENT_LENGTH, Long.toString(length));
|
{
|
||||||
|
httpServletResponse.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||||
ServletOutputStream os = res.getOutputStream();
|
httpServletResponse.setHeader(HEADER_CONTENT_TYPE, MULTIPART_BYTERANGES_HEADER);
|
||||||
|
httpServletResponse.setHeader(HEADER_CONTENT_LENGTH, Long.toString(length));
|
||||||
|
os = httpServletResponse.getOutputStream();
|
||||||
|
}
|
||||||
|
else if (webScriptResponse != null)
|
||||||
|
{
|
||||||
|
webScriptResponse.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||||
|
webScriptResponse.setHeader(HEADER_CONTENT_TYPE, MULTIPART_BYTERANGES_HEADER);
|
||||||
|
webScriptResponse.setHeader(HEADER_CONTENT_LENGTH, Long.toString(length));
|
||||||
|
os =webScriptResponse.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
InputStream is = null;
|
InputStream is = null;
|
||||||
try
|
try
|
||||||
@@ -274,7 +395,8 @@ public class HttpRangeProcessor
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// output the header bytes for the range
|
// output the header bytes for the range
|
||||||
r.outputHeader(os);
|
if (os instanceof ServletOutputStream)
|
||||||
|
r.outputHeader((ServletOutputStream) os);
|
||||||
|
|
||||||
// output the binary data for the range
|
// output the binary data for the range
|
||||||
// need a new reader for each new InputStream
|
// need a new reader for each new InputStream
|
||||||
@@ -284,7 +406,8 @@ public class HttpRangeProcessor
|
|||||||
is = null;
|
is = null;
|
||||||
|
|
||||||
// section marker and flush stream
|
// section marker and flush stream
|
||||||
os.println();
|
if (os instanceof ServletOutputStream)
|
||||||
|
((ServletOutputStream) os).println();
|
||||||
os.flush();
|
os.flush();
|
||||||
}
|
}
|
||||||
catch (IOException err)
|
catch (IOException err)
|
||||||
@@ -304,7 +427,8 @@ public class HttpRangeProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// end marker
|
// end marker
|
||||||
os.println(MULTIPART_BYTERANGES_BOUNDRY_END);
|
if (os instanceof ServletOutputStream)
|
||||||
|
((ServletOutputStream) os).println(MULTIPART_BYTERANGES_BOUNDRY_END);
|
||||||
os.close();
|
os.close();
|
||||||
processedRange = true;
|
processedRange = true;
|
||||||
}
|
}
|
||||||
@@ -322,7 +446,7 @@ public class HttpRangeProcessor
|
|||||||
*
|
*
|
||||||
* @return current InputStream position - so the stream can be reused if required
|
* @return current InputStream position - so the stream can be reused if required
|
||||||
*/
|
*/
|
||||||
private void streamRangeBytes(final Range r, final InputStream is, final ServletOutputStream os, long offset)
|
private void streamRangeBytes(final Range r, final InputStream is, final OutputStream os, long offset)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
final Log logger = getLogger();
|
final Log logger = getLogger();
|
||||||
@@ -496,7 +620,6 @@ public class HttpRangeProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the logger
|
* @return the logger
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user