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

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

/*
 * 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.jetty94;

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.RuntimePb.UPRequest;
import com.google.apphosting.base.protos.RuntimePb.UPResponse;
import com.google.apphosting.runtime.AppVersion;
import com.google.apphosting.runtime.MutableUpResponse;
import com.google.apphosting.runtime.ServletEngineAdapter;
import com.google.apphosting.runtime.jetty9.JettyLogger;
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.Optional;
import javax.servlet.ServletException;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
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());
    // 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) {
    server = new Server(new QueuedThreadPool(MAX_THREAD_POOL_THREADS, MIN_THREAD_POOL_THREADS));
    rpcConnector = new RpcConnector(server);
    server.setConnectors(new Connector[] {rpcConnector});
    AppVersionHandlerFactory appVersionHandlerFactory =
        new AppVersionHandlerFactory(
            server, serverInfo, contextFactory, /*useJettyErrorPageHandler=*/ false);
    appVersionHandlerMap = new AppVersionHandlerMap(appVersionHandlerFactory);

    if (!"java8".equals(System.getenv("GAE_RUNTIME"))) {
      SizeLimitHandler sizeLimitHandler = new SizeLimitHandler(-1, MAX_RESPONSE_SIZE);
      sizeLimitHandler.setHandler(appVersionHandlerMap);
      server.setHandler(sizeLimitHandler);
    } else {
      server.setHandler(appVersionHandlerMap);
    }

    if (runtimeOptions.useJettyHttpProxy()) {
      server.setAttribute(
          "com.google.apphosting.runtime.jetty94.appYaml",
          appYaml.orElseGet(() -> JettyServletEngineAdapter.getAppYaml(runtimeOptions)));
      JettyHttpProxy.startServer(runtimeOptions);
    }

    try {
      server.start();
    } catch (Exception ex) {
      // TODO: Should we have a wrapper exception for this
      // type of thing in ServletEngineAdapter?
      throw new RuntimeException(ex);
    }
  }

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

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

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

  /**
   * Sets the {@link com.google.apphosting.runtime.SessionStoreFactory} that will be used to create
   * the list of {@link com.google.apphosting.runtime.SessionStore SessionStores} to which the HTTP
   * Session will be stored, if sessions are enabled. This method must be invoked after {@link
   * #start(String)}.
   */
  @Override
  public void setSessionStoreFactory(com.google.apphosting.runtime.SessionStoreFactory factory) {
    appVersionHandlerMap.setSessionStoreFactory(factory);
  }

  @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