All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fabric8.kubernetes.client.jdkhttp.JdkHttpClientImpl Maven / Gradle / Ivy

/*
 * Copyright (C) 2015 Red Hat, 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 io.fabric8.kubernetes.client.jdkhttp;

import io.fabric8.kubernetes.client.http.AsyncBody;
import io.fabric8.kubernetes.client.http.AsyncBody.Consumer;
import io.fabric8.kubernetes.client.http.BufferUtil;
import io.fabric8.kubernetes.client.http.HttpRequest;
import io.fabric8.kubernetes.client.http.HttpResponse;
import io.fabric8.kubernetes.client.http.StandardHttpClient;
import io.fabric8.kubernetes.client.http.StandardHttpRequest;
import io.fabric8.kubernetes.client.http.StandardHttpRequest.BodyContent;
import io.fabric8.kubernetes.client.http.StandardHttpRequest.ByteArrayBodyContent;
import io.fabric8.kubernetes.client.http.StandardHttpRequest.InputStreamBodyContent;
import io.fabric8.kubernetes.client.http.StandardHttpRequest.StringBodyContent;
import io.fabric8.kubernetes.client.http.StandardWebSocketBuilder;
import io.fabric8.kubernetes.client.http.WebSocket;
import io.fabric8.kubernetes.client.http.WebSocket.Listener;
import io.fabric8.kubernetes.client.http.WebSocketResponse;
import io.fabric8.kubernetes.client.http.WebSocketUpgradeResponse;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandler;
import java.net.http.HttpResponse.BodyHandlers;
import java.net.http.HttpResponse.BodySubscriber;
import java.net.http.HttpResponse.ResponseInfo;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Flow;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import static io.fabric8.kubernetes.client.http.StandardHttpHeaders.CONTENT_TYPE;

/**
 * TODO:
 * - Mapping to a Reader is always UTF-8
 */
public class JdkHttpClientImpl extends StandardHttpClient {

  /**
   * Adapts the BodyHandler to immediately complete the body
   */
  private static final class BodyHandlerAdapter implements BodyHandler {
    private final AsyncBodySubscriber subscriber;
    private final BodyHandler handler;

    private BodyHandlerAdapter(AsyncBodySubscriber subscriber, BodyHandler handler) {
      this.subscriber = subscriber;
      this.handler = handler;
    }

    @Override
    public BodySubscriber apply(ResponseInfo responseInfo) {
      BodySubscriber bodySubscriber = handler.apply(responseInfo);
      return new BodySubscriber() {
        CompletableFuture cf = CompletableFuture.completedFuture(subscriber);

        @Override
        public void onSubscribe(Subscription subscription) {
          bodySubscriber.onSubscribe(subscription);
        }

        @Override
        public void onNext(List item) {
          // there doesn't seem to be a guarantee that the buffer won't be modified by the caller
          // after passing it in, so we'll create a copy
          bodySubscriber.onNext(item.stream().map(BufferUtil::copy).collect(Collectors.toList()));
        }

        @Override
        public void onError(Throwable throwable) {
          bodySubscriber.onError(throwable);
        }

        @Override
        public void onComplete() {
          bodySubscriber.onComplete();
        }

        @Override
        public CompletionStage getBody() {
          return cf;
        }
      };
    }
  }

  private static final class AsyncBodySubscriber implements Subscriber, AsyncBody {
    private final AsyncBody.Consumer consumer;
    private final CompletableFuture done = new CompletableFuture();
    private final CompletableFuture subscription = new CompletableFuture<>();

    private AsyncBodySubscriber(AsyncBody.Consumer consumer) {
      this.consumer = consumer;
    }

    @Override
    public void onSubscribe(Subscription subscription) {
      if (this.subscription.isDone()) {
        subscription.cancel();
        return;
      }
      this.subscription.complete(subscription);
    }

    @Override
    public void onNext(T item) {
      try {
        if (item == null) {
          done.complete(null);
        } else {
          consumer.consume(item, this);
        }
      } catch (Exception e) {
        subscription.thenAccept(Subscription::cancel);
        done.completeExceptionally(e);
      }
    }

    @Override
    public void onError(Throwable throwable) {
      done.completeExceptionally(throwable);
    }

    @Override
    public void onComplete() {
      done.complete(null);
    }

    @Override
    public void consume() {
      if (done.isDone()) {
        return;
      }
      this.subscription.thenAccept(s -> s.request(1));
    }

    @Override
    public CompletableFuture done() {
      return done;
    }

    @Override
    public void cancel() {
      subscription.thenAccept(Subscription::cancel);
      done.cancel(false);
    }

  }

  private static class JdkHttpResponseImpl implements HttpResponse {

    private java.net.http.HttpResponse response;
    private T body;

    public JdkHttpResponseImpl(java.net.http.HttpResponse response) {
      this(response, response.body());
    }

    public JdkHttpResponseImpl(java.net.http.HttpResponse response, T body) {
      this.response = response;
      this.body = body;
    }

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

    @Override
    public Map> headers() {
      return response.headers().map();
    }

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

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

    @Override
    public HttpRequest request() {
      java.net.http.HttpRequest request = response.request();
      // TODO: could try to subscribe to the request body, will just assume a null body for now
      return new StandardHttpRequest(request.headers().map(), request.uri(), request.method(), null);
    }

    @Override
    public Optional> previousResponse() {
      return response.previousResponse().map(JdkHttpResponseImpl::new);
    }

  }

  private java.net.http.HttpClient httpClient;

  public JdkHttpClientImpl(JdkHttpClientBuilderImpl builder, HttpClient httpClient, AtomicBoolean closed) {
    super(builder, closed);
    this.httpClient = httpClient;
  }

  @Override
  public void doClose() {
    if (this.httpClient == null) {
      return;
    }
    builder.getClientFactory().closeHttpClient(this);
    // help with default cleanup, which is based upon garbage collection
    this.httpClient = null;
  }

  @Override
  public DerivedClientBuilder newBuilder() {
    return this.builder.copy(this);
  }

  @Override
  public CompletableFuture> consumeBytesDirect(StandardHttpRequest request,
      Consumer> consumer) {
    AsyncBodySubscriber> subscriber = new AsyncBodySubscriber<>(consumer);
    BodyHandler handler = BodyHandlers.fromSubscriber(subscriber);
    BodyHandler handlerAdapter = new BodyHandlerAdapter(subscriber, handler);

    return this.getHttpClient().sendAsync(requestBuilder(request).build(), handlerAdapter)
        .thenApply(r -> new JdkHttpResponseImpl<>(r, r.body()));
  }

  java.net.http.HttpRequest.Builder requestBuilder(StandardHttpRequest request) {
    java.net.http.HttpRequest.Builder requestBuilder = java.net.http.HttpRequest.newBuilder();

    Duration timeout = request.getTimeout();
    if (timeout != null && !java.time.Duration.ZERO.equals(timeout)) {
      requestBuilder.timeout(timeout);
    }

    request.headers().entrySet().stream()
        .forEach(e -> e.getValue().stream().forEach(v -> requestBuilder.header(e.getKey(), v)));
    if (request.getContentType() != null) {
      requestBuilder.setHeader(CONTENT_TYPE, request.getContentType());
    }

    BodyContent body = request.body();
    if (body != null) {
      if (body instanceof StringBodyContent) {
        requestBuilder.method(request.method(), BodyPublishers.ofString(((StringBodyContent) body).getContent()));
      } else if (body instanceof ByteArrayBodyContent) {
        requestBuilder.method(request.method(), BodyPublishers.ofByteArray(((ByteArrayBodyContent) body).getContent()));
      } else if (body instanceof InputStreamBodyContent) {
        InputStreamBodyContent bodyContent = (InputStreamBodyContent) body;
        BodyPublisher publisher = BodyPublishers.ofInputStream(bodyContent::getContent);
        requestBuilder.method(request.method(), new BodyPublisher() {

          @Override
          public void subscribe(Subscriber subscriber) {
            publisher.subscribe(subscriber);
          }

          @Override
          public long contentLength() {
            return bodyContent.getLength();
          }
        });
      } else {
        throw new AssertionError("Unsupported body content");
      }
    }

    requestBuilder.uri(request.uri());
    if (request.isExpectContinue() && this.builder.getClientFactory().useExpectContinue()) {
      requestBuilder.expectContinue(true);
    }
    return requestBuilder;
  }

  @Override
  public CompletableFuture buildWebSocketDirect(
      StandardWebSocketBuilder standardWebSocketBuilder, Listener listener) {
    StandardHttpRequest request = standardWebSocketBuilder.asHttpRequest();
    java.net.http.WebSocket.Builder newBuilder = this.getHttpClient().newWebSocketBuilder();
    request.headers().forEach((k, v) -> v.forEach(s -> newBuilder.header(k, s)));
    if (standardWebSocketBuilder.getSubprotocol() != null) {
      newBuilder.subprotocols(standardWebSocketBuilder.getSubprotocol());
    }
    Duration timeout = request.getTimeout();
    if (timeout != null && !java.time.Duration.ZERO.equals(timeout)) {
      newBuilder.connectTimeout(timeout);
    }

    // use a responseholder to convey both the exception and the websocket
    CompletableFuture response = new CompletableFuture<>();

    URI uri = WebSocket.toWebSocketUri(request.uri());
    final JdkWebSocketImpl fabric8WebSocket = new JdkWebSocketImpl(listener);
    newBuilder.buildAsync(uri, fabric8WebSocket).whenComplete((jdkWebSocket, t) -> {
      if (t instanceof CompletionException && t.getCause() != null) {
        t = t.getCause();
      }
      if (t instanceof java.net.http.WebSocketHandshakeException) {
        final java.net.http.HttpResponse jdkResponse = ((java.net.http.WebSocketHandshakeException) t).getResponse();
        final WebSocketUpgradeResponse upgradeResponse = new WebSocketUpgradeResponse(
            request, jdkResponse.statusCode(), jdkResponse.headers().map());
        response.complete(new WebSocketResponse(upgradeResponse, t));
      } else if (t != null) {
        response.completeExceptionally(t);
      } else {
        response.complete(new WebSocketResponse(new WebSocketUpgradeResponse(request), fabric8WebSocket));
      }
    });

    return response;
  }

  java.net.http.HttpClient getHttpClient() {
    if (httpClient == null) {
      throw new IllegalStateException("Client already closed");
    }
    return httpClient;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy