はじめに
以前、C++でStimulusを使う!という記事をQiitaで書きました
それからFirebaseを使用したリアルタイムチャットアプリなどをC++/Stimilusで作ったりしていました
ただ、このアプリはFireBaseに依存しているので、サービスが止まるなどした時に使えないという課題が残っていました
そこで、C++/Stimulusのみでリアルタイムチャットアプリが実装できないか試してみました
これはその時の実装内容などをまとめた記事になります
作ったもの
こんな感じで、メッセージを追加すると自動的に別ブラウザにも送信したメッセージが表示されるようになっています
やったこと
C++/Stimulusでの開発環境構築
以前書いたC++でStimulusを使う!をそのまま利用します
cpp-httplibで基礎構築
まず、cpp-httplib
を使い、C++でのWeb開発環境を構築します
#include <iostream>
#include <httplib.h>
#include <fstream>
#include <sstream>
#include <string>
const std::string load_assets(const std::string& path) {
std::ifstream file(path.c_str(), std::ios::in);
std::stringstream stream;
stream << file.rdbuf();
file.close();
std::string assets(stream.str());
return assets;
}
int main() {
httplib::Server svr;
std::string body = load_assets("./assets/index.html");
std::string indexjs = load_assets("./assets/index.js");
svr.Get("/", [&](const httplib::Request& req, httplib::Response& res) {
res.set_content(body, "text/html");
});
svr.Get("/index.js", [&](const httplib::Request& req, httplib::Response& res) {
res.set_content(indexjs, "text/javascript");
});
svr.listen("localhost", 3000);
return 0;
}
後は以下のようにビルドします
g++ main.cpp httplib.h
Stimulusを導入
次にmain.cpp
と同じ階層にassets
ディレクトリを作成し、各ファイルを作成します
module.exports = {
entry: './src/index.js',
output: {
filename: 'index.js',
path: `${__dirname}`
}
}
{
"scripts": {
"build": "webpack --display-error-details"
},
"dependencies": {
"stimulus": "^1.1.1",
"webpack": "^4.29.4",
"webpack-cli": "^3.2.3"
}
}
<html>
<body>
<div data-controller="chat">
</div>
<script src="./index.js" type="text/javascript"></script>
</body>
</html>
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"
const application = Application.start()
const context = require.context("./controllers", true, /\.js$/)
application.load(definitionsFromContext(context))
import { Controller } from "stimulus"
export default class extends Controller {
content() {
console.log("Hello Stimulus!")
}
}
あとは、assets
ディレクトリ以下で、yarn install
とyarn build
を実行すればOKです
yarn install
yarn build
ローカルサーバを起動
main.cpp
がある階層に戻って以下のように実行します
./a
これでlocalhost:3000
にアクセスできます
ブラウザのコンソールにHello Stimulus!
と表示されていればC++/Stimulus開発環境は構築完了です!
リアルタイムチャットを実装
C++でAPI作成
まず、C++側でチャットのメッセージを受け取ったり、表示するAPIを作成します
#include <iostream>
#include <httplib.h>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
const std::string load_assets(const std::string& path) {
std::ifstream file(path.c_str(), std::ios::in);
std::stringstream stream;
stream << file.rdbuf();
file.close();
std::string assets(stream.str());
return assets;
}
int main() {
httplib::Server svr;
std::vector<std::string> chats;
std::string body = load_assets("./assets/index.html");
std::string indexjs = load_assets("./assets/index.js");
svr.Get("/", [&](const httplib::Request& req, httplib::Response& res) {
res.set_content(body, "text/html");
});
svr.Get("/messages", [&](const httplib::Request& req, httplib::Response& res) {
std::ostringstream chat;
for(auto&& c : chats)
chat << "<p>" << c.c_str() << "</p>";
res.set_content(chat.str(), "text/plain");
});
svr.Post("/messages", [&](const httplib::Request& req, httplib::Response& res) {
chats.emplace_back(std::move(req.body));
res.set_content(std::to_string(req.get_param_value_count("chat")), "text/html");
});
svr.Get("/index.js", [&](const httplib::Request& req, httplib::Response& res) {
res.set_content(indexjs, "text/javascript");
});
svr.listen("localhost", 3000);
return 0;
}
変更箇所としてはstd::vector<std::string> chats
でチャットのメッセージを保存するコンテナを作成している点と、以下のようにlocalhost:3000/messages
のGET/POSTを作成した点です
svr.Get("/messages", [&](const httplib::Request& req, httplib::Response& res) {
std::ostringstream chat;
for(auto&& c : chats)
chat << "<p>" << c.c_str() << "</p>";
res.set_content(chat.str(), "text/plain");
});
svr.Post("/messages", [&](const httplib::Request& req, httplib::Response& res) {
chats.emplace_back(std::move(req.body));
res.set_content(std::to_string(req.get_param_value_count("chat")), "text/html");
});
Stimulusでのチャット表示
localhost:3000/messages
に表示されているメッセージ一覧を受け取り、localhost:3000
で表示できるようにします
<!DOCTYPE html>
<html>
<body>
<div data-controller="chat" data-chat-url="/messages">
<div data-target="chat.response"></div>
<div data-target="chat.chats"></div>
<input data-target="chat.content">
<button data-action="click->chat#chat">add</button>
</div>
<script src="./index.js" type="text/javascript"></script>
</body>
</html>
import { Controller } from "stimulus";
import axios from "axios";
export default class extends Controller {
static get targets() {
return ["chats", "content", "response"];
}
connect() {
this.load();
}
load() {
axios.get(this.data.get("url")).then((res) => {
this.responseTarget.innerHTML = res.data;
}, (error) => {
console.log(error);
})
}
chat() {
axios.post(this.data.get("url"), `${this.contentTarget.value}`).then((res) => {
console.log(res);
}, (error) => {
console.log(error);
})
}
}
StimulusではJavaScript側に渡したいデータなどをdata-chat-url="/messages"
として渡すことができます
受け取る際にはthis.data.get("url")
とすると/messages
というURLを受け取ることができます
上記のコードではaxios
を使い、先ほど作成したAPIにGET/POSTのリクエストを送っています
最後に、axios
をyarn
で追加します
yarn add axios
あとは、localhost:3000
にアクセスして適当に入力してadd
をクリックしてください
リアルタイム機能はまだですが、ページをリロードすると送信したメッセージが表示されているはずです
リアルタイムに更新
最後に、リアルタイムにチャットができるように実装していきます
まずはasstes/index.html
にStimulusで使用するDataMap:data-chat-refresh-interval="10"
を追加します
<!DOCTYPE html>
<html>
<body>
<div data-controller="chat" data-chat-url="/messages" data-chat-refresh-interval="10">
<div data-target="chat.response"></div>
<div data-target="chat.chats"></div>
<input data-target="chat.content">
<button data-action="click->chat#chat">add</button>
</div>
<script src="./index.js" type="text/javascript"></script>
</body>
</html>
その後、assets/src/controllers/chat_controller.js
を以下のようにします
import { Controller } from "stimulus";
import axios from "axios";
export default class extends Controller {
static get targets() {
return ["chats", "content", "response"];
}
connect() {
this.load();
if (this.data.has("refreshInterval")) {
this.startRefreshing()
}
}
load() {
axios.get(this.data.get("url")).then((res) => {
this.responseTarget.innerHTML = res.data;
}, (error) => {
console.log(error);
})
}
chat() {
axios.post(this.data.get("url"), `${this.contentTarget.value}`).then((res) => {
console.log(res);
}, (error) => {
console.log(error);
})
}
startRefreshing() {
this.refreshTimer = setInterval(() => {
this.load()
}, this.data.get("refreshInterval"))
}
stopRefreshing() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer)
}
}
}
実装内容としてはdata-chat-refresh-interval="10"
でチャットの内容を再読み込みするまでの時間をセットし、その時間が経過すると更新されたチャットの内容を受け取りに行くという感じです
実装にあたってはStimulus Handbook: Working With External Resources
を参考にさせていただきました!
これで以下のようにリアルタイムにチャットができるようになりました!
おわりに
Stimulusでのリアルタイムチャット機能が実装できたので、今度はRailsに組み込んでチャットアプリを作ってみるのも面白いかなーと思いました
あと、意外とStimulusで出来ることの幅が広く、その上シンプルにコードが書けるので今後も重宝しそうとか思いました