ubnt-intrepid's blog

書いてあることがブログの内容です

Rust 用のマイクロWebフレームワークを作ってみた

前回 は Hyper 用のルーティングライブラリを作ってみましたが,もう少し手を加えればちょっとしたマイクロフレームワークを作れるのではないと思い至ってしまったので勢いで作ってみました。

github.com

実用には程遠いレベルですが,ある程度フレームワークっぽい程度には実装できたので忘れないうちに内容をまとめておきます。

概要

プロジェクト名は「なんか日本神話っぽい名前のプロジェクト作りたい」という雑な考えで適当につけました。とくにスサノオノミコトへの思い入れがあるわけではないです…

コンセプトとしては,とにかくフレームワーク自体は小さくし,hyperfutures が扱っている領域は可能な限りそれらのクレート自体の機能をそのまま使えるようにというのを意識しています。 今のところ提供している機能は以下の通りです。

  • ルーティング(hyper-router の実装をそのまま移植した)
  • ミドルウェアのサポート

使い方

※ 本記事内のサンプルコードは執筆時(2017-07-07)のバージョンに基づく

基本的なサンプル

単純な例を下に示します。まぁよくあるマイクロフレームワークの書き方と同じ感じですね…

extern crate susanoo;

use susanoo::{Server, Context, AsyncResult};
use susanoo::contrib::futures::{future, Future};
use susanoo::contrib::hyper::{StatusCode, Response, Get};

fn hello(_ctx: Context) -> AsyncResult {
  let response = Response::new()
    .with_status(StatusCode::Ok)
    .with_body("<h1>Hello!</h1>");
  future::ok(response).boxed()
}

fn main() {
  let server = Server::new()
    .with_route(Get, "/", hello);
  server.run("0.0.0.0:4000");
}

ミドルウェアの使用

nickel と同様,ミドルウェアを用いて前段に処理をかませることが可能です。 ミドルウェア自体の実装は今後ちまちま進めていきたいと考えています。

// Authorization ヘッダの検証をするミドルウェア
fn check_auth(mut ctx: Context) -> AsyncResult {
  /* omit */
}

fn index(ctx: Context) -> AsyncResult {
    let user = ctx.map.get::<User>().unwrap();
    future::ok(
        Response::new()
            .with_status(StatusCode::Ok)
            .with_body(format!("<h1>Welcome, {}!</h1>", user.username))
            .into(),
    ).boxed()
}

fn main() {
    let server = Server::new()
        .with_middleware(check_auth)
        .with_route(Get, "/", index);

    server.run("0.0.0.0:4000")
}

その他細かい仕様やサンプルなどはソースコードを参照してください(正直具体的な仕様もまだ確定していないのですが…)

作ってみた感想など

  • フレームワークを作るの自体はすごく簡単だった
    • 主要な機能は hyper / futures / tokio が提供しているものを使えば良い
    • とにかくRustはエコシステムが充実してきており良い
  • 所有権回り,借用チェッカとの闘いが辛い
    • いまだに慣れない…
    • 特に並行処理周りの知識が足りないせいか,雰囲気で型制約を書いてしまっている箇所が多い

おわりに

正直主要なフレームワークiron / nickel / rocket)が futures に対応するまでの話な気もしますが,フレームワークの自作自体は結構面白かったので今後もチマチマとメンテナンスしていけたらなぁと

Hyper 0.11用のルーティングライブラリを作った

IronでWebアプリを作っていたら「もういっそのことHyper直に触ったほうが楽なのでは」という悪魔のささやきが聞こえてきたので勢いで作ってみた。

github.com

基本的に Iron のルーティング用ミドルウェアである router と似たような使い勝手になるよう意識しているが,route-recognizer ではなく正規表現でマッチする形式を採用した(route-recognizer 対応も後でする予定)。 リダイレクトや404ページなどの対応は今後少しずつ行っていきたい…

extern crate hyper;
extern crate hyper_router;
extern crate futures;
extern crate regex;

use hyper::server::{Http, Request, Response};
use hyper::{Method, StatusCode};
use hyper::Error as HyperError;
use hyper_router::RouteBuilder;
use futures::Future;
use futures::future::{self, BoxFuture};
use regex::Captures;

// 第2引数にマッチ結果が格納されている
fn hello(_req: &Request, cap: &Captures) -> BoxFuture<Response, HyperError> {
    future::ok(
        Response::new()
            .with_status(StatusCode::Ok)
            .with_body(format!("Captures: {:?}", cap)),
    ).boxed()
}

fn main() {
    let router = RouteBuilder::default()
        .route(Method::Get, r"/([^/]+)", hello)
        .finish();

    let addr = "0.0.0.0:4000".parse().unwrap();
    let server = Http::new().bind(&addr, router).unwrap();
    server.run().unwrap();
}

こういう思い付きで雑に作ったものを放置する癖を何とかしないといけない…

hyper 0.10 以前であればルーティング用のクレートはいくつか実装されているらしい

React + Iron を使った Web アプリケーションのテンプレートを作った

事の発端となったツイート

まぁ正直ただの妄言なのだが(そもそも Ruby on Rails を置き換えるほど影響力のあるフレームワークを作るスキルがない) ,それはそれとしてさっとWebアプリケーションを作るテンプレート的なものが欲しくなったので勢いで作ってみた.

github.com

Rust で使える Web フレームワークは色々あるが,開発体制が比較的しっかりしていそうなこと,stable でも使えることなどを考慮して Iron にした. Rocket 良さそうなんだけど nightly 前提と言われると手が出しにくいという問題がある. あと,Iron は各種ミドルウェア(セッション管理,CSRF対策など)がそれなりに充実しているのが良さそうだった.

メモ

  • フロントエンド
    • ES2015 + Babel + Webpack
    • React のみ(Reduxとかはなし)
  • サーバサイド

おわりに

正直 Web アプリケーション開発の知識も乏しい状態で作ったのでこれで必要なものがそろっているのか不安だが,その辺りは今後少しづつ追加していこうと思います…

近況

近況報告です.

Rust でブログっぽいWebアプリを作った

題意の通り,Rust でブログっぽいWebアプリケーションを作っていました. 諸々作りこみが雑なのでもう少し改善する必要があるのですが,初心を忘れないうちに備忘録がてら作業記録を残しておきます.

vcs.ubnt-intrepid.nagoya

使用した crate たち

  • iron
    Rust 用のWebフレームワークのうち最もポピュラーだと思われる. 他にも nickel, rocket などがあるが rocket は nightly 必須なのが個人的に好きくなく,nickel は何か放置されてる感が 強く不安だったので,最終的に妥協して iron になった.

    使い勝手は Python でいう Flask に近く,必要最小限のものがそろってるなぁという感じ.とはいっても Middleware がそれなりに出揃っているので,最小限のサービスを構築するのは十分可能だと思った(かかる労力のことはここでは考えていない).

    あと,iron 公式が提供している Middleware 用の crate の名前に一般的なもの(router, params など)が多く個人的には「まじかよ」という思いです…

  • diesel - Diesel
    OR マッパ/クエリビルダ用の crate. もともとこいつの使い勝手を確認しておきたいという意図で今回のプロジェクトを作成した. 基本的には公式ページの Getting started を読めば基本的な使い方は分かると思うが,まだ情報が少ないので細かいところで躓いたときに苦労しそう. マイグレーション用の SQL を自前で書く必要があったり若干面倒なところがある気もするが,その辺は慣れればいいっぽい.

あとは毎度のごとく serdeclap などにお世話になりつつ作っております. もう少し具体的に各 crate の使い方を説明しても良いのだけど,面倒になったのであとはリポジトリの方をご参照ください…

Rust/WebAssembly 勉強会に行ってきた

misoca.doorkeeper.jp

だいぶ日にちが経ってしまいましたが,名古屋でRust関係のイベントがあるということで行ってきました. 内容自体は基礎的なもの(所有権・借用まわり)でしたが,複数人で The Rust Programming Language の内容にツッコミを入れながら深めに読み進めたからか,今まで適当に感覚的に理解していたものをある程度理詰めで理解できたような気がします. フロントエンド界隈の普段 JavaScript を書いている人たちが結構混乱していて,普段使用している言語の違いでここまで理解度に差が出るのだなぁと思ったりしました(別に煽る意図はないですが…). 個人的には, C/C++ 関係の資料を併用すると良い気がします.

あと,勉強会後の飲み会で「OCaml」「SML#」などといった単語が自然に出てくるあたりさすが名古屋だなぁという感じで妙な居心地の良さを感じました(とはいってもそこまで関数型言語を使えるわけではないのですが…)

東海圏で Rust 界隈のつながりができるいい機会なので,今後も続くと良いな…

「終わっていない議論」について

良く知らんけど,「迷惑行為(クソリプ)に困っています」と言っているのに新たに迷惑行為を産んじゃダメだろう…という程度の思いです. 「技術力で勝負」という考え方は大事だと思うが,「技術力で勝負しているんだから何やっても許される」とはならんでしょう…

おわりに

本当は最近流行している某新興SNSと連携できるよう OStatus 周りを実装するところまで行きたいのだが,そこまで到達する前に力尽きてしまいそう…

特に最近は下のような感情が支配的になっているので何とかしたい

わん

mattn.kaoriya.net

上の記事を読んでいて,最後のコード例を眺めていたらできそうな気がしたので作ってみました.

github.com

やってることは wandbox-run と同じ(パクリ)ですが,serdecurl-rust を使っている分 Rust っぽい感じに出来ていると思います.

せっかく作ったので,今後は wandbox のコマンドラインインターフェースとして成熟させたいなぁなどと思っています.

Rustacean のためのリポジトリ管理ツールを作りました

何度かつぶやいたりしていましたが、READMEを書くのが面倒になったので より多くの人に使ってもらうため少し真面目に解説記事を作りました。

rhq の概要

GitHub - ubnt-intrepid/rhq: Manages your local repositories

言わずと知れた Golang 製のリポジトリ管理ツールである ghq を Rust に移植したものです。 もともと ghqrs というものを作って放置していたのですが、内部の実装に若干気に入らない点があり再度自前で実装したのが本ツールになります。 現状では ghq すべての機能を移植できているわけではないので代替というには若干役不足ですが、自分の欲しい機能は概ね実装できたので思い切って公開することにしました。

基本的に出来ることは ghq と同じような感じですが,あまり使っていない機能などは省略しています。 下のコマンド実行例を見れば雰囲気だけでも伝わると思います。

$ rhq clone ubnt-intrepid/rhq
# git clone https://github.com/ubnt-intrepid/rhq.git ~/.rhq/github.com/ubnt-intrepid/rhq と等価
$ rhq list | sk

インストール方法

現状はソースのみの配布なため、Rust および Cargo が必要になります(ビルド済みバイナリの配布は検討中です)。 すでに Rust の開発環境が整っている場合は cargo コマンドでインストールできます。

$ cargo install rhq
# 開発版を使用する場合
$ cargo install --git https://github.com/ubnt-intrepid/rhq.git

ディレクトリ構成

基本的には ghq (および Golang)と同様、ルートディレクトリ(デフォルトでは ~/.rhq)直下に ホスト名/ユーザ名/リポジトリ名 という形式でクローンしたリポジトリが格納されます. ghq では複数のルートディレクトリを指定することが出来るようになっていますが、クローン時のディレクトリの決定が面倒だったためrhq では原則として単一のルートディレクトリを用いるようにしました. ただしそれでは不便な場合もあるので,後述するようにリポジトリ探索用のディレクトリを追加するための設定項目を設けています.

  • ~/.rhq/
    • github.com/
      • user1/
        • project1/
        • project2/
      • user2/
        • project1/
    • gitlab.com/
      • user1/
        • project3/

コマンド一覧

