kopia lustrzana https://github.com/gaul/s3proxy
Refactor S3ProxyHandler to be agnostic to Jetty. Added a new file,
S3ProxyHandlerJetty, to have codes related to Jetty.pull/132/head
rodzic
cd54c6f8ef
commit
4fb4a718b9
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2014-2016 Andrew Gaul <andrew@gaul.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.gaul.s3proxy;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public final class S3Exception extends Exception {
|
||||
private final S3ErrorCode error;
|
||||
private final Map<String, String> elements;
|
||||
|
||||
S3Exception(S3ErrorCode error) {
|
||||
this(error, error.getMessage(), (Throwable) null,
|
||||
ImmutableMap.<String, String>of());
|
||||
}
|
||||
|
||||
S3Exception(S3ErrorCode error, String message) {
|
||||
this(error, message, (Throwable) null,
|
||||
ImmutableMap.<String, String>of());
|
||||
}
|
||||
|
||||
S3Exception(S3ErrorCode error, Throwable cause) {
|
||||
this(error, error.getMessage(), cause,
|
||||
ImmutableMap.<String, String>of());
|
||||
}
|
||||
|
||||
S3Exception(S3ErrorCode error, String message, Throwable cause,
|
||||
Map<String, String> elements) {
|
||||
super(message, cause);
|
||||
this.error = requireNonNull(error);
|
||||
this.elements = ImmutableMap.copyOf(elements);
|
||||
}
|
||||
|
||||
S3ErrorCode getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
Map<String, String> getElements() {
|
||||
return elements;
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ import org.jclouds.blobstore.BlobStore;
|
|||
*/
|
||||
public final class S3Proxy {
|
||||
private final Server server;
|
||||
private final S3ProxyHandler handler;
|
||||
private final S3ProxyHandlerJetty handler;
|
||||
private final boolean listenHTTP;
|
||||
private final boolean listenHTTPS;
|
||||
|
||||
|
@ -101,7 +101,7 @@ public final class S3Proxy {
|
|||
} else {
|
||||
listenHTTPS = false;
|
||||
}
|
||||
handler = new S3ProxyHandler(builder.blobStore, builder.identity,
|
||||
handler = new S3ProxyHandlerJetty(builder.blobStore, builder.identity,
|
||||
builder.credential, Optional.fromNullable(builder.virtualHost),
|
||||
builder.v4MaxNonChunkedRequestSize,
|
||||
builder.ignoreUnknownHeaders, builder.corsAllowAll);
|
||||
|
@ -221,7 +221,7 @@ public final class S3Proxy {
|
|||
}
|
||||
|
||||
public void setBlobStoreLocator(BlobStoreLocator lookup) {
|
||||
handler.setBlobStoreLocator(lookup);
|
||||
handler.getHandler().setBlobStoreLocator(lookup);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,10 @@ public final class S3ProxyConstants {
|
|||
"s3proxy.keystore-path";
|
||||
public static final String PROPERTY_KEYSTORE_PASSWORD =
|
||||
"s3proxy.keystore-password";
|
||||
|
||||
/** Request attributes. */
|
||||
public static final String ATTRIBUTE_QUERY_ENCODING = "queryEncoding";
|
||||
|
||||
/**
|
||||
* Configure servicing of virtual host buckets. Setting to localhost:8080
|
||||
* allows bucket-in-hostname requests, e.g., bucketname.localhost:8080.
|
||||
|
|
|
@ -45,7 +45,6 @@ import java.util.SortedMap;
|
|||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
|
@ -58,7 +57,6 @@ import javax.xml.stream.XMLStreamWriter;
|
|||
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
|
@ -82,10 +80,7 @@ import com.google.common.net.HttpHeaders;
|
|||
import com.google.common.net.PercentEscaper;
|
||||
|
||||
import org.apache.commons.fileupload.MultipartStream;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.jclouds.blobstore.BlobStore;
|
||||
import org.jclouds.blobstore.ContainerNotFoundException;
|
||||
import org.jclouds.blobstore.KeyNotFoundException;
|
||||
import org.jclouds.blobstore.domain.Blob;
|
||||
import org.jclouds.blobstore.domain.BlobAccess;
|
||||
|
@ -109,11 +104,11 @@ import org.jclouds.io.ContentMetadataBuilder;
|
|||
import org.jclouds.io.Payload;
|
||||
import org.jclouds.io.Payloads;
|
||||
import org.jclouds.rest.AuthorizationException;
|
||||
import org.jclouds.util.Throwables2;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
final class S3ProxyHandler extends AbstractHandler {
|
||||
/** HTTP server-independent handler for S3 requests. */
|
||||
public class S3ProxyHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(
|
||||
S3ProxyHandler.class);
|
||||
private static final String AWS_XMLNS =
|
||||
|
@ -211,7 +206,7 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
S3ProxyHandler(final BlobStore blobStore, final String identity,
|
||||
public S3ProxyHandler(final BlobStore blobStore, final String identity,
|
||||
final String credential, Optional<String> virtualHost,
|
||||
long v4MaxNonChunkedRequestSize, boolean ignoreUnknownHeaders,
|
||||
boolean corsAllowAll) {
|
||||
|
@ -252,69 +247,9 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
return blobStore.getContext().unwrap().getProviderMetadata().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest,
|
||||
HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException {
|
||||
try (InputStream is = request.getInputStream()) {
|
||||
doHandle(baseRequest, request, response, is);
|
||||
baseRequest.setHandled(true);
|
||||
} catch (ContainerNotFoundException cnfe) {
|
||||
S3ErrorCode code = S3ErrorCode.NO_SUCH_BUCKET;
|
||||
sendSimpleErrorResponse(request, response, code, code.getMessage(),
|
||||
ImmutableMap.<String, String>of());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (HttpResponseException hre) {
|
||||
HttpResponse httpResponse = hre.getResponse();
|
||||
response.sendError(httpResponse == null ?
|
||||
HttpServletResponse.SC_INTERNAL_SERVER_ERROR :
|
||||
httpResponse.getStatusCode());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (KeyNotFoundException knfe) {
|
||||
S3ErrorCode code = S3ErrorCode.NO_SUCH_KEY;
|
||||
sendSimpleErrorResponse(request, response, code, code.getMessage(),
|
||||
ImmutableMap.<String, String>of());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (S3Exception se) {
|
||||
sendSimpleErrorResponse(request, response, se.getError(),
|
||||
se.getMessage(), se.getElements());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (Throwable throwable) {
|
||||
if (Throwables2.getFirstThrowableOfType(throwable,
|
||||
AuthorizationException.class) != null) {
|
||||
S3ErrorCode code = S3ErrorCode.ACCESS_DENIED;
|
||||
sendSimpleErrorResponse(request, response, code,
|
||||
code.getMessage(), ImmutableMap.<String, String>of());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} else if (Throwables2.getFirstThrowableOfType(throwable,
|
||||
TimeoutException.class) != null) {
|
||||
S3ErrorCode code = S3ErrorCode.REQUEST_TIMEOUT;
|
||||
sendSimpleErrorResponse(request, response, code,
|
||||
code.getMessage(), ImmutableMap.<String, String>of());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} else {
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doHandle(Request baseRequest, HttpServletRequest request,
|
||||
HttpServletResponse response, InputStream is)
|
||||
throws IOException, S3Exception {
|
||||
public final void doHandle(HttpServletRequest baseRequest,
|
||||
HttpServletRequest request, HttpServletResponse response,
|
||||
InputStream is) throws IOException, S3Exception {
|
||||
String method = request.getMethod();
|
||||
String uri = request.getRequestURI();
|
||||
logger.debug("request: {}", request);
|
||||
|
@ -2353,7 +2288,13 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
HttpServletRequest request, HttpServletResponse response,
|
||||
String headerName, String overrideHeaderName, String value) {
|
||||
String override = request.getParameter(overrideHeaderName);
|
||||
response.addHeader(headerName, override != null ? override : value);
|
||||
|
||||
// NPE in if value is null
|
||||
override = (override != null) ? override : value;
|
||||
|
||||
if (override != null) {
|
||||
response.addHeader(headerName, override);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addMetadataToResponse(HttpServletRequest request,
|
||||
|
@ -2401,7 +2342,7 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private void sendSimpleErrorResponse(
|
||||
protected final void sendSimpleErrorResponse(
|
||||
HttpServletRequest request, HttpServletResponse response,
|
||||
S3ErrorCode code, String message,
|
||||
Map<String, String> elements) throws IOException {
|
||||
|
@ -2437,46 +2378,6 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
}
|
||||
}
|
||||
|
||||
public void setBlobStoreLocator(BlobStoreLocator locator) {
|
||||
this.blobStoreLocator = locator;
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static final class S3Exception extends Exception {
|
||||
private final S3ErrorCode error;
|
||||
private final Map<String, String> elements;
|
||||
|
||||
S3Exception(S3ErrorCode error) {
|
||||
this(error, error.getMessage(), (Throwable) null,
|
||||
ImmutableMap.<String, String>of());
|
||||
}
|
||||
|
||||
S3Exception(S3ErrorCode error, String message) {
|
||||
this(error, message, (Throwable) null,
|
||||
ImmutableMap.<String, String>of());
|
||||
}
|
||||
|
||||
S3Exception(S3ErrorCode error, Throwable cause) {
|
||||
this(error, error.getMessage(), cause,
|
||||
ImmutableMap.<String, String>of());
|
||||
}
|
||||
|
||||
S3Exception(S3ErrorCode error, String message, Throwable cause,
|
||||
Map<String, String> elements) {
|
||||
super(message, cause);
|
||||
this.error = requireNonNull(error);
|
||||
this.elements = ImmutableMap.copyOf(elements);
|
||||
}
|
||||
|
||||
S3ErrorCode getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
Map<String, String> getElements() {
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Amazon V2 signature. Reference:
|
||||
* http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
|
||||
|
@ -2562,7 +2463,8 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
* Create v4 signature. Reference:
|
||||
* http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||
*/
|
||||
private static String createAuthorizationSignatureV4(Request request,
|
||||
private static String createAuthorizationSignatureV4(
|
||||
HttpServletRequest request,
|
||||
byte[] payload, String uri, String credential)
|
||||
throws InvalidKeyException, IOException, NoSuchAlgorithmException,
|
||||
S3Exception {
|
||||
|
@ -2602,9 +2504,9 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
return mac.doFinal(data);
|
||||
}
|
||||
|
||||
private static String createCanonicalRequest(Request request, String uri,
|
||||
byte[] payload, String hashAlgorithm) throws IOException,
|
||||
NoSuchAlgorithmException {
|
||||
private static String createCanonicalRequest(HttpServletRequest request,
|
||||
String uri, byte[] payload, String hashAlgorithm)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
String authorizationHeader = request.getHeader("Authorization");
|
||||
String xAmzContentSha256 = request.getHeader("x-amz-content-sha256");
|
||||
String digest;
|
||||
|
@ -2646,7 +2548,7 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
return authorization.substring(startHeaders + 1, endSigned).split(";");
|
||||
}
|
||||
|
||||
private static String buildCanonicalHeaders(Request request,
|
||||
private static String buildCanonicalHeaders(HttpServletRequest request,
|
||||
String[] signedHeaders) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (String header : signedHeaders) {
|
||||
|
@ -2673,14 +2575,13 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
return Joiner.on("\n").join(headersWithValues);
|
||||
}
|
||||
|
||||
private static String buildCanonicalQueryString(Request request)
|
||||
private static String buildCanonicalQueryString(HttpServletRequest request)
|
||||
throws UnsupportedEncodingException {
|
||||
// The parameters are required to be sorted
|
||||
List<String> parameters = Collections.list(request.getParameterNames());
|
||||
Collections.sort(parameters);
|
||||
List<String> queryParameters = new ArrayList<>();
|
||||
String charsetName = Objects.firstNonNull(request.getQueryEncoding(),
|
||||
"UTF-8");
|
||||
|
||||
for (String key : parameters) {
|
||||
// re-encode keys and values in AWS normalized form
|
||||
String value = request.getParameter(key);
|
||||
|
@ -2803,4 +2704,12 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
public void close() throws IOException {
|
||||
}
|
||||
}
|
||||
|
||||
public final BlobStoreLocator getBlobStoreLocator() {
|
||||
return blobStoreLocator;
|
||||
}
|
||||
|
||||
public final void setBlobStoreLocator(BlobStoreLocator locator) {
|
||||
this.blobStoreLocator = locator;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright 2014-2016 Andrew Gaul <andrew@gaul.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.gaul.s3proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.jclouds.blobstore.BlobStore;
|
||||
import org.jclouds.blobstore.ContainerNotFoundException;
|
||||
import org.jclouds.blobstore.KeyNotFoundException;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.http.HttpResponseException;
|
||||
import org.jclouds.rest.AuthorizationException;
|
||||
import org.jclouds.util.Throwables2;
|
||||
|
||||
/** Jetty-specific handler for S3 requests. */
|
||||
final class S3ProxyHandlerJetty extends AbstractHandler {
|
||||
private final S3ProxyHandler handler;
|
||||
|
||||
S3ProxyHandlerJetty(final BlobStore blobStore, final String identity,
|
||||
final String credential, Optional<String> virtualHost,
|
||||
long v4MaxNonChunkedRequestSize, boolean ignoreUnknownHeaders,
|
||||
boolean corsAllowAll) {
|
||||
handler = new S3ProxyHandler(blobStore, identity, credential,
|
||||
virtualHost, v4MaxNonChunkedRequestSize, ignoreUnknownHeaders,
|
||||
corsAllowAll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest,
|
||||
HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException {
|
||||
try (InputStream is = request.getInputStream()) {
|
||||
|
||||
// Set query encoding
|
||||
baseRequest.setAttribute(S3ProxyConstants.ATTRIBUTE_QUERY_ENCODING,
|
||||
baseRequest.getQueryEncoding());
|
||||
|
||||
handler.doHandle(baseRequest, request, response, is);
|
||||
baseRequest.setHandled(true);
|
||||
} catch (ContainerNotFoundException cnfe) {
|
||||
S3ErrorCode code = S3ErrorCode.NO_SUCH_BUCKET;
|
||||
handler.sendSimpleErrorResponse(request, response, code,
|
||||
code.getMessage(), ImmutableMap.<String, String>of());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (HttpResponseException hre) {
|
||||
HttpResponse httpResponse = hre.getResponse();
|
||||
response.sendError(httpResponse == null ?
|
||||
HttpServletResponse.SC_INTERNAL_SERVER_ERROR :
|
||||
httpResponse.getStatusCode());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (KeyNotFoundException knfe) {
|
||||
S3ErrorCode code = S3ErrorCode.NO_SUCH_KEY;
|
||||
handler.sendSimpleErrorResponse(request, response, code,
|
||||
code.getMessage(), ImmutableMap.<String, String>of());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (S3Exception se) {
|
||||
handler.sendSimpleErrorResponse(request, response,
|
||||
se.getError(), se.getMessage(), se.getElements());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} catch (Throwable throwable) {
|
||||
if (Throwables2.getFirstThrowableOfType(throwable,
|
||||
AuthorizationException.class) != null) {
|
||||
S3ErrorCode code = S3ErrorCode.ACCESS_DENIED;
|
||||
handler.sendSimpleErrorResponse(request, response, code,
|
||||
code.getMessage(), ImmutableMap.<String, String>of());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} else if (Throwables2.getFirstThrowableOfType(throwable,
|
||||
TimeoutException.class) != null) {
|
||||
S3ErrorCode code = S3ErrorCode.REQUEST_TIMEOUT;
|
||||
handler.sendSimpleErrorResponse(request, response, code,
|
||||
code.getMessage(), ImmutableMap.<String, String>of());
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
} else {
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public S3ProxyHandler getHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue