![JAR search and dependency download from the Maven repository](/logo.png)
com.netflix.spectator.sandbox.HttpRequestBuilder Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014-2019 Netflix, Inc.
*
* 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 com.netflix.spectator.sandbox;
import com.netflix.spectator.impl.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.Deflater;
/**
* Helper for executing simple HTTP client requests using {@link HttpURLConnection}
* and logging via {@link HttpLogEntry}. This is mostly used for simple use-cases
* where it is undesirable to have additional dependencies on a more robust HTTP
* library.
*
* @deprecated Moved to {@code com.netflix.spectator.ipc.http} package. This is now just a
* thin wrapper to preserve compatibility. This class is scheduled for removal in a future release.
*/
@Deprecated
public class HttpRequestBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpRequestBuilder.class);
private final URI uri;
private final HttpLogEntry entry;
private String method = "GET";
private Map reqHeaders = new LinkedHashMap<>();
private byte[] entity = HttpUtils.EMPTY;
private int connectTimeout = 1000;
private int readTimeout = 30000;
private long initialRetryDelay = 1000L;
private int numAttempts = 1;
private HostnameVerifier hostVerifier = null;
private SSLSocketFactory sslFactory = null;
/** Create a new instance for the specified URI. */
public HttpRequestBuilder(String clientName, URI uri) {
this.uri = uri;
this.entry = new HttpLogEntry()
.withRequestUri(uri)
.withClientName(clientName)
.withMethod(method);
}
/** Set the request method (GET, PUT, POST, DELETE). */
public HttpRequestBuilder withMethod(String m) {
this.method = m;
entry.withMethod(method);
return this;
}
/**
* Add a header to the request. Note the content type will be set automatically
* when providing the content payload and should not be set here.
*/
public HttpRequestBuilder addHeader(String name, String value) {
reqHeaders.put(name, value);
entry.withRequestHeader(name, value);
return this;
}
/** Add user-agent header. */
public HttpRequestBuilder userAgent(String agent) {
return addHeader("User-Agent", agent);
}
/** Add header to accept {@code application/json} data. */
public HttpRequestBuilder acceptJson() {
return addHeader("Accept", "application/json");
}
/** Add accept header. */
public HttpRequestBuilder accept(String type) {
return addHeader("Accept", type);
}
/** Add header to accept-encoding of gzip. */
public HttpRequestBuilder acceptGzip() {
return acceptEncoding("gzip");
}
/** Add accept-encoding header. */
public HttpRequestBuilder acceptEncoding(String enc) {
return addHeader("Accept-Encoding", enc);
}
/** Set the connection timeout for the request in milliseconds. */
public HttpRequestBuilder withConnectTimeout(int timeout) {
this.connectTimeout = timeout;
return this;
}
/** Set the read timeout for the request milliseconds. */
public HttpRequestBuilder withReadTimeout(int timeout) {
this.readTimeout = timeout;
return this;
}
/** Set the request body as JSON. */
public HttpRequestBuilder withJsonContent(String content) {
return withContent("application/json", content);
}
/** Set the request body. */
public HttpRequestBuilder withContent(String type, String content) {
return withContent(type, content.getBytes(StandardCharsets.UTF_8));
}
/** Set the request body. */
public HttpRequestBuilder withContent(String type, byte[] content) {
addHeader("Content-Type", type);
entity = content;
return this;
}
/**
* Compress the request body using the default compression level.
* The content must have already been set on the builder.
*/
public HttpRequestBuilder compress() throws IOException {
return compress(Deflater.DEFAULT_COMPRESSION);
}
/**
* Compress the request body using the specified compression level.
* The content must have already been set on the builder.
*/
public HttpRequestBuilder compress(int level) throws IOException {
addHeader("Content-Encoding", "gzip");
entity = HttpUtils.gzip(entity, level);
return this;
}
/** How many times to retry if the intial attempt fails? */
public HttpRequestBuilder withRetries(int n) {
Preconditions.checkArg(n >= 0, "number of retries must be >= 0");
this.numAttempts = n + 1;
entry.withMaxAttempts(numAttempts);
return this;
}
/**
* How long to delay before retrying if the request is throttled. This will get doubled
* for each attempt that is throttled. Unit is milliseconds.
*/
public HttpRequestBuilder withInitialRetryDelay(long delay) {
Preconditions.checkArg(delay >= 0L, "initial retry delay must be >= 0");
this.initialRetryDelay = delay;
return this;
}
private void requireHttps(String msg) {
Preconditions.checkState("https".equals(uri.getScheme()), msg);
}
/** Sets the policy used to verify hostnames when using HTTPS. */
public HttpRequestBuilder withHostnameVerifier(HostnameVerifier verifier) {
requireHttps("hostname verification cannot be used with http, switch to https");
this.hostVerifier = verifier;
return this;
}
/**
* Specify that all hosts are allowed. Using this option effectively disables hostname
* verification. Use with caution.
*/
public HttpRequestBuilder allowAllHosts() {
return withHostnameVerifier((host, session) -> true);
}
/** Sets the socket factory to use with HTTPS. */
public HttpRequestBuilder withSSLSocketFactory(SSLSocketFactory factory) {
requireHttps("ssl cannot be used with http, use https");
this.sslFactory = factory;
return this;
}
/** Send the request and log/update metrics for the results. */
@SuppressWarnings("PMD.ExceptionAsFlowControl")
public HttpResponse send() throws IOException {
HttpResponse response = null;
for (int attempt = 1; attempt <= numAttempts; ++attempt) {
entry.withAttempt(attempt);
try {
response = sendImpl();
int s = response.status();
if (s == 429 || s == 503) {
// Request is getting throttled, exponentially back off
// - 429 client sending too many requests
// - 503 server unavailable
try {
long delay = initialRetryDelay << (attempt - 1);
LOGGER.debug("request throttled, delaying for {}ms: {} {}", delay, method, uri);
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("request failed " + method + " " + uri, e);
}
} else if (s < 500) {
// 4xx errors other than 429 are not considered retriable, so for anything
// less than 500 just return the response to the user
return response;
}
} catch (IOException e) {
// All exceptions are considered retriable. Some like UnknownHostException are
// debatable, but we have seen them in some cases if there is a high latency for
// DNS lookups. So for now assume all exceptions are transient issues.
if (attempt == numAttempts) {
throw e;
} else {
LOGGER.warn("attempt {} of {} failed: {} {}", attempt, numAttempts, method, uri);
}
}
}
if (response == null) {
// Should not get here
throw new IOException("request failed " + method + " " + uri);
}
return response;
}
private void configureHTTPS(HttpURLConnection http) {
if (http instanceof HttpsURLConnection) {
HttpsURLConnection https = (HttpsURLConnection) http;
if (hostVerifier != null) {
https.setHostnameVerifier(hostVerifier);
}
if (sslFactory != null) {
https.setSSLSocketFactory(sslFactory);
}
}
}
/** Send the request and log/update metrics for the results. */
protected HttpResponse sendImpl() throws IOException {
HttpURLConnection con = (HttpURLConnection) uri.toURL().openConnection();
con.setConnectTimeout(connectTimeout);
con.setReadTimeout(readTimeout);
con.setRequestMethod(method);
for (Map.Entry h : reqHeaders.entrySet()) {
con.setRequestProperty(h.getKey(), h.getValue());
}
configureHTTPS(con);
boolean canRetry = true;
try {
con.setDoInput(true);
// HttpURLConnection will change method to POST if there is a body associated
// with a GET request. Only try to write entity if it is not empty.
entry.withRequestContentLength(entity.length).mark("start");
if (entity.length > 0) {
con.setDoOutput(true);
try (OutputStream out = con.getOutputStream()) {
out.write(entity);
}
}
int status = con.getResponseCode();
canRetry = (status >= 500 || status == 429);
entry.mark("complete").withStatusCode(status);
// A null key is used to return the status line, remove it before sending to
// the log entry or creating the response object
Map> headers = new LinkedHashMap<>(con.getHeaderFields());
headers.remove(null);
for (Map.Entry> h : headers.entrySet()) {
for (String v : h.getValue()) {
entry.withResponseHeader(h.getKey(), v);
}
}
try (InputStream in = (status >= 400) ? con.getErrorStream() : con.getInputStream()) {
byte[] data = readAll(in);
entry.withResponseContentLength(data.length);
return new HttpResponse(status, headers, data);
}
} catch (IOException e) {
entry.mark("complete").withException(e);
throw e;
} finally {
entry.withCanRetry(canRetry);
HttpLogEntry.logClientRequest(entry);
}
}
@SuppressWarnings("PMD.AssignmentInOperand")
private byte[] readAll(InputStream in) throws IOException {
if (in == null) {
// For error status codes with a content-length of 0 we see this case
return new byte[0];
} else {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int length;
while ((length = in.read(buffer)) > 0) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy