blob: 785442c73c636d5921c49068d17a9639509102a6 [file] [log] [blame]
/*
* Copyright 2012-present Facebook, 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.facebook.buck.util;
import com.facebook.buck.log.Logger;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.io.CharStreams;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* HttpEndpoint implementation which only allows a certain number of concurrent requests to be in
* flight at any given point in time.
*/
public class BlockingHttpEndpoint implements HttpEndpoint, Closeable {
private static final Logger LOG = Logger.get(BlockingHttpEndpoint.class);
public static final int DEFAULT_COMMON_TIMEOUT_MS = 5000;
private URL url;
private int timeoutMillis;
private final ListeningExecutorService requestService;
private static final ThreadFactory threadFactory =
new ThreadFactoryBuilder().setNameFormat(BlockingHttpEndpoint.class.getSimpleName() + "-%d")
.build();
public BlockingHttpEndpoint(
String url,
int maxParallelRequests,
int timeoutMillis) throws MalformedURLException {
this.url = new URL(url);
this.timeoutMillis = timeoutMillis;
// Create an ExecutorService that blocks after N requests are in flight. Taken from
// http://www.springone2gx.com/blog/billy_newport/2011/05/there_s_more_to_configuring_threadpools_than_thread_pool_size
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(maxParallelRequests);
ExecutorService executor = new ThreadPoolExecutor(maxParallelRequests,
maxParallelRequests,
2L,
TimeUnit.MINUTES,
workQueue,
threadFactory,
new ThreadPoolExecutor.CallerRunsPolicy());
requestService = MoreExecutors.listeningDecorator(executor);
}
@Override
public ListenableFuture<HttpResponse> post(final String content) {
return requestService.submit(
new Callable<HttpResponse>() {
@Override
public HttpResponse call() {
try {
HttpURLConnection connection = buildConnection("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
return send(connection, content);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
});
}
@VisibleForTesting
HttpResponse send(final HttpURLConnection connection, final String content) throws IOException {
try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) {
out.writeBytes(content);
out.flush();
out.close();
InputStream inputStream = connection.getInputStream();
String response = CharStreams.toString(
new InputStreamReader(inputStream, Charsets.UTF_8));
return new HttpResponse(response);
} finally {
connection.disconnect();
}
}
private HttpURLConnection buildConnection(String httpMethod) throws IOException {
HttpURLConnection connection = (HttpURLConnection) this.url.openConnection();
connection.setUseCaches(false);
connection.setDoOutput(true);
connection.setConnectTimeout(timeoutMillis);
connection.setReadTimeout(timeoutMillis);
connection.setRequestMethod(httpMethod);
return connection;
}
/**
* Attempt to complete submitted requests on close so that as much information is recorded as
* possible. This aids debugging when close is called during exception processing.
*/
@Override
public void close() {
requestService.shutdown();
try {
if (!requestService.awaitTermination(timeoutMillis, TimeUnit.MILLISECONDS)) {
LOG.warn(Joiner.on(System.lineSeparator()).join(
"A BlockingHttpEndpoint failed to shut down within the standard timeout.",
"Your build might have succeeded, but some requests made to ",
this.url + " were probably lost.",
"Here's some debugging information:",
requestService.toString()));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}