Google App Engine (Java 8) + Servlet API 3.1 + Gradle で Hello World

## 概要

- Google App Engine Java 8 スタンダード環境 + Servlet API 3.1 の構成でシンプルなサーブレットと JSP (Java Server Pages) をデプロイする

## 環境

- Google App Engine Java 8 スタンダード環境
- Java 8
- Servlet API 3.1
- Gradle 6.0

## Google App Engine Java 8 スタンダード環境

Jetty 9 + Servlet API 3.1 (or 2.5) を使用可能。
開発する側としては war ファイルを作ってデプロイするように作れば OK なので Jetty のことは気にしなくても良い。

[Migrating from Java 7 to Java 8 Runtime  \|  App Engine standard environment for Java 8  \|  Google Cloud](https://cloud.google.com/appengine/docs/standard/java/migrating-to-java8)

> Jetty 9 supports both Servlet 2.5 and 3.1 web applications, including servlet annotations.

## ソースコード

### ソースコード一覧

```
├── build.gradle
├── settings.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── HelloServlet.java
        └── webapp
            ├── WEB-INF
            │   └── appengine-web.xml
            └── index.jsp
```

### build.gradle

Gradle のビルド設定ファイル。

```build.gradle
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    // Google App Engine Gradle plugin を使用
    classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.+'
  }
}

repositories {
  mavenCentral()
}

apply plugin: 'java'
apply plugin: 'war'

// App Engine tasks
apply plugin: 'com.google.cloud.tools.appengine'

dependencies {

  // HTML エスケープのために Apache Commons Text を使う
  implementation 'org.apache.commons:commons-text:1.8'

  // Servlet API 3.1 を使う
  providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}

group = "com.example" // Generated output GroupId
version = "1.0.0" // Version in generated output

sourceCompatibility = '1.8'
targetCompatibility = '1.8'
```

### settings.gradle

```settings.gradle
rootProject.name = 'hello'
```

### HelloServlet.java

https://プロジェクトID.appspot.com/hello　にアクセスしたときに HTML を返すサーブレット。

```java
package com.example;

import org.apache.commons.text.StringEscapeUtils;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

    resp.setContentType("text/html; charset=utf-8");

    PrintWriter out = resp.getWriter();
    out.println("<html><body>");
    out.println("<h1>Hello, Servlet World!</h1>");

    // システム・プロパティを出力する
    out.println("<h2>System Properties</h2>");
    Properties props = System.getProperties();
    for (Object k : new TreeMap<>(props).keySet()) {
      String key = (String) k;
      out.println(h(key) + ": " + h((String) props.get(key)) + "<br>");
    }

    // 環境変数を出力する
    out.println("<h2>Environment Variables</h2>");
    Map<String, String> env = System.getenv();
    for (String key : new TreeMap<>(env).keySet()) {
      out.println(h(key) + ": " + h(env.get(key)) + "<br>");
    }

    // ランタイム情報を出力する
    out.println("<h2>Runtime.getRuntime()</h2>");
    Runtime r = Runtime.getRuntime();
    out.println("freeMemory: " + r.freeMemory() + "<br>");
    out.println("maxMemory: " + r.maxMemory() + "<br>");
    out.println("totalMemory: " + r.totalMemory() + "<br>");

    out.println("</body></html>");
    out.flush();
    out.close();
  }

  private static String h(String s) {
    return StringEscapeUtils.escapeHtml4(s);
  }
}
```

### index.jsp

https://プロジェクトID.appspot.com/　にアクセスしたときに HTML を返す JSP ファイル。

```index.jsp
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello, JSP!</title>
</head>
<body>
<p>Hello, JSP!</p>
</body>
</html>
```

### appengine-web.xml

```appengine-web.xml 
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <runtime>java8</runtime>
  <threadsafe>true</threadsafe>
  <instance-class>F1</instance-class>
  <automatic-scaling>
    <target-cpu-utilization>0.95</target-cpu-utilization>
    <min-instances>0</min-instances>
    <max-instances>1</max-instances>
    <max-concurrent-requests>10</max-concurrent-requests>
  </automatic-scaling>
  <!-- システム・プロパティを設定できる -->
  <system-properties>
    <property name="com.example.HelloServlet.message" value="Hello, system property."/>
  </system-properties>
  <!-- 環境変数を設定できる -->
  <env-variables>
    <env-var name="COM_EXAMPLE_HELLOSERVLET_MESSAGE" value="Hello, environment variable." />
  </env-variables>
</appengine-web-app>
```

## オペレーション

### Google App Engine Gradle plugin のタスク

Google App Engine Gradle plugin の機能を使ってデプロイなどができる。
gradle tasks コマンドで実行可能なタスクを確認できる。

```
$ gradle tasks

> Task :tasks

------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------

App Engine Standard environment tasks
-------------------------------------
appengineDeploy - Deploy an App Engine application
appengineDeployCron - Deploy Cron configuration
appengineDeployDispatch - Deploy Dispatch configuration
appengineDeployDos - Deploy Dos configuration
appengineDeployIndex - Deploy Index configuration
appengineDeployQueue - Deploy Queue configuration
appengineRun - Run an App Engine standard environment application locally
appengineShowConfiguration - Show current App Engine plugin configuration
appengineStage - Stage an App Engine standard environment application for deployment
appengineStart - Run an App Engine standard environment application locally in the background
appengineStop - Stop a locally running App Engine standard environment application
explodeWar - Explode a war into a directory
(以下略)
```

### gradle appengineRun でローカルにサーバを起動

gradle appengineRun でローカルサーバ http://localhost:8080/ を起動できる。

```
$ gradle appengineRun
```

### gradle appengineDeploy で Google App Engine にデプロイ

gradle appengineDeploy で Google App Engine にデプロイできる。
デプロイ前には「gcloud config set project プロジェクトID」でデプロイ先のプロジェクトIDを指定しておく。

```
$ gradle appengineDeploy
```

## 

## 参考資料
- [App Engine スタンダード環境での Java 8 のクイックスタート  \|  Java 8 の App Engine スタンダード環境  \|  Google Cloud](https://cloud.google.com/appengine/docs/standard/java/quickstart?hl=ja)
- [Java 8 ランタイム環境  \|  Java 8 の App Engine スタンダード環境  \|  Google Cloud](https://cloud.google.com/appengine/docs/standard/java/runtime?hl=ja)
- [getting\-started\-java/appengine\-standard\-java8/helloworld at master · GoogleCloudPlatform/getting\-started\-java · GitHub](https://github.com/GoogleCloudPlatform/getting-started-java/tree/master/appengine-standard-java8/helloworld)