com.amazonaws.http.AmazonRxNettyHttpClient Maven / Gradle / Ivy
package com.amazonaws.http;
import java.io.ByteArrayInputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import rx.Observable;
import io.netty.buffer.ByteBuf;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.reactivex.netty.RxNetty;
import io.reactivex.netty.protocol.http.AbstractHttpConfigurator;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import io.reactivex.netty.protocol.http.server.HttpResponseHeaders;
import io.reactivex.netty.protocol.http.client.HttpClient;
import io.reactivex.netty.protocol.http.client.HttpClient.HttpClientConfig;
import io.reactivex.netty.protocol.http.client.HttpClientRequest;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import io.reactivex.netty.protocol.http.client.HttpClientPipelineConfigurator;
import io.reactivex.netty.protocol.http.client.HttpRequestHeaders;
import io.reactivex.netty.pipeline.PipelineConfigurator;
import io.reactivex.netty.pipeline.PipelineConfiguratorComposite;
import io.reactivex.netty.pipeline.ssl.DefaultFactories;
import org.w3c.dom.Node;
import com.amazonaws.*;
import com.amazonaws.auth.*;
import com.amazonaws.event.*;
import com.amazonaws.handlers.*;
import com.amazonaws.http.*;
import com.amazonaws.internal.*;
import com.amazonaws.metrics.*;
import com.amazonaws.regions.*;
import com.amazonaws.transform.*;
import com.amazonaws.util.*;
import com.amazonaws.util.AWSRequestMetrics.Field;
import org.joda.time.format.ISODateTimeFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
abstract public class AmazonRxNettyHttpClient extends AmazonWebServiceClient {
private static final String HMAC_SHA_256 = "HmacSHA256";
private static final String SHA_256 = "SHA-256";
private static final Mac MAC_HMAC_SHA_256;
private static final MessageDigest MESSAGE_DIGEST_SHA_256;
private static final Map> CLIENTS =
new ConcurrentHashMap>();
protected String mkToken(String... tokens) {
if (tokens.length == 1)
return tokens[0];
else if (Arrays.stream(tokens).anyMatch(t -> { return t != null; }))
return Arrays.stream(tokens).reduce((s1, s2) -> s1 + "|" + s2).get();
else
return null;
}
static {
try {
MAC_HMAC_SHA_256 = Mac.getInstance(HMAC_SHA_256);
MESSAGE_DIGEST_SHA_256 = MessageDigest.getInstance(SHA_256);
}
catch (java.security.NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private AWSCredentialsProvider awsCredentialsProvider;
public AmazonRxNettyHttpClient() {
this(new DefaultAWSCredentialsProviderChain(), new ClientConfiguration());
}
public AmazonRxNettyHttpClient(AWSCredentialsProvider awsCredentialsProvider) {
this(awsCredentialsProvider, new ClientConfiguration());
}
public AmazonRxNettyHttpClient(ClientConfiguration clientConfiguration) {
this(new DefaultAWSCredentialsProviderChain(), clientConfiguration);
}
public AmazonRxNettyHttpClient(
AWSCredentialsProvider awsCredentialsProvider,
ClientConfiguration clientConfiguration
) {
super(clientConfiguration);
this.awsCredentialsProvider = awsCredentialsProvider;
init();
}
abstract protected void init();
private byte[] hmacSHA256(String data, byte[] key) {
try {
Mac mac = (Mac) MAC_HMAC_SHA_256.clone();
mac.init(new SecretKeySpec(key, HMAC_SHA_256));
return mac.doFinal(data.getBytes("UTF8"));
}
catch (java.lang.CloneNotSupportedException e) {
throw new RuntimeException(e);
}
catch (java.security.InvalidKeyException e) {
throw new RuntimeException(e);
}
catch (java.io.UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private byte[] getSignatureKey(String key, String dateStamp, String region, String service) {
try {
byte[] kSecret = ("AWS4" + key).getBytes("UTF8");
byte[] kDate = hmacSHA256(dateStamp, kSecret);
byte[] kRegion = hmacSHA256(region, kDate);
byte[] kService = hmacSHA256(service, kRegion);
return hmacSHA256("aws4_request", kService);
}
catch (java.io.UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private String hexString(byte[] data) {
return DatatypeConverter.printHexBinary(data).toLowerCase();
}
private String computeSHA256(String input) {
try {
MessageDigest messageDigest = (MessageDigest) MESSAGE_DIGEST_SHA_256.clone();
return hexString(messageDigest.digest(input.getBytes()));
}
catch (java.lang.CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
private Observable getBackoffStrategyDelay(Request request, int cnt, AmazonClientException error) {
if (cnt == 0) return Observable.just(0L);
else {
long delay = clientConfiguration.getRetryPolicy().getBackoffStrategy().delayBeforeNextRetry(request.getOriginalRequest(), error, cnt);
return Observable.timer(delay, TimeUnit.MILLISECONDS);
}
}
protected Observable invokeStax(
Request request,
Unmarshaller unmarshaller,
List> errorUnmarshallers,
ExecutionContext executionContext
) {
StaxRxNettyResponseHandler responseHandler = new StaxRxNettyResponseHandler(unmarshaller);
XmlRxNettyErrorResponseHandler errorResponseHandler = new XmlRxNettyErrorResponseHandler(errorUnmarshallers);
return invoke(request, responseHandler, errorResponseHandler, executionContext);
}
protected Observable invokeJson(
Request request,
Unmarshaller unmarshaller,
List errorUnmarshallers,
ExecutionContext executionContext
) {
JsonRxNettyResponseHandler responseHandler = new JsonRxNettyResponseHandler(unmarshaller);
JsonRxNettyErrorResponseHandler errorResponseHandler = new JsonRxNettyErrorResponseHandler(request.getServiceName(), errorUnmarshallers);
return invoke(request, responseHandler, errorResponseHandler, executionContext);
}
protected Observable invoke(
Request request,
RxNettyResponseHandler> responseHandler,
RxNettyResponseHandler errorResponseHandler,
ExecutionContext executionContext
) {
final AtomicReference error = new AtomicReference(null);
final AtomicInteger cnt = new AtomicInteger(0);
return Observable.using(
() -> { return ""; },
(ignore) -> {
assert(cnt.get() == 0 || error.get() != null);
if (cnt.get() == 0 || (cnt.get() < clientConfiguration.getRetryPolicy().getMaxErrorRetry() && clientConfiguration.getRetryPolicy().getRetryCondition().shouldRetry(request.getOriginalRequest(), error.get(), cnt.get()))) {
return getBackoffStrategyDelay(request, cnt.get(), error.get())
.flatMap(i -> {
try {
return invokeImpl(request, responseHandler, errorResponseHandler, executionContext);
}
catch (java.io.UnsupportedEncodingException e) {
return Observable.error(e);
}
})
.doOnNext(n -> {
error.set(null);
})
.onErrorResumeNext(t -> {
error.set((AmazonClientException) t);
return Observable.just((X) null);
});
}
else return Observable.error(error.get());
},
(ignore) -> {
cnt.getAndIncrement();
}
)
.repeat()
.filter((v) -> {
return v != null;
})
.first();
}
protected Observable invokeImpl(
Request request,
RxNettyResponseHandler> responseHandler,
RxNettyResponseHandler errorResponseHandler,
ExecutionContext executionContext
) throws java.io.UnsupportedEncodingException {
request.setEndpoint(endpoint);
request.setTimeOffset(timeOffset);
AmazonWebServiceRequest originalRequest = request.getOriginalRequest();
for (Map.Entry e : originalRequest.copyPrivateRequestParameters().entrySet()) {
request.addParameter(e.getKey(), e.getValue());
}
AWSCredentials credentials = request.getOriginalRequest().getRequestCredentials();
if (credentials == null) {
credentials = awsCredentialsProvider.getCredentials();
}
executionContext.setCredentials(credentials);
// execute
ProgressListener listener = originalRequest.getGeneralProgressListener();
if (originalRequest.getCustomRequestHeaders() != null) {
request.getHeaders().putAll(originalRequest.getCustomRequestHeaders());
}
// new
long startTime = System.currentTimeMillis();
String method = "POST";
String version = "2014-10-01";
String service = request.getServiceName().substring(6).toLowerCase();
if (service.endsWith("v2")) service = service.substring(0, service.length() - 2);
String host = endpoint.getHost();
String region = AwsHostNameUtils.parseRegionName(host, service);
StringBuffer sb = new StringBuffer();
for (Map.Entry e : request.getParameters().entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(e.getKey()).append("=").append(URLEncoder.encode(e.getValue(), "UTF-8"));
}
String canonicalQuerystring = sb.toString();
String content = (request.getContent() == null) ? "" : ((StringInputStream) request.getContent()).getString();
String accessKey = credentials.getAWSAccessKeyId();
String secretKey = credentials.getAWSSecretKey();
String amzDate = ISODateTimeFormat.basicDateTimeNoMillis().withZoneUTC().print(startTime);
String datestamp = ISODateTimeFormat.basicDate().withZoneUTC().print(startTime);
Map headers = new ConcurrentHashMap();
headers.put("Accept-encoding", "gzip");
headers.put("Host", host);
headers.put("X-Amz-Date", amzDate);
if (credentials instanceof AWSSessionCredentials)
headers.put("x-amz-security-token", ((AWSSessionCredentials) credentials).getSessionToken());
request.getHeaders().entrySet().stream().forEach(e -> {
headers.put(e.getKey(), e.getValue());
});
String algorithm = "AWS4-HMAC-SHA256";
String credentialScope = datestamp + "/" + region + "/" + service + "/aws4_request";
String amzCredential = URLEncoder.encode(accessKey + "/" + credentialScope, "UTF-8");
String canonicalHeaders = headers.entrySet().stream().sorted((e1, e2) -> {
return e1.getKey().toLowerCase().compareTo(e2.getKey().toLowerCase());
}).map(e -> {
return e.getKey().toLowerCase() + ":" + e.getValue() + "\n";
}).reduce((s1, s2) -> s1 + s2).get();
String signedHeaders = headers.entrySet().stream().sorted((e1, e2) -> {
return e1.getKey().toLowerCase().compareTo(e2.getKey().toLowerCase());
}).map(e -> {
return e.getKey().toLowerCase();
}).reduce((s1, s2) -> s1 + ";" + s2).get();
String canonicalUri = request.getResourcePath();
if (canonicalUri == null || canonicalUri.length() == 0) canonicalUri = "/";
//String canonicalQuerystring = "";
String payloadHash = computeSHA256(content);
String canonicalRequest = method + "\n" + canonicalUri + "\n" + canonicalQuerystring + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash;
String stringToSign = algorithm + "\n" + amzDate + "\n" + credentialScope + "\n" + computeSHA256(canonicalRequest);
byte[] signingKey = getSignatureKey(secretKey, datestamp, region, service);
String signature = hexString(hmacSHA256(stringToSign, signingKey));
String authorizationHeader = algorithm + " Credential=" + accessKey + "/" + credentialScope + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature;
String path = canonicalUri + ((canonicalQuerystring.length() == 0) ? "" : "?" + canonicalQuerystring );
HttpClientRequest rxRequest = HttpClientRequest.create(HttpMethod.valueOf(request.getHttpMethod().toString()), path);
HttpRequestHeaders rHeaders = rxRequest.getHeaders();
rHeaders.set("Authorization", authorizationHeader);
headers.entrySet().stream().forEach(e -> {
rHeaders.set(e.getKey(), e.getValue());
});
rxRequest.withContent(content);
return getClient(host).submit(rxRequest)
.flatMap(response -> {
if (response.getStatus().code() / 100 == 2) {
try {
return responseHandler.handle(response).map(r -> { return r.getResult(); });
}
catch (Exception e) {
return Observable.error(e);
}
}
else {
try {
return errorResponseHandler.handle(response).flatMap(e -> {
e.setServiceName(request.getServiceName());
return Observable.error(e);
});
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.onErrorResumeNext(t -> {
if (t instanceof AmazonClientException) return Observable.error(t);
else return Observable.error(new AmazonClientException(t));
});
}
private HttpClient getClient(String host) {
Protocol protocol = clientConfiguration.getProtocol();
String key = protocol + "|" + host;
if (!CLIENTS.containsKey(key)) {
boolean isSecure;
int port;
if (Protocol.HTTP.equals(protocol)) {
isSecure = false;
port = 80;
}
else if (Protocol.HTTPS.equals(protocol)) {
isSecure = true;
port = 443;
}
else {
throw new IllegalStateException("unknown protocol: " + protocol);
}
HttpClientConfig config = new HttpClient.HttpClientConfig.Builder()
.setFollowRedirect(true)
.readTimeout(clientConfiguration.getSocketTimeout(), TimeUnit.MILLISECONDS)
.build();
HttpClient client = RxNetty.newHttpClientBuilder(host, port)
.withName(host + "." + port)
.config(config)
.channelOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, clientConfiguration.getConnectionTimeout())
.withMaxConnections(clientConfiguration.getMaxConnections())
.withIdleConnectionsTimeoutMillis(clientConfiguration.getConnectionTTL())
.enableWireLogging(LogLevel.ERROR)
.withSslEngineFactory((isSecure) ? DefaultFactories.trustAll() : null)
.pipelineConfigurator(
new PipelineConfiguratorComposite,HttpClientRequest>(
new HttpClientPipelineConfigurator(),
new HttpDecompressionConfigurator()
)
)
.build();
CLIENTS.putIfAbsent(key, client);
}
return CLIENTS.get(key);
}
public class HttpDecompressionConfigurator implements PipelineConfigurator {
@Override
public void configureNewPipeline(ChannelPipeline pipeline) {
pipeline.addLast("deflater", new HttpContentDecompressor());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy