com.github.davidmoten.aws.lw.client.Request Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-lightweight-client-java Show documentation
Show all versions of aws-lightweight-client-java Show documentation
Lightweight client for all AWS services (but still with useful builders and XML parser)
The newest version!
package com.github.davidmoten.aws.lw.client;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.github.davidmoten.aws.lw.client.internal.Retries;
import com.github.davidmoten.aws.lw.client.internal.util.Preconditions;
import com.github.davidmoten.aws.lw.client.internal.util.Util;
import com.github.davidmoten.aws.lw.client.xml.XmlElement;
public final class Request {
private final Client client;
private Optional region;
private String url;
private HttpMethod method = HttpMethod.GET;
private final Map> headers = new HashMap<>();
private byte[] requestBody;
private int connectTimeoutMs;
private int readTimeoutMs;
private int attributeNumber = 1;
private Retries retries;
private String attributePrefix = "Attribute";
private String[] pathSegments;
private final List queries = new ArrayList<>();
private boolean signPayload = true;
Request(Client client, String url, String... pathSegments) {
this.client = client;
this.url = url;
this.pathSegments = pathSegments;
this.region = client.region();
this.connectTimeoutMs = client.connectTimeoutMs();
this.readTimeoutMs = client.readTimeoutMs();
this.retries = client.retries().copy();
}
public Request method(HttpMethod method) {
Preconditions.checkNotNull(method);
this.method = method;
return this;
}
public Request query(String name, String value) {
Preconditions.checkNotNull(name);
queries.add(new NameValue(name, value));
return this;
}
public Request query(String name) {
return query(name, null);
}
public Request attributePrefix(String attributePrefix) {
this.attributePrefix = attributePrefix;
this.attributeNumber = 1;
return this;
}
public Request attribute(String name, String value) {
int i = attributeNumber;
attributeNumber++;
return query(attributePrefix + "." + i + ".Name", name) //
.query(attributePrefix + "." + i + ".Value", value);
}
public Request header(String name, String value) {
Preconditions.checkNotNull(name);
Preconditions.checkNotNull(value);
RequestHelper.put(headers, name, value);
return this;
}
public Request signPayload(boolean signPayload) {
this.signPayload = signPayload;
return this;
}
public Request unsignedPayload() {
return signPayload(false);
}
/**
* Adds the header {@code x-amz-meta-KEY:value}. {@code KEY} is obtained from
* {@code key} by converting to lower-case (headers are case-insensitive) and
* only retaining alphabetical and digit characters.
*
* @param key metadata key
* @param value metadata value
* @return request builder
*/
public Request metadata(String key, String value) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(value);
return header("x-amz-meta-" + Util.canonicalMetadataKey(key), value);
}
public Request requestBody(byte[] requestBody) {
Preconditions.checkNotNull(requestBody);
this.requestBody = requestBody;
return this;
}
public Request requestBody(String requestBody) {
Preconditions.checkNotNull(requestBody);
return requestBody(requestBody.getBytes(StandardCharsets.UTF_8));
}
public Request region(String region) {
Preconditions.checkNotNull(region);
this.region = Optional.of(region);
return this;
}
public Request connectTimeout(long duration, TimeUnit unit) {
Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
Preconditions.checkNotNull(unit, "unit cannot be null");
this.connectTimeoutMs = (int) unit.toMillis(duration);
return this;
}
public Request readTimeout(long duration, TimeUnit unit) {
Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
Preconditions.checkNotNull(unit, "unit cannot be null");
this.readTimeoutMs = (int) unit.toMillis(duration);
return this;
}
public Request retryInitialInterval(long duration, TimeUnit unit) {
Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
Preconditions.checkNotNull(unit, "unit cannot be null");
retries = retries.withInitialIntervalMs(unit.toMillis(duration));
return this;
}
public Request retryMaxAttempts(int maxAttempts) {
Preconditions.checkArgument(maxAttempts >=0, "retryMaxAttempts cannot be negative");
retries = retries.withMaxAttempts(maxAttempts);
return this;
}
/**
* Sets the level of randomness applied to the next retry interval. The next
* calculated retry interval is multiplied by
* {@code (1 - jitter * Math.random())}. A value of zero means no jitter, 1
* means max jitter.
*
* @param jitter level of randomness applied to the retry interval
* @return this
*/
public Request retryJitter(double jitter) {
Preconditions.checkArgument(jitter >= 0 && jitter <= 1, "jitter must be between 0 and 1");
retries = retries.withJitter(jitter);
return this;
}
public Request retryBackoffFactor(double factor) {
Preconditions.checkArgument(factor >=0, "retryBackoffFactor cannot be negative");
retries = retries.withBackoffFactor(factor);
return this;
}
public Request retryMaxInterval(long duration, TimeUnit unit) {
Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
Preconditions.checkNotNull(unit, "unit cannot be null");
retries = retries.withMaxIntervalMs(unit.toMillis(duration));
return this;
}
/**
* Opens a connection and makes the request. This method returns all the
* response information including headers, status code, request body as an
* InputStream. If an error status code is encountered (outside 200-299) then an
* exception is not thrown (unlike the other methods .response*). The
* caller must close the InputStream when finished with it.
*
* @return all response information, the caller must close the InputStream when
* finished with it
*/
public ResponseInputStream responseInputStream() {
String u = calculateUrl(url, client.serviceName(), region, queries, Arrays.asList(pathSegments),
client.baseUrlFactory());
return retries //
.call(() -> RequestHelper.request(client.clock(), client.httpClient(), u, method,
RequestHelper.combineHeaders(headers), requestBody, client.serviceName(), region,
client.credentials(), connectTimeoutMs, readTimeoutMs, signPayload));
}
/**
* Opens a connection and makes the request. This method returns all the
* response information including headers, status code, request body as a byte
* array. If an error status code is encountered (outside 200-299) then an
* exception is not thrown (unlike the other methods .response*).
*
* @return all response information
*/
public Response response() {
ResponseInputStream r = responseInputStream();
final byte[] bytes;
if (hasBody(r)) {
bytes = Util.readBytesAndClose(r);
} else {
bytes = new byte[0];
}
return new Response(r.headers(), bytes, r.statusCode());
}
/**
* Opens a connection and makes the request. This method returns all the
* response information including headers, status code, request body as a byte
* array. If the expected status code is not encountered then a
* {@link ServiceException} is thrown.
*
* @return all response information
* @throws ServiceException
*/
public Response responseExpectStatusCode(int expectedStatusCode) {
Response r = response();
if (r.statusCode() == expectedStatusCode) {
return r;
} else {
throw new ServiceException(r.statusCode(), r.contentUtf8());
}
}
// VisibleForTesting
static boolean hasBody(ResponseInputStream r) {
return r.header("Content-Length").isPresent()
|| r.header("Transfer-Encoding").orElse("").equalsIgnoreCase("chunked");
}
private static String calculateUrl(String url, String serviceName, Optional region, List queries,
List pathSegments, BaseUrlFactory baseUrlFactory) {
String u = url;
if (u == null) {
String baseUrl = baseUrlFactory.create(serviceName, region);
Preconditions.checkNotNull(baseUrl, "baseUrl cannot be null");
u = trimAndEnsureHasTrailingSlash(baseUrl) //
+ pathSegments //
.stream() //
.map(x -> trimAndRemoveLeadingAndTrailingSlashes(x)) //
.collect(Collectors.joining("/"));
}
// add queries
for (NameValue nv : queries) {
if (!u.contains("?")) {
u += "?";
}
if (!u.endsWith("?")) {
u += "&";
}
if (nv.value != null) {
u += Util.urlEncode(nv.name, false) + "=" + Util.urlEncode(nv.value, false);
} else {
u += Util.urlEncode(nv.name, false);
}
}
return u;
}
// VisibleForTesting
static String trimAndEnsureHasTrailingSlash(String s) {
String r = s.trim();
if (r.endsWith("/")) {
return r;
} else {
return r + "/";
}
}
public byte[] responseAsBytes() {
Response r = response();
Optional extends RuntimeException> exception = client.exceptionFactory().create(r);
if (!exception.isPresent()) {
return r.content();
} else {
throw exception.get();
}
}
/**
* Returns true if and only if status code is 2xx. Returns false if status code
* is 404 (NOT_FOUND) and throws a {@link ServiceException} otherwise.
*
* @return true if status code 2xx, false if 404 otherwise throws
* ServiceException
* @throws ServiceException if status code other than 2xx or 404
*/
public boolean exists() {
return response().exists();
}
public void execute() {
responseAsBytes();
}
public String responseAsUtf8() {
return new String(responseAsBytes(), StandardCharsets.UTF_8);
}
public XmlElement responseAsXml() {
return XmlElement.parse(responseAsUtf8());
}
public String presignedUrl(long expiryDuration, TimeUnit unit) {
String u = calculateUrl(url, client.serviceName(), region, queries, Arrays.asList(pathSegments),
client.baseUrlFactory());
return RequestHelper.presignedUrl(client.clock(), u, method.toString(), RequestHelper.combineHeaders(headers),
requestBody, client.serviceName(), region, client.credentials(), connectTimeoutMs, readTimeoutMs,
unit.toSeconds(expiryDuration), signPayload);
}
// VisibleForTesting
static String trimAndRemoveLeadingAndTrailingSlashes(String s) {
Preconditions.checkNotNull(s);
s = s.trim();
if (s.startsWith("/")) {
s = s.substring(1);
}
if (s.endsWith("/")) {
s = s.substring(0, s.length() - 1);
}
return s;
}
private static final class NameValue {
final String name;
final String value;
NameValue(String name, String value) {
this.name = name;
this.value = value;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy