Introduce BlockingHttpEndpoint.
diff --git a/src/com/facebook/buck/cli/BUCK b/src/com/facebook/buck/cli/BUCK
index 34c27d8..6b8f806 100644
--- a/src/com/facebook/buck/cli/BUCK
+++ b/src/com/facebook/buck/cli/BUCK
@@ -54,6 +54,7 @@
     '//src/com/facebook/buck/util:constants',
     '//src/com/facebook/buck/util:exceptions',
     '//src/com/facebook/buck/util:io',
+    '//src/com/facebook/buck/util:network',
     '//src/com/facebook/buck/util:util',
     '//src/com/facebook/buck/util/environment:environment',
     '//src/com/facebook/buck/timing:timing',
diff --git a/src/com/facebook/buck/cli/Main.java b/src/com/facebook/buck/cli/Main.java
index fecf397..6c93040 100644
--- a/src/com/facebook/buck/cli/Main.java
+++ b/src/com/facebook/buck/cli/Main.java
@@ -465,6 +465,8 @@
 
     loadListenersFromBuckConfig(eventListenersBuilder, projectFilesystem, config);
 
+
+
     ImmutableList<BuckEventListener> eventListeners = eventListenersBuilder.build();
 
     for (BuckEventListener eventListener : eventListeners) {
@@ -472,6 +474,7 @@
     }
 
     JavaUtilsLoggingBuildListener.ensureLogFileIsWritten();
+
     return eventListeners;
   }
 
diff --git a/src/com/facebook/buck/event/listener/BUCK b/src/com/facebook/buck/event/listener/BUCK
index 0ab6760..8f78998 100644
--- a/src/com/facebook/buck/event/listener/BUCK
+++ b/src/com/facebook/buck/event/listener/BUCK
@@ -20,8 +20,8 @@
     '//src/com/facebook/buck/util:constants',
     '//src/com/facebook/buck/util:exceptions',
     '//src/com/facebook/buck/util/environment:environment',
-    '//src/com/facebook/buck/util:network',
     '//src/com/facebook/buck/util:io',
+    '//src/com/facebook/buck/util:network',
     '//src/com/facebook/buck/util:util',
   ],
   visibility = [
diff --git a/src/com/facebook/buck/util/BUCK b/src/com/facebook/buck/util/BUCK
index d3623bf..37b372c 100644
--- a/src/com/facebook/buck/util/BUCK
+++ b/src/com/facebook/buck/util/BUCK
@@ -51,6 +51,7 @@
 )
 
 NETWORK_SRCS = [
+  'BlockingHttpEndpoint.java',
   'HttpEndpoint.java',
 ]
 java_library(
diff --git a/src/com/facebook/buck/util/BlockingHttpEndpoint.java b/src/com/facebook/buck/util/BlockingHttpEndpoint.java
new file mode 100644
index 0000000..6c3c3c7
--- /dev/null
+++ b/src/com/facebook/buck/util/BlockingHttpEndpoint.java
@@ -0,0 +1,95 @@
+/*
+ * 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.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+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 {
+
+  public static final int DEFAULT_COMMON_TIMEOUT_MS = 5000;
+
+  private URL url;
+  private int timeout = DEFAULT_COMMON_TIMEOUT_MS;
+  private final ListeningExecutorService requestService;
+
+  public BlockingHttpEndpoint(String url, int maxParallelRequests) throws MalformedURLException {
+    this.url = new URL(url);
+
+    // 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,
+        new ThreadPoolExecutor.CallerRunsPolicy());
+    requestService = MoreExecutors.listeningDecorator(executor);
+  }
+
+  @Override
+  public ListenableFuture<?> post(String content) throws IOException {
+    HttpURLConnection connection = buildConnection("POST");
+    connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+    return send(connection, content);
+  }
+
+  private ListenableFuture<?> send(final HttpURLConnection connection, final String content) {
+    return requestService.submit(new Runnable() {
+      @Override
+      public void run() {
+        try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) {
+          out.writeBytes(content);
+          out.flush();
+          out.close();
+          try (InputStream response = connection.getInputStream()) {
+            ByteStreams.copy(response, ByteStreams.nullOutputStream());
+          }
+        }  catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+  }
+
+  private HttpURLConnection buildConnection(String httpMethod) throws IOException {
+    HttpURLConnection connection = (HttpURLConnection) this.url.openConnection();
+    connection.setUseCaches(false);
+    connection.setDoOutput(true);
+    connection.setConnectTimeout(timeout);
+    connection.setReadTimeout(timeout);
+    connection.setRequestMethod(httpMethod);
+    return connection;
+  }
+}
diff --git a/src/com/facebook/buck/util/HttpEndpoint.java b/src/com/facebook/buck/util/HttpEndpoint.java
index c8e144d..5053545 100644
--- a/src/com/facebook/buck/util/HttpEndpoint.java
+++ b/src/com/facebook/buck/util/HttpEndpoint.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-present Facebook, Inc.
+ * Copyright 2013-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
@@ -13,74 +13,13 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.facebook.buck.util;
 
-import com.google.common.base.Charsets;
+import com.google.common.util.concurrent.ListenableFuture;
 
-import java.io.DataOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.Map;
 
-/**
- * HTTP POST things to a commonly used endpoint.
- */
-public class HttpEndpoint {
-
-  public static final int DEFAULT_COMMON_TIMEOUT_MS = 5000;
-
-  private URL url;
-  private int timeout = DEFAULT_COMMON_TIMEOUT_MS;
-
-  public HttpEndpoint(String url) throws IOException {
-    this.url = new URL(url);
-  }
-
-  public InputStream post(Map<String,Object> params) throws IOException {
-    return post(encodeParameters(params));
-  }
-
-  public InputStream post(String content) throws IOException {
-    HttpURLConnection connection = buildConnection("POST");
-    connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-    return send(connection, content);
-  }
-
-  private InputStream send(HttpURLConnection connection, String content) throws IOException {
-    DataOutputStream out = new DataOutputStream(connection.getOutputStream());
-    out.writeBytes(content);
-    out.flush();
-    out.close();
-    return connection.getInputStream();
-  }
-
-  private HttpURLConnection buildConnection(String httpMethod) throws IOException {
-    HttpURLConnection connection = (HttpURLConnection) this.url.openConnection();
-    connection.setUseCaches(false);
-    connection.setDoOutput(true);
-    connection.setConnectTimeout(timeout);
-    connection.setReadTimeout(timeout);
-    connection.setRequestMethod(httpMethod);
-    return connection;
-  }
-
-  private static String encodeParameters(Map<String,Object> params)
-      throws UnsupportedEncodingException {
-    String content = "";
-    for (Object key : params.keySet()) {
-      Object value = params.get(key);
-      String ukey = URLEncoder.encode((String) key, String.valueOf(Charsets.UTF_8));
-      String uvalue = URLEncoder.encode(String.valueOf(value), String.valueOf(Charsets.UTF_8));
-      content += ukey + "=" + uvalue + "&";
-    }
-    return content.substring(0, content.length()-1);
-  }
-
-  public void setTimeout(int timeout) {
-    this.timeout = timeout;
-  }
+public interface HttpEndpoint {
+  ListenableFuture<?> post(String content) throws IOException;
 }
diff --git a/test/com/facebook/buck/event/listener/BUCK b/test/com/facebook/buck/event/listener/BUCK
index 66cf52a..2212b4e 100644
--- a/test/com/facebook/buck/event/listener/BUCK
+++ b/test/com/facebook/buck/event/listener/BUCK
@@ -27,6 +27,7 @@
     '//src/com/facebook/buck/util:constants',
     '//src/com/facebook/buck/util:io',
     '//src/com/facebook/buck/util/environment:environment',
+    '//src/com/facebook/buck/util:network',
     '//test/com/facebook/buck/event:testutil',
     '//test/com/facebook/buck/model:BuildTargetFactory',
     '//test/com/facebook/buck/rules:testutil',