Webシステム構築（超基礎）④：Webシステムの構築

#目的
[Webシステム構築（超基礎）②：APサーバ構築と基本動作](https://qiita.com/MARTOON/items/f685b4ea7cbfe4033839)と[Webシステム構築（超基礎）③：DBサーバ構築と基本動作](https://qiita.com/MARTOON/items/b037ba8161790c874e94)で構築したWeb/APサーバとDBサーバを用い、Webシステムを構築する。

#環境条件
- APサーバ
    - EC2：t2.micro
    - OS：Red Hat Enterprise Linux 8 (HVM), SSD Volume Type
    - Disk：汎用SSD（GP2） 10GB
    - Tomcat：Apache Tomcat 9
    - Java：JDK 1.8
- DBサーバ
    - EC2：t2.micro
    - OS：Red Hat Enterprise Linux 8 (HVM), SSD Volume Type
    - Disk：汎用SSD（GP2） 10GB
    - MySQL：MySQL 8

セキュリティグループの設定等はいい感じに。

#構築手順

以下の流れで実施する。
1. Web/APサーバOSからDBサーバのMySQLに接続できる状態にする
2. Web/APサーバからDBサーバのMySQLに接続できる状態にする
3. データベースへのデータ登録
4. アプリケーションの準備
5. 動作確認

##1. Web/APサーバOSからDBサーバのMySQLに接続できる状態にする
```
DBサーバにec2-userでログイン

rootユーザにスイッチ
$ sudo su - 

MySQLにログイン
#mysql -uroot -ppassword

接続許可ユーザの確認
> select user,host from mysql.user;
+------------------+-----------+
| user             | host      |
+------------------+-----------+
| mysql.infoschema | localhost |
| mysql.session    | localhost |
| mysql.sys        | localhost |
| root             | localhost |
+------------------+-----------+

接続用ユーザの作成
> CREATE USER 'appuser'@'%' IDENTIFIED BY 'appuser';

appuserの権限を変更
> GRANT ALL ON *.* TO 'appuser'@'%';

appuserが全ての場所から接続できる状態になっていることを確認する
> select user,host from mysql.user;
+------------------+-----------+
| user             | host      |
+------------------+-----------+
| appuser          | %         |
| mysql.infoschema | localhost |
| mysql.session    | localhost |
| mysql.sys        | localhost |
| root             | localhost |
+------------------+-----------+
```

##2. Web/APサーバからDBサーバのMySQLに接続できる状態にする

```
ドライバーのダウンロード
# curl -O https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.12/mysql-connector-java-8.0.12.jar

Tomcatのlibディレクトリにドライバーを配置
# mv mysql-connector-java-8.0.12.jar /opt/apache-tomcat-9.0.30/lib/

ドライバーにシンボリックリンクを貼る
# cd /opt/apache-tomcat-9.0.30/lib/
# ln -s mysql-connector-java-8.0.12.jar mysql-connector-java.jar
```

##3. データベースへのデータ登録
```
サンプルデータをデータベースに登録するためのSQL情報を取得
# curl -O https://downloads.mysql.com/docs/world.sql.zip

unzipのインストール
# yum install -y unzip

SQLファイルの解凍
# unzip world.sql.zip 

DBにサンプルデータを投入する
# mysql -uappuser -pappuser  < world.sql

MySQLにログイン
# mysql -uappuser -pappuser

worldデータベースが追加されていることを確認する
> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| world              |
+--------------------+

worldデータベースに含まれる表を確認する
> use world;
> show tables;
+-----------------+
| Tables_in_world |
+-----------------+
| city            |
| country         |
| countrylanguage |
+-----------------+

各表定義は以下の様になっており、


> desc city;
+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| ID          | int(11)  | NO   | PRI | NULL    | auto_increment |
| Name        | char(35) | NO   |     |         |                |
| CountryCode | char(3)  | NO   | MUL |         |                |
| District    | char(20) | NO   |     |         |                |
| Population  | int(11)  | NO   |     | 0       |                |
+-------------+----------+------+-----+---------+----------------+

> desc country;
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
| Field          | Type                                                                                  | Null | Key | Default | Extra |
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
| Code           | char(3)                                                                               | NO   | PRI |         |       |
| Name           | char(52)                                                                              | NO   |     |         |       |
| Continent      | enum('Asia','Europe','North America','Africa','Oceania','Antarctica','South America') | NO   |     | Asia    |       |
| Region         | char(26)                                                                              | NO   |     |         |       |
| SurfaceArea    | decimal(10,2)                                                                         | NO   |     | 0.00    |       |
| IndepYear      | smallint(6)                                                                           | YES  |     | NULL    |       |
| Population     | int(11)                                                                               | NO   |     | 0       |       |
| LifeExpectancy | decimal(3,1)                                                                          | YES  |     | NULL    |       |
| GNP            | decimal(10,2)                                                                         | YES  |     | NULL    |       |
| GNPOld         | decimal(10,2)                                                                         | YES  |     | NULL    |       |
| LocalName      | char(45)                                                                              | NO   |     |         |       |
| GovernmentForm | char(45)                                                                              | NO   |     |         |       |
| HeadOfState    | char(60)                                                                              | YES  |     | NULL    |       |
| Capital        | int(11)                                                                               | YES  |     | NULL    |       |
| Code2          | char(2)                                                                               | NO   |     |         |       |
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+

> desc countrylanguage;
+-------------+---------------+------+-----+---------+-------+
| Field       | Type          | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+-------+
| CountryCode | char(3)       | NO   | PRI |         |       |
| Language    | char(30)      | NO   | PRI |         |       |
| IsOfficial  | enum('T','F') | NO   |     | F       |       |
| Percentage  | decimal(4,1)  | NO   |     | 0.0     |       |
+-------------+---------------+------+-----+---------+-------+
```

##4. アプリケーションの準備
JSPとJavaプログラムを利用した簡単なWebアプリケーションを構築し、APサーバ上で動作させる。
Web画面上で国名を入力し検索すると、主要5都市とその人口、また公用語とその利用割合を、
DB検索を実施しながら表示するプログラムである。

```world.jsp 
<%@ page language="java" contentType="text/html; charset=UTF-8" %>

<!DOCTYPE html>
<html>
    <head>
        <title>requestForm</title>
    </head>
    <body>
        <p>各国の主要都市と公用語の検索</p>

        <%-- GETメソッドでテキストを送信 --%>
        <form action="./WorldServlet">
            <p>
                国名（英語表記）を入力してください：<input type="text" name="text1"><br>
                例）日本の場合はJapanと入力
            </p>
            <input type="submit" value="検索実行">
        </form>
</html>
```

```WorldServlet.java
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.NumberFormat;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/WorldServlet")
public class WorldServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
    /**
     * コンストラクタ.
     */
    public WorldServlet() {
        super();
    }
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
    	String inputText = "";	// テキスト1格納用変数
    	
    	inputText = request.getParameter("text1");
    	
        String servername     = "172.31.45.98";
        String databasename   = "world";
        String user = "appuser";
        String password = "appuser";
        String serverencoding = "UTF-8";
        String url =  "jdbc:mysql://" + servername + "/" + databasename + "?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B9:00&rewriteBatchedStatements=true";
    	
        Connection con = null;

        try {
	        Class.forName( "com.mysql.cj.jdbc.Driver" ).newInstance();
	        con = DriverManager.getConnection( url, user, password );
	        Statement stat = con.createStatement();
	        String sqlStr1 = "select country.name, city.name, city.population from city inner join country on city.countrycode=country.code  where country.name='" + inputText + "' order by city.population desc limit 5;";
	        String sqlStr2 = "select country.name, countrylanguage.language, countrylanguage.percentage from country inner join countrylanguage on country.code=countrylanguage.countrycode where country.name='" + inputText + "' order by countrylanguage.percentage desc limit 1;";
	        System.out.println("sqlStr1: " + sqlStr1);
	        System.out.println("sqlStr2: " + sqlStr2);
	        
	        ResultSet resultset1 = stat.executeQuery( sqlStr1 );
	     
	        String country_name = null;
	        String city_name = null;
	        Integer city_population = 0;
	        String countrylanguage_language = null;
	        Double countrylanguage_percentage = 0.0;
	        
	        //NumberFormatインスタンスを生成
	        NumberFormat nfNum = NumberFormat.getNumberInstance();    //カンマ区切り形式
	        NumberFormat nfPer = NumberFormat.getPercentInstance();   //パーセント形式
	        
    		// 画面に出力する内容の設定
            // 出力する内容がHTMLであることを設定
            response.setContentType("text/html");
            // 出力する画面の文字コードをUTF-8に設定
            response.setCharacterEncoding("UTF-8");

            // 画面に出力するためのWriterクラスインスタンスを取得
            PrintWriter pw = response.getWriter();

            // HTMLを出力
            pw.println("<html>");
            pw.println("<head>");
            pw.println("<title>入力結果</title>");
            pw.println("</head>");
            pw.println("<body>");
            pw.println("<h1>検索結果</h1>");
	        pw.println("<h2>" + inputText + "の主要都市とその人口は以下である</h2>");
	        pw.println("<table border='1'>");
	        pw.println("<tr>");
	        pw.println("<th>都市名</th>");
	        pw.println("<th>人口</th>");
	        pw.println("</tr>");
			while( resultset1.next() )
	        {
	            /*getString()メソッドは、引数に指定されたフィールド名(列)の値をStringとして取得する*/
				city_name = resultset1.getString("city.name");
				city_population = resultset1.getInt("city.population");
	        	System.out.print("都市名：" + city_name);
	        	System.out.print("都市の人口：" + city_population);
		        pw.println("<tr>");
		        pw.println("<th>"+ city_name +"</th>");
		        pw.println("<th>"+ nfNum.format(city_population) +"人</th>");
		        pw.println("</tr>");
	        	
	        }
			pw.println("</table>");
			resultset1.close();
			
	        ResultSet resultset2 = stat.executeQuery( sqlStr2 );
            
	        pw.println("<h2>" + inputText + "の公用語とその利用割合は以下である</h2>");
	        pw.println("<table border='1'>");
	        pw.println("<tr>");
	        pw.println("<th>公用語</th>");
	        pw.println("<th>利用割合</th>");
	        pw.println("</tr>");
	        while( resultset2.next() )
	        {
	            /*getString()メソッドは、引数に指定されたフィールド名(列)の値をStringとして取得する*/
	        	countrylanguage_language = resultset2.getString("countrylanguage.language");
	        	countrylanguage_percentage = resultset2.getDouble("countrylanguage.percentage");
	        	System.out.print("公用語：" + countrylanguage_language);
	        	System.out.print("利用割合：" + countrylanguage_percentage);
		        pw.println("<tr>");
		        pw.println("<th>"+ countrylanguage_language +"</th>");
		        pw.println("<th>"+ nfPer.format(countrylanguage_percentage / 100) +"</th>");
		        pw.println("</tr>");
	        	
	        }
	        pw.println("</table>");
	        
			resultset2.close();
            stat.close();
            con.close();

            pw.println("</body>");
            pw.println("</html>");       
        } 
        catch( SQLException e ){

            /*エラーメッセージ出力*/
            System.out.println( "Connection Failed. : " + e.toString() );

            /*例外を投げちゃうぞ*/
            try {
				throw new Exception();
			} catch (Exception e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}

        }catch (ClassNotFoundException e){

            /*エラーメッセージ出力*/
            System.out.println("ドライバを読み込めませんでした " + e);
        } catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        finally{
            try{
                if( con != null ){ 
                    con.close();
                }
            }
            catch(Exception e){

                /*エラーメッセージ出力*/
                System.out.println( "Exception2! :" + e.toString() );

                /*例外を投げちゃうぞ*/
                try {
					throw new Exception();
				} catch (Exception e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
            }
        }

    }
}
```

```web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You 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.
-->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">

	<servlet>
		<servlet-name>WorldServlet</servlet-name>
    	　　　　<servlet-class>WorldServlet.WorldServlet</servlet-class>
	</servlet>
 
	<servlet-mapping>
		<servlet-name>WorldServlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
 
</web-app>

```

以下の様にアプリケーションを配置する。
```
/opt/apache-tomcat-9.0.30/webapps/world
```

```
world
├── WEB-INF
│   ├── lib
│   │   └── World.jar
│   └── web.xml
└── world.jsp
```

##5. 動作確認

```
Tomcatサービスを再起動する
# service tomcat restart
Redirecting to /bin/systemctl start tomcat.service

ブラウザからWebサーバの「パブリックDNS:8080/world/world.jsp」に接続し、
作成したWebページの表示及び、処理の動作が正しく行われることを確認する。
```