2020-07-19
IchigoJamで動作する数十byte単位のプログラムも実装可能なモダンプログラミング言語 Rust。 サーバー実装の実験にリアルタイムwebの要、WebSocketを使って単純に送った文字列が返ってくる、echoサーバーづくり!

JavaScriptのランタイム Denoと、HTTPサーバーモジュール Servest を使った WebSocket の echo server のコードは Servest を使ってこんな感じ。 (servest 1.1.0 では、Deno 1.2.0 でエラーが発生、stdモジュールを新版への差し替えプルリク、下記は servest v1.1.1で動作します!)

import { createApp } from "https://servestjs.org/@v1.1.1/mod.ts"; function handleHandshake(sock) { async function handleMessage(sock) { for await (const msg of sock) { if (typeof msg === "string") { sock.send(msg); } } } handleMessage(sock); } const app = createApp(); app.ws("/ws/", handleHandshake); app.listen({ port: 8080 });

続いて、WebSocketのclient

import { WebSocket } from "https://deno.land/x/websocket/mod.ts"; const endpoint = "ws://127.0.0.1:8080/ws/"; const ws = new WebSocket(endpoint); ws.on("open", () =< { ws.send("hello!"); }); ws.on("message", message =< { console.log("recv", message); ws.close(); });

シンプルに書けて、いい感じ!(deno run -A server.js と den run -A client.js で実行)

続いて、Rustで書く WebSocket の echo server には、速そうな(TechEmpower Web Framework Benchmarksで現在2位)の Actix を使ってみました。

use actix::*; use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; use actix_web_actors::ws; struct EchoSession { } impl Actor for EchoSession { type Context = ws::WebsocketContext<Self>; fn started(&mut self, _ctx: &mut Self::Context) { } fn stopping(&mut self, _ctx: &mut Self::Context) -> Running { Running::Stop } } impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for EchoSession { fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) { let msg = match msg { Err(_) => { ctx.stop(); return; } Ok(msg) => msg, }; match msg { ws::Message::Ping(_) => { }, ws::Message::Pong(_) => { }, ws::Message::Text(text) => { ctx.text(text); } ws::Message::Binary(_) => println!("Unexpected binary"), ws::Message::Close(reason) => { ctx.close(reason); ctx.stop(); } ws::Message::Continuation(_) => { ctx.stop(); } ws::Message::Nop => (), } } } async fn echo_route(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> { ws::start(EchoSession {}, &req, stream) } #[actix_rt::main] async fn main() -> std::io::Result<()> { HttpServer::new(move || { App::new() .service(web::resource("/ws/").to(echo_route)) }) .bind("127.0.0.1:8080")? .run() .await }

必要なものはきっちり書くスタイルなので、少し長いですが、やっていることは一緒です。Cargo.tomlにパッケージ名を記入して、cargo runでコンパイル&実行しておきましょう。

[dependencies] actix = "0.9.0" actix-web = "2.0.0" actix-web-actors = "2.0.0"

続いて、client は、こう書けます。

use websocket::{ClientBuilder, Message}; fn main() { let mut client = ClientBuilder::new("ws://127.0.0.1:8080/ws/") .unwrap() .connect_insecure() .unwrap(); let message = Message::text("hello!"); client.send_message(&message).unwrap(); let mes = client.recv_message().unwrap(); println!("{:?}", mes); }

Cargo.toml の dependencies に websocket="*" を追加し、Cargo run で動きます。

コネクション確立後、「hello!」を送って返ってくるものを100回繰り返して、時間を計測。 最速タイムで、Deno:35msec、Rust:25msecと、Rustが速いですが、Deno/JavaScriptでもベースは十分速そうです。

ちょっと応用、RustのActix、ブラウザから接続するチャットのサンプルを動かしてみました。 JavaScriptのコードが、jQueryコードだったので、モダンスタイルに更新!(src on GitHub)

速さが要のリアルタイムweb用サーバーづくり、Rustで実装、楽しいかも?

Tweet
クリエイティブ・コモンズ・ライセンス
本ブログの記事や写真は「Creative Commons — CC BY 4.0」の下に提供します。記事内で紹介するプログラムや作品は、それぞれに記載されたライセンスを参照ください。
CC BY / @taisukef / アイコン画像 / プロフィール画像 / 「一日一創」画像 / RSS