Skip to content

Commit

Permalink
Merge pull request #257 from jcarvalho/low-level-file-store
Browse files Browse the repository at this point in the history
[io] Added low level API for file download
  • Loading branch information
pedrosan7os committed Dec 10, 2015
2 parents 4aee81d + 3129668 commit 150722d
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.common.io.Files;

/**
Expand Down Expand Up @@ -86,30 +88,58 @@ public boolean isDefault() {
}

/**
* Returns the 'sendfile' path for the given file.
* Attempts to download the given file into the given response, in a more efficient manner than loading the file into memory
* and dumping it into the output stream.
*
* <strong>PRIVATE API:</strong>
* Note that this is Bennu IO private API, and should not be used outside this module.
* If you do use it, do it at your own risk, as this API is subject to change without
* any warning.
* This method returns a boolean indicating whether the low-level download was performed. If not, callers are expected to
* handle the file download in the regular manner.
*
* @param file
* The file to check
* The file to download
* @param request
* The HttpServletRequest that originated the file download request
* @param response
* The HttpServletResponse to which the response should be written to
* @param start
* The start offset within the file. The value of this number MUST be between 0 and the size of the file
* @param end
* The end offset within the file. The value within this number MUST be between 1 and the size of the file
* @return
* An optional sendfile path
* Whether a low-level download was performed
* @throws IOException
* If an exception occurs while downloading the file
* @throws NullPointerException
* If any of the arguments is null
*/
public static Optional<String> sendfilePath(GenericFile file) {
return file.getStorage().getSendfilePath(file.getContentKey());
public static boolean tryDownloadFile(GenericFile file, HttpServletRequest request, HttpServletResponse response, long start,
long end) throws IOException {
return file.getFileStorage().tryLowLevelDownload(file, request, response, start, end);
}

/*
* Retrieves the file path for the given content key,
* if this storage supports it, and the file is indeed stored
* in the file system.
/**
* Attempts to perform a low-level download of the given file into the given response. {@link FileStorage} instances are
* expected to implement this whenever possible.
*
* The default implementation returns {@code false}, as just tells any caller to fallback to downloading the file in the
* regular manner.
*
* By default returns an empty optional.
* @param file
* The file to download
* @param request
* The HttpServletRequest that originated the file download request
* @param response
* The HttpServletResponse to which the response should be written to
* @param start
* The start offset within the file. The value of this number MUST be between 0 and the size of the file
* @param end
* The end offset within the file. The value within this number MUST be between 1 and the size of the file
* @return
* Whether a low-level download was performed
* @throws IOException
* If an exception occurs while downloading the file
*/
Optional<String> getSendfilePath(String contentKey) {
return Optional.empty();
protected boolean tryLowLevelDownload(GenericFile file, HttpServletRequest request, HttpServletResponse response, long start,
long end) throws IOException {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

import jvstm.PerTxBox;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -220,14 +223,63 @@ public InputStream readAsInputStream(String uniqueIdentification) {
}
}

/*
* Attempt to use the 'sendfile' primitive to download the file.
*
* This feature may not be supported, or the file may not be stored in the
* filesystem, causing this not to work.
*
* However, when it works, it provides great benefits, as the file must not
* be read to the Java Heap, only to be written to a socket, thus greatly
* reducing memory consumption.
*/
@Override
protected boolean tryLowLevelDownload(GenericFile file, HttpServletRequest request, HttpServletResponse response, long start,
long end) {
if (supportsSendfile(request)) {
Optional<String> filePath = getSendfilePath(file.getContentKey());
if (filePath.isPresent()) {
handleSendfile(file, filePath.get(), request, response, start, end);
return true;
} else {
return false;
}
}
return false;
}

/*
* Sendfile is available, and the file is stored in the filesystem, so instruct
* the container to directly write the file.
*
* For now, we only support the Tomcat-specific 'sendfile' implementation.
* See: http://tomcat.apache.org/tomcat-7.0-doc/aio.html#Asynchronous_writes
*/
private static void handleSendfile(GenericFile file, String filename, HttpServletRequest request,
HttpServletResponse response, long start, long end) {
response.setHeader("X-Bennu-Sendfile", "true");
request.setAttribute("org.apache.tomcat.sendfile.filename", filename);
request.setAttribute("org.apache.tomcat.sendfile.start", Long.valueOf(start));
request.setAttribute("org.apache.tomcat.sendfile.end", Long.valueOf(end + 1));
}

/*
* Checks if the container supports usage of the 'sendfile' primitive.
*
* For now, we only support the Tomcat-specific 'sendfile' implementation.
* See: http://tomcat.apache.org/tomcat-7.0-doc/aio.html#Asynchronous_writes
*/
private static boolean supportsSendfile(HttpServletRequest request) {
return Boolean.TRUE.equals(request.getAttribute("org.apache.tomcat.sendfile.support"));
}

/*
* Returns the absolute path for the given content key.
*
* It must first check if the file indeed exists, in order
* for the application to throw the proper exception.
*/
@Override
Optional<String> getSendfilePath(String uniqueIdentification) {
private Optional<String> getSendfilePath(String uniqueIdentification) {
String path = getFullPath(uniqueIdentification) + uniqueIdentification;
if (new File(path).exists()) {
return Optional.of(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -126,7 +125,7 @@ public static void downloadFile(GenericFile file, HttpServletRequest request, Ht
response.setStatus(HttpServletResponse.SC_OK);
}

if (sendFile(file, request, response, start, end)) {
if (FileStorage.tryDownloadFile(file, request, response, start, end)) {
return;
}
try (InputStream stream = file.getStream()) {
Expand Down Expand Up @@ -162,53 +161,4 @@ private static void copyStream(InputStream in, OutputStream out, long start, lon
}
}

/*
* Attempt to use the 'sendfile' primitive to download the file.
*
* This feature may not be supported, or the file may not be stored in the
* filesystem, causing this not to work.
*
* However, when it works, it provides great benefits, as the file must not
* be read to the Java Heap, only to be written to a socket, thus greatly
* reducing memory consumption.
*/
private static boolean sendFile(GenericFile file, HttpServletRequest request, HttpServletResponse response, long start,
long end) {
if (supportsSendfile(request)) {
Optional<String> filePath = FileStorage.sendfilePath(file);
if (filePath.isPresent()) {
handleSendfile(file, filePath.get(), request, response, start, end);
return true;
} else {
return false;
}
}
return false;
}

/*
* Sendfile is available, and the file is stored in the filesystem, so instruct
* the container to directly write the file.
*
* For now, we only support the Tomcat-specific 'sendfile' implementation.
* See: http://tomcat.apache.org/tomcat-7.0-doc/aio.html#Asynchronous_writes
*/
private static void handleSendfile(GenericFile file, String filename, HttpServletRequest request,
HttpServletResponse response, long start, long end) {
response.setHeader("X-Bennu-Sendfile", "true");
request.setAttribute("org.apache.tomcat.sendfile.filename", filename);
request.setAttribute("org.apache.tomcat.sendfile.start", Long.valueOf(start));
request.setAttribute("org.apache.tomcat.sendfile.end", Long.valueOf(end + 1));
}

/*
* Checks if the container supports usage of the 'sendfile' primitive.
*
* For now, we only support the Tomcat-specific 'sendfile' implementation.
* See: http://tomcat.apache.org/tomcat-7.0-doc/aio.html#Asynchronous_writes
*/
private static boolean supportsSendfile(HttpServletRequest request) {
return Boolean.TRUE.equals(request.getAttribute("org.apache.tomcat.sendfile.support"));
}

}

0 comments on commit 150722d

Please sign in to comment.