class OkHttpResponseImpl<T> implements HttpResponse<T> {

    private final Response response;
    private T body;
    private Class<T> type;

    public OkHttpResponseImpl(Response response, T body) {
      this.response = response;
      this.body = body;
    }

    public OkHttpResponseImpl(Response response, Class<T> type) throws IOException {
      this.response = response;
      this.type = type;
      ResponseBody responseBody = response.body();
      if (responseBody != null) {
        if (type == null) {
          responseBody.close();
        } else if (type == String.class) {
          body = (T) responseBody.string();
        } else if (type == Reader.class) {
          body = (T) responseBody.charStream();
        } else if (type == byte[].class) {
          body = (T) responseBody.bytes();
        } else {
          body = (T) responseBody.byteStream();
        }
      }
    }

    @Override
    public int code() {
      return response.code();
    }

    @Override
    public T body() {
      return body;
    }

    @Override
    public HttpRequest request() {
      String bodyString = null;
      Request request = response.request();
      if (request.body() != null) {
        Buffer buffer = new Buffer();
        try {
          request.body().writeTo(buffer);
          bodyString = buffer.readUtf8();
        } catch (IOException e) {
          // just ignore, the bodyString is only for informational purposes
        }
      }
      return new StandardHttpRequest(
          request.headers().toMultimap(),
          request.url().uri(),
          request.method(),
          bodyString);
    }

    @Override
    public Optional<HttpResponse<?>> previousResponse() {
      Response previous = response.priorResponse() != null ? response.priorResponse() : response;
      previous = previous.networkResponse() != null ? previous.networkResponse() : previous;
      try {
        return Optional.ofNullable(previous == response ? null : new OkHttpResponseImpl<>(previous, type));
      } catch (IOException e) {
        throw new IllegalArgumentException(e);
      }
    }

    @Override
    public List<String> headers(String key) {
      return response.headers(key);
    }

    @Override
    public Map<String, List<String>> headers() {
      return response.headers().toMultimap();
    }

  }

  private final okhttp3.OkHttpClient httpClient;

  public OkHttpClientImpl(OkHttpClient client, OkHttpClientBuilderImpl builder, AtomicBoolean closed) {
    super(builder, closed);
    this.httpClient = client;
  }

  @Override
  public void doClose() {
    ConnectionPool connectionPool = httpClient.connectionPool();

    Dispatcher dispatcher = httpClient.dispatcher();
    ExecutorService executorService = httpClient.dispatcher() != null ? httpClient.dispatcher().executorService() : null;

    if (dispatcher != null) {
      dispatcher.cancelAll();
    }

    if (connectionPool != null) {
      connectionPool.evictAll();

      // begin hack to terminate the idle task, which is not necessary after 4.3.0 - https://github.com/square/okhttp/commit/bc3ad111ad01100a77846f7dc433b0c0f5b58dba
      // to immediately clean it up, we need to notify the thread waiting on the ConnectionPool / RealConnectionPool
      Object realConnectionPool = connectionPool;

      try {
        // 3.14+ holds a delegate to the real pool
        Method method = Internal.class.getMethod("realConnectionPool", ConnectionPool.class);
        realConnectionPool = method.invoke(Internal.instance, connectionPool);
      } catch (Exception e) {
        // could be 3.12
      }
      synchronized (realConnectionPool) {
        realConnectionPool.notifyAll();
      }
    }

    if (executorService != null) {
      executorService.shutdownNow();
    }
  }

  private CompletableFuture<HttpResponse<AsyncBody>> sendAsync(StandardHttpRequest request,
      Function<BufferedSource, AsyncBody> handler) {<FILL_FUNCTION_BODY>

    CompletableFuture<HttpResponse<AsyncBody>> future = new CompletableFuture<>();

    okhttp3.OkHttpClient.Builder clientBuilder = null;
    if (request.getTimeout() != null) {
      clientBuilder = httpClient.newBuilder();
      clientBuilder.callTimeout(request.getTimeout());
    }
    if (request.isForStreaming()) {
      if (clientBuilder == null) {
        clientBuilder = httpClient.newBuilder();
      }
      clientBuilder.cache(null);
    }

    Call call = Optional.ofNullable(clientBuilder).map(okhttp3.OkHttpClient.Builder::build).orElse(httpClient)
        .newCall(requestBuilder(request).build());
    try {
      call.enqueue(new Callback() {

        @Override
        public void onResponse(Call call, Response response) throws IOException {
          BufferedSource source = response.body().source();

          AsyncBody asyncBody = handler.apply(source);

          future.complete(new OkHttpResponseImpl<>(response, asyncBody));
        }

        @Override
        public void onFailure(Call call, IOException e) {
          Throwable t = e;
          if (e instanceof InterruptedIOException && e.getCause() instanceof RejectedExecutionException) {
            t = wrapRejected((RejectedExecutionException) e.getCause());
          }
          future.completeExceptionally(t);
        }
      });
    } catch (RejectedExecutionException e) {
      throw wrapRejected(e);
    }
    future.whenComplete((r, t) -> {
      if (future.isCancelled()) {
        call.cancel();
      }
    });
    return future;
  
<methods>public abstract CompletableFuture<io.fabric8.kubernetes.client.http.WebSocketResponse> buildWebSocketDirect(io.fabric8.kubernetes.client.http.StandardWebSocketBuilder, io.fabric8.kubernetes.client.http.WebSocket.Listener) ,public final void close() ,public CompletableFuture<HttpResponse<io.fabric8.kubernetes.client.http.AsyncBody>> consumeBytes(io.fabric8.kubernetes.client.http.HttpRequest, Consumer<List<java.nio.ByteBuffer>>) ,public abstract CompletableFuture<HttpResponse<io.fabric8.kubernetes.client.http.AsyncBody>> consumeBytesDirect(io.fabric8.kubernetes.client.http.StandardHttpRequest, Consumer<List<java.nio.ByteBuffer>>) ,public java.util.concurrent.atomic.AtomicBoolean getClosed() ,public V getTag(Class<V>) ,public boolean isClosed() ,public io.fabric8.kubernetes.client.http.HttpClient.DerivedClientBuilder newBuilder() ,public io.fabric8.kubernetes.client.http.HttpRequest.Builder newHttpRequestBuilder() ,public io.fabric8.kubernetes.client.http.WebSocket.Builder newWebSocketBuilder() ,public CompletableFuture<HttpResponse<V>> sendAsync(io.fabric8.kubernetes.client.http.HttpRequest, Class<V>) <variables>private static final Logger LOG,private static final long MAX_ADDITIONAL_REQUEST_TIMEOUT,protected StandardHttpClientBuilder<io.fabric8.kubernetes.client.okhttp.OkHttpClientImpl,io.fabric8.kubernetes.client.okhttp.OkHttpClientFactory,io.fabric8.kubernetes.client.okhttp.OkHttpClientBuilderImpl> builder,protected java.util.concurrent.atomic.AtomicBoolean closed