クライアントからSpring WebFluxにAPIアクセスを試してみた

# 背景・目的
過去に、Spring Bootについて下記の記事にまとめました。今まではサーバサイドとは代わり、クライアント側からサーバサイドアプリケーションを呼び出す処理を書いてみます

- [SpringBootでSpring WebFluxを試してみた](https://qiita.com/zumax/items/f07ce794cfd597ca58b1)
- [Spring BootでDAOからデータベースにアクセスしてみた-その2（クライテリア）](https://qiita.com/zumax/items/0eab43e74eb76143530e)
- [Spring BootでDAOからデータベースにアクセスしてみた-その1（DAOとアノテーション）](https://qiita.com/zumax/items/4dd856cd108d23a15188)
- [Spring Bootでバリデーションを試してみた](https://qiita.com/zumax/items/f09bacf18f3bb96d1331)
- [Spring Bootでデータを保存する-その2](https://qiita.com/zumax/items/06d0465218478840a949)
- [Spring Bootでデータを保存する-その1](https://qiita.com/zumax/items/2e387254c736659bf20c)
- [Spring Bootでデータベースへのアクセスを試してみた](https://qiita.com/zumax/items/7487b86f56ea12c31d44)
- [Sprint Bootの環境を構築したときのメモ](https://qiita.com/zumax/items/8effa97f338dd0224b22)

# まとめ
下記に特徴をまとめる

|特徴|説明|
|:--|:--|
|React |リアクティブなをフレームワーク<BR><BR>Reactは、Metaが開発するOSSのフロントエンドフレームワークである|
|CORS|・Cross-Origin Resource Sharingの略<BR><BR>・HTTPヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権をブラウザに指示する仕組み<BR><BR>・セキュリティ上の理由から、ブラウザにはスクリプトにより開始されるオリジン間HTTPリクエストを制限している|

# 概要
## React
Webページからリアルタイムにバックエンドへアクセスし、常に表示が更新されるアプリケーションの場合、リアクティブな処理をフレームワークに実装する事が多い。その一つとしてReactがある。
- Reactは、Metaが開発するOSSのフロントエンドフレームワークである
- Reactの開発を行うには、Node.jsが必要

### Reactプロジェクトの基本構成

|構成 |種別 |説明|
|:--|:--|:--|
|node_modules|フォルダ|アプリで利用するパッケージが保管されている|
|public|フォルダ|公開フォルダ<BR>トップページであるindex.htmlやログのファイルが用意されている|
|src|フォルダ|アプリケーションの本体<BR>Reactアプリのコードを用意する|
|.gitignore|ファイル|Gitが利用するファイル<BR>Gitのトラッキングの対象外とするファイルやディレクトリを指定できる。|
|package.json|プロジェクトのパッケージ情報が記載されている|
|package-lock.json|パッケージ管理ツールnpmが使う|
|README.md|使用方法等が記載されている|

## CORS
> オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。ウェブアプリケーションは、自分とは異なるオリジン (ドメイン、プロトコル、ポート番号) にあるリソースをリクエストするとき、オリジン間 HTTP リクエストを実行します。

> オリジン間リクエストとは、例えば https://domain-a.com で提供されているウェブアプリケーションのフロントエンド JavaScript コードが XMLHttpRequest を使用して https://domain-b.com/data.json へリクエストを行うような場合です。

> セキュリティ上の理由から、ブラウザーは、スクリプトによって開始されるオリジン間 HTTP リクエストを制限しています。例えば、 XMLHttpRequestや Fetch API は同一オリジンポリシー (same-origin policy) に従います。つまり、これらの API を使用するウェブアプリケーションは、そのアプリケーションが読み込まれたのと同じオリジンに対してのみリソースのリクエストを行うことができ、それ以外のオリジンからの場合は正しい CORS ヘッダーを含んでいることが必要です。

- Cross-Origin Resource Sharingの略
- HTTPヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権をブラウザに指示する仕組み
- セキュリティ上の理由から、ブラウザにはスクリプトにより開始されるオリジン間HTTPリクエストを制限している




# 実践
## 前提
前回、構築した[SpringBootでSpring WebFluxを試してみた](https://qiita.com/zumax/items/f07ce794cfd597ca58b1#%E5%AE%9F%E8%B7%B5)を使用します。

## 静的HTMLファイル
まずは、静的なアクセスから試します

1. resourcesフォルダの配下に、publicフォルダを作成します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/9bb7a8ec-442e-a9f8-43ec-adafe49e1c37.png)

1. static.htmlファイルを作成します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/b426fa31-c71c-eb1d-9a69-f091e4cda202.png)

1. static.htmlに下記のコードを書きます
    ```html
    <!DOCTYPE html>
    <html>
      <head>
        <title></title>
        <meta http-equiv="Content-type" content="text/html"; charset="UTF-8" />
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" >
      </head>
      <body class="container">
        <h1 class="display-4">Static html</h1>
        <p class="msg">This is static html file.</p>
      </body>
    </html>
    ```
### 確認
1. ビルドとSpring Bootを起動します
    ```bash
    $ mvn package
    $ java -jar target/sample-0.0.1-SNAPSHOT.jar
    ```
1. ブラウザでlocalhost:8080/static.html にアクセスします。表示されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/9be73155-cf23-424a-a894-a2e7fa588b54.png)

## fetch関数
JavaScriptを使用してAPIにアクセスします。JavaScriptで指定したURLにアクセスするには、fetch関数を使います。
この、fetch関数は、非同期になるため、thenコールバック処理を用意するか、awaitでも戻り値を受け取り処理します。

### APIからPostレコードを取得する
1. static.htmlを下記のように修正します
    ```html
    <!DOCTYPE html>
    <html>
      <head>
        <title></title>
        <meta http-equiv="Content-type" content="text/html"; charset="UTF-8" />
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" >
      </head>
      <body class="container">
        <h1 class="display-4">Static html</h1>
        <p class="msg">This is static html file.</p>
        <div class="border border border-1 p-3" id="container"></div>
        <script>
          async function getData() {
            const response = await fetch('/post');
            const data = await response.json();
            document.querySelector('#container').textContent = JSON.stringify(data);
          };
          getData();
        </script>
      </body>
    </html>
    ```
    - /postにアクセスし、ダミーとして作成したPostを取得している
### 確認
1. ビルドとSpring Bootを起動します
    ```bash
    $ mvn package
    $ java -jar target/sample-0.0.1-SNAPSHOT.jar
    ```
1. ブラウザでlocalhost:8080/static.html にアクセスします。表示されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/4359e224-9aa5-5a71-c34f-704b6a297039.png)

##　フォーム入力を利用する
ユーザからの入力に応じて、アクセスする場合を想定します。

### HTMLを修正する
1. 下記のように修正します
    ```html
    <!DOCTYPE html>
    <html>
      <head>
        <title></title>
        <meta http-equiv="Content-type" content="text/html"; charset="UTF-8" />
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" >
      </head>
      <body class="container">
        <h1 class="display-4">Static html</h1>
        <p class="msg">This is static html file.</p>
        <div class="border border-1 p-3 mb-4" id="area">no data.</div>
        <div class="input-group">
          <input type="text" class="form-control me-1" 
            id="input" />
          <span class="input-group-btn">
            <input type="submit" class="btn btn-primary px-4" 
              onclick="submit();" value="Click" /> 
          </span>
        </div>
        <script>
          async function submit() {
            const id = document.querySelector("#input").value;
            const result = await getData('/post/' + id);
            document.querySelector('#area').textContent 
              = JSON.stringify(result);
          }
          async function getData(url) {
            const response = await fetch(url);
            return await response.json();
          };
          getData();
        </script>
      </body>
    </html>
    ```
### 確認
1. ビルドとSpring Bootを起動します
    ```bash
    $ mvn package
    $ java -jar target/sample-0.0.1-SNAPSHOT.jar
    ```
1. ブラウザでlocalhost:8080/static.html にアクセスします。表示されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/c95a5147-0a70-9548-aaa8-028dc6479e27.png)

1. 次に1を入力し、「Click」ボタンをクリックします。no dataから値が変わりました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/c3e37017-2df4-bd21-9194-73cd906afd56.png)

## Reactアプリケーション

### 環境構築
Reactの開発を行うために、Node.jsをインストールします

1. brewでインストールします。その他の方法は、`https://nodejs.org/en/download/package-manager`を参考にインストールしてください
    ```bash
    $ brew install node@20
    ```
1. パスを通します
    ```bash
    $ echo 'export PATH="/usr/local/opt/node@20/bin:$PATH"' >> /Users/XXXX/.zshrc
    $ source /Users/XXXX/.zshrc 
    $ node -v
    v20.15.1
    $ npm -v
    10.7.0
    $
    ```
#### Reactプロジェクトを作成
1. プロジェクト用のフォルダを作成する
    ```bash
    $ mkdir react
    $ cd react 
    $
    ```
1. プロジェクトを作成する
    ```bash
    npx create-react-app react_flux_app
    ```
1. できました
    ```bash
    $ ls
    react_flux_app
    $
    ```
1. 下記の構成になっています
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/9f19ba66-b217-98c1-66f4-c3814b26c540.png)


### Appコンポーネントからアクセスする
#### App.js
1. srcフォルダのApp.jsを開きます
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/49e5fac1-eded-9e86-64d1-3b52176fc29e.png)

1. 下記のように修正します
    ```js
    import React, {useState} from 'react';
    import './App.css';
    
    function App() {
      const url = "http://localhost:8080/post";
      const [data,setData] = useState("");
    
      async function getData(){
        const response = await fetch(url);
        const data = await response.json();
        const str = JSON.stringify(data, null, '  ');
        setData(str);
      }
    
      getData();
    
      return(
        <div className='App'>
          <h1> React app.</h1>
          <pre>{data}</pre>
        </div>
      )
    }
    
    export default App;
    ```
#### App.css
1. srcフォルダのApp.cssを開きます
1. 下記のコードを追記します
    ```css
    pre{
      border:1px solid gray;
      text-align: left;
      padding: 20px;
      margin: 20px;
    }
    ```
1. Nodeを起動します
    ```bash
    $ npm start
    ```

#### 確認

1. ブラウザにlocalhost:3000/ を入力しアクセスします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/8b180753-0c7a-7d44-450c-5a859bfad5d3.png)


