@Override
  public Cancellable getSocket(final AsyncHttpClientMiddleware.GetSocketData data) {
    final URI uri = data.request.getUri();
    final int port = getSchemePort(data.request.getUri());
    if (port == -1) {
      return null;
    }

    ConnectionInfo info = getConnectionInfo(uri.getScheme(), uri.getHost(), port);
    if (info.openCount >= maxConnectionCount) {
      // wait for a connection queue to free up
      SimpleCancellable queueCancel = new SimpleCancellable();
      info.queue.add(data);
      return queueCancel;
    }

    info.openCount++;

    final String lookup = computeLookup(uri, port, data.request);

    data.state.putBoolean(getClass().getCanonicalName() + ".owned", true);

    synchronized (this) {
      final HashSet<AsyncSocket> sockets = mSockets.get(lookup);
      if (sockets != null) {
        for (final AsyncSocket socket : sockets) {
          if (socket.isOpen()) {
            sockets.remove(socket);
            socket.setClosedCallback(null);

            mClient
                .getServer()
                .post(
                    new Runnable() {
                      @Override
                      public void run() {
                        data.request.logd("Reusing keep-alive socket");
                        data.connectCallback.onConnectCompleted(null, socket);
                      }
                    });

            // replace above code with immediate callback?
            //                        data.request.logd("Reusing keep-alive socket");
            //                        data.connectCallback.onConnectCompleted(null, socket);

            // just a noop/dummy, as this can't actually be cancelled.
            return new SimpleCancellable();
          }
        }
      }
    }

    if (!connectAllAddresses || proxyHost != null || data.request.getProxyHost() != null) {
      // just default to connecting to a single address
      data.request.logd("Connecting socket");
      String unresolvedHost;
      int unresolvedPort;
      if (data.request.getProxyHost() != null) {
        unresolvedHost = data.request.getProxyHost();
        unresolvedPort = data.request.getProxyPort();
        // set the host and port explicitly for proxied connections
        data.request
            .getHeaders()
            .getHeaders()
            .setStatusLine(data.request.getProxyRequestLine().toString());
      } else if (proxyHost != null) {
        unresolvedHost = proxyHost;
        unresolvedPort = proxyPort;
        // set the host and port explicitly for proxied connections
        data.request
            .getHeaders()
            .getHeaders()
            .setStatusLine(data.request.getProxyRequestLine().toString());
      } else {
        unresolvedHost = uri.getHost();
        unresolvedPort = port;
      }
      return mClient
          .getServer()
          .connectSocket(
              unresolvedHost, unresolvedPort, wrapCallback(data.connectCallback, uri, port));
    }

    // try to connect to everything...
    data.request.logv("Resolving domain and connecting to all available addresses");
    return mClient
        .getServer()
        .getAllByName(uri.getHost())
        .then(
            new TransformFuture<AsyncSocket, InetAddress[]>() {
              Exception lastException;

              @Override
              protected void error(Exception e) {
                super.error(e);
                data.connectCallback.onConnectCompleted(e, null);
              }

              @Override
              protected void transform(final InetAddress[] result) throws Exception {
                Continuation keepTrying =
                    new Continuation(
                        new CompletedCallback() {
                          @Override
                          public void onCompleted(Exception ex) {
                            // if it completed, that means that the connection failed
                            if (lastException == null)
                              lastException =
                                  new ConnectionFailedException(
                                      "Unable to connect to remote address");
                            setComplete(lastException);
                          }
                        });

                for (final InetAddress address : result) {
                  keepTrying.add(
                      new ContinuationCallback() {
                        @Override
                        public void onContinue(
                            Continuation continuation, final CompletedCallback next)
                            throws Exception {
                          mClient
                              .getServer()
                              .connectSocket(
                                  new InetSocketAddress(address, port),
                                  wrapCallback(
                                      new ConnectCallback() {
                                        @Override
                                        public void onConnectCompleted(
                                            Exception ex, AsyncSocket socket) {
                                          if (isDone()) {
                                            lastException =
                                                new Exception("internal error during connect");
                                            next.onCompleted(null);
                                            return;
                                          }

                                          // try the next address
                                          if (ex != null) {
                                            lastException = ex;
                                            next.onCompleted(null);
                                            return;
                                          }

                                          // if the socket is no longer needed, just hang onto it...
                                          if (isDone() || isCancelled()) {
                                            data.request.logd(
                                                "Recycling extra socket leftover from cancelled operation");
                                            idleSocket(socket);
                                            recycleSocket(socket, data.request);
                                            return;
                                          }

                                          if (setComplete(null, socket)) {
                                            data.connectCallback.onConnectCompleted(ex, socket);
                                          }
                                        }
                                      },
                                      uri,
                                      port));
                        }
                      });
                }

                keepTrying.start();
              }
            });
  }
  // step 1) see if we can serve request from the cache directly.
  // also see if this can be turned into a conditional cache request.
  @Override
  public Cancellable getSocket(final GetSocketData data) {
    if (cache == null) return null;

    if (!caching) return null;
    if (data.request.getHeaders().isNoCache()) return null;
    //        Log.i(LOGTAG, "getting cache socket: " + request.getUri().toString());

    String key = uriToKey(data.request.getUri());
    DiskLruCache.Snapshot snapshot;
    Entry entry;
    try {
      snapshot = cache.get(key);
      if (snapshot == null) {
        //                Log.i(LOGTAG, "snapshot fail");
        return null;
      }
      entry = new Entry(snapshot.getInputStream(ENTRY_METADATA));
    } catch (IOException e) {
      // Give up because the cache cannot be read.
      return null;
    }

    if (!entry.matches(
        data.request.getUri(),
        data.request.getMethod(),
        data.request.getHeaders().getHeaders().toMultimap())) {
      snapshot.close();
      return null;
    }

    ResponseSource responseSource = ResponseSource.NETWORK;

    CacheResponse candidate =
        entry.isHttps()
            ? new EntrySecureCacheResponse(entry, snapshot)
            : new EntryCacheResponse(entry, snapshot);

    Map<String, List<String>> responseHeadersMap;
    InputStream cachedResponseBody;
    try {
      responseHeadersMap = candidate.getHeaders();
      cachedResponseBody = candidate.getBody();
    } catch (Exception e) {
      return null;
    }
    if (responseHeadersMap == null || cachedResponseBody == null) {
      try {
        cachedResponseBody.close();
      } catch (Exception e) {
      }
      return null;
    }

    RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap);
    ResponseHeaders cachedResponseHeaders =
        new ResponseHeaders(data.request.getUri(), rawResponseHeaders);

    long now = System.currentTimeMillis();
    responseSource = cachedResponseHeaders.chooseResponseSource(now, data.request.getHeaders());

    if (responseSource == ResponseSource.CACHE) {
      cacheStoreCount++;
      final CachedSocket socket =
          entry.isHttps()
              ? new CachedSSLSocket((EntrySecureCacheResponse) candidate)
              : new CachedSocket((EntryCacheResponse) candidate);

      client
          .getServer()
          .post(
              new Runnable() {
                @Override
                public void run() {
                  data.connectCallback.onConnectCompleted(null, socket);
                  socket.spewInternal();
                }
              });
    } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
      CacheData cacheData = new CacheData();
      cacheData.cachedResponseHeaders = cachedResponseHeaders;
      cacheData.candidate = candidate;
      data.state.putParcelable("cache-data", cacheData);

      return null;
    } else {
      // NETWORK or other
      try {
        cachedResponseBody.close();
      } catch (Exception e) {
      }
      return null;
    }

    return new SimpleCancelable();
  }