0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C++/Stimulusでのリアルタイムチャット!

Last updated at Posted at 2019-02-24

はじめに

以前、C++でStimulusを使う!という記事をQiitaで書きました

それからFirebaseを使用したリアルタイムチャットアプリなどをC++/Stimilusで作ったりしていました

CtimulusFireBase

ただ、このアプリはFireBaseに依存しているので、サービスが止まるなどした時に使えないという課題が残っていました

そこで、C++/Stimulusのみでリアルタイムチャットアプリが実装できないか試してみました

これはその時の実装内容などをまとめた記事になります

作ったもの

StimulusCpp.gif

StimulusCpp

こんな感じで、メッセージを追加すると自動的に別ブラウザにも送信したメッセージが表示されるようになっています

やったこと

C++/Stimulusでの開発環境構築

以前書いたC++でStimulusを使う!をそのまま利用します

cpp-httplibで基礎構築

まず、cpp-httplibを使い、C++でのWeb開発環境を構築します

main.cpp
#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ディレクトリを作成し、各ファイルを作成します

assets/webpack.config.js
module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'index.js',
        path: `${__dirname}`
    }
}
package.json
{
  "scripts": {
    "build": "webpack --display-error-details"
  },
  "dependencies": {
    "stimulus": "^1.1.1",
    "webpack": "^4.29.4",
    "webpack-cli": "^3.2.3"
  }
}
assets/index.html
<html>
    <body>
        <div data-controller="chat">
        </div>
        <script src="./index.js" type="text/javascript"></script>
    </body>
</html>
assets/src/index.js
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))
assets/src/controllers/chat_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
    content() {
        console.log("Hello Stimulus!")
    }
}

あとは、assetsディレクトリ以下で、yarn installyarn buildを実行すればOKです

yarn install
yarn build

ローカルサーバを起動

main.cppがある階層に戻って以下のように実行します

./a

これでlocalhost:3000にアクセスできます

ブラウザのコンソールにHello Stimulus!と表示されていればC++/Stimulus開発環境は構築完了です!

リアルタイムチャットを実装

C++でAPI作成

まず、C++側でチャットのメッセージを受け取ったり、表示するAPIを作成します

main.cpp
#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で表示できるようにします

assets/index.html
<!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>
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();
   }

    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のリクエストを送っています

最後に、axiosyarnで追加します

yarn add axios

あとは、localhost:3000にアクセスして適当に入力してaddをクリックしてください
リアルタイム機能はまだですが、ページをリロードすると送信したメッセージが表示されているはずです

リアルタイムに更新

最後に、リアルタイムにチャットができるように実装していきます

まずはasstes/index.htmlにStimulusで使用するDataMap:data-chat-refresh-interval="10"を追加します

assets/index.html
<!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を以下のようにします

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を参考にさせていただきました!

これで以下のようにリアルタイムにチャットができるようになりました!

StimulusCpp.gif

おわりに

Stimulusでのリアルタイムチャット機能が実装できたので、今度はRailsに組み込んでチャットアプリを作ってみるのも面白いかなーと思いました

あと、意外とStimulusで出来ることの幅が広く、その上シンプルにコードが書けるので今後も重宝しそうとか思いました

参考

Stimulus Handbook: Working With External Resources

yhirose/cpp-httplib

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?