## CORSの設定
上記のままでは、アクセスができません。
WebFluxアプリと、Reactアプリでは、ポートが異なるため、オリジンが異なると判断されアクセスを拒否されています。
これを回避するために、CORS（Cross-Origin Resource Sharing）の設定を行います。

### SampleRestControllerクラスの修正
WebFluxアプリにCrossOriginアノテーションを設定します

1. postメソッドにアノテーションを追加します
    ```java
      @RequestMapping("/post")
      @CrossOrigin(value={"http://localhost:3000/"})
      public Mono<Post> post(){
        Post post = new Post(0,0,"title-a","body-a");
        return Mono.just(post);
      }
    ```
### 確認
1. ビルドとSpring Bootを起動します
    ```bash
    $ mvn package
    $ java -jar target/sample-0.0.1-SNAPSHOT.jar
    ```
1. ブラウザにlocalhost:3000/ を入力しアクセスします。見えました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/3c6a00cd-cd0d-784b-fd86-24363155cacf.png)


## SWR
Reactでは、ステートフックと呼ばれるものを利用することで、値が更新されると自動的にWebページの表示も更新されるような仕組みを簡単に作成できます。

このステートフックとして、NWアクセスの取得結果を扱えるようにするパッケージが用意されている。SWRです。
利用することで、URLの値を変更するとリアルタイムにデータの内容が更新されるような処理を作成できます。

### 環境構築

1. SWRをインストールします

    ```bash
    $ npm install swr
    $ npm list       
    react_flux_app@0.1.0 /Users/XXX/git/react/react_flux_app
    ├── @testing-library/jest-dom@5.17.0
    ├── @testing-library/react@13.4.0
    ├── @testing-library/user-event@13.5.0
    ├── react-dom@18.3.1
    ├── react-scripts@5.0.1
    ├── react@18.3.1
    ├── swr@2.2.5
    └── web-vitals@2.1.4
    
    $
    ```

### コントローラ
1. SampleRestControllerを修正します。CrossOriginアノテーションを追加します
    ```java
      @RequestMapping("/post/{id}")
      @CrossOrigin(value={"http://localhost:3000/"})
      public Mono<Post> post(@PathVariable int id){
        Post post = repository.findById(id);
        return Mono.just(post);
      }
    
    ```
#### ビルドと起動
1. ビルドとSpring Bootを起動します
    ```bash
    $ mvn package
    $ java -jar target/sample-0.0.1-SNAPSHOT.jar
    ```

### App.jsを修正
1. App.jsを下記のように修正します
    ```js
    import React, {useState} from 'react';
    import useSWR from 'swr';
    import './App.css';
    
    function App() {
      const baseUrl = "http://localhost:8080/post";
      const fetcher = (url) => fetch(url).then(res => res.json());
      const [url,setUrl] = useState(baseUrl);;
      const {data} = useSWR(url, fetcher);
    
      const updateUrl = (e)=> {
        setUrl(baseUrl + '/' + e.target.value);
      }
    
      return(
        <div className="App">
          <h1>React App</h1>
          <input type="number" onChange={updateUrl} />
          <pre>
            <ul>
              <li>ID:     {data ? data.id : ''}</li>
              <li>USERID: {data ? data.userId : ''}</li>
              <li>TITLE:  {data ? data.title : ''}</li>
              <li>BODY:   {data ? data.body : ''}</li>
            </ul>
          </pre>
        </div>
      );
    
    }
    export default App;
    ```
### App.cssを修正
1. App.cssを下記のように修正します
    ```css
    ul{
      text-align: left;
      font-size: large;
      font-weight: bold;
    }
    ```

#### Nodeを起動
1. Nodeを起動します
    ```bash
    $ npm start
    ```

#### 確認
1. ブラウザに、`localhost:3000/`を入力します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/142399fa-997f-99da-d99d-18baa94f97fb.png)

1. プルダウンで1を選択します。反映されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/f7d010d3-1490-9707-3155-468b01df3f72.png)

1. プルダウンで2を選択します。反映されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/af762c36-401f-f559-c9a1-657ceddb2c43.png)

1. プルダウンで3を選択します。反映されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/8670a1a3-220a-6984-c06b-93f52a683f30.png)


# 考察
今回は、Reactを使って、Spring WebFluxにAPIアクセスを試してみました。クライアントからのアクセスとサーバサイドのAPIの挙動について、理解ができました。
今後は、Beanについて試してみたいと思います。

# 参考
https://nodejs.org/en/download/package-manager

https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

https://swr.vercel.app/ja

[Spring Boot 3 プログラミング入門](https://www.amazon.co.jp/Spring-Boot-3-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%85%A5%E9%96%80-%E6%8E%8C%E7%94%B0%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798069167)