rhq clone [<query>] [--arg=<arg>] [-n | --dry-run]

<query> に指定した文字列を元にリモートリポジトリのURLを推測し, ルートディレクトリ(デフォルトでは ~/.rhq)下の指定場所にクローンします(要は ghqghq get です)。 例えばこのプロジェクトのリポジトリを取得したい場合は次のようにします.

$ rhq clone ubnt-intrepid/rhq
# git clone https://github.com/ubnt-intrepid/rhq.git ~/.rhq/github.com/ubnt-intrepid/rhq が実行される

--arg オプションで git clone に渡すオプションを指定できます。 ghq get では --shallow オプションなどを用いてクローン時のオプションを設定しますが,より柔軟な制御をおこなえるように rhq では Git のオプションをそのまま渡す方針を取りました。

<query> を省略した場合は,標準入力から1行ずつ読みこみリポジトリのクローンを行います。

rhq list

ルートディレクトリ直下にあるリポジトリのパスを取得し、標準出力に表示します。 後述する探索用ディレクトリに vim-plug や zplug のディレクトリを指定しておくことで,(別途スクリプトを用意することなく)一括してリポジトリを管理することが可能です。

rhq foreach [-n | --dry-run] <command> [<args>...]

管理下のリポジトリディレクトリ下で指定したコマンドを実行します。 例えば、管理下のリポジトリ全てに対しリモート側の変更を同期する場合は次のように実行します。

$ rhq foreach -- git fetch --all

現状はシングルスレッドで実行するためすべてのコマンドの実行が完了するまでに時間を要します。そのうち並列しようと思ってはいますが現状では未定です…

rhq completion <shell> [<out-file>]

各種シェル用の補完スクリプトを生成します。 clap の補完スクリプト生成機能をそのまま使っているだけですが. <out-file> にはスクリプトの出力先のパスを指定し,省略した場合は標準出力にスクリプトを吐き出します.

設定ファイル

TOML 形式での設定ファイルの読み込みをサポートしています. 設定ファイルは ~/.config/rhq/config~/.rhqconfig の順番で読み込み,各項目は上書き・追記されます. 現在は以下の項目を用意しています.

root = "~/.rhq"

supplements = [
  "~/.go/src",
  "~/.vim/plugged",
  "~/.zplug/repos",
  "~/.dotfiles"
]

応用例

ghq 側の応用例がほぼそのまま使うことができます。 そのため、そこまで説明する必要はない気もしますが、はじめての方向けに簡単に紹介しておきます。

GitHub から特定ユーザのリポジトリを一括クローンする

GitHub APIコマンドラインJSONを整形できる jq を使った例です。 現状は rhq clone が並列化をしていないので,愚直に xargs で並列化したほうが良い気がしますが…

$ ghuser=ubnt-intrepid
$ curl -s "https://api.github.com/users/$ghuser/repos?max_pages=100" \
  | jq -r '.[].name' \
  | awk '{printf("'$ghuser'/%s\n",$1)}'
  | rhq clone --arg="--depth 50"

この辺 を使って直接 GitHub API を呼び出しても面白そう

シェル・テキストエディタとの連携

peco や fzf などの fuzzy selector を用いることで端末上での作業が効率化できることが知られています。 例として、Zsh 上で Rust 製の fuzzy selector である skim と連携した例を紹介します. 下のコードを ~/.zshrc などに書いておくことで, Ctrl+Gリポジトリの一覧を skim で表示し, 選択したディレクトリに移動することが出来ます.

function __fuzzy-select-repositories() {
  local selected=$(rhq list | sk --prompt='REPOS> ' --query="$LBUFFER")
  if [[ -n $selected ]]; then
    BUFFER="cd \"${selected}\""
    zle accept-line
  fi
  zle clear-screen
}

zle -N __fuzzy-select-repositories
bindkey '^g' __fuzzy-select-repositories

Vim では Unite や ctrlp を使用することが考えられます. 例えば mattn/ctrlp-ghq を使用する場合は次のような感じになります.

Plug 'mattn/ctrlp-ghq'

