Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
pl.codewise.canaveral.mock.s3.InMemoryS3Handler Maven / Gradle / Ivy
package pl.codewise.canaveral.mock.s3;
import com.amazonaws.services.s3.Headers;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.base.Strings.*;
import static java.util.Comparator.comparing;
import static javax.servlet.http.HttpServletResponse.*;
import static org.apache.commons.lang3.StringUtils.defaultString;
public class InMemoryS3Handler extends AbstractHandler {
private static final String RESPONSE_GET_KEY_NOT_FOUND =
"" +
"" +
" NoSuchKey
" +
" The specified key does not exist. " +
" ${key} " +
" 6D57E03E89170217 " +
" AUqRbON70RPGuk/BPsnwEUzWNOZGvEEMzglqD1pYFJVcdZR9huq97PN5v34XCt9n " +
" ";
private static final String RESPONSE_NO_SUCH_BUCKET =
"\n" +
"\n" +
" NoSuchBucket
\n" +
" The specified bucket does not exist. \n" +
" ${bucket} \n" +
" 4442587FB7D0A2F9 \n" +
" ";
private static final String RESPONSE_INTERNAL_ERROR =
"\n" +
"\n" +
" InternalError
\n" +
" We encountered an internal error. Please try again. \n" +
" ${bucket} \n" +
" 4442587FB7D0A2F9 \n" +
" ";
private static final String RESPONSE_GET_BUCKET_LOCATION =
"\n" +
" ";
private static final String RESPONSE_GET_ALL_BUCKETS =
"\n" +
"\n" +
" \n" +
" bcaf1ffd86f461ca5fb16fd081034f \n" +
" webfile \n" +
" \n" +
" \n" +
" ${buckets}\n" +
" \n" +
" ";
private static final String ELEM_BUCKET =
" \n" +
" ${name} \n" +
" ${date} \n" +
" ";
private static final String RESPONSE_GET_BUCKET =
"\n" +
"\n" +
" ${bucket} \n" +
" ${prefix} \n" +
" ${marker} \n" +
" ${max-keys} \n" +
" ${is-truncated} \n" +
"${commonPrefixes}" +
"${contents}" +
" ";
private static final String ELEM_CONTENTS =
" \n" +
" ${key} \n" +
" ${last-modified} \n" +
" "${etag}" \n" +
" ${size} \n" +
" STANDARD \n" +
" \n" +
" bcaf161ca5fb16fd081034f \n" +
" webfile \n" +
" \n" +
" \n";
private static final String ELEM_COMMON_PREFIXES =
" \n" +
" ${commonPrefix} \n" +
" \n";
private static final DateTimeFormatter DATE_TIME_FORMAT_ISO = ISODateTimeFormat.dateTime();
private static final DateTimeFormatter DATE_TIME_FORMAT_HTTP =
DateTimeFormat.forPattern("EE, dd MMM yyyy HH:mm:ss 'GMT'").withLocale(Locale.ENGLISH)
.withZone(DateTimeZone.UTC);
private static final String SERVER_START_DATE_TIME = DATE_TIME_FORMAT_ISO.print(DateTime.now());
private static final Logger LOG = LoggerFactory.getLogger(InMemoryS3Handler.class);
private static final String IPADDRESS_PATTERN =
"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
private static GetRequestHandler[] GET_HANDLERS = {
InMemoryS3Handler::handleGetListBuckets,
InMemoryS3Handler::handleGetBucketLocation,
InMemoryS3Handler::handleGetBucket,
InMemoryS3Handler::handleGetObject,
};
private final HashMapS3Storage s3MemoryStorage;
private Pattern pattern;
InMemoryS3Handler(HashMapS3Storage s3MemoryStorage) {
this.s3MemoryStorage = s3MemoryStorage;
pattern = Pattern.compile(IPADDRESS_PATTERN);
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
Matcher matcher = pattern.matcher(request.getServerName());
String bucket, key;
String url = URLDecoder.decode(request.getRequestURI().substring(1), "UTF-8");
if (matcher.matches()) {
bucket = url.split("/")[0];
if (url.length() > bucket.length() + 1) {
key = url.substring(bucket.length() + 1);
} else {
key = "";
}
} else {
bucket = request.getServerName()
.replace("s3.amazonaws.com", "")
.replace("localhost", "")
.split("\\.")[0];
key = url;
}
LOG.info("Bucket {}, handling {} request: {}", bucket, request.getMethod(), key);
if ("PUT".equals(request.getMethod())) {
handlePut(request, response, key, bucket);
} else if ("GET".equals(request.getMethod())) {
handleGet(response, key, bucket, request);
} else if ("HEAD".equals(request.getMethod())) {
handleHead(response, key, bucket, request);
} else {
response.setStatus(SC_METHOD_NOT_ALLOWED);
}
baseRequest.setHandled(true);
}
private String getBase64EncodedMD5Hash(byte[] bytes) {
return DigestUtils.md5Hex(bytes);
}
private void handlePut(HttpServletRequest request, HttpServletResponse response, String key, String bucket)
throws IOException {
byte[] content = null;
if (request.getHeader("x-amz-decoded-content-length") != null) {
AWSChunkReader reader = new AWSChunkReader(request.getInputStream(), 2048);
content = new byte[] {};
while (reader.hasNext()) {
reader.next(); // header - ignore
byte[] chunk = reader.next();
content = ArrayUtils.addAll(content, chunk);
if (LOG.isTraceEnabled()) {
LOG.trace(Arrays.toString(content));
}
}
}
if (content == null) {
content = IOUtils.toByteArray(request.getInputStream());
}
s3MemoryStorage.put(bucket, key, content);
String contentMd5Hash = getBase64EncodedMD5Hash(content);
response.addHeader(Headers.ETAG, contentMd5Hash);
response.setStatus(HttpServletResponse.SC_OK);
}
private void handleGet(HttpServletResponse response, String key, String bucket, HttpServletRequest request)
throws IOException, ServletException {
for (GetRequestHandler handler : GET_HANDLERS) {
try {
if (handler.handle(this, response, key, bucket, request)) {
return;
}
} catch (IOException e) {
throw e;
} catch (Throwable throwable) {
throw new ServletException(throwable);
}
}
sendResponseInternalError(response);
}
private void handleHead(HttpServletResponse response, String key, String bucket, HttpServletRequest request)
throws IOException {
Preconditions.checkArgument(!isNullOrEmpty(key), "missing key");
Preconditions.checkArgument(!isNullOrEmpty(bucket), "missing bucket");
S3MockObject s3Object = s3MemoryStorage.get(bucket, key);
if (s3Object == null) {
sendResponseKeyNotFound(response, key);
return;
}
String contentType =
defaultString(emptyToNull(request.getParameter("response-content-type")), "application/octet-stream");
String contentLanguage = emptyToNull(request.getParameter("response-content-language"));
String expires = emptyToNull(request.getParameter("response-expires"));
String cacheControl = emptyToNull(request.getParameter("response-cache-control"));
String contentDisposition = emptyToNull(request.getParameter("response-content-disposition"));
String contentEncoding = emptyToNull(request.getParameter("response-content-encoding"));
response.setContentType(contentType);
response.setHeader("ETag", String.format("\"%s\"", getBase64EncodedMD5Hash(s3Object.content())));
response.setHeader("Server", "AmazonS3");
response.setHeader("Last-Modified", DATE_TIME_FORMAT_HTTP.print(s3Object.lastModified()));
if (contentLanguage != null) {
response.setHeader("Content-Language", contentLanguage);
}
if (expires != null) {
response.setHeader("Expires", expires);
}
if (cacheControl != null) {
response.setHeader("Cache-Control", cacheControl);
}
if (contentDisposition != null) {
response.setHeader("Content-Disposition", contentDisposition);
}
if (contentEncoding != null) {
response.setHeader("Content-Encoding", contentEncoding);
}
sendResponseWithOk(response, new byte[] {});
}
private boolean handleGetObject(HttpServletResponse response, String key, String bucket, HttpServletRequest request)
throws IOException {
if (!isNullOrEmpty(key) && !isNullOrEmpty(bucket)) {
S3MockObject s3Object = s3MemoryStorage.get(bucket, key);
if (s3Object != null) {
String contentType = defaultString(emptyToNull(request.getParameter("response-content-type")),
"application/octet-stream");
String contentLanguage = emptyToNull(request.getParameter("response-content-language"));
String expires = emptyToNull(request.getParameter("response-expires"));
String cacheControl = emptyToNull(request.getParameter("response-cache-control"));
String contentDisposition = emptyToNull(request.getParameter("response-content-disposition"));
String contentEncoding = emptyToNull(request.getParameter("response-content-encoding"));
response.setContentType(contentType);
response.setHeader("ETag", String.format("\"%s\"", getBase64EncodedMD5Hash(s3Object.content())));
response.setHeader("Server", "AmazonS3");
response.setHeader("Last-Modified", DATE_TIME_FORMAT_HTTP.print(s3Object.lastModified()));
if (contentLanguage != null) {
response.setHeader("Content-Language", contentLanguage);
}
if (expires != null) {
response.setHeader("Expires", expires);
}
if (cacheControl != null) {
response.setHeader("Cache-Control", cacheControl);
}
if (contentDisposition != null) {
response.setHeader("Content-Disposition", contentDisposition);
}
if (contentEncoding != null) {
response.setHeader("Content-Encoding", contentEncoding);
}
sendResponseWithOk(response, s3Object.content());
} else {
sendResponseKeyNotFound(response, key);
}
return true;
}
return false;
}
private boolean handleGetBucket(HttpServletResponse response, String key, String bucket, HttpServletRequest request)
throws IOException {
if (isNullOrEmpty(key) && !isNullOrEmpty(bucket)) {
if (s3MemoryStorage.listBuckets().contains(bucket)) {
String prefix = nullToEmpty(request.getParameter("prefix"));
String delimeter = defaultString(emptyToNull(request.getParameter("delimiter")), "/");
String marker = nullToEmpty(request.getParameter("marker"));
//Amazon in client (probably) above 1.10 expects encoded signs like '+' (not URLEncoded full
// response, because it also will encode '/')
boolean escape = false;
String userAgent = request.getHeader("User-Agent");
if (StringUtils.contains(userAgent, "aws-sdk-java")) {
String version = userAgent.substring(userAgent.indexOf("/") + 1, userAgent.indexOf(" "));
String[] split = version.split("\\.");
if (Integer.parseInt(split[0]) > 1 || Integer.parseInt(split[1]) >= 10) {
escape = true;
}
}
final boolean finalEscape = escape;
int maxKeys = Integer.parseInt(defaultString(emptyToNull(request.getParameter("max-keys")), "1000"));
Stream> entryStream =
s3MemoryStorage.listBucket(bucket).entrySet().stream()
.sorted(comparing(Map.Entry::getKey));
if (!isNullOrEmpty(marker)) {
entryStream = entryStream.filter(e -> e.getKey().compareTo(marker) > 0);
}
if (!isNullOrEmpty(prefix)) {
entryStream = entryStream.filter(e -> e.getKey().startsWith(prefix));
}
List> entries =
entryStream.limit(maxKeys + 1).collect(Collectors.toList());
StringBuilder contents = new StringBuilder();
for (int idx = 0; idx < Math.min(maxKeys, entries.size()); idx++) {
Map.Entry entry = entries.get(idx);
contents.append(ELEM_CONTENTS
.replace("${key}", encodeSpecialCharters(entry.getKey(), escape))
.replace("${last-modified}", DATE_TIME_FORMAT_ISO.print(entry.getValue().lastModified()))
.replace("${etag}", getBase64EncodedMD5Hash(entry.getValue().content()))
.replace("${size}", Integer.toString(entry.getValue().content().length)));
}
StringBuilder commonPrefixes = new StringBuilder();
if (!isNullOrEmpty(prefix)) {
entries.stream()
.map(entry -> entry.getKey().replace(prefix, ""))
.map(temp -> temp.substring(0, temp.indexOf(delimeter) + 1))
.distinct()
.forEach(each -> commonPrefixes.append(ELEM_COMMON_PREFIXES
.replace("${commonPrefix}", encodeSpecialCharters(prefix + each, finalEscape))));
}
sendResponseWithOk(response, RESPONSE_GET_BUCKET
.replace("${bucket}", bucket)
.replace("${prefix}", encodeSpecialCharters(prefix, escape))
.replace("${marker}", encodeSpecialCharters(marker, escape))
.replace("${max-keys}", Integer.toString(maxKeys))
.replace("${is-truncated}", Boolean.toString(entries.size() > maxKeys))
.replace("${commonPrefixes}", commonPrefixes.toString())
.replace("${contents}", contents.toString())
.getBytes(Charsets.UTF_8));
} else {
sendResponseNoSuchBucket(response, bucket);
}
return true;
}
return false;
}
private String encodeSpecialCharters(String s, boolean enable) {
if (enable) {
return s.replace("+", "%2B")
.replace("*", "%2A");
}
return s;
}
private boolean handleGetBucketLocation(HttpServletResponse response, String key, String bucket,
HttpServletRequest request) throws IOException {
if (isNullOrEmpty(key) && !isNullOrEmpty(bucket) && request.getParameterMap().get("location") != null) {
sendResponseWithOk(response, RESPONSE_GET_BUCKET_LOCATION.getBytes(Charsets.UTF_8));
return true;
}
return false;
}
private boolean handleGetListBuckets(HttpServletResponse response, String key, String bucket,
HttpServletRequest request) throws IOException {
if (isNullOrEmpty(key) && isNullOrEmpty(bucket)) {
StringBuilder result = new StringBuilder();
for (String bucketName : s3MemoryStorage.listBuckets()) {
result.append(ELEM_BUCKET.replace("${name}", bucketName).replace("${date}", SERVER_START_DATE_TIME));
}
sendResponseWithOk(response,
RESPONSE_GET_ALL_BUCKETS.replace("${buckets}", result.toString()).getBytes(Charsets.UTF_8));
return true;
}
return false;
}
private void sendResponseWithOk(HttpServletResponse response, byte[] content) throws IOException {
response.setContentLength(content.length);
IOUtils.write(content, response.getOutputStream());
response.setStatus(SC_OK);
}
private void sendResponseKeyNotFound(HttpServletResponse response, String key) throws IOException {
LOG.warn("Key {} not found", key);
IOUtils.write(RESPONSE_GET_KEY_NOT_FOUND.replace("${key}", key), response.getOutputStream());
response.setStatus(SC_NOT_FOUND);
}
private void sendResponseNoSuchBucket(HttpServletResponse response, String bucket) throws IOException {
LOG.warn("Bucket {} not found", bucket);
IOUtils.write(RESPONSE_NO_SUCH_BUCKET.replace("${bucket}", bucket), response.getOutputStream());
response.setStatus(SC_NOT_FOUND);
}
private void sendResponseInternalError(HttpServletResponse response) throws IOException {
IOUtils.write(RESPONSE_INTERNAL_ERROR, response.getOutputStream());
response.setStatus(SC_INTERNAL_SERVER_ERROR);
}
@FunctionalInterface
private interface GetRequestHandler {
boolean handle(InMemoryS3Handler instance, HttpServletResponse response,
String key, String bucket,
HttpServletRequest request) throws Throwable;
}
}