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

com.google.apphosting.runtime.http.JdkHttpApiHostClient Maven / Gradle / Ivy

There is a newer version: 2.0.31
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.google.apphosting.runtime.http;

import static java.lang.Math.max;

import com.google.apphosting.base.protos.RuntimePb.APIResponse;
import com.google.apphosting.runtime.anyrpc.AnyRpcCallback;
import com.google.common.flogger.GoogleLogger;
import com.google.common.io.ByteStreams;
import com.google.common.primitives.Ints;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * An alternative API client that uses the JDK's built-in HTTP client. This is likely to be much
 * less performant than {@link JettyHttpApiHostClient} but should allow us to determine whether
 * communications problems we are seeing are due to the Jetty client.
 */
class JdkHttpApiHostClient extends HttpApiHostClient {
  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();

  private static final int MAX_LENGTH = MAX_PAYLOAD + EXTRA_CONTENT_BYTES;

  private static final AtomicInteger threadCount = new AtomicInteger();

  private final URL url;
  private final Executor executor;

  private JdkHttpApiHostClient(Config config, URL url, Executor executor) {
    super(config);
    this.url = url;
    this.executor = executor;
  }

  static JdkHttpApiHostClient create(String url, Config config) {
    try {
      ThreadFactory factory =
          runnable -> {
            Thread t = new Thread(rootThreadGroup(), runnable);
            t.setName("JdkHttp-" + threadCount.incrementAndGet());
            t.setDaemon(true);
            return t;
          };
      Executor executor = Executors.newCachedThreadPool(factory);
      return new JdkHttpApiHostClient(config, new URL(url), executor);
    } catch (MalformedURLException e) {
      throw new UncheckedIOException(e);
    }
  }

  private static ThreadGroup rootThreadGroup() {
    ThreadGroup group = Thread.currentThread().getThreadGroup();
    ThreadGroup parent;
    while ((parent = group.getParent()) != null) {
      group = parent;
    }
    return group;
  }

  @Override
  void send(
      byte[] requestBytes,
      HttpApiHostClient.Context context,
      AnyRpcCallback callback) {
    executor.execute(() -> doSend(requestBytes, context, callback));
  }

  private void doSend(
      byte[] requestBytes,
      HttpApiHostClient.Context context,
      AnyRpcCallback callback) {
    try {
      HttpURLConnection connection = (HttpURLConnection) url.openConnection();
      connection.setDoOutput(true);
      HEADERS.forEach(connection::addRequestProperty);
      connection.addRequestProperty("Content-Type", "application/octet-stream");
      if (context.getDeadlineNanos().isPresent()) {
        double deadlineSeconds = context.getDeadlineNanos().get() / 1e9;
        connection.addRequestProperty(DEADLINE_HEADER, Double.toString(deadlineSeconds));
        int deadlineMillis =
            Ints.saturatedCast(max(1, context.getDeadlineNanos().get() / 1_000_000));
        connection.setReadTimeout(deadlineMillis);
      }
      connection.setFixedLengthStreamingMode(requestBytes.length);
      connection.setRequestMethod("POST");
      try (OutputStream out = connection.getOutputStream()) {
        out.write(requestBytes);
      }
      if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
        int length = connection.getContentLength();
        if (length > MAX_LENGTH) {
          connection.getInputStream().close();
          responseTooBig(callback);
        } else {
          byte[] buffer = new byte[length];
          try (InputStream in = connection.getInputStream()) {
            ByteStreams.readFully(in, buffer); // EOFException (an IOException) if too few bytes
            receivedResponse(buffer, length, context, callback);
          }
        }
      }
    } catch (SocketTimeoutException e) {
      logger.atWarning().withCause(e).log("SocketTimeoutException");
      timeout(callback);
    } catch (IOException e) {
      logger.atWarning().withCause(e).log("IOException");
      communicationFailure(context, e.toString(), callback, e);
    }
  }

  @Override
  public void enable() {
    throw new UnsupportedOperationException();
  }

  @Override
  public void disable() {
    throw new UnsupportedOperationException();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy