FileServlet.java
package controller.utils;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.OutputStream;
import java.io.Closeable;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.GZIPOutputStream;
@WebServlet(name = "FileServlet", urlPatterns = "/file/*", loadOnStartup = 0)
@MultipartConfig
public class FileServlet extends HttpServlet {
/**
* Size del buffer.
*/
private static final int DEFAULT_BUFFER_SIZE = 10240; // ..bytes = 10KB.
/**
* Expire time.
*/
private static final long DEFAULT_EXPIRE_TIME = 604800000L; // ..ms = 1 week.
/**
* Multipart byteranges.
*/
private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES";
/**
* BasePath dove vengono messe le immagini.
*/
private static String basePath = System.getenv("CATALINA_HOME")
+ File.separator + "webapps" + File.separator + "uploads";
/**
* Initialize the servlet.
*/
@Override
public void init() throws ServletException {
System.out.println(basePath);
// Validate base path.
if (this.basePath == null) {
throw new ServletException("FileServlet init param "
+ "'basePath' is required.");
} else {
File path = new File(this.basePath);
if (!path.exists()) {
throw new ServletException("FileServlet init param 'basePath' "
+ "value '" + this.basePath
+ "' does actually not exist in file system.");
} else if (!path.isDirectory()) {
throw new ServletException("FileServlet init param 'basePath' "
+ "value '" + this.basePath
+ "' is actually not a directory in file system.");
} else if (!path.canRead()) {
throw new ServletException("FileServlet init param 'basePath' "
+ "value '" + this.basePath
+ "' is actually not readable in file system.");
}
}
}
/**
* Process HEAD request. This returns the same headers as GET request,
* but without content.
*/
@Override
protected void doHead(final HttpServletRequest request,
final HttpServletResponse response)
throws IOException {
// Process request without content.
processRequest(request, response, false);
}
/**
* Process GET request.
*/
@Override
public void doGet(final HttpServletRequest request,
final HttpServletResponse response)
throws IOException {
// Process request with content.
processRequest(request, response, true);
}
/**
* Process the actual request.
*
* @param request The request to be processed.
* @param response The response to be created.
* @param content Whether the request body should
* be written (GET) or not (HEAD).
* @throws IOException If something fails at I/O level.
*/
private void processRequest(final HttpServletRequest request,
final HttpServletResponse response,
final boolean content)
throws IOException {
File file = helper(request, response);
if (file == null) {
return;
}
String fileName = file.getName();
long length = file.length();
long lastModified = file.lastModified();
String eTag = fileName + "_" + length + "_" + lastModified;
long expires = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME;
String ifNoneMatch = request.getHeader("If-None-Match");
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader("ETag", eTag); // Required in 304.
response.setDateHeader("Expires", expires);
return;
}
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
final int size = 1000;
if (ifNoneMatch == null && ifModifiedSince != -1
&& ifModifiedSince + size > lastModified) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader("ETag", eTag); // Required in 304.
response.setDateHeader("Expires", expires);
return;
}
String ifMatch = request.getHeader("If-Match");
if (ifMatch != null && !matches(ifMatch, eTag)) {
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
return;
}
long ifUnmodifiedSince = request.getDateHeader("If-Unmodified-Since");
if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + size <= lastModified) {
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
return;
}
Range full = new Range(0, length - 1, length);
List<Range> ranges = new ArrayList<Range>();
String range = request.getHeader("Range");
if (range != null) {
if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
response.setHeader("Content-Range",
"bytes */" + length); // Required in 416.
response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
return;
}
String ifRange = request.getHeader("If-Range");
if (ifRange != null && !ifRange.equals(eTag)) {
try {
long ifRangeTime = request.getDateHeader("If-Range");
if (ifRangeTime != -1 && ifRangeTime + size < lastModified) {
ranges.add(full);
}
} catch (IllegalArgumentException ignore) {
ranges.add(full);
}
}
if (ranges.isEmpty()) {
final int len = 6;
for (String part : range.substring(len).split(",")) {
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;
}
if (start > end) {
response.setHeader("Content-Range", "bytes */" + length);
response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
return;
}
ranges.add(new Range(start, end, length));
}
}
}
String contentType = getServletContext().getMimeType(fileName);
boolean acceptsGzip = false;
String disposition = "inline";
if (contentType == null) {
contentType = "application/octet-stream";
}
if (contentType.startsWith("text")) {
String acceptEncoding = request.getHeader("Accept-Encoding");
acceptsGzip = acceptEncoding != null
&& accepts(acceptEncoding, "gzip");
contentType += ";charset=UTF-8";
} else if (!contentType.startsWith("image")) {
String accept = request.getHeader("Accept");
disposition = accept != null
&& accepts(accept, contentType) ? "inline" : "attachment";
}
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);
RandomAccessFile input = null;
OutputStream output = null;
try {
input = new RandomAccessFile(file, "r");
output = response.getOutputStream();
if (ranges.isEmpty() || ranges.get(0) == full) {
Range r = full;
response.setContentType(contentType);
if (content) {
if (acceptsGzip) {
response.setHeader("Content-Encoding", "gzip");
output = new GZIPOutputStream(output, DEFAULT_BUFFER_SIZE);
} else {
response.setHeader("Content-Length",
String.valueOf(r.length));
}
copy(input, output, r.start, r.length);
}
} else if (ranges.size() == 1) {
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.
if (content) {
copy(input, output, r.start, r.length);
}
} else {
response.setContentType("multipart/byteranges; boundary="
+ MULTIPART_BOUNDARY);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.
if (content) {
ServletOutputStream sos = (ServletOutputStream) output;
for (Range r : ranges) {
sos.println();
sos.println("--" + MULTIPART_BOUNDARY);
sos.println("Content-Type: " + contentType);
sos.println("Content-Range: bytes " + r.start
+ "-" + r.end + "/" + r.total);
copy(input, output, r.start, r.length);
}
sos.println(); sos.println("--" + MULTIPART_BOUNDARY + "--");
}
}
} finally {
close(output); close(input);
}
}
/**
* Help the actual request.
*
* @param request The request to be processed.
* @param response The response to be created.
* @return file
* @throws IOException If something fails at I/O level.
*/
private File helper(final HttpServletRequest request,
final HttpServletResponse response) throws IOException {
String requestedFile = request.getPathInfo();
if (requestedFile == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
File file = new File(basePath, URLDecoder.decode(requestedFile,
StandardCharsets.UTF_8));
if (!file.exists()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
return file;
}
/**
* Returns true if the given accept header accepts the given value.
*
* @param acceptHeader The accept header.
* @param toAccept The value to be accepted.
* @return True if the given accept header accepts the given value.
*/
private static boolean accepts(final String acceptHeader,
final 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;
}
/**
* Returns true if the given match header matches the given value.
*
* @param matchHeader The match header.
* @param toMatch The value to be matched.
* @return True if the given match header matches the given value.
*/
private static boolean matches(final String matchHeader,
final String toMatch) {
String[] matchValues = matchHeader.split("\\s*,\\s*");
Arrays.sort(matchValues);
return Arrays.binarySearch(matchValues, toMatch) > -1
|| Arrays.binarySearch(matchValues, "*") > -1;
}
/**
* Returns a substring of the given string value from the given begin
* index to the given end
* index as a long. If the substring is empty, then -1 will be returned
*
* @param value The string value to return a substring as long for.
* @param beginIndex The begin index of the substring to be returned as long.
* @param endIndex The end index of the substring to be returned as long.
* @return A substring of the given string value as long or -1
* if substring is empty.
*/
private static long sublong(final String value,
final int beginIndex,
final int endIndex) {
String substring = value.substring(beginIndex, endIndex);
return (substring.length() > 0) ? Long.parseLong(substring) : -1;
}
/**
* Copy the given byte range of the given input to the given output.
*
* @param input The input to copy the given range to the given output for.
* @param output The output to copy the given range from the given input for.
* @param start Start of the byte range.
* @param length Length of the byte range.
* @throws IOException If something fails at I/O level.
*/
private static void copy(final RandomAccessFile input,
final OutputStream output,
final long start,
final long length)
throws IOException {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int read;
if (input.length() == length) {
// Write full range.
while ((read = input.read(buffer)) > 0) {
output.write(buffer, 0, read);
}
} else {
// Write partial range.
input.seek(start);
long toRead = length;
while ((read = input.read(buffer)) > 0) {
toRead -= read;
if (toRead > 0) {
output.write(buffer, 0, read);
} else {
output.write(buffer, 0, (int) toRead + read);
break;
}
}
}
}
/**
* Close the given resource.
*
* @param resource The resource to be closed.
*/
private static void close(final Closeable resource) {
if (resource != null) {
try {
resource.close();
} catch (IOException ignore) {
}
}
}
/**
* This class represents a byte range.
*/
protected class Range {
/**
* start range.
*/
private long start;
/**
* end range.
*/
private long end;
/**
* length range.
*/
private long length;
/**
* total range.
*/
private long total;
/**
* Construct a byte range.
*
* @param st Start of the byte range.
* @param en End of the byte range.
* @param tot Total length of the byte source.
*/
public Range(final long st,
final long en,
final long tot) {
this.start = st;
this.end = en;
this.length = en - st + 1;
this.total = tot;
}
}
/**
* Metodo per upload delle foto.
*
* @param request Request da dove prelevare le foto
* @return lista dei nomi dei file
* @throws ServletException
* @throws IOException
*/
public static List<String> uploadFoto(final HttpServletRequest request)
throws ServletException, IOException {
List<String> fileNameList = new ArrayList<>();
for (Part p : request.getParts()) {
if (p.getSubmittedFileName() != null
&& !p.getSubmittedFileName().isEmpty()) {
try (InputStream is = p.getInputStream()) {
String path = FileServlet.basePath + File.separator;
String nameFile = LocalDateTime.now()
.truncatedTo(ChronoUnit.MINUTES).toString()
.replace(":", "-") + p.getSubmittedFileName();
File file = new File(path + nameFile);
Files.copy(is, file.toPath(),
StandardCopyOption.REPLACE_EXISTING);
fileNameList.add(nameFile);
}
}
}
return fileNameList;
}
}