Introduce S3Exception

This elides some boilerplate and ensures that we consistently handle
errors.
pull/47/head
Andrew Gaul 2015-02-28 13:03:09 -08:00
rodzic 7e0c817660
commit c2724b4399
1 zmienionych plików z 103 dodań i 150 usunięć

Wyświetl plik

@ -176,6 +176,18 @@ final class S3ProxyHandler extends AbstractHandler {
public void handle(String target, Request baseRequest, public void handle(String target, Request baseRequest,
HttpServletRequest request, HttpServletResponse response) HttpServletRequest request, HttpServletResponse response)
throws IOException { throws IOException {
try {
doHandle(target, baseRequest, request, response);
} catch (S3Exception se) {
sendSimpleErrorResponse(response, se.getError());
baseRequest.setHandled(true);
return;
}
}
private void doHandle(String target, Request baseRequest,
HttpServletRequest request, HttpServletResponse response)
throws IOException, S3Exception {
String method = request.getMethod(); String method = request.getMethod();
String uri = request.getRequestURI(); String uri = request.getRequestURI();
logger.debug("request: {}", request); logger.debug("request: {}", request);
@ -220,24 +232,15 @@ final class S3ProxyHandler extends AbstractHandler {
try { try {
date = request.getDateHeader(HttpHeaders.DATE); date = request.getDateHeader(HttpHeaders.DATE);
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.ACCESS_DENIED, iae);
S3ErrorCode.ACCESS_DENIED);
baseRequest.setHandled(true);
return;
} }
if (date < 0) { if (date < 0) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
S3ErrorCode.ACCESS_DENIED);
baseRequest.setHandled(true);
return;
} }
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (now + TimeUnit.DAYS.toMillis(1) < date || if (now + TimeUnit.DAYS.toMillis(1) < date ||
now - TimeUnit.DAYS.toMillis(1) > date) { now - TimeUnit.DAYS.toMillis(1) > date) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.REQUEST_TIME_TOO_SKEWED);
S3ErrorCode.REQUEST_TIME_TOO_SKEWED);
baseRequest.setHandled(true);
return;
} }
} }
@ -253,47 +256,30 @@ final class S3ProxyHandler extends AbstractHandler {
String[] values = String[] values =
headerAuthorization.substring(4).split(":", 2); headerAuthorization.substring(4).split(":", 2);
if (values.length != 2) { if (values.length != 2) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT);
S3ErrorCode.INVALID_ARGUMENT);
baseRequest.setHandled(true);
return;
} }
headerIdentity = values[0]; headerIdentity = values[0];
headerSignature = values[1]; headerSignature = values[1];
} else if (headerAuthorization != null && } else if (headerAuthorization != null &&
headerAuthorization.startsWith("AWS4-HMAC-SHA256 ")) { headerAuthorization.startsWith("AWS4-HMAC-SHA256 ")) {
// Fail V4 signature requests to allow clients to retry with V2. // Fail V4 signature requests to allow clients to retry with V2.
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT);
baseRequest.setHandled(true);
return;
} }
String parameterIdentity = request.getParameter("AWSAccessKeyId"); String parameterIdentity = request.getParameter("AWSAccessKeyId");
String parameterSignature = request.getParameter("Signature"); String parameterSignature = request.getParameter("Signature");
if (headerIdentity != null && headerSignature != null) { if (headerIdentity != null && headerSignature != null) {
if (!identity.equals(headerIdentity)) { if (!identity.equals(headerIdentity)) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.INVALID_ACCESS_KEY_ID);
S3ErrorCode.INVALID_ACCESS_KEY_ID);
baseRequest.setHandled(true);
return;
} else if (!expectedSignature.equals(headerSignature)) { } else if (!expectedSignature.equals(headerSignature)) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.SIGNATURE_DOES_NOT_MATCH);
S3ErrorCode.SIGNATURE_DOES_NOT_MATCH);
baseRequest.setHandled(true);
return;
} }
} else if (parameterIdentity != null && } else if (parameterIdentity != null &&
parameterSignature != null) { parameterSignature != null) {
if (!identity.equals(parameterIdentity)) { if (!identity.equals(parameterIdentity)) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.INVALID_ACCESS_KEY_ID);
S3ErrorCode.INVALID_ACCESS_KEY_ID);
baseRequest.setHandled(true);
return;
} else if (!expectedSignature.equals(parameterSignature)) { } else if (!expectedSignature.equals(parameterSignature)) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.SIGNATURE_DOES_NOT_MATCH);
S3ErrorCode.SIGNATURE_DOES_NOT_MATCH);
baseRequest.setHandled(true);
return;
} }
String expiresString = request.getParameter("Expires"); String expiresString = request.getParameter("Expires");
@ -301,16 +287,11 @@ final class S3ProxyHandler extends AbstractHandler {
long expires = Long.parseLong(expiresString); long expires = Long.parseLong(expiresString);
long nowSeconds = System.currentTimeMillis() / 1000; long nowSeconds = System.currentTimeMillis() / 1000;
if (nowSeconds > expires) { if (nowSeconds > expires) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
S3ErrorCode.ACCESS_DENIED);
baseRequest.setHandled(true);
return;
} }
} }
} else { } else {
sendSimpleErrorResponse(response, S3ErrorCode.ACCESS_DENIED); throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
baseRequest.setHandled(true);
return;
} }
} }
@ -320,9 +301,7 @@ final class S3ProxyHandler extends AbstractHandler {
if (!SUPPORTED_PARAMETERS.contains(parameter)) { if (!SUPPORTED_PARAMETERS.contains(parameter)) {
logger.error("Unknown parameters {} with URI {}", logger.error("Unknown parameters {} with URI {}",
parameter, request.getRequestURI()); parameter, request.getRequestURI());
sendSimpleErrorResponse(response, S3ErrorCode.NOT_IMPLEMENTED); throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED);
baseRequest.setHandled(true);
return;
} }
} }
@ -443,9 +422,7 @@ final class S3ProxyHandler extends AbstractHandler {
default: default:
logger.error("Unknown method {} with URI {}", logger.error("Unknown method {} with URI {}",
method, request.getRequestURI()); method, request.getRequestURI());
sendSimpleErrorResponse(response, S3ErrorCode.NOT_IMPLEMENTED); throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED);
baseRequest.setHandled(true);
return;
} }
} }
@ -526,7 +503,7 @@ final class S3ProxyHandler extends AbstractHandler {
private void handleSetContainerAcl(HttpServletRequest request, private void handleSetContainerAcl(HttpServletRequest request,
HttpServletResponse response, String containerName) HttpServletResponse response, String containerName)
throws IOException { throws IOException, S3Exception {
ContainerAccess access; ContainerAccess access;
String cannedAcl = request.getHeader("x-amz-acl"); String cannedAcl = request.getHeader("x-amz-acl");
@ -535,8 +512,7 @@ final class S3ProxyHandler extends AbstractHandler {
} else if ("public-read".equals(cannedAcl)) { } else if ("public-read".equals(cannedAcl)) {
access = ContainerAccess.PUBLIC_READ; access = ContainerAccess.PUBLIC_READ;
} else if (cannedAcl == null || CANNED_ACLS.contains(cannedAcl)) { } else if (cannedAcl == null || CANNED_ACLS.contains(cannedAcl)) {
sendSimpleErrorResponse(response, S3ErrorCode.NOT_IMPLEMENTED); throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED);
return;
} else { } else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST); response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return; return;
@ -625,7 +601,7 @@ final class S3ProxyHandler extends AbstractHandler {
private void handleSetBlobAcl(HttpServletRequest request, private void handleSetBlobAcl(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, String containerName,
String blobName) throws IOException { String blobName) throws IOException, S3Exception {
BlobAccess access; BlobAccess access;
String cannedAcl = request.getHeader("x-amz-acl"); String cannedAcl = request.getHeader("x-amz-acl");
@ -634,8 +610,7 @@ final class S3ProxyHandler extends AbstractHandler {
} else if ("public-read".equals(cannedAcl)) { } else if ("public-read".equals(cannedAcl)) {
access = BlobAccess.PUBLIC_READ; access = BlobAccess.PUBLIC_READ;
} else if (cannedAcl == null || CANNED_ACLS.contains(cannedAcl)) { } else if (cannedAcl == null || CANNED_ACLS.contains(cannedAcl)) {
sendSimpleErrorResponse(response, S3ErrorCode.NOT_IMPLEMENTED); throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED);
return;
} else { } else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST); response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return; return;
@ -705,30 +680,27 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleListMultipartUploads(HttpServletResponse response, private void handleListMultipartUploads(HttpServletResponse response,
String uploadId) throws IOException { String uploadId) throws IOException, S3Exception {
// TODO: list all blobs starting with uploadId // TODO: list all blobs starting with uploadId
sendSimpleErrorResponse(response, S3ErrorCode.NOT_IMPLEMENTED); throw new S3Exception(S3ErrorCode.NOT_IMPLEMENTED);
} }
private void handleContainerExists(HttpServletResponse response, private void handleContainerExists(HttpServletResponse response,
String containerName) throws IOException { String containerName) throws IOException, S3Exception {
if (!blobStore.containerExists(containerName)) { if (!blobStore.containerExists(containerName)) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET);
return;
} }
} }
private void handleContainerCreate(HttpServletRequest request, private void handleContainerCreate(HttpServletRequest request,
HttpServletResponse response, String containerName) HttpServletResponse response, String containerName)
throws IOException { throws IOException, S3Exception {
if (containerName.isEmpty()) { if (containerName.isEmpty()) {
sendSimpleErrorResponse(response, S3ErrorCode.METHOD_NOT_ALLOWED); throw new S3Exception(S3ErrorCode.METHOD_NOT_ALLOWED);
return;
} }
if (containerName.length() < 3 || containerName.length() > 255 || if (containerName.length() < 3 || containerName.length() > 255 ||
!VALID_BUCKET_PATTERN.matcher(containerName).matches()) { !VALID_BUCKET_PATTERN.matcher(containerName).matches()) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_BUCKET_NAME); throw new S3Exception(S3ErrorCode.INVALID_BUCKET_NAME);
return;
} }
Collection<String> locations; Collection<String> locations;
@ -755,9 +727,7 @@ final class S3ProxyHandler extends AbstractHandler {
} }
} }
if (location == null) { if (location == null) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.INVALID_LOCATION_CONSTRAINT);
S3ErrorCode.INVALID_LOCATION_CONSTRAINT);
return;
} }
} }
logger.debug("Creating bucket with location: {}", location); logger.debug("Creating bucket with location: {}", location);
@ -775,12 +745,10 @@ final class S3ProxyHandler extends AbstractHandler {
try { try {
contentLength = Long.parseLong(contentLengthString); contentLength = Long.parseLong(contentLengthString);
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT, nfe);
return;
} }
if (contentLength < 0) { if (contentLength < 0) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT);
return;
} }
} }
@ -794,28 +762,24 @@ final class S3ProxyHandler extends AbstractHandler {
errorCode, errorCode.getMessage(), "BucketName", errorCode, errorCode.getMessage(), "BucketName",
containerName); containerName);
} catch (AuthorizationException ae) { } catch (AuthorizationException ae) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.BUCKET_ALREADY_EXISTS, ae);
S3ErrorCode.BUCKET_ALREADY_EXISTS);
return;
} }
} }
private void handleContainerDelete(HttpServletResponse response, private void handleContainerDelete(HttpServletResponse response,
String containerName) throws IOException { String containerName) throws IOException, S3Exception {
if (!blobStore.containerExists(containerName)) { if (!blobStore.containerExists(containerName)) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET);
return;
} }
if (!blobStore.deleteContainerIfEmpty(containerName)) { if (!blobStore.deleteContainerIfEmpty(containerName)) {
sendSimpleErrorResponse(response, S3ErrorCode.BUCKET_NOT_EMPTY); throw new S3Exception(S3ErrorCode.BUCKET_NOT_EMPTY);
return;
} }
response.setStatus(HttpServletResponse.SC_NO_CONTENT); response.setStatus(HttpServletResponse.SC_NO_CONTENT);
} }
private void handleBlobList(HttpServletRequest request, private void handleBlobList(HttpServletRequest request,
HttpServletResponse response, String containerName) HttpServletResponse response, String containerName)
throws IOException { throws IOException, S3Exception {
ListContainerOptions options = new ListContainerOptions(); ListContainerOptions options = new ListContainerOptions();
String delimiter = request.getParameter("delimiter"); String delimiter = request.getParameter("delimiter");
if (!(delimiter != null && delimiter.equals("/"))) { if (!(delimiter != null && delimiter.equals("/"))) {
@ -835,8 +799,7 @@ final class S3ProxyHandler extends AbstractHandler {
try { try {
maxKeys = Integer.parseInt(maxKeysString); maxKeys = Integer.parseInt(maxKeysString);
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT, nfe);
return;
} }
} }
options = options.maxResults(maxKeys); options = options.maxResults(maxKeys);
@ -845,8 +808,7 @@ final class S3ProxyHandler extends AbstractHandler {
try { try {
set = blobStore.list(containerName, options); set = blobStore.list(containerName, options);
} catch (ContainerNotFoundException cnfe) { } catch (ContainerNotFoundException cnfe) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET, cnfe);
return;
} }
try (Writer writer = new OutputStreamWriter(response.getOutputStream(), try (Writer writer = new OutputStreamWriter(response.getOutputStream(),
@ -980,13 +942,13 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleBlobRemove(HttpServletResponse response, private void handleBlobRemove(HttpServletResponse response,
String containerName, String blobName) throws IOException { String containerName, String blobName)
throws IOException, S3Exception {
try { try {
blobStore.removeBlob(containerName, blobName); blobStore.removeBlob(containerName, blobName);
response.sendError(HttpServletResponse.SC_NO_CONTENT); response.sendError(HttpServletResponse.SC_NO_CONTENT);
} catch (ContainerNotFoundException cnfe) { } catch (ContainerNotFoundException cnfe) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET, cnfe);
return;
} }
} }
@ -1020,17 +982,16 @@ final class S3ProxyHandler extends AbstractHandler {
} }
private void handleBlobMetadata(HttpServletResponse response, private void handleBlobMetadata(HttpServletResponse response,
String containerName, String blobName) throws IOException { String containerName, String blobName)
throws IOException, S3Exception {
BlobMetadata metadata; BlobMetadata metadata;
try { try {
metadata = blobStore.blobMetadata(containerName, blobName); metadata = blobStore.blobMetadata(containerName, blobName);
} catch (ContainerNotFoundException cnfe) { } catch (ContainerNotFoundException cnfe) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET, cnfe);
return;
} }
if (metadata == null) { if (metadata == null) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_KEY); throw new S3Exception(S3ErrorCode.NO_SUCH_KEY);
return;
} }
response.setStatus(HttpServletResponse.SC_OK); response.setStatus(HttpServletResponse.SC_OK);
@ -1039,7 +1000,7 @@ final class S3ProxyHandler extends AbstractHandler {
private void handleGetBlob(HttpServletRequest request, private void handleGetBlob(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, String containerName,
String blobName) throws IOException { String blobName) throws IOException, S3Exception {
int status = HttpServletResponse.SC_OK; int status = HttpServletResponse.SC_OK;
GetOptions options = new GetOptions(); GetOptions options = new GetOptions();
String range = request.getHeader(HttpHeaders.RANGE); String range = request.getHeader(HttpHeaders.RANGE);
@ -1063,12 +1024,10 @@ final class S3ProxyHandler extends AbstractHandler {
try { try {
blob = blobStore.getBlob(containerName, blobName, options); blob = blobStore.getBlob(containerName, blobName, options);
} catch (ContainerNotFoundException cnfe) { } catch (ContainerNotFoundException cnfe) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET, cnfe);
return;
} }
if (blob == null) { if (blob == null) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_KEY); throw new S3Exception(S3ErrorCode.NO_SUCH_KEY);
return;
} }
response.setStatus(status); response.setStatus(status);
@ -1082,7 +1041,7 @@ final class S3ProxyHandler extends AbstractHandler {
private void handleCopyBlob(HttpServletRequest request, private void handleCopyBlob(HttpServletRequest request,
HttpServletResponse response, String destContainerName, HttpServletResponse response, String destContainerName,
String destBlobName) throws IOException { String destBlobName) throws IOException, S3Exception {
String copySourceHeader = request.getHeader("x-amz-copy-source"); String copySourceHeader = request.getHeader("x-amz-copy-source");
if (copySourceHeader.startsWith("/")) { if (copySourceHeader.startsWith("/")) {
// Some clients like boto do not include the leading slash // Some clients like boto do not include the leading slash
@ -1100,20 +1059,17 @@ final class S3ProxyHandler extends AbstractHandler {
if (sourceContainerName.equals(destContainerName) && if (sourceContainerName.equals(destContainerName) &&
sourceBlobName.equals(destBlobName) && sourceBlobName.equals(destBlobName) &&
!replaceMetadata) { !replaceMetadata) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_REQUEST); throw new S3Exception(S3ErrorCode.INVALID_REQUEST);
return;
} }
Blob blob; Blob blob;
try { try {
blob = blobStore.getBlob(sourceContainerName, sourceBlobName); blob = blobStore.getBlob(sourceContainerName, sourceBlobName);
} catch (ContainerNotFoundException cnfe) { } catch (ContainerNotFoundException cnfe) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET, cnfe);
return;
} }
if (blob == null) { if (blob == null) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_KEY); throw new S3Exception(S3ErrorCode.NO_SUCH_KEY);
return;
} }
try (InputStream is = blob.getPayload().openStream()) { try (InputStream is = blob.getPayload().openStream()) {
@ -1163,7 +1119,7 @@ final class S3ProxyHandler extends AbstractHandler {
private void handlePutBlob(HttpServletRequest request, private void handlePutBlob(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, String containerName,
String blobName) throws IOException { String blobName) throws IOException, S3Exception {
// Flag headers present since HttpServletResponse.getHeader returns // Flag headers present since HttpServletResponse.getHeader returns
// null for empty headers values. // null for empty headers values.
String contentLengthString = null; String contentLengthString = null;
@ -1184,30 +1140,24 @@ final class S3ProxyHandler extends AbstractHandler {
contentMD5 = HashCode.fromBytes( contentMD5 = HashCode.fromBytes(
BaseEncoding.base64().decode(contentMD5String)); BaseEncoding.base64().decode(contentMD5String));
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_DIGEST); throw new S3Exception(S3ErrorCode.INVALID_DIGEST, iae);
return;
} }
if (contentMD5.bits() != Hashing.md5().bits()) { if (contentMD5.bits() != Hashing.md5().bits()) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_DIGEST); throw new S3Exception(S3ErrorCode.INVALID_DIGEST);
return;
} }
} }
if (contentLengthString == null) { if (contentLengthString == null) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.MISSING_CONTENT_LENGTH);
S3ErrorCode.MISSING_CONTENT_LENGTH);
return;
} }
long contentLength; long contentLength;
try { try {
contentLength = Long.parseLong(contentLengthString); contentLength = Long.parseLong(contentLengthString);
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT, nfe);
return;
} }
if (contentLength < 0) { if (contentLength < 0) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT);
return;
} }
try (InputStream is = request.getInputStream()) { try (InputStream is = request.getInputStream()) {
@ -1227,8 +1177,7 @@ final class S3ProxyHandler extends AbstractHandler {
eTag = blobStore.putBlob(containerName, builder.build(), eTag = blobStore.putBlob(containerName, builder.build(),
options); options);
} catch (ContainerNotFoundException cnfe) { } catch (ContainerNotFoundException cnfe) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_BUCKET); throw new S3Exception(S3ErrorCode.NO_SUCH_BUCKET, cnfe);
return;
} catch (HttpResponseException hre) { } catch (HttpResponseException hre) {
HttpResponse hr = hre.getResponse(); HttpResponse hr = hre.getResponse();
if (hr == null) { if (hr == null) {
@ -1238,9 +1187,7 @@ final class S3ProxyHandler extends AbstractHandler {
switch (status) { switch (status) {
case HttpServletResponse.SC_BAD_REQUEST: case HttpServletResponse.SC_BAD_REQUEST:
case 422: // Swift returns 422 Unprocessable Entity case 422: // Swift returns 422 Unprocessable Entity
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.BAD_DIGEST);
S3ErrorCode.BAD_DIGEST);
break;
default: default:
// TODO: emit hre.getContent() ? // TODO: emit hre.getContent() ?
response.sendError(status); response.sendError(status);
@ -1250,9 +1197,7 @@ final class S3ProxyHandler extends AbstractHandler {
} catch (RuntimeException re) { } catch (RuntimeException re) {
if (Throwables2.getFirstThrowableOfType(re, if (Throwables2.getFirstThrowableOfType(re,
TimeoutException.class) != null) { TimeoutException.class) != null) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.REQUEST_TIMEOUT, re);
S3ErrorCode.REQUEST_TIMEOUT);
return;
} else { } else {
throw re; throw re;
} }
@ -1306,11 +1251,10 @@ final class S3ProxyHandler extends AbstractHandler {
private void handleCompleteMultipartUpload(HttpServletRequest request, private void handleCompleteMultipartUpload(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, String containerName,
String blobName, String uploadId) throws IOException { String blobName, String uploadId) throws IOException, S3Exception {
try (InputStream is = request.getInputStream();
Writer writer = response.getWriter()) {
Collection<String> partNames = new ArrayList<>(); Collection<String> partNames = new ArrayList<>();
long totalContentLength = 0; long totalContentLength = 0;
try (InputStream is = request.getInputStream()) {
for (Iterator<String> it = parseSimpleXmlElements(is, for (Iterator<String> it = parseSimpleXmlElements(is,
"PartNumber").iterator(); it.hasNext();) { "PartNumber").iterator(); it.hasNext();) {
String partName = uploadId + "." + it.next(); String partName = uploadId + "." + it.next();
@ -1321,20 +1265,18 @@ final class S3ProxyHandler extends AbstractHandler {
metadata.getContentMetadata().getContentLength(); metadata.getContentMetadata().getContentLength();
if (contentLength < MINIMUM_MULTIPART_PART_SIZE && if (contentLength < MINIMUM_MULTIPART_PART_SIZE &&
it.hasNext()) { it.hasNext()) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.ENTITY_TOO_SMALL);
S3ErrorCode.ENTITY_TOO_SMALL);
return;
} }
totalContentLength += contentLength; totalContentLength += contentLength;
} }
if (partNames.isEmpty()) { if (partNames.isEmpty()) {
// Amazon requires at least one part // Amazon requires at least one part
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.MALFORMED_X_M_L);
S3ErrorCode.MALFORMED_X_M_L); }
return;
} }
try (Writer writer = response.getWriter()) {
BlobMetadata blobMetadata = blobStore.blobMetadata( BlobMetadata blobMetadata = blobStore.blobMetadata(
containerName, uploadId); containerName, uploadId);
ContentMetadata contentMetadata = ContentMetadata contentMetadata =
@ -1400,10 +1342,9 @@ final class S3ProxyHandler extends AbstractHandler {
private void handleAbortMultipartUpload(HttpServletRequest request, private void handleAbortMultipartUpload(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, String containerName,
String blobName, String uploadId) throws IOException { String blobName, String uploadId) throws IOException, S3Exception {
if (!blobStore.blobExists(containerName, uploadId)) { if (!blobStore.blobExists(containerName, uploadId)) {
sendSimpleErrorResponse(response, S3ErrorCode.NO_SUCH_UPLOAD); throw new S3Exception(S3ErrorCode.NO_SUCH_UPLOAD);
return;
} }
PageSet<? extends StorageMetadata> pageSet = blobStore.list( PageSet<? extends StorageMetadata> pageSet = blobStore.list(
containerName, containerName,
@ -1533,7 +1474,8 @@ final class S3ProxyHandler extends AbstractHandler {
private void handleUploadPart(HttpServletRequest request, private void handleUploadPart(HttpServletRequest request,
HttpServletResponse response, String containerName, HttpServletResponse response, String containerName,
String blobName, String uploadId) throws IOException { String blobName, String uploadId)
throws IOException, S3Exception {
// TODO: duplicated from handlePutBlob // TODO: duplicated from handlePutBlob
String contentLengthString = null; String contentLengthString = null;
String contentMD5String = null; String contentMD5String = null;
@ -1553,30 +1495,24 @@ final class S3ProxyHandler extends AbstractHandler {
contentMD5 = HashCode.fromBytes( contentMD5 = HashCode.fromBytes(
BaseEncoding.base64().decode(contentMD5String)); BaseEncoding.base64().decode(contentMD5String));
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_DIGEST); throw new S3Exception(S3ErrorCode.INVALID_DIGEST, iae);
return;
} }
if (contentMD5.bits() != Hashing.md5().bits()) { if (contentMD5.bits() != Hashing.md5().bits()) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_DIGEST); throw new S3Exception(S3ErrorCode.INVALID_DIGEST);
return;
} }
} }
if (contentLengthString == null) { if (contentLengthString == null) {
sendSimpleErrorResponse(response, throw new S3Exception(S3ErrorCode.MISSING_CONTENT_LENGTH);
S3ErrorCode.MISSING_CONTENT_LENGTH);
return;
} }
long contentLength; long contentLength;
try { try {
contentLength = Long.parseLong(contentLengthString); contentLength = Long.parseLong(contentLengthString);
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT, nfe);
return;
} }
if (contentLength < 0) { if (contentLength < 0) {
sendSimpleErrorResponse(response, S3ErrorCode.INVALID_ARGUMENT); throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT);
return;
} }
String partNumber = request.getParameter("partNumber"); String partNumber = request.getParameter("partNumber");
@ -1682,6 +1618,23 @@ final class S3ProxyHandler extends AbstractHandler {
} }
} }
static class S3Exception extends Exception {
private final S3ErrorCode error;
S3Exception(S3ErrorCode error) {
this(error, null);
}
S3Exception(S3ErrorCode error, Throwable cause) {
super(cause);
this.error = checkNotNull(error);
}
S3ErrorCode getError() {
return error;
}
}
/** /**
* Create Amazon V2 signature. Reference: * Create Amazon V2 signature. Reference:
* http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html * http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html