let g:ctrlp_ghq_command = 'rhq'
let g:ctrlp_ghq_actions = [
  \   { "label": "Open", "action": "Explore", "path": 0 },
  \ ]

" <leader>g でセレクタを起動する
noremap <Leader>g :<C-u>CtrlPGhq<CR>

Visual Studio Code への組み込みですが,せっかくなので自前で拡張機能作ってみましたVisual Studio Code Marketplace で公開しているので ext install vscode-rhq などしてインストールして下さい.

おわりに

今後の計画として、せっかくなので少しリッチな機能を用意しようかなぁと思っています…

  • rhq clone および rhq foreach の並列化
  • custom hook
  • ホストの設定

特に並列化などは良さげな並列処理のライブラリを試してみる意味でも早急に実装しようと考えています…

プルリクエストやツッコミなど、随時募集しています。

最近作った・メンテしたRustプロダクト

供養および近況報告

rustplotlib

github.com

Matplotlib の Rust バインディングが欲しいなぁと思っていたので勢いで作ったもの. Rust という言語の特徴を考えると必要となるケースはあまりない気がするが,作ってしまった以上便利なものであるとだけ言っておく.

使い方は,まず下のように Builder でグラフを構築した後,

extern crate rustplotlib;
use rustplotlib::{Figure, Axes2D, Scatter, Line2D, FillBetween};

let x: Vec<f64> = (0..40).into_iter().map(|i| (i as f64) * 0.08 * PI).collect();
let y1: Vec<f64> = x.iter().map(|x| x.sin()).collect();
let y2: Vec<f64> = x.iter().map(|x| x.cos()).collect();

let ax1 = Axes2D::new()
  .add(Scatter::new(r"$y_1 = \sin(x)$")
    .data(&x, &y1)
    .marker("o"))
  .add(Line2D::new(r"$y_2 = \cos(x)$")
    .data(&x, &y2)
    .color("red")
    .marker("x")
    .linestyle("--")
    .linewidth(1.0))
  .xlabel("Time [sec]")
  .ylabel("Distance [mm]")
  .legend("lower right")
  .xlim(0.0, 8.0)
  .ylim(-2.0, 2.0);

let ax2 = Axes2D::new()
  .add(FillBetween::new()
    .data(&x, &y1, &y2)
    .interpolate(true))
  .xlim(0.0, 8.0)
  .ylim(-1.5, 1.5);

let fig = Figure::new()
  .subplots(2, 1, vec![Some(ax1), Some(ax2)])

Figure::apply(&mut backend) を呼び出すことで実際の描画に反映させる.

use rustplotlib::Backend;
use rustplotlib::backend::Matplotlib;

let mut mpl = Matplotlib::new().unwrap();
mpl.set_style("ggplot").unwrap();

fig.apply(&mut mpl).unwrap();

mpl.savefig("simple.png").unwrap();
mpl.dump_pickle("simple.fig.pickle").unwrap();
mpl.wait().unwrap();

調子乗った名前をしている割には大した機能を実装できていないので,余裕が出来たら適宜機能を拡張していく予定…

rwm

github.com

なんかウィンドウマネージャ作ってみたくなったので作ったもの. といってもウィンドウマネージャを実装するための知識が皆無だったので,以下のサイトで紹介されている Standard ML の実装をベースに Rust に移植した.

MLでウィンドウマネージャ作成

現状はリサイズ時の処理に不具合があり,クリック場所が悪いと panic してウィンドウマネージャが終了する.

rhq

github.com

以前作成した ghqrs という ghq クローンを再度書き直したもの. ghqrs は元々 Bash on Windowsgo build が動かなくてムキーッとなって作ったという経緯があり, メンテナンスするのが面倒になった 実装の所々に気に入らない箇所が目立ったので作り直した.

おわりに

  • 生きるの辛い
  • Windows10 Creators Update で Rust が BashOnWindows でも動くようになるらしいですね(今も動いてない…?)
  • ご意見,プルリクエストなどお待ちしています