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

com.google.appengine.tools.development.LocalEnvironment Maven / Gradle / Ivy

Go to download

SDK for dev_appserver (local development) with some of the dependencies shaded (repackaged)

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.appengine.tools.development;

import static java.nio.charset.StandardCharsets.US_ASCII;

import com.google.appengine.api.NamespaceManager;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.utils.config.WebModule;
// 
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * {@code LocalEnvironment} is a simple
 * {@link com.google.apphosting.api.ApiProxy.Environment} that reads
 * application-specific details (e.g. application identifer) directly from the
 * custom deployment descriptor.
 *
 */
abstract public class LocalEnvironment implements ApiProxy.Environment {
  private static final Logger logger = Logger.getLogger(LocalEnvironment.class.getName());
  static final Pattern APP_ID_PATTERN = Pattern.compile("([^:.]*)(:([^:.]*))?(.*)?");

  // Keep this in sync with other strings.
  private static final String APPS_NAMESPACE_KEY =
      NamespaceManager.class.getName() + ".appsNamespace";

  // Default port for tests that do not specify a port.
  public static final Integer TESTING_DEFAULT_PORT = Integer.valueOf(8080);

  // Environment attribute key where the instance id is stored. Keep in sync
  // with ModulesServiceImpl.INSTANCE_ID_ENV_ATTRIBUTE.
  public static final String INSTANCE_ID_ENV_ATTRIBUTE = "com.google.appengine.instance.id";

  // Environment attribute key where the instance port is stored. Keep in sync with
  // duplicate definitions of this value found in testing service stubs.
  public static final String PORT_ID_ENV_ATTRIBUTE = "com.google.appengine.instance.port";

  /**
   * {@code ApiProxy.Environment} instances used with {@code
   * ApiProxyLocalFactory} should have a entry in the map returned by
   * {@code getAttributes()} with this key, and a {@link
   * java.util.concurrent.Semaphore} as the value.  This is used
   * internally to track asynchronous API calls.
   */
  public static final String API_CALL_SEMAPHORE =
      "com.google.appengine.tools.development.api_call_semaphore";

  /**
   * The name of an {@link #getAttributes() attribute} that contains a
   * (String) unique identifier for the curent request.
   */
  public static final String REQUEST_ID =
      "com.google.appengine.runtime.request_log_id";

  /**
   * The name of an {@link #getAttributes() attribute} that contains a
   * a {@link Date} object representing the time this request was
   * started.
   */
  public static final String START_TIME_ATTR =
      "com.google.appengine.tools.development.start_time";

  /**
   * The name of an {@link #getAttributes() attribute} that contains a {@code
   * Set}. The set of {@link RequestEndListener
   * RequestEndListeners} is populated by from within the service calls. The
   * listeners are invoked at the end of a user request.
   */
  public static final String REQUEST_END_LISTENERS =
      "com.google.appengine.tools.development.request_end_listeners";

  /**
   * The name of an {@link #getAttributes() attribute} that contains the {@link
   * javax.servlet.http.HttpServletRequest} instance.
   */
  public static final String HTTP_SERVLET_REQUEST =
      "com.google.appengine.http_servlet_request";

  private static final String REQUEST_THREAD_FACTORY_ATTR =
      "com.google.appengine.api.ThreadManager.REQUEST_THREAD_FACTORY";

  private static final String BACKGROUND_THREAD_FACTORY_ATTR =
      "com.google.appengine.api.ThreadManager.BACKGROUND_THREAD_FACTORY";

  /**
   * For historical and probably compatibility reasons dev appserver assigns all
   * versions a minor version of 1.
   */
  private static final String MINOR_VERSION_SUFFIX = ".1";

  /**
   * In production, this is a constant that defines the {@link #getAttributes() attribute} name
   * that contains the hostname on which the default version is listening.
   * In the local development server, the {@link #getAttributes() attribute} contains the
   * listening port in addition to the hostname, and is the one and only frontend app instance
   * hostname and port.
   */
  // Must be kept in sync with java/com/google/apphosting/runtime/ApiProxyImpl.java
  // Must be kept in sync with java/com/google/appengine/api/backends/BackendServiceImpl.java
  public static final String DEFAULT_VERSION_HOSTNAME =
      "com.google.appengine.runtime.default_version_hostname";

  private final String appId;
  private final String moduleId;
  private final String versionId;

  private final Collection requestEndListeners;

  // This map can be accessed by multiple threads in the event of
  // asynchronous API calls.
  protected final ConcurrentMap attributes =
      new ConcurrentHashMap();

  // null means the request has no deadline
  @Nullable private final Long endTime;
  /**
   * Instance number for a main instance.
   * 

* Clients depend on this literal having the value -1 so do not change this * value without making needed updates to clients. */ public static final int MAIN_INSTANCE = -1; /** * Deprecated constructor used by tests. */ @Deprecated protected LocalEnvironment(String appId, String majorVersionId) { this(appId, WebModule.DEFAULT_MODULE_NAME, majorVersionId, LocalEnvironment.MAIN_INSTANCE, TESTING_DEFAULT_PORT, null); } /** * Deprecated constructor used by tests. */ @Deprecated protected LocalEnvironment(String appId, String majorVersionId, Long deadlineMillis) { this(appId, WebModule.DEFAULT_MODULE_NAME, majorVersionId, LocalEnvironment.MAIN_INSTANCE, TESTING_DEFAULT_PORT, deadlineMillis); } protected LocalEnvironment(String appId, String moduleName, String majorVersionId, int instance, Integer port, Long deadlineMillis) { this.appId = appId; this.moduleId = moduleName; this.versionId = (majorVersionId != null ? majorVersionId : "no_version") + MINOR_VERSION_SUFFIX; if (deadlineMillis == null) { this.endTime = null; } else if (deadlineMillis < 0) { throw new IllegalArgumentException("deadlineMillis must be a non-negative integer."); } else { this.endTime = System.currentTimeMillis() + deadlineMillis; } setInstance(attributes, instance); setPort(attributes, port); requestEndListeners = Collections.newSetFromMap(new ConcurrentHashMap(10)); attributes.put(REQUEST_ID, generateRequestId()); attributes.put(REQUEST_END_LISTENERS, requestEndListeners); attributes.put(START_TIME_ATTR, new Date()); attributes.put(REQUEST_THREAD_FACTORY_ATTR, new RequestThreadFactory()); attributes.put(BACKGROUND_THREAD_FACTORY_ATTR, new BackgroundThreadFactory(appId, moduleName, majorVersionId)); } /** Sets the instance for the provided attributes. */ public static void setInstance(Map attributes, int instance) { // First we remove the old value if there is one. attributes.remove(INSTANCE_ID_ENV_ATTRIBUTE); // Next we set the new value if needed. if (instance != LocalEnvironment.MAIN_INSTANCE) { attributes.put(INSTANCE_ID_ENV_ATTRIBUTE, Integer.toString(instance)); } } /** * Sets the {@link #PORT_ID_ENV_ATTRIBUTE} value to the provided port value or clears it if port * is null. */ public static void setPort(Map attributes, Integer port) { if (port == null) { attributes.remove(PORT_ID_ENV_ATTRIBUTE); } else { attributes.put(PORT_ID_ENV_ATTRIBUTE, port); } } /** * Returns the current instance or {@link #MAIN_INSTANCE} if none is defined. */ static int getCurrentInstance() { int result = MAIN_INSTANCE; String instance = (String) ApiProxy.getCurrentEnvironment().getAttributes().get( LocalEnvironment.INSTANCE_ID_ENV_ATTRIBUTE); if (instance != null) { result = Integer.parseInt(instance); } return result; } /** * Returns the current instance's port or null if none is defined. */ static Integer getCurrentPort() { return (Integer) ApiProxy.getCurrentEnvironment().getAttributes().get( LocalEnvironment.PORT_ID_ENV_ATTRIBUTE); } private static AtomicInteger requestID = new AtomicInteger(); /** * Generates a unique request ID using the same algorithm as the Python dev appserver. It is * similar to the production algorithm, but with less embedded data. The primary goal is that the * IDs be sortable by timestamp, so the initial bytes consist of seconds then microseconds packed * in big-endian byte order. To ensure uniqueness, a hash of the incrementing counter is appended. * Hexadecimal encoding is used instead of base64 in order to preserve comparison order. */ // private String generateRequestId() { try { ByteBuffer buf = ByteBuffer.allocate(12); // 8 bytes of timestamp. ByteByffer is big-endian by default. long now = System.currentTimeMillis(); buf.putInt((int) (now / 1000)); // seconds buf.putInt((int) ((now * 1000) % 1000000)); // microseconds // 4 bytes of hashed counter. See // http://g3doc/apphosting/g3doc/wiki-carryover/logsv2Design_doc.md#Row_Name_Format String nextID = Integer.toString(requestID.getAndIncrement()); byte[] hashBytes = MessageDigest.getInstance("SHA-1").digest(nextID.getBytes(US_ASCII)); buf.put(hashBytes, 0, 4); // 2 hex characters per byte. return String.format("%x", new BigInteger(buf.array())); } catch (Exception e) { return ""; } } @Override public String getAppId() { return appId; } @Override public String getModuleId() { return moduleId; } @Override public String getVersionId() { return versionId; } @Override public String getAuthDomain() { // Always return 'Google Accounts' in dev appserver. // Note that this is a property of the request not the appinfo, but the // default is set in the appinfo. return "gmail.com"; } @Override @Deprecated public final String getRequestNamespace() { String appsNamespace = (String) getAttributes().get(APPS_NAMESPACE_KEY); return appsNamespace == null ? "" : appsNamespace; } @Override public ConcurrentMap getAttributes() { return attributes; } public void callRequestEndListeners() { for (RequestEndListener listener : requestEndListeners) { try { listener.onRequestEnd(this); } catch (Exception ex) { logger.log(Level.WARNING, "Exception while attempting to invoke RequestEndListener " + listener.getClass() + ": ", ex); } } // Clear the end listeners to allow unit tests to call this manually but not // have the automatic invocation run them twice. requestEndListeners.clear(); } @Override public long getRemainingMillis() { if (endTime != null) { return endTime - System.currentTimeMillis(); } // NOTE: In the case of background threads -- or the fictitious // LocalApiProxyServletFilter -- let the request run forever. // Or approximately forever, which is almost 300 000 000 // years. return Long.MAX_VALUE; } /** * Returns the major version component from the provided version id or null * if the provided version id has no major version component. */ public static String getMajorVersion(String versionId) { Matcher matcher = APP_ID_PATTERN.matcher(versionId); matcher.find(); return matcher.group(3) == null ? matcher.group(1) : matcher.group(3); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy