envVar = System.getenv();
- c = new StringBuilder(1024);
- for (String key : envVar.keySet()) {
- c.append(tr(td(key) + td(envVar.get(key))));
- }
- sb.append(table("Envirionment Variables", c.toString()));
+ m = new TreeMap<>(envVar);
+ ctx.setVariable("envvar", m);
+ // The metadata server is only on a production system
if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production) {
- c = new StringBuilder(1024);
- for (String key : v1) {
- String val = fetchMetadata(key);
- if (val != null) {
- c.append(tr(td(key) + td(val)));
- }
+
+ m = new TreeMap<>();
+ for (String k : metaPath) {
+ m.put(k, fetchMetadata(key));
}
- sb.append(table("Metadata", c.toString()));
-
- c = new StringBuilder(1024);
- for (String key : v1Acct) {
- key = key.replace("{account}", appIdentity.getServiceAccountName());
- String val = fetchMetadata(key);
- if (val != null) {
- c.append(tr(td(key) + td(val)));
- }
+ ctx.setVariable("Metadata", m.descendingMap());
+
+ m = new TreeMap<>();
+ for (String k : metaServiceAcct) {
+ // substitute a service account for {account}
+ k = k.replace("{account}", appIdentity.getServiceAccountName());
+ m.put(k, fetchMetadata(k));
}
- sb.append(table("ServiceAccount Metadata", c.toString()));
-
- sb.append("Recursive service-accounts
"
- + recursMetadata("/computeMetadata/v1/instance/service-accounts/")
- + "
");
- sb.append("Recursive all metadata
"
- + recursMetadata("/")
- + "
");
- }
+ ctx.setVariable("sam", m.descendingMap());
- sb.append("");
- p.append(sb);
- p.close();
+ // Recursivly get all info about service accounts -- Note tokens are leftout by default.
+ ctx.setVariable("rsa",
+ fetchJsonMetadata("/computeMetadata/v1/instance/service-accounts/?recursive=true"));
+ // Recursivly get all data on Metadata server.
+ ctx.setVariable("ram", fetchJsonMetadata("/?recursive=true"));
+ }
+ templateEngine.process("index", ctx, resp.getWriter());
}
}
// [END example]
diff --git a/appengine-java8/gaeinfo/src/main/webapp/WEB-INF/templates/index.html b/appengine-java8/gaeinfo/src/main/webapp/WEB-INF/templates/index.html
new file mode 100644
index 00000000000..8b4a61fcebc
--- /dev/null
+++ b/appengine-java8/gaeinfo/src/main/webapp/WEB-INF/templates/index.html
@@ -0,0 +1,99 @@
+
+
+
+
+ GAE standard Metadata
+
+
+
+AppIdentity
+
+ ServiceAccountName | |
+ GCS Bucket | |
+
+SystemProperties
+
+ appId | |
+ appVer | |
+ version | |
+ environment | |
+
+Environment Attributes
+
+Headers
+
+Cookies
+
+Java SystemProperties
+
+Envirionment Variables
+
+
+
+
Metadata
+
+
ServiceAccount Metadata
+
+
Recursive service-accounts
+
+
Recursive all metadata
+
+
+
No Local Metadata Server
+
+
+
diff --git a/appengine-java8/metadata/README.md b/appengine-java8/metadata/README.md
new file mode 100644
index 00000000000..cce04b74c42
--- /dev/null
+++ b/appengine-java8/metadata/README.md
@@ -0,0 +1,51 @@
+# Google App Engine - Metadata Inspection
+
+This sample displays what's going on in your app. It dumps the environment and lots more.
+
+See the [Google App Engine standard environment documentation][ae-docs] for more
+detailed instructions.
+
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+
+## Setup
+
+Use either:
+
+* `gcloud init`
+* `gcloud auth application-default login`
+* `gcloud components update`
+
+## Maven
+### Running locally
+
+ $ mvn appengine:run
+
+### Deploying
+
+ $ mvn appengine:deploy
+
+
diff --git a/appengine-java8/metadata/pom.xml b/appengine-java8/metadata/pom.xml
new file mode 100644
index 00000000000..a3bde4ceec1
--- /dev/null
+++ b/appengine-java8/metadata/pom.xml
@@ -0,0 +1,107 @@
+
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+ com.example.appengine
+ metadata-j8
+
+
+ appengine-java8-samples
+ com.google.cloud
+ 1.0.0
+ ..
+
+
+
+
+ 1.8
+ 1.8
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ ${appengine.sdk.version}
+
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ jar
+ provided
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 3.8.1
+
+
+
+ com.google.code.gson
+ gson
+ 2.8.1
+
+
+
+ org.thymeleaf
+ thymeleaf
+ 3.0.7.RELEASE
+
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.0.0
+
+
+
+
+ ${basedir}/src/main/webapp/WEB-INF
+ true
+ WEB-INF
+
+
+
+
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 1.3.1
+
+ true
+ true
+
+
+
+
+
+
+
diff --git a/appengine-java8/metadata/src/main/java/com/example/appengine/standard/MetadataServlet.java b/appengine-java8/metadata/src/main/java/com/example/appengine/standard/MetadataServlet.java
new file mode 100644
index 00000000000..b160d5e3b4e
--- /dev/null
+++ b/appengine-java8/metadata/src/main/java/com/example/appengine/standard/MetadataServlet.java
@@ -0,0 +1,161 @@
+/**
+ * Copyright 2017 Google 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 com.example.appengine.standard;
+
+import com.google.appengine.api.appidentity.AppIdentityService;
+import com.google.appengine.api.appidentity.AppIdentityServiceFactory;
+import com.google.appengine.api.utils.SystemProperty;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.context.WebContext;
+import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
+
+
+// [START example]
+@SuppressWarnings({"serial"})
+// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
+@WebServlet(name = "Metadata", description = "Metadata: Write info about GAE Standard",
+ urlPatterns = "/metadata")
+public class MetadataServlet extends HttpServlet {
+
+ private final String[] metaPath = {
+ "/computeMetadata/v1/project/numeric-project-id", // (pending)
+ "/computeMetadata/v1/project/project-id",
+ "/computeMetadata/v1/instance/zone",
+ "/computeMetadata/v1/instance/service-accounts/default/aliases",
+ "/computeMetadata/v1/instance/service-accounts/default/",
+ "/computeMetadata/v1/instance/service-accounts/default/scopes",
+// Tokens work - but are a security risk to display
+// "/computeMetadata/v1/instance/service-accounts/default/token"
+ };
+
+ final String[] metaServiceAcct = {
+ "/computeMetadata/v1/instance/service-accounts/{account}/aliases",
+ "/computeMetadata/v1/instance/service-accounts/{account}/email",
+ "/computeMetadata/v1/instance/service-accounts/{account}/scopes",
+// Tokens work - but are a security risk to display
+// "/computeMetadata/v1/instance/service-accounts/{account}/token"
+ };
+
+ private final String metadata = "http://metadata.google.internal";
+ private TemplateEngine templateEngine;
+
+ // Use OkHttp from Square as it's quite easy to use for simple fetches.
+ private final OkHttpClient ok = new OkHttpClient.Builder()
+ .readTimeout(500, TimeUnit.MILLISECONDS) // Don't dawdle
+ .writeTimeout(500, TimeUnit.MILLISECONDS)
+ .build();
+
+ // Setup to pretty print returned json
+ private final Gson gson = new GsonBuilder()
+ .setPrettyPrinting()
+ .create();
+ private final JsonParser jp = new JsonParser();
+
+ // Fetch Metadata
+ String fetchMetadata(String key) throws IOException {
+ Request request = new Request.Builder()
+ .url(metadata + key)
+ .addHeader("Metadata-Flavor", "Google")
+ .get()
+ .build();
+
+ Response response = ok.newCall(request).execute();
+ return response.body().string();
+ }
+
+ String fetchJsonMetadata(String prefix) throws IOException {
+ Request request = new Request.Builder()
+ .url(metadata + prefix )
+ .addHeader("Metadata-Flavor", "Google")
+ .get()
+ .build();
+
+ Response response = ok.newCall(request).execute();
+
+ // Convert json to prety json
+ return gson.toJson(jp.parse(response.body().string()));
+ }
+
+ @Override
+ public void init() {
+ // Setup ThymeLeaf
+ ServletContextTemplateResolver templateResolver =
+ new ServletContextTemplateResolver(this.getServletContext());
+
+ templateResolver.setPrefix("/WEB-INF/templates/");
+ templateResolver.setSuffix(".html");
+ templateResolver.setCacheTTLMs(Long.valueOf(1200000L)); // TTL=20m
+
+ // Cache is set to true by default. Set to false if you want templates to
+ // be automatically updated when modified.
+ templateResolver.setCacheable(true);
+
+ templateEngine = new TemplateEngine();
+ templateEngine.setTemplateResolver(templateResolver);
+ }
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
+ WebContext ctx = new WebContext(req, resp, getServletContext(), req.getLocale());
+
+ resp.setContentType("text/html");
+
+ ctx.setVariable("production", SystemProperty.environment.value().name());
+
+ // The metadata server is only on a production system
+ if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production) {
+
+ TreeMap m = new TreeMap<>();
+
+ for (String key : metaPath) {
+ m.put(key, fetchMetadata(key));
+ }
+
+ ctx.setVariable("Metadata", m.descendingMap());
+
+ m = new TreeMap<>();
+ for (String key : metaServiceAcct) {
+ // substitute a service account for {account}
+ key = key.replace("{account}", appIdentity.getServiceAccountName());
+ m.put(key, fetchMetadata(key));
+ }
+ ctx.setVariable("sam", m.descendingMap());
+
+ // Recursivly get all info about service accounts -- Note tokens are leftout by default.
+ ctx.setVariable("rsa",
+ fetchJsonMetadata("/computeMetadata/v1/instance/service-accounts/?recursive=true"));
+ // Recursivly get all data on Metadata server.
+ ctx.setVariable("ram", fetchJsonMetadata("/?recursive=true"));
+ }
+
+ templateEngine.process("index", ctx, resp.getWriter());
+
+ }
+}
+// [END example]
diff --git a/appengine-java8/metadata/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java8/metadata/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..87d254db02b
--- /dev/null
+++ b/appengine-java8/metadata/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ true
+ true
+ java8
+
+
+
diff --git a/appengine-java8/metadata/src/main/webapp/WEB-INF/templates/index.html b/appengine-java8/metadata/src/main/webapp/WEB-INF/templates/index.html
new file mode 100644
index 00000000000..0f4e99cbde3
--- /dev/null
+++ b/appengine-java8/metadata/src/main/webapp/WEB-INF/templates/index.html
@@ -0,0 +1,49 @@
+
+
+
+
+ GAE standard Metadata
+
+
+
+
+
+
Metadata
+
+
ServiceAccount Metadata
+
+
Recursive service-accounts
+
+
Recursive all metadata
+
+
+
No Local Metadata Server
+
+
+
\ No newline at end of file
diff --git a/appengine-java8/metadata/src/main/webapp/WEB-INF/web.xml b/appengine-java8/metadata/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..82253902cbc
--- /dev/null
+++ b/appengine-java8/metadata/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ metadata
+
+
diff --git a/appengine-java8/pom.xml b/appengine-java8/pom.xml
index e4880272fe8..9c203faf31c 100644
--- a/appengine-java8/pom.xml
+++ b/appengine-java8/pom.xml
@@ -61,9 +61,15 @@
mailgun
mailjet
memcache
+
+ metadata
+
multitenancy
+
oauth2
+
postgres
+
requests
remote-client
diff --git a/appengine/pom.xml b/appengine/pom.xml
index e07b0e4806f..4a7b7e11161 100644
--- a/appengine/pom.xml
+++ b/appengine/pom.xml
@@ -58,6 +58,9 @@
endpoints-frameworks-v2/migration-example
firebase-event-proxy/gae-firebase-event-proxy
firebase-tictactoe
+
+ gaeinfo
+
guestbook-objectify
helloworld
helloworld-new-plugins
@@ -74,7 +77,11 @@
sendgrid
remote/remote-client
remote/remote-server
+
+ sockets
+
static-files
+
taskqueue-push
twilio
urlfetch
diff --git a/appengine/sockets/README.md b/appengine/sockets/README.md
new file mode 100644
index 00000000000..2643cff3e9c
--- /dev/null
+++ b/appengine/sockets/README.md
@@ -0,0 +1,56 @@
+
+# Java Socket API Example App: Whois Client
+
+This sample displays what's going on in your app. It dumps the environment and lots more.
+
+See the [Google App Engine standard environment documentation][ae-docs] for more
+detailed instructions.
+
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+
+## Setup
+
+Use either:
+
+* `gcloud init`
+* `gcloud auth application-default login`
+
+## Maven
+### Running locally
+
+ $ mvn appengine:run
+
+### Deploying
+
+ $ mvn appengine:deploy
+
+
+
+This small example application demonstrates the use of the App Engine Socket API to query a Whois server.
+
+To build, install [maven](http://maven.apache.org/), then run `mvn package`.
+You can run the local dev app server via `mvn appengine:devserver` and deploy via `mvnappengine:update`.
diff --git a/appengine/sockets/README_LIBS.md b/appengine/sockets/README_LIBS.md
new file mode 100644
index 00000000000..8e0c5139636
--- /dev/null
+++ b/appengine/sockets/README_LIBS.md
@@ -0,0 +1,41 @@
+
+# Using the Socket API with Some Common Third-Party Libraries
+
+## Using the Socket API with JavaPNS
+
+**Note:** JavaPNS
+is an open source library for Apple Push Notifications. With socket support for
+App Engine, many applications can now use JavaPNS on App Engine directly.
+
+App Engine does not officially support JavaPNS, although it may work
+with these caveats:
+
+- **JavaPNS** uses
+ Security.getProperty,
+ which was not permitted in versions of App Engine earlier than 1.7.3. However,
+ if you need this to work in 1.7.2, you may change the `javapns`
+ source to not use `Security.getProperty`, and simply set
+ **ALGORITHM** to ``sunx509`. (Future versions are expected to fix this by replacing the use of
+ `Security.getProperty` with `KeyManagerFactory.getDefaultAlgorithm()`, which will work correctly
+ with older versions of App Engine.
+
+- App Engine does not support signed JAR files; accordingly the "Bouncy Castle" jar provided with the javapns
distribution fails to load. To fix this,
+remove the META-INF/MANIFEST.MF
file from ``bcprov-jdk15-146.jar`` using the following command line:
+
+ zip -d bcprov-jdk15-146.jar META-INF/MANIFEST.MF
+
+
+## Using JavaMail with the Socket API
+
+App Engine does not officially support JavaMail, although it may work
+with these caveats:
+
+JavaMail
+ 1.4.5 is compatible with App Engine but requires a work-around to
+ resolve the issue of
+ class loader
+ ordering. Currently, there are JavaMail classes in the `appengine-api.jar`, which is scanned
+ before all other JAR files. This causes the wrong `javax.mail` classes to be loaded. The work-around is to unzip the JavaMail 1.4.5
+ `mailapi.jar` file (excluding the `META-INF/MANIFEST.MF` file) into the `WEB-INF/classes` directory. This causes the correct
+ classes to be loaded before those in `appengine-api.jar`.
+
diff --git a/appengine/sockets/pom.xml b/appengine/sockets/pom.xml
new file mode 100755
index 00000000000..ada36c8e6ee
--- /dev/null
+++ b/appengine/sockets/pom.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+
+ com.example.appengine
+ sockets
+
+
+
+ appengine-doc-samples
+ com.google.cloud
+ 1.0.0
+ ..
+
+
+
+
+ 1.7
+ 1.7
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ ${appengine.sdk.version}
+
+
+
+ javax.servlet
+ servlet-api
+ 2.5
+ provided
+
+
+
+
+ org.jsoup
+ jsoup
+ 1.10.3
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.0.0
+
+
+
+
+ ${basedir}/src/main/webapp/WEB-INF
+ true
+ WEB-INF
+
+
+
+
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 1.3.1
+
+ true
+ true
+
+
+
+
+
+
diff --git a/appengine/sockets/src/main/java/com/example/appengine/sockets/WhoIsClientServlet.java b/appengine/sockets/src/main/java/com/example/appengine/sockets/WhoIsClientServlet.java
new file mode 100644
index 00000000000..39bfc397392
--- /dev/null
+++ b/appengine/sockets/src/main/java/com/example/appengine/sockets/WhoIsClientServlet.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright 2017 Google 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 com.example.appengine.sockets;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.Socket;
+import java.util.logging.Logger;
+
+import javax.servlet.http.*;
+
+import org.jsoup.Jsoup;
+import org.jsoup.safety.Whitelist;
+
+
+
+/**
+ * This simple example uses the Socket API to access a WHOIS server and query
+ * for domains that contain the string "google.com".
+ **/
+@SuppressWarnings("serial")
+public class WhoIsClientServlet extends HttpServlet {
+
+ private static final Logger log = Logger.getLogger(WhoIsClientServlet.class.getName());
+
+ private static final int DEFAULT_PORT = 43;
+ private static final String DEFAULT_SERVER = "whois.internic.net";
+
+ void writeHeader(HttpServletResponse resp, String name) throws IOException {
+ resp.setContentType("text/html");
+ resp.setCharacterEncoding("UTF-8");
+ String header = "App Engine Whois example result for " + name + "" +
+ "\n";
+ resp.getWriter().print(header);
+ }
+
+ void writeFooter(HttpServletResponse resp) throws IOException {
+ resp.getWriter().println("");
+ }
+
+ static String getParam(HttpServletRequest req, String attributeName, String defaultValue) {
+ String value = req.getParameter(attributeName);
+ if (value == null) {
+ value = defaultValue;
+ }
+ return value;
+ }
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+
+ String name = getParam(req, "name", "google.com");
+ // use jsoup to sanitize the name, since it will be output
+ name = Jsoup.clean(name, Whitelist.basic());
+ String server = getParam(req, "server", DEFAULT_SERVER);
+ int port = Integer.parseInt(getParam(req, "port", Integer.toString(DEFAULT_PORT)));
+
+ writeHeader(resp, name);
+ resp.getWriter().println("
");
+ resp.getWriter().println(doWhoIs(server, port, name));
+ resp.getWriter().println("
");
+ writeFooter(resp);
+ }
+
+ /**
+ * Open a socket to the whois server, write out the query, and receive
+ * the results.
+ **/
+ String doWhoIs(String server, int port, String name) {
+ Socket socket = null;
+ try {
+ socket = new Socket(server, port);
+ Writer out = new OutputStreamWriter(socket.getOutputStream(), "8859_1");
+
+ socket.setSoTimeout(10000);
+ Reader recv = new InputStreamReader(socket.getInputStream(), "8859_1");
+ out.write("=" + name + "\r\n");
+ out.flush();
+
+ StringBuilder builder = new StringBuilder();
+ for (int c = 0; (c = recv.read()) != -1;) {
+ builder.append(String.valueOf((char) c));
+ }
+
+ return builder.toString();
+ } catch (IOException e) {
+ String message = "whois server failed: " + server + " exception:" + e.toString();
+ log.warning(message);
+ return message;
+ } finally {
+ try {
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (IOException e) {
+ // don't care.
+ }
+ }
+ }
+}
diff --git a/appengine/sockets/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/sockets/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..a6a25f304a7
--- /dev/null
+++ b/appengine/sockets/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,25 @@
+
+
+
+ true
+ true
+
+
+
+
+
+
diff --git a/appengine/sockets/src/main/webapp/WEB-INF/logging.properties b/appengine/sockets/src/main/webapp/WEB-INF/logging.properties
new file mode 100644
index 00000000000..a7f787071a5
--- /dev/null
+++ b/appengine/sockets/src/main/webapp/WEB-INF/logging.properties
@@ -0,0 +1 @@
+.level = INFO
diff --git a/appengine/sockets/src/main/webapp/WEB-INF/web.xml b/appengine/sockets/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..22ae85a79a0
--- /dev/null
+++ b/appengine/sockets/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ whois
+ com.example.appengine.sockets.WhoIsClientServlet
+
+
+ whois
+ /whois
+
+
+ redirect.jsp
+
+
+
diff --git a/appengine/sockets/src/main/webapp/favicon.ico b/appengine/sockets/src/main/webapp/favicon.ico
new file mode 100644
index 00000000000..23c553a2966
Binary files /dev/null and b/appengine/sockets/src/main/webapp/favicon.ico differ
diff --git a/appengine/sockets/src/main/webapp/redirect.jsp b/appengine/sockets/src/main/webapp/redirect.jsp
new file mode 100644
index 00000000000..6965fdb55d2
--- /dev/null
+++ b/appengine/sockets/src/main/webapp/redirect.jsp
@@ -0,0 +1,18 @@
+
+<%
+ request.getRequestDispatcher("/whois").forward(request, response);
+%>
diff --git a/flexible/gaeinfo/README.md b/flexible/gaeinfo/README.md
new file mode 100644
index 00000000000..bbc44092165
--- /dev/null
+++ b/flexible/gaeinfo/README.md
@@ -0,0 +1,52 @@
+# Google App Engine Information
+
+## WARNING - this version runs on App Engine Flexible using the deprecated COMPAT runtime.
+## Most users will prefer to use the Metadata example for flex (in progress)
+This sample displays what's going on in your app. It dumps the environment and lots more.
+
+See the [Google App Engine standard environment documentation][ae-docs] for more
+detailed instructions.
+
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+
+## Setup
+
+Use either:
+
+* `gcloud init`
+* `gcloud auth application-default login`
+
+## Maven
+### Running locally
+
+ $ mvn appengine:run
+
+### Deploying
+
+ $ mvn appengine:deploy
+
+
diff --git a/flexible/gaeinfo/pom.xml b/flexible/gaeinfo/pom.xml
new file mode 100644
index 00000000000..0991cbf0ad3
--- /dev/null
+++ b/flexible/gaeinfo/pom.xml
@@ -0,0 +1,107 @@
+
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+ com.example.appengine
+ gaeinfo-flex-compat
+
+
+ appengine-flexible
+ com.google.cloud
+ 1.0.0
+ ..
+
+
+
+
+ 1.8
+ 1.8
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ 1.9.52
+
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ jar
+ provided
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 3.8.1
+
+
+
+ com.google.code.gson
+ gson
+ 2.8.1
+
+
+
+ org.thymeleaf
+ thymeleaf
+ 3.0.7.RELEASE
+
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.0.0
+
+
+
+
+ ${basedir}/src/main/webapp/WEB-INF
+ true
+ WEB-INF
+
+
+
+
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 1.3.1
+
+ true
+ true
+
+
+
+
+
+
+
diff --git a/flexible/gaeinfo/src/main/java/com/example/appengine/flex_compat/GaeInfoServlet.java b/flexible/gaeinfo/src/main/java/com/example/appengine/flex_compat/GaeInfoServlet.java
new file mode 100644
index 00000000000..a0bebe998f2
--- /dev/null
+++ b/flexible/gaeinfo/src/main/java/com/example/appengine/flex_compat/GaeInfoServlet.java
@@ -0,0 +1,218 @@
+/**
+ * Copyright 2017 Google 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 com.example.appengine.flex_compat;
+
+import com.google.appengine.api.appidentity.AppIdentityService;
+import com.google.appengine.api.appidentity.AppIdentityServiceFactory;
+import com.google.appengine.api.utils.SystemProperty;
+import com.google.apphosting.api.ApiProxy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.context.WebContext;
+import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
+
+
+// [START example]
+@SuppressWarnings({"serial"})
+// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
+@WebServlet(name = "GAEInfo", description = "GAEInfo: Write info about GAE Standard",
+ urlPatterns = "/gaeinfo")
+public class GaeInfoServlet extends HttpServlet {
+
+ private final String[] metaPath = {
+ "/computeMetadata/v1/project/numeric-project-id", // (pending)
+ "/computeMetadata/v1/project/project-id",
+ "/computeMetadata/v1/instance/zone",
+ "/computeMetadata/v1/instance/service-accounts/default/aliases",
+ "/computeMetadata/v1/instance/service-accounts/default/",
+ "/computeMetadata/v1/instance/service-accounts/default/scopes",
+// Tokens work - but are a security risk to display
+// "/computeMetadata/v1/instance/service-accounts/default/token"
+ };
+
+ final String[] metaServiceAcct = {
+ "/computeMetadata/v1/instance/service-accounts/{account}/aliases",
+ "/computeMetadata/v1/instance/service-accounts/{account}/email",
+ "/computeMetadata/v1/instance/service-accounts/{account}/scopes",
+// Tokens work - but are a security risk to display
+// "/computeMetadata/v1/instance/service-accounts/{account}/token"
+ };
+
+ private final String metadata = "http://metadata.google.internal";
+ private TemplateEngine templateEngine;
+
+ // Use OkHttp from Square as it's quite easy to use for simple fetches.
+ private final OkHttpClient ok = new OkHttpClient.Builder()
+ .readTimeout(500, TimeUnit.MILLISECONDS) // Don't dawdle
+ .writeTimeout(500, TimeUnit.MILLISECONDS)
+ .build();
+
+ // Setup to pretty print returned json
+ private final Gson gson = new GsonBuilder()
+ .setPrettyPrinting()
+ .create();
+ private final JsonParser jp = new JsonParser();
+
+ // Fetch Metadata
+ String fetchMetadata(String key) throws IOException {
+ Request request = new Request.Builder()
+ .url(metadata + key)
+ .addHeader("Metadata-Flavor", "Google")
+ .get()
+ .build();
+
+ Response response = ok.newCall(request).execute();
+ return response.body().string();
+ }
+
+ String fetchJsonMetadata(String prefix) throws IOException {
+ Request request = new Request.Builder()
+ .url(metadata + prefix )
+ .addHeader("Metadata-Flavor", "Google")
+ .get()
+ .build();
+
+ Response response = ok.newCall(request).execute();
+
+ // Convert json to prety json
+ return gson.toJson(jp.parse(response.body().string()));
+ }
+
+ @Override
+ public void init() {
+ // Setup ThymeLeaf
+ ServletContextTemplateResolver templateResolver =
+ new ServletContextTemplateResolver(this.getServletContext());
+
+ templateResolver.setPrefix("/WEB-INF/templates/");
+ templateResolver.setSuffix(".html");
+ templateResolver.setCacheTTLMs(Long.valueOf(1200000L)); // TTL=20m
+
+ // Cache is set to true by default. Set to false if you want templates to
+ // be automatically updated when modified.
+ templateResolver.setCacheable(true);
+
+ templateEngine = new TemplateEngine();
+ templateEngine.setTemplateResolver(templateResolver);
+ }
+
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ String key ="";
+ final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
+ WebContext ctx = new WebContext(req, resp, getServletContext(), req.getLocale());
+
+ resp.setContentType("text/html");
+
+ ctx.setVariable("production", SystemProperty.environment.value().name());
+ ctx.setVariable("ServiceAccountName", appIdentity.getServiceAccountName());
+ ctx.setVariable("gcs", appIdentity.getDefaultGcsBucketName());
+
+ ctx.setVariable("appId", SystemProperty.applicationId.get());
+ ctx.setVariable("appVer", SystemProperty.applicationVersion.get());
+ ctx.setVariable("version", SystemProperty.version.get());
+ ctx.setVariable("environment", SystemProperty.environment.get());
+
+ // Environment Atributes
+ ApiProxy.Environment env = ApiProxy.getCurrentEnvironment();
+ Map attr = env.getAttributes();
+ TreeMap m = new TreeMap<>();
+
+ for (String k : attr.keySet()) {
+ Object o = attr.get(k);
+
+ if (o.getClass().getCanonicalName().equals("java.lang.String")) {
+ m.put(k, (String) o);
+ } else if (o.getClass().getCanonicalName().equals("java.lang.Boolean")) {
+ m.put(k, ((Boolean) o).toString());
+ } else {
+ m.put(k, "a " + o.getClass().getCanonicalName());
+ }
+ }
+ ctx.setVariable("attribs", m);
+
+ m = new TreeMap<>();
+ for (Enumeration e = req.getHeaderNames(); e.hasMoreElements(); ) {
+ key = e.nextElement();
+ m.put(key, req.getHeader(key));
+ }
+ ctx.setVariable("headers", m);
+
+ Cookie[] cookies = req.getCookies();
+ m = new TreeMap<>();
+ if (cookies != null && cookies.length != 0) {
+ for (Cookie co : cookies) {
+ m.put(co.getName(), co.getValue());
+ }
+ }
+ ctx.setVariable("cookies", m);
+
+ Properties properties = System.getProperties();
+ m = new TreeMap<>();
+ for (Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) {
+ key = (String) e.nextElement();
+ m.put(key, (String) properties.get(key));
+ }
+ ctx.setVariable("systemprops", m);
+
+ Map envVar = System.getenv();
+ m = new TreeMap<>(envVar);
+ ctx.setVariable("envvar", m);
+
+ // The metadata server is only on a production system
+ if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production) {
+
+ m = new TreeMap<>();
+ for (String k : metaPath) {
+ m.put(k, fetchMetadata(key));
+ }
+ ctx.setVariable("Metadata", m.descendingMap());
+
+ m = new TreeMap<>();
+ for (String k : metaServiceAcct) {
+ // substitute a service account for {account}
+ k = k.replace("{account}", appIdentity.getServiceAccountName());
+ m.put(k, fetchMetadata(k));
+ }
+ ctx.setVariable("sam", m.descendingMap());
+
+ // Recursivly get all info about service accounts -- Note tokens are leftout by default.
+ ctx.setVariable("rsa",
+ fetchJsonMetadata("/computeMetadata/v1/instance/service-accounts/?recursive=true"));
+ // Recursivly get all data on Metadata server.
+ ctx.setVariable("ram", fetchJsonMetadata("/?recursive=true"));
+ }
+
+ templateEngine.process("index", ctx, resp.getWriter());
+ }
+}
+// [END example]
diff --git a/flexible/gaeinfo/src/main/webapp/WEB-INF/appengine-web.xml b/flexible/gaeinfo/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..4a50e55a042
--- /dev/null
+++ b/flexible/gaeinfo/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ true
+ true
+ flex
+
+
+
+
+
+
diff --git a/flexible/gaeinfo/src/main/webapp/WEB-INF/templates/index.html b/flexible/gaeinfo/src/main/webapp/WEB-INF/templates/index.html
new file mode 100644
index 00000000000..8b4a61fcebc
--- /dev/null
+++ b/flexible/gaeinfo/src/main/webapp/WEB-INF/templates/index.html
@@ -0,0 +1,99 @@
+
+
+
+
+ GAE standard Metadata
+
+
+
+AppIdentity
+
+ ServiceAccountName | |
+ GCS Bucket | |
+
+SystemProperties
+
+ appId | |
+ appVer | |
+ version | |
+ environment | |
+
+Environment Attributes
+
+Headers
+
+Cookies
+
+Java SystemProperties
+
+Envirionment Variables
+
+
+
+
Metadata
+
+
ServiceAccount Metadata
+
+
Recursive service-accounts
+
+
Recursive all metadata
+
+
+
No Local Metadata Server
+
+
+
diff --git a/flexible/gaeinfo/src/main/webapp/WEB-INF/web.xml b/flexible/gaeinfo/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..e31839a606f
--- /dev/null
+++ b/flexible/gaeinfo/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ gaeinfo
+
+
diff --git a/flexible/pom.xml b/flexible/pom.xml
index f68b4abc72d..4777e659429 100644
--- a/flexible/pom.xml
+++ b/flexible/pom.xml
@@ -40,6 +40,7 @@
disk
errorreporting
extending-runtime
+ gaeinfo
helloworld
mailgun
mailjet