use of com.bluenimble.platform.streams.StreamDecorator in project serverless by bluenimble.
the class StreamMediaProcessor method writeRanges.
private void writeRanges(ApiRequest request, ApiResponse response, final OutputStream ros, ApiOutput output, String contentType) throws IOException {
String id = (String) output.get(ApiOutput.Defaults.Id);
Date timestamp = (Date) output.get(ApiOutput.Defaults.Timestamp);
final long length = output.length();
// Prepare some variables. The full Range represents the complete file.
Chunk full = new Chunk(0, output.length() - 1);
List<Chunk> chunks = new ArrayList<Chunk>();
// Validate and process Range and If-Range headers.
String range = (String) request.get(ApiHeaders.Range, Scope.Header);
if (!Lang.isNullOrEmpty(range)) {
// 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*)*$")) {
// Required in 416.
response.set(ApiHeaders.ContentRange, "bytes */" + output.length());
response.setStatus(ApiResponse.REQUESTED_RANGE_NOT_SATISFIABLE);
response.flushHeaders();
return;
}
String ifRange = (String) request.get(ApiHeaders.IfRange, Scope.Header);
if (ifRange != null && !ifRange.equals(id)) {
try {
// Throws IAE if invalid.
Lang.toDate(ifRange, HeadersDateFormat);
} catch (ParseException ignore) {
chunks.add(full);
}
}
// If any valid If-Range header, then process each part of byte range.
if (chunks.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) {
// Required in 416.
response.set(ApiHeaders.ContentRange, "bytes */" + length);
response.flushHeaders();
response.setStatus(ApiResponse.REQUESTED_RANGE_NOT_SATISFIABLE);
return;
}
// Add range.
chunks.add(new Chunk(start, end));
}
}
}
// Prepare and initialize response --------------------------------------------------------
// Get content type by file name and set content disposition.
String disposition = (String) output.get(ApiOutput.Defaults.Disposition);
if (Lang.isNullOrEmpty(disposition)) {
disposition = Disposition.Inline;
}
// Initialize response.
response.setBuffer(BUFFER_SIZE);
response.set(ApiHeaders.ContentType, contentType);
response.set(ApiHeaders.ContentDisposition, disposition + ";filename=\"" + output.name() + "\"");
response.set(ApiHeaders.AcceptRanges, RangeBytes);
if (id != null) {
response.set(ApiHeaders.ETag, id);
}
if (timestamp != null) {
response.set(ApiHeaders.LastModified, Lang.toString(timestamp, HeadersDateFormat));
}
Long expireTime = (Long) output.get(ApiOutput.Defaults.Expires);
if (expireTime == null) {
expireTime = EXPIRE_TIME;
}
response.set(ApiHeaders.Expires, Lang.toString(new Date(System.currentTimeMillis() + expireTime), HeadersDateFormat));
// Return full file.
if (chunks.isEmpty() || chunks.get(0) == full) {
response.set(ApiHeaders.ContentType, contentType);
response.set(ApiHeaders.ContentRange, RangeBytes + Lang.SPACE + full.start() + Lang.DASH + full.end() + Lang.SLASH + length);
response.set(ApiHeaders.ContentLength, full.size());
response.flushHeaders();
output.pipe(ros, full.start(), full.size());
} else if (chunks.size() == 1) {
Chunk chunk = chunks.get(0);
response.set(ApiHeaders.ContentType, contentType);
response.set(ApiHeaders.ContentRange, RangeBytes + Lang.SPACE + chunk.start() + Lang.DASH + chunk.end() + Lang.SLASH + length);
response.set(ApiHeaders.ContentLength, chunk.size());
// 206.
response.setStatus(ApiResponse.PARTIAL_CONTENT);
response.flushHeaders();
output.pipe(ros, chunk.start(), chunk.size());
} else {
final String boundry = Long.toHexString(System.currentTimeMillis());
// Return multiple parts of file.
response.set(ApiHeaders.ContentType, MultipartBoundary + boundry);
// 206.
response.setStatus(ApiResponse.PARTIAL_CONTENT);
response.flushHeaders();
final String ct = contentType;
output.pipe(ros, new StreamDecorator() {
@Override
public void start() throws IOException {
}
@Override
public void pre(Chunk chunk, int index) throws IOException {
ros.write(Lang.ENDLN.getBytes());
ros.write(("--" + boundry + Lang.ENDLN).getBytes());
ros.write((ApiHeaders.ContentType + Lang.COLON + Lang.SPACE + ct + Lang.ENDLN).getBytes());
ros.write((ApiHeaders.ContentRange + Lang.COLON + Lang.SPACE + RangeBytes + Lang.SPACE + chunk.start() + Lang.DASH + chunk.end() + Lang.SLASH + length + Lang.ENDLN).getBytes());
}
@Override
public void post(Chunk chunk, int index) throws IOException {
}
@Override
public void end() throws IOException {
ros.write(("\n--" + boundry + "--\n").getBytes());
}
}, (Chunk[]) chunks.toArray());
}
}
Aggregations