Search in sources :

Example 1 with StreamDecorator

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());
    }
}
Also used : StreamDecorator(com.bluenimble.platform.streams.StreamDecorator) ArrayList(java.util.ArrayList) ParseException(java.text.ParseException) Chunk(com.bluenimble.platform.streams.Chunk) Date(java.util.Date)

Aggregations

Chunk (com.bluenimble.platform.streams.Chunk)1 StreamDecorator (com.bluenimble.platform.streams.StreamDecorator)1 ParseException (java.text.ParseException)1 ArrayList (java.util.ArrayList)1 Date (java.util.Date)1