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

com.google.apphosting.runtime.jetty9.JettyServletEngineAdapter Maven / Gradle / Ivy

The 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.jetty9;

import static com.google.apphosting.runtime.AppEngineConstants.GAE_RUNTIME;
import static com.google.apphosting.runtime.AppEngineConstants.HTTP_CONNECTOR_MODE;
import static com.google.apphosting.runtime.AppEngineConstants.IGNORE_RESPONSE_SIZE_LIMIT;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.apphosting.base.AppVersionKey;
import com.google.apphosting.base.protos.AppinfoPb;
import com.google.apphosting.base.protos.EmptyMessage;
import com.google.apphosting.base.protos.RuntimePb.UPRequest;
import com.google.apphosting.base.protos.RuntimePb.UPResponse;
import com.google.apphosting.runtime.AppVersion;
import com.google.apphosting.runtime.LocalRpcContext;
import com.google.apphosting.runtime.MutableUpResponse;
import com.google.apphosting.runtime.ServletEngineAdapter;
import com.google.apphosting.runtime.SessionStoreFactory;
import com.google.apphosting.runtime.anyrpc.EvaluationRuntimeServerInterface;
import com.google.apphosting.utils.config.AppEngineConfigException;
import com.google.apphosting.utils.config.AppYaml;
import com.google.common.flogger.GoogleLogger;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Objects;
import java.util.Optional;
import javax.servlet.ServletException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.SizeLimitHandler;
import org.eclipse.jetty.util.thread.QueuedThreadPool;

/**
 * This is an implementation of ServletEngineAdapter that uses the third-party Jetty servlet engine.
 */
public class JettyServletEngineAdapter implements ServletEngineAdapter {
  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
  private static final String DEFAULT_APP_YAML_PATH = "/WEB-INF/appengine-generated/app.yaml";
  private static final int MIN_THREAD_POOL_THREADS = 0;
  private static final int MAX_THREAD_POOL_THREADS = 100;
  private static final long MAX_RESPONSE_SIZE = 32 * 1024 * 1024;
  private AppVersionKey lastAppVersionKey;

  static {
    // Tell Jetty to use our custom logging class (that forwards to
    // java.util.logging) instead of writing to System.err
    // Documentation: http://www.eclipse.org/jetty/documentation/current/configuring-logging.html
    System.setProperty("org.eclipse.jetty.util.log.class", JettyLogger.class.getName());
    if (Objects.equals(GAE_RUNTIME, "java8")) {
      // Remove internal URLs.
      System.setProperty("java.vendor.url", "");
      System.setProperty("java.vendor.url.bug", "");
    }
  }

  private Server server;
  private RpcConnector rpcConnector;
  private AppVersionHandlerMap appVersionHandlerMap;
  private final WebAppContextFactory contextFactory;
  private final Optional appYaml;

  public JettyServletEngineAdapter() {
    this(Optional.empty(), Optional.empty());
  }

  public JettyServletEngineAdapter(
      Optional contextFactory, Optional appYaml) {
    this.contextFactory = contextFactory.orElseGet(AppEngineWebAppContextFactory::new);
    this.appYaml = appYaml;
  }

  private static AppYaml getAppYaml(ServletEngineAdapter.Config runtimeOptions) {
    String applicationPath = runtimeOptions.fixedApplicationPath();
    File appYamlFile = new File(applicationPath + DEFAULT_APP_YAML_PATH);

    AppYaml appYaml = null;
    try {
      appYaml = AppYaml.parse(new InputStreamReader(new FileInputStream(appYamlFile), UTF_8));
    } catch (FileNotFoundException | AppEngineConfigException e) {
      logger.atWarning().log(
          "Failed to load app.yaml file at location %s - %s",
          appYamlFile.getPath(), e.getMessage());
    }
    return appYaml;
  }

  @Override
  public void start(String serverInfo, ServletEngineAdapter.Config runtimeOptions) {
    boolean isHttpConnectorMode = Boolean.getBoolean(HTTP_CONNECTOR_MODE);
    server = new Server(new QueuedThreadPool(MAX_THREAD_POOL_THREADS, MIN_THREAD_POOL_THREADS));

    if (!isHttpConnectorMode) {
      rpcConnector = new RpcConnector(server);
      server.addConnector(rpcConnector);
    }

    AppVersionHandlerFactory appVersionHandlerFactory =
        new AppVersionHandlerFactory(
            server, serverInfo, contextFactory, /* useJettyErrorPageHandler= */ false);
    appVersionHandlerMap = new AppVersionHandlerMap(appVersionHandlerFactory);
    server.setHandler(appVersionHandlerMap);

    boolean ignoreResponseSizeLimit =
        Objects.equals(GAE_RUNTIME, "java8")
            || Boolean.getBoolean(IGNORE_RESPONSE_SIZE_LIMIT);
    if (!ignoreResponseSizeLimit && !isHttpConnectorMode) {
      server.insertHandler(new SizeLimitHandler(-1, MAX_RESPONSE_SIZE));
    }

    try {
      boolean startJettyHttpProxy = false;
      if (runtimeOptions.useJettyHttpProxy()) {
        AppVersionKey appVersionKey;
        /* The init actions are not done in the constructor as they are not used when testing */
        String appRoot = runtimeOptions.applicationRoot();
        String appPath = runtimeOptions.fixedApplicationPath();
        AppInfoFactory appInfoFactory = new AppInfoFactory(System.getenv());
        AppinfoPb.AppInfo appinfo = appInfoFactory.getAppInfoFromFile(appRoot, appPath);
        // TODO Should we also call ApplyCloneSettings()?
        LocalRpcContext context = new LocalRpcContext<>(EmptyMessage.class);
        EvaluationRuntimeServerInterface evaluationRuntimeServerInterface =
            Objects.requireNonNull(runtimeOptions.evaluationRuntimeServerInterface());
        evaluationRuntimeServerInterface.addAppVersion(context, appinfo);
        EmptyMessage unused = context.getResponse();

        if (isHttpConnectorMode) {
          logger.atInfo().log("Using HTTP_CONNECTOR_MODE to bypass RPC");
          appVersionKey = AppVersionKey.fromAppInfo(appinfo);
          AppVersion appVersion = appVersionHandlerMap.getAppVersion(appVersionKey);
          ServerConnector connector = JettyHttpProxy.newConnector(server, runtimeOptions);
          server.addConnector(connector);
          server.insertHandler(
              new JettyHttpHandler(runtimeOptions, appVersion, appVersionKey, appInfoFactory, connector));
          JettyHttpProxy.insertHandlers(server, ignoreResponseSizeLimit);
        } else {
          server.setAttribute(
              "com.google.apphosting.runtime.jetty9.appYaml",
              appYaml.orElseGet(() -> JettyServletEngineAdapter.getAppYaml(runtimeOptions)));
          // Delay start of JettyHttpProxy until after the main server and application is started.
          startJettyHttpProxy = true;
        }
      }

      ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
      Thread.currentThread()
          .setContextClassLoader(JettyServletEngineAdapter.class.getClassLoader());
      try {
        server.start();
        if (startJettyHttpProxy) {
          JettyHttpProxy.startServer(runtimeOptions);
        }
      } finally {
        Thread.currentThread().setContextClassLoader(oldContextClassLoader);
      }
    } catch (Exception e) {
      if (e instanceof InterruptedException) {
        Thread.currentThread().interrupt();
      }
      throw new IllegalStateException(e);
    }
  }

  @Override
  public void stop() {
    try {
      server.stop();
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }

  @Override
  public void addAppVersion(AppVersion appVersion) {
    appVersionHandlerMap.addAppVersion(appVersion);
  }

  @Override
  public void deleteAppVersion(AppVersion appVersion) {
    appVersionHandlerMap.removeAppVersion(appVersion.getKey());
  }

  @Override
  public void setSessionStoreFactory(SessionStoreFactory factory) {
    // No op with the new Jetty Session management.
  }

  @Override
  public void serviceRequest(UPRequest upRequest, MutableUpResponse upResponse)
      throws ServletException, IOException {
    if (upRequest.getHandler().getType() != AppinfoPb.Handler.HANDLERTYPE.CGI_BIN_VALUE) {
      upResponse.setError(UPResponse.ERROR.UNKNOWN_HANDLER_VALUE);
      upResponse.setErrorMessage("Unsupported handler type: " + upRequest.getHandler().getType());
      return;
    }

    // Optimise this adaptor assuming one deployed appVersionKey, so use the last one if it matches
    // and only check the handler is available if we see a new/different key.
    AppVersionKey appVersionKey = lastAppVersionKey;
    if (appVersionKey == null
        || !appVersionKey.getAppId().equals(upRequest.getAppId())
        || !appVersionKey.getVersionId().equals(upRequest.getVersionId())) {
      appVersionKey = AppVersionKey.fromUpRequest(upRequest);
      // Check that a handler exists so we can deal with error condition here
      if (appVersionHandlerMap.getHandler(appVersionKey) == null) {
        upResponse.setError(UPResponse.ERROR.UNKNOWN_APP_VALUE);
        upResponse.setErrorMessage("Unknown app: " + appVersionKey);
        return;
      }
      lastAppVersionKey = appVersionKey;
    }

    rpcConnector.serviceRequest(appVersionKey, upRequest, upResponse);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy