フロントエンド、バックエンド間のデータ受け渡し
1. はじめに
以前、RailsチュートリアルでRuby on Railsを少しだけ勉強しました。
その時はバックエンドからフロントエンドに値を渡して表示することが簡単にできていたので、そこに障害があるという認識がありませんでした。
ですが、実際はフロントエンドとバックエンドは隔絶していて、Webサーバを使わないと情報のやり取りができないようです。
そこで今回は、このデータのやりとりについてまとめていきます。
2. 実行環境と注意事項
2.1. 実行環境
- Ruby (3.2.2)
- Webrick (1.8.1)
2.2. 注意事項
コンソールに304という数字がでているときは、本来のページと別のページが表示されている可能性があるので、そういった場合は新しくシークレットウインドウを開いて、localhostにアクセスしてください。
127.0.0.1 - - [18/Nov/2023:17:06:34 東京 (標準時)] "GET / HTTP/1.1" 304 0
上のようにでていたら、ブラウザが忖度したページが表示されてしまいます。
3. Webサーバを使ってみる
冒頭でも述べた通り、フロントエンドとバックエンドがデータをやりとりするためには、Webサーバが必要になります。
フロントエンドはWebサーバにHTTPリクエスト(GETやPOSTなど)を送り、それに応じてWebサーバはバックエンド側で処理を指示します。
このフロントエンド → Webサーバ(バックエンド) → フロントエンドという一連の流れを通じてデータは渡されます。
このWebサーバを担うrubyプログラムを記述するためのライブラリの一つがWebrickです。
3.1. HTMLファイルを表示してみる
以下のようにファイルを作ってruby server.rb
と打つだけで、Webサーバを起動してindex.htmlを表示することができます。
.
├── index.html
└── server.rb
server.rb
require 'webrick'
server = WEBrick::HTTPServer.new({
:DocumentRoot => './',
:BindAddress => '127.0.0.1',
:Port => 8000
})
server.start
index.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>表示確認画面</title>
</head>
<body>
<h1>表示確認画面</h1>
<p>無事にindex.htmlを表示することができました。</p>
</body>
</html>
:DocumentRootに設定したディレクトリに存在するファイルやサブディレクトリはすべて読み込まれ、パスを指定することで表示することができるようになります。
また、Webrickでサーバを作ると、http://localhost:8000にアクセスしたときに、ルート直下に存在するindex.htmlを表示するように自動で設定してくれます(今回はportに8000を指定したのでこのアドレス)。
4. フロントエンド → バックエンド
4.1. フォームで送信した内容をファイルに書き込む
serverを起動してるときは、puts
してもコンソールに表示されないので、バックエンドにデータがちゃんと来ていることを確かめるために、ファイルに出力して確認しようと思います。
Webrickでは、メソッドによって処理を分けることができます。フロントエンドから送られてくるリクエストのうち、多くはGETとPOSTだと思うので、それら2つについて書いていきます。
〇GETメソッド
まずは、GETメソッドの時のデータ受け取りを試してみます。
.
├── index.html
├── output.txt
└── server.rb
server.rb
require 'webrick'
server = WEBrick::HTTPServer.new({
:DocumentRoot => './',
:BindAddress => '127.0.0.1',
:Port => 8000
})
# /user_nameにアクセスされたときの処理
class UserNameServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
# リクエストから情報を取得
File.open('output.txt', 'w') do |f|
f.puts(req.query)
end
end
end
# serverをCtrl+Cでシャットダウンするためのもの
trap('INT') { server.shutdown }
server.mount('/user_name', UserNameServlet)
server.start
index.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>名前入力画面</title>
</head>
<body>
<h1>名前入力画面</h1>
<form id="get-form" action="/user_name" method="get">
<label for="get">GETメソッドで送信:</label><br>
<input type="text" id="user_name" name="user_name" value="sakana"><br>
<input type="submit" value="送信">
</form>
</body>
</html>
output.txt
{"user_name"=>"sakana"}
server.mount(dir, Servlet)
で、dir
をServlet
にマウントすることができます。これにより、dir
にアクセスしたときに、Servlet
に書いてある処理を実行してくれるようになります。
そして、do_GET(req, res)
を定義すると、dir
にアクセスしたときに、GETメソッドの場合だけ行う処理を書くことができます。
フロントエンドからのリクエストがreq
、フロントエンドへのレスポンスがres
に入ります。
今回は、フロントエンドからどんなデータが送られてきているのかを見たいので、req.query
をテキストに書きだして見てみます。
成功すると、output.txtにuser_nameがハッシュで書かれているはずです。
〇POSTメソッド
次に、POSTメソッドの時のデータ受け取りを試してみます。
.
├── index.html
├── output.txt
└── server.rb
server.rb
require 'webrick'
server = WEBrick::HTTPServer.new({
:DocumentRoot => './',
:BindAddress => '127.0.0.1',
:Port => 8000
})
# /user_idにアクセスされたときの処理
class UserIDServlet < WEBrick::HTTPServlet::AbstractServlet
def do_POST(req, res)
# リクエストから情報を取得
File.open('output.txt', 'w') do |f|
f.puts(req.query)
end
end
end
# serverをCtrl+Cでシャットダウンするためのもの
trap('INT') { server.shutdown }
server.mount('/user_id', UserIDServlet)
server.start
index.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>名前入力画面</title>
</head>
<body>
<h1>名前入力画面</h1>
<form id="post-form" action="/user_id" method="post">
<label for="get">POSTメソッドで送信:</label><br>
<input type="text" id="user_id" name="user_id" value="11"><br>
<input type="submit" value="送信">
</form>
</body>
</html>
output.txt
{"user_id"=>"11"}
先ほどとほとんど変わりありませんが、POSTなので、URLに”?=”みたいな感じで表立って表記されなくなります。
それでも、ちゃんとテキストにuser_idが書きだされるはずです。
〇GET + POSTメソッド
do_GETとdo_POSTは、メソッド毎に別々に処理してくれるので、上記2つは同じサーブレットにまとめることもできます。
.
├── index.html
├── output.txt
└── server.rb
server.rb
require 'webrick'
server = WEBrick::HTTPServer.new({
:DocumentRoot => './',
:BindAddress => '127.0.0.1',
:Port => 8000
})
# /userにアクセスされたときの処理
class UserServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
# リクエストから情報を取得
File.open('output.txt', 'w') do |f|
f.puts(req.query)
end
end
def do_POST(req, res)
# リクエストから情報を取得
File.open('output.txt', 'w') do |f|
f.puts(req.query)
end
end
end
server.mount('/user', UserServlet)
server.start
index.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>名前入力画面</title>
</head>
<body>
<h1>名前入力画面</h1>
<form id="get-form" action="/user" method="get">
<label for="get">GETメソッドで送信:</label><br>
<input type="text" id="user_name" name="user_name" value="sakana"><br>
<input type="submit" value="送信">
</form>
<form id="post-form" action="/user" method="post">
<label for="get">POSTメソッドで送信:</label><br>
<input type="text" id="user_id" name="user_id" value="11"><br>
<input type="submit" value="送信">
</form>
</body>
</html>
ここは想像通りだと思います。
5. バックエンド → フロントエンド
ここまで、フロントエンド→バックエンドのデータの受け渡しについてみてきましたが、実は何も指定していなくても、そのままフロントエンドにデータが返っています。
serverを起動したコンソールを見ていると、GETメソッドやPOSTメソッドを実行した際に何かが表示されているのがわかります。
それこそが、バックエンドからフロントエンドに渡されているデータです。
デフォルトでも、正常に通信できたことを表すHTTPステータスの200などが返ってきているのが見てとれるでしょう。
このように、フロントエンドからWebサーバにアクセスした際は
フロントエンド → Webサーバ(バックエンド) → フロントエンド
というデータの流れになっていて、4. フロントエンド → バックエンドでは、この前半部分についてみていきました。
それでは、次は後半部分についてみていきましょう。
5.1. HTMLファイルを渡して、フロントエンドで表示する
〇Root直下のHTMLファイルを表示
まずは、Root直下にindex.html以外の表示したいHTMLファイルを置いて、表示してみましょう。
.
├── index.html
├── sample.html
└── server.rb
server.rb
require 'webrick'
server = WEBrick::HTTPServer.new({
:DocumentRoot => './',
:BindAddress => '127.0.0.1',
:Port => 8000
})
# /userにアクセスされたときの処理
class URLServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
# HTTPステータスとコンテンツタイプを返す
res.status = 200
res['Content-Type'] = 'text/html'
# HTMLを返す
html = File.open('sample.html', 'r')
res.body = html
end
end
# serverをCtrl+Cでシャットダウンするためのもの
trap('INT') { server.shutdown }
server.mount('/url', URLServlet)
server.start
index.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>URL選択画面</title>
</head>
<body>
<h1>URL選択画面</h1>
<ul>
<li><a href = "/url">URL1</li>
</ul>
</body>
</html>
sample.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>サンプルHTMLファイル</title>
</head>
<body>
<h1>サンプルHTMLファイル</h1>
<p>サンプルHTMLファイルです。</p>
</body>
</html>
HTMLを表示するというよりは、HTMLの中身をres.body
に渡しているという感じです。
また、明示していなくてもHTTPステータスは200として返っていましたが、他の方のコードを見ると、明示的に書かれていることが多いので、今後はres.status = 200
としておきます。
もう一つ、コンテンツタイプも指定した方が良いみたいなので、res['Content-Type'] = 'text/html’
とします。
今までわかりやすさのために省略していましたが、フロントエンドからバックエンドにデータを渡したいだけのときでも、この2つは書いた方が良いと思います。
〇ディレクトリ内のHTMLファイルを表示
実際には、ファイル一つ一つに対してこのディレクトリのこのファイルを参照してくださいと書くのは面倒なので、次のように書くことも多いと思います。
.
├── article_manager
│ ├── articles
│ │ ├── article1.html
│ │ ├── article2.html
│ │ └── article3.html
│ └── manager.html
├── index.html
└── server.rb
server.rb
require 'webrick'
server = WEBrick::HTTPServer.new({
:DocumentRoot => './',
:BindAddress => '127.0.0.1',
:Port => 8000
})
# /article_managerにアクセスされたときの処理
class ArticleManagerServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
# HTTPステータスとコンテンツタイプを返す
res.status = 200
res['Content-Type'] = 'text/html'
# HTMLを返す
path = '.' + req.path
html = File.open(path, 'r')
res.body = html
end
end
# serverをCtrl+Cでシャットダウンするためのもの
trap('INT') { server.shutdown }
server.mount('/article_manager', ArticleManagerServlet)
server.start
index.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>URL選択画面</title>
</head>
<body>
<h1>URL選択画面</h1>
<ul>
<li><a href = "/article_manager/manager.html">記事管理ページ</li>
</ul>
</body>
</html>
manager.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>HTML管理ページ</title>
</head>
<body>
<h1>HTML管理ページ</h1>
<ul>
<li><a href = "./articles/article1.html">article1</li>
<li><a href = "./articles/article2.html">article2</li>
<li><a href = "./articles/article3.html">article3</li>
</ul>
</body>
</html>
article_n.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>Article_n</title>
</head>
<body>
<h1>Article_n</h1>
</body>
</html>
req.pathからパスを取り出して、ファイルへのアクセスに使うことができますが、相対パスを利用したい場合は先頭にドットを付ける必要があります。
パス周りは混乱しやすいので、こう変えるとつながる、こう変えるとつながらないというのを試行錯誤してみるのが良いと思います。
5.2. Rubyの変数をJavaScriptの変数に代入する
HTMLファイルに直接書き込む形ではなく、JavaScriptの変数に、Rubyの変数を渡したいことがあると思います。
そういう場合は、以下を参考にしてください。
.
├── fish.csv
├── index.html
├── script.js
└── server.rb
server.rb
require 'webrick'
require 'csv'
require 'json'
server = WEBrick::HTTPServer.new({
:DocumentRoot => './',
:BindAddress => '127.0.0.1',
:Port => 8000
})
# /get_dataにアクセスされたときの処理
class GetDataServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
# HTTPステータスとコンテンツタイプを返す
res.status = 200
res['Content-Type'] = 'application/json'
# dataを返す
fish_list = CSV.read("./fish.csv")
res.body = fish_list.to_json
end
end
# serverをCtrl+Cでシャットダウンするためのもの
trap('INT') { server.shutdown }
server.mount('/get_data', GetDataServlet)
server.start
index.html
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="UTF-8">
<title>待機画面</title>
</head>
<body>
<h1>待機画面</h1>
<script src="script.js"></script>
</body>
</html>
script.js
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:8000/get_data");
xhr.send();
xhr.responseType = "json";
xhr.onload = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
const data = xhr.response;
console.log("Success")
console.log(data);
} else {
console.log(`Error: ${xhr.status}`);
}
};
fish.csv
1,maguro,150
2,sake,200
3,tara,100
※fetchでも多分書けますが、参考にしたのがXMLHTTPRequestだったので、そちらで書いています。
これで、serverを起動して表示されたindex.htmlを開発者モードにしてコンソールを見ると、データがちゃんと渡っているのがわかります。
res.bodyにデータを渡すだけでいいみたいです。ただ、ちゃんと配列をjson形式に変換してから入れないと、フロントエンドから見たresponseの中身はnullになってしまいます。
6. まとめ
- フロントエンド → Webサーバ(バックエンド) → フロントエンドというデータの流れになっている
- アクセスするアドレスとサーブレットを登録することで、そのアドレスにアクセスしたときの処理を記述できる
- reqにフロントエンドからの情報が入っている
- resにフロントエンドに返す情報を入れる
7. 最後に
Webサーバのこと何もわからなかったので、最初は絶望していましたが、動くコードをかき集めている内に多少は何が起きているのか分かったような気がします。
この記事が少しでも、Webサーバ初学者の方の助けになれば幸いです。
Discussion