【Java発展学習9日目】Java標準ライブラリを用いたネットワークアクセス

# ネットワーク通信における低水準(low-level)と高水準(high-level)
[参考: java.netパッケージ](https://docs.oracle.com/javase/jp/16/docs/api/java.base/java/net/package-summary.html)
`ネットワーク通信`を行う`java.net`パッケージの`API`は、以下の2つに分類される。

| API水準 | 内容 | クラス |
|:-:|:--|:--|
| `低水準API` | IPアドレス | ・`Inet4Address`<br>・`Inet6Address` |
|  | ソケット | ・`Socket`<br>・`ServerSocket`<br>・`DatagramSocket`<br>・`MulticastSocket` |
|  | インタフェース | `NetworkInterface` |
|  | プロキシ | `Proxy` |
| `高水準API` | URI | `URI` |
|  | URL | `URL` |
|  | 接続 | ・`URLConnection`<br>・`HttpURLConnection` |

`低水準API`は`OSI参照モデル`の`物理層〜トランスポート層`を担っており、
`高水準API`は`セッション層〜アプリケーション層`を担う。

***

# URLを用いたHTML情報の取得
[参考: jsoup(外部ライブラリ)](https://jsoup.org/)
`Java標準ライブラリ`を用いて`HTML情報`を取得する場合、`URL`クラスの`openStream()`メソッドを用いることで、
`バイトストリーム`を経由してデータを取得(=`GET`)することができる。

```java:GetHTML.java
import java.io.*;
import java.net.URL;

public class GetHTML {
    public static void main(String[] args) {
        URL url = null;
        try {
            // 「URL情報」を保持するURLオブジェクトの生成
            url = new URL("http://httpbin.org/get");
        } catch (Exception e) {
            e.printStackTrace();
        }

        try (
            // URLオブジェクトによるバイトストリームの生成
            InputStream is = url.openStream();

            // バイトストリーム -> 文字ストリーム への変換を行うInputStreamReaderフィルタの生成
            InputStreamReader isr = new InputStreamReader(is);

            // 「バッファリング」を行うBufferedReaderフィルタの生成
            BufferedReader br = new BufferedReader(isr);
        ) {
            // HTML情報の取得・コンソールへの出力
            String html = br.readLine();
            while (!(html.equals(null))) {
                System.out.println(html);
                html = br.readLine();
            }
        }
        catch (NullPointerException e) {
            return;
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

```html:実行結果
{
  "args": {}, 
  "headers": {
    "Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2", 
    "Host": "httpbin.org", 
    "User-Agent": "Java/16.0.1", 
    "X-Amzn-Trace-Id": "Root=1-60ebdd33-6afcea7b6527b0532e8eccc0"
  }, 
  "origin": "90.149.37.69", 
  "url": "http://httpbin.org/get"
}
```

***

# Socketを用いたWebページの取得
`IPアドレス(=ホスト名)`と`ポート番号`を指定して`TCP/IP接続`を行う場合は、`Socket`クラスを利用する。

```java:GetHTMLSocket.java
import java.io.*;
import java.net.Socket;

class GetHTMLSocket {
    public static void main(String[] args) {
        try (
            // 「ホスト名」「ポート番号」を指定してSocketオブジェクトを生成
            Socket s = new Socket("httpbin.org", 80);

            // Socketオブジェクト -> 入出力バイトストリームの生成
            InputStream is = s.getInputStream();
            OutputStream os = s.getOutputStream();

            // バイトストリーム -> 文字ストリーム への変換を行うInputStreamReaderフィルタ、
            // 文字ストリーム -> バイトストリーム への変換を行うOutputStreamWriterフィルタの生成
            InputStreamReader ism = new InputStreamReader(is);
            OutputStreamWriter osw = new OutputStreamWriter(os);

            // バッファリングフィルタの生成
            BufferedReader br = new BufferedReader(ism);
            BufferedWriter bw = new BufferedWriter(osw);
        ) {
            // GETリクエストの送信
            bw.write("GET /get HTTP/1.1\n");
            bw.write("Host: httpbin.org\n");
            bw.write("\n");

            // 強制書き込み
            bw.flush();

            // GETメソッドによる取得結果の表示
            String str = "";
            while (true) {
                str = br.readLine();
                if (str == null) {
                    break;
                }
                System.out.println(str);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

```java:実行結果
HTTP/1.1 200 OK
Date: Mon, 12 Jul 2021 06:12:27 GMT
Content-Type: application/json
Content-Length: 197
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

{
  "args": {}, 
  "headers": {
    "Host": "httpbin.org", 
    "X-Amzn-Trace-Id": "Root=1-60ebdd4b-3a90ff7b7dfbebfc6dcd2d7c"
  }, 
  "origin": "90.149.37.69", 
  "url": "http://httpbin.org/get"
}
```

***

# サーバーサイドプログラムの作成
[参考: ソケット通信](https://itsakura.com/java-socket)
指定した`ポート番号`で接続待機を行う`サーバサイドプログラム`を実装する場合は、`ServerSocket`クラスを利用する。

```java:ServerSide.java
import java.io.*;
import java.net.*;

public class ServerSide {
    public static void main(String[] args) {
        try (
            // 「待ち受けポート番号」の指定
            ServerSocket svSocket = new ServerSocket(39648);

            // クライアントサイドからの接続要求の待機
            // -> 接続確立後は「クライアントIPアドレス」と「ポート番号」を保持するSocketオブジェクトが生成
            Socket socket = svSocket.accept();

            // サーバー -> クライアント への出力バイトストリームの生成
            OutputStream os = socket.getOutputStream();

            // 文字ストリーム -> バイトストリーム への変換を行うOutputStreamWriterフィルタの生成
            OutputStreamWriter osw = new OutputStreamWriter(os);

            // バッファリングフィルタの生成
            BufferedWriter bw = new BufferedWriter(osw);
        ) {
            // クライアントの情報の取得・出力
            System.out.println("from: " + socket.getInetAddress());

            // クライアントへの出力
            bw.write("Hello!");

            // 強制書き込み
            bw.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

***

# HTTPリクエスト/レスポンス
[参考1: HTTPリクエスト/HTTPレスポンス](https://itsakura.com/network-http-get-post)
[参考2: HTTPヘッダ](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers)
`HTTPリクエスト`・`HTTPレスポンス`の内部データ構造は、以下の通り。

```text:HTTPリクエスト
// 1行目: リクエストライン
GET <リソースパス>　HTTP/1.1

// 2行目-: リクエストヘッダ
Host: URLの先頭部分
Connection: トランザクション完了後の接続状態
Upgrade-Insequre-Requests: クライアントの設定情報
User-Agent: ブラウザのバージョン情報
Referer: 遷移元URL
Accept-Encoding: デコード可能なエンコーディング形式
Accept-Language: 利用可能言語

// 最終行: リクエストボディ
// -> サーバに追加・上書きするコンテンツ内容を記述(GET, DELETEの場合は空白行)

```

```text:HTTPレスポンス
// 1行目: ステータスライン
HTTP/1.1 <ステータスコード>

// 2行目-(n-1)行目: レスポンスヘッダ
Date: 接続時刻
Server: Webサーバー名称・バージョン情報
Content-Length: リソースサイズ[Byte]
Keep-Alive: タイムアウトに関する設定情報
Connection: トランザクション完了後の接続状態
Content-Type: MIMEタイプ

// n行目: 区切り行(必ず空白行)

// n+1行目-: レスポンスボディ
<HTML情報>
```

## ステータスコード
`HTTPレスポンス`の実行結果を表すステータスコードは、以下の通り。

なお、`100`番台は`情報`、`200`番台は`正常`、`300`番台は`リダイレクト(転送)`、
`400`番台は`クライアント側エラー`、`500番台`は`サーバ側エラー`を表す。

| 分類 | コード | メッセージ | 内容 |
|:--|:--|:--|:--|
| 成功 | 200 | OK | リクエストの成功 |
| 転送 | 301 | Moved Permanently | URLの恒久的な移動 |
|  | 302 | Moved Temporarily | URLの一時的な移動 |
| クライアント側エラー | 400 | Bad Request | 不正なリクエスト |
|  | 401 | Unauthorized | 認証の失敗 |
|  | 403 | Forbidden | アクセス権なし |
|  | 404 | Not Found | 存在しないURL |
| サーバ側エラー | 500 | Internal Server Error | 内部エラーの発生 |
|  | 502 | Bad Gateway | 無効のレスポンスが返却 |
|  | 503 | Service Unavailable | サービス利用不可 |

***

# HttpClient/HttpRequestを用いたデータの送信・追加
[参考1: httpbinの紹介](https://qiita.com/sameyasu/items/adacceb8a1bee893599b)
[参考2: httpbin](https://httpbin.org/#/)
`データ取得`以外の`HTTPリクエスト`を送信する場合は、`リクエスト内容`を保持する`HttpRequest`クラスと
`HTTPリクエスト`を送信する`HttpClient`クラスを利用する。

```java:HTTPGetPost.java
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpClient.Redirect;
import java.net.http.HttpClient.Version;

public class HTTPGetPost {
    public static void main(String[] args) {
        // HTTPリクエストを送信するHttpClientオブジェクトの生成
        HttpClient client = HttpClient
            // HttpClient.Builderオブジェクトの生成
            .newBuilder()
            // HTTPプロトコルのバージョン指定
            .version(Version.HTTP_1_1)
            // リダイレクトポリシーの指定
            .followRedirects(Redirect.NORMAL)
            // HttpClientオブジェクトの生成
            .build();
        

        // -- GETメソッド --
        // HTTPリクエスト内容を保持するHttpRequestオブジェクトの生成
        HttpRequest requestGet = HttpRequest
            // HttpRequest.Builderオブジェクトの生成
            .newBuilder()
            // URLの指定
            .uri(URI.create("http://httpbin.org/get"))
            // HTTPリクエストメソッドの指定
            .GET()
            // HttpRequestオブジェクトの生成
            .build();
        
        // HTTPリクエストの送信
        try {
            HttpResponse<String> response = client.send(requestGet, 
                // レスポンスボディ(=HTML情報)を「文字列」として処理するHttpResponse.BodyHandlersクラスのメソッドを利用
                HttpResponse.BodyHandlers.ofString()
            );

            // レスポンスボディの取得
            String body = response.body();

            // ステータスコードの取得
            int status = response.statusCode();

            // レスポンス内容の表示
            System.out.println("status code: " + status);
            System.out.println("-- response body(GET) --");
            System.out.println(body);
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        // -- POSTメソッド --
        // HTTPリクエスト内容を保持するHttpRequestオブジェクトの生成
        HttpRequest requestPost = HttpRequest
            // HttpRequest.Builderオブジェクトの生成
            .newBuilder()
            // URLの指定
            .uri(URI.create("http://httpbin.org/post"))
            // リクエストヘッダの追加
            .header("Content-Type", "application/json")
            // リクエストヘッダの追加
            .header("Accept", "application/json")
            // HTTPリクエストメソッドの指定
            .POST(HttpRequest.BodyPublishers.ofString(
                "Hello!"
            ))
            // HttpRequestオブジェクトの生成
            .build();

        // HTTPリクエストの送信
        try {
            HttpResponse<String> response = client.send(requestPost, 
                // レスポンスボディ(=HTML情報)を「文字列」として処理するHttpResponse.BodyHandlersクラスのメソッドを利用
                HttpResponse.BodyHandlers.ofString()
            );

            // レスポンスボディの取得
            String body = response.body();

            // ステータスコードの取得
            int status = response.statusCode();

            // レスポンス内容の表示
            System.out.println("status code: " + status);
            System.out.println("-- response body(POST) --");
            System.out.println(body);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

```java:実行結果
status code: 200
-- response body(GET) --
{
  "args": {}, 
  "headers": {
    "Content-Length": "0", 
    "Host": "httpbin.org", 
    "User-Agent": "Java-http-client/16.0.1", 
    "X-Amzn-Trace-Id": "Root=1-60ecd4a1-73b2e7923fe00bca0152d92f"
  }, 
  "origin": "90.149.37.69", 
  "url": "http://httpbin.org/get"
}

status code: 200
-- response body(POST) --
{
  "args": {}, 
  "data": "Hello!", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "application/json", 
    "Content-Length": "6", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "Java-http-client/16.0.1", 
    "X-Amzn-Trace-Id": "Root=1-60ecd4a1-46be2e1837f0f1e948219b43"
  }, 
  "json": null, 
  "origin": "90.149.37.69", 
  "url": "http://httpbin.org/post"
}
```

***

# 用語集

| 用語 | 内容 |
|:--|:--|
| ソケット(socket) | `IPアドレス`と`ポート番号`の組み合わせ。 |
| プロキシ(proxy) | `内部ネットワーク`に接続された`ホスト`の代わりに`外部ネットワーク`にアクセスする機能。 |
| Webアプリケーション(web application) | `HTTPリクエスト`をトリガーとして、処理結果を`HTML`で返却するプログラム。 |
| RFC(Request For Comment) | `IETF`が定める標準化技術をまとめた文書。 |
| ユーザエージェント(UA; user agent) | `クライアント`側で通信を行うソフトウェア。 |
| 冪等性(idempotent) | 同一リクエストを送信した場合でも副作用が生じない性質。 |
| クエリパラメータ(query parameter) | `URL`の末尾に`?`を付与し、`キー`と`値`を指定することで表示結果を限定する補足情報。 |
| REST(REpresentative State Transfer) | 一意な`URL`に対して、各リクエストメソッドを送信し、`JSON/XML`形式でレスポンスを行う`Web API`の設計哲学。 |