背景・目的
過去に、Spring Bootについて下記の記事にまとめました。今まではサーバサイドとは代わり、クライアント側からサーバサイドアプリケーションを呼び出す処理を書いてみます
- SpringBootでSpring WebFluxを試してみた
- Spring BootでDAOからデータベースにアクセスしてみた-その2(クライテリア)
- Spring BootでDAOからデータベースにアクセスしてみた-その1(DAOとアノテーション)
- Spring Bootでバリデーションを試してみた
- Spring Bootでデータを保存する-その2
- Spring Bootでデータを保存する-その1
- Spring Bootでデータベースへのアクセスを試してみた
- Sprint Bootの環境を構築したときのメモ
まとめ
下記に特徴をまとめる
特徴 | 説明 |
---|---|
React | リアクティブなをフレームワーク Reactは、Metaが開発するOSSのフロントエンドフレームワークである |
CORS | ・Cross-Origin Resource Sharingの略 ・HTTPヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権をブラウザに指示する仕組み ・セキュリティ上の理由から、ブラウザにはスクリプトにより開始されるオリジン間HTTPリクエストを制限している |
概要
React
Webページからリアルタイムにバックエンドへアクセスし、常に表示が更新されるアプリケーションの場合、リアクティブな処理をフレームワークに実装する事が多い。その一つとしてReactがある。
- Reactは、Metaが開発するOSSのフロントエンドフレームワークである
- Reactの開発を行うには、Node.jsが必要
Reactプロジェクトの基本構成
構成 | 種別 | 説明 |
---|---|---|
node_modules | フォルダ | アプリで利用するパッケージが保管されている |
public | フォルダ | 公開フォルダ トップページであるindex.htmlやログのファイルが用意されている |
src | フォルダ | アプリケーションの本体 Reactアプリのコードを用意する |
.gitignore | ファイル | Gitが利用するファイル 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を試してみたを使用します。
静的HTMLファイル
まずは、静的なアクセスから試します
-
static.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>
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/static.html にアクセスします。表示されました
fetch関数
JavaScriptを使用してAPIにアクセスします。JavaScriptで指定したURLにアクセスするには、fetch関数を使います。
この、fetch関数は、非同期になるため、thenコールバック処理を用意するか、awaitでも戻り値を受け取り処理します。
APIからPostレコードを取得する
- static.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を取得している
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/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-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>
確認
-
ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
Reactアプリケーション
環境構築
Reactの開発を行うために、Node.jsをインストールします
- brewでインストールします。その他の方法は、
https://nodejs.org/en/download/package-manager
を参考にインストールしてください$ brew install node@20
- パスを通します
$ 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プロジェクトを作成
- プロジェクト用のフォルダを作成する
$ mkdir react $ cd react $
- プロジェクトを作成する
npx create-react-app react_flux_app
- できました
$ ls react_flux_app $
- 下記の構成になっています
Appコンポーネントからアクセスする
App.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
- srcフォルダのApp.cssを開きます
- 下記のコードを追記します
pre{ border:1px solid gray; text-align: left; padding: 20px; margin: 20px; }
- Nodeを起動します
$ npm start
確認
CORSの設定
上記のままでは、アクセスができません。
WebFluxアプリと、Reactアプリでは、ポートが異なるため、オリジンが異なると判断されアクセスを拒否されています。
これを回避するために、CORS(Cross-Origin Resource Sharing)の設定を行います。
SampleRestControllerクラスの修正
WebFluxアプリにCrossOriginアノテーションを設定します
- postメソッドにアノテーションを追加します
@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); }
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザにlocalhost:3000/ を入力しアクセスします。見えました
SWR
Reactでは、ステートフックと呼ばれるものを利用することで、値が更新されると自動的にWebページの表示も更新されるような仕組みを簡単に作成できます。
このステートフックとして、NWアクセスの取得結果を扱えるようにするパッケージが用意されている。SWRです。
利用することで、URLの値を変更するとリアルタイムにデータの内容が更新されるような処理を作成できます。
環境構築
-
SWRをインストールします
$ 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 $
コントローラ
- SampleRestControllerを修正します。CrossOriginアノテーションを追加します
@RequestMapping("/post/{id}") @CrossOrigin(value={"http://localhost:3000/"}) public Mono<Post> post(@PathVariable int id){ Post post = repository.findById(id); return Mono.just(post); }
ビルドと起動
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
App.jsを修正
- App.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を修正
- App.cssを下記のように修正します
ul{ text-align: left; font-size: large; font-weight: bold; }
Nodeを起動
- Nodeを起動します
$ npm start
確認
考察
今回は、Reactを使って、Spring WebFluxにAPIアクセスを試してみました。クライアントからのアクセスとサーバサイドのAPIの挙動について、理解ができました。
今後は、Beanについて試してみたいと思います。
参考