Hatena::ブログ(Diary)

Eukleides project

データ構造とアルゴリズム学習帳
きっかけは某助教授の一言
「これ(岩波ソフトウエア科学3)、Cで全部書いたら熱いよね」
あつすぎ(゜∀゜)ッ!!

August 19(Sun), 2018 h2o+mruby+redisでproxy cache serverを作る2

cachebase64エンコードして保存。

前回はテキストデータのみだったが、mruby-base64を入れることで、バイナリデータのキャッシュbase64にしてnull charを回避できそうです。

https://github.com/mattn/mruby-base64

導入

[root@cac96f2dd83a h2o]# git submodule add https://github.com/mattn/mruby-base64.git deps/mruby-base64
[root@cac96f2dd83a h2o]# cmake -DWITH_MRUBY=on .
[root@cac96f2dd83a h2o]# make h2o
[root@cac96f2dd83a h2o]# make install

設定

etc/h2o.conf

hosts:
  "localhost:8080":
    listen:
      port: 8080
      host: 0.0.0.0
    paths:
      "/":
        mruby.handler: |
          origin = "www.hatena.ne.jp"
          path = env["PATH_INFO"]
          scheme = "https"
          redis = H2O::Redis.new(host: "127.0.0.1", port: "6379")
          redis.connect
          Proc.new do |env|
            extend_resheader = {}
            cached = redis.call("GET",path).join
            if cached.nil? then
              headers = {}
              env.each do |key, value|
                if /^HTTP_/.match(key)
                  headers[$'] = value
                end
              end
              headers["Host"] = origin
              cached = Base64.encode(http_request(
                scheme+"://"+origin+path
                method: env["REQUEST_METHOD"],
                headers: headers,
                body: env["rack.input"],
              ).join[2].join)
              extend_resheader["X-Cache"]="MISS"
              redis.call("SET", path, Base64.decode(cached)).join
            else
              extend_resheader["X-Cache"]="HIT"
            end
            [200,extend_resheader,[cached]]
          end

May 25(Fri), 2018 h2o+mruby+redisでproxy cache serverを作る

h2oとは

CPU使用率の小さいユーザに対して、迅速なレスポンスを返答する新世代HTTPサーバ

また、優先コンテンツ配信や、server push機能を含むHTTP2の機能をフルに活用し、webサイト訪問者に傑出した経験を提供する新世代HTTPサーバのようです(意訳)

https://h2o.examp1e.net/

https://github.com/h2o/h2o

mrubyとは

軽量実装されたruby

h2oでは、組み込み言語として採用されている。nginxでいうluaな立ち位置との認識。nginxでもmrubyは使えるけど。

luaはよくわからないけど、rubyならわかりそう←

https://github.com/mruby/mruby


redisとは

in-memoryタイプのno-sql。メモリにテーブルを持つためデータのR/Wは高速、データ永続化は定期的にdiskに書くことで実施しているらしい...。

https://redis.io/

h2oで単純にmrubyのredisライブラリを使うと、IOをブロックしてしまい、h2oの高速なイベントループが使えなくなるらしい。

故に下に貼ったプレゼンリンク曰く、ノンブロッキングなredisラッパを実装中とのこと。

h2o with mrubyの導入方法(centos7 on docker)

yum -y install ruby bison cmake gcc gcc-c++ git openssl openssl-devel
git clone https://github.com/h2o/h2o
cd h2o
cmake -DWITH_MRUBY=on .
make h2o
sudo make install
[  0%] Building C object CMakeFiles/h2o.dir/deps/picotls/deps/cifra/src/sha512.c.o
/root/h2o/deps/picotls/deps/cifra/src/sha512.c: In function 'sha512_update_block':
/root/h2o/deps/picotls/deps/cifra/src/sha512.c:115:3: error: 'for' loop initial declarations are only allowed in C99 mode
   for (size_t t = 0; t < 80; t++)
   ^
/root/h2o/deps/picotls/deps/cifra/src/sha512.c:115:3: note: use option -std=c99 or -std=gnu99 to compile your code
make[3]: *** [CMakeFiles/h2o.dir/deps/picotls/deps/cifra/src/sha512.c.o] Error 1
make[2]: *** [CMakeFiles/h2o.dir/all] Error 2
make[1]: *** [CMakeFiles/h2o.dir/rule] Error 2
make: *** [h2o] Error 2

makeで上のように怒られたので下のようにpatch

[root@66a789150ee9 h2o]# git diff
diff --git a/deps/picotls/deps/cifra/src/sha512.c b/deps/picotls/deps/cifra/src/sha512.c
index 2d1c896..44e3f23 100644
--- a/deps/picotls/deps/cifra/src/sha512.c
+++ b/deps/picotls/deps/cifra/src/sha512.c
@@ -111,8 +111,8 @@ static void sha512_update_block(void *vctx, const uint8_t *inp)
            g = ctx->H[6],
            h = ctx->H[7],
            Wt;
-
-  for (size_t t = 0; t < 80; t++)
+  size_t t;
+  for (t = 0; t < 80; t++)
   {
     if (t < 16)
     {  

動作確認

[root@66a789150ee9 h2o]# h2o -v
h2o version 2.3.0-DEV@6c7c850
OpenSSL: OpenSSL 1.0.2k-fips  26 Jan 2017
mruby: YES

ok!

redisの導入方法(centos7 on docker):

yum install epel-release
yum install redis
/usr/local/bin/redis-server
#systemctlに登録してサービス化する,何も指定せずに起動すると、6379でlistenする 

h2o+mruby+redisでproxy cacheサーバを作る

https://www.slideshare.net/ichitonagata/h2o-x-mruby-72949986

h2oのgithubを見る限り、どうも上のスライドを作った作者がpushしたと思われるmruby-redisというブランチがmasterにマージされており、masterのソースを見る限り、mrubyでredisが使えるようになっている。ということはredisを使ってコンテンツキャッシュできるんじゃなかろうか?と思い、confを書いてみた。

スライドの例のごとくは書けないものの、https://github.com/h2o/h2o/blob/master/share/h2o/mruby/redis.rbのソースを読みながら書いてみた。

hosts:
  "localhost:8080":
    listen:
      port: 8080
      host: 0.0.0.0
    paths:
      "/":
        mruby.handler: |
          redis = H2O::Redis.new(host: "127.0.0.1", port: "6379")
          redis.connect
          Proc.new do |env|
            extend_resheader = {}
            cached = redis.call("GET","key").join
            if cached.nil? then
              headers = {}
              env.each do |key, value|
                if /^HTTP_/.match(key)
                  headers[$'] = value
                end
              end
              cached = http_request(
                "https://www.hatena.ne.jp",
                method: env["REQUEST_METHOD"],
                headers: headers,
                body: env["rack.input"],
              ).join[2].join
              extend_resheader["X-Cache"]="MISS"
              redis.call("SET", "key", cached).join
            else
              extend_resheader["X-Cache"]="HIT"
            end
            [200,extend_resheader,[cached]]
          end

1回目のhttp://localhost:8080/へのリクエストでは、https://www.hatena.ne.jpにhttp_requestでproxyする。

そのレスポンスbodyを"key":(httpレスポンスbody)でredisに保存する。

2回目以降のhttp://localhost:8080/へのリクエストでは、redisからkeyで1回目に保存した結果をgetで取り出し

レスポンスを返す。

extend_resheaderにはproxy先からコンテンツを返したことを示すヘッダ X-Cache:MISS、redisからキャッシュを返した

X-Cache:HITを設定してクライアントへ返す。

July 26(Wed), 2017 Rustを勉強中4

実践的なアプリケーションを書いてみよう! Rustの構造化プログラミング【第二言語としてのRust】

写経物

    • cargo

  いわゆるビルドツール

  rustcがgccなら、cargoはmake

  パッケージ管理も行うのでAnt,Maven,Gradle,cocoa podといった類?

$ cargo new PROJECTNAME --bin

$ cd PROJECTNAME

    • rsgrep

  Unixコマンドのgrepを書いてみようとのこと。以下にざっと実装方針をまとめる

解説

      • ライブラリ機能を盛り込むには以下のように書けば良い

use クレート名::型、トレイト;

use クレート名::

e.g. use regex::Regex;

extern crate クレート名;

e.g. extern crate regex;

[dependencies]

regex = "0.2.1"

let pattern = match env::args().nth(1)

let filename = match env::args().nth(2)

      • 基本はmatch式とSome,None,Ok,Errで判定し、処理をするブロックが立て続け

let reg = match Regex::new(&pattern)

let file =match File::open(&filename)

let line = match line

      • BufReader::new(file)でファイル入力

参照型ではなく実体を入れる

      • for line in input.lines()でファイル内容を1行ごとに読み込む
      • reg.is_match(&line)でパターンマッチング

と言ったような流れ。だいぶリーダブルなコードである。

July 21(Fri), 2017 Rustを勉強中3

プログラムをよりオブジェクト指向チックに扱うために以下の構造を示す機能を勉強した。

実践的なアプリケーションを書いてみよう! Rustの構造化プログラミング【第二言語としてのRust】

写経物

関数において、型による再定義をしなくて良くなる。

golangにはない機能で、関数抽象化機能としてはすごい使いやすい。


    • 構造体

構造体単体で利用するというよりも、トレイトと一緒に利用すると強力なポリモーフィズムを実現できる。

    • 列挙型

match式と一緒に利用すると、直感的な書き方で処理を記述できる。

    • トレイト

ポリモーフィズムを実現する手段の1つ

Rust自体は継承といった機能は、トレイトと呼ばれる多重継承を許可してコンストラクタを持たない抽象クラス

のようなものを使う

javascalaに見られる抽象クラスとトレイトの違いは以下の述べられている。

https://gist.github.com/gakuzzzz/10081860:title = trait と abstract class の使い分け]

    • トレイト境界

あるトレイトを実装した型を、ジェネリクスで受け取る

duck_goはDuckLikeを実装した、Duckは受け取れるが、例えばコメントアウトしているfloatなどはDuckLikeを実装していないためコンパイルエラーになる。

July 20(Thu), 2017 Rustを勉強中2

前回の続き

    • 所有権と参照とライフタイム
      • 所有権

Rustでは値を使うとなくなるが、実際はソースの所有権が、別の所有者に移る。

右式の変数が以降で使えなくなる。

左式の変数がソース"this is a resource"の所有権をもつ

fn main() {

let s = "this is a resource".to_string();

// 以下の行で、`s`が束縛されている文字列の所有権が`t`に移る。以後`s`は使えない。

let t = s;

// 以下の行で、文字列の所有権が`t`から`print_string`に移る。以後`t`は使えない。

print_string(t);

// もう一度`t`を使おうとしてもエラー。

// print_string(t); // error[E0382]: use of moved value: `t`

// 同じくsを使おうとしてもエラー。

// print_string(s); // error[E0382]: use of moved value: `s`

}

      • 借用

一度使ってしまうと値がなくなっては不便なので、所有権を自分に残したまま、値を他人に貸す。

借用には参照型を使う

参照には、ミュータブルなものとイミュータブルなものがある

イミュータブルな参照

fn ref_string(s: &String) {

println!("{}", s);

}

fn main() {

let s = "this is a resource".to_string();

// 参照1つめ。

let t = &s;

// 参照2つめ。同時に2つ存在できる。

ref_string(&s);

}

ミュータブルな参照

fn refmut_string(s: &mut String) {

// ここでsに対して変更を加えるなどの操作も可能。

println!("{}", s);

}

fn main() {

let mut s = "this is a resource".to_string();

// ミュータブルな参照1つめ。

let t = &mut s;

// ミュータブルな参照2つめはエラー。

// refmut_string(&s); // error[E0499]: cannot borrow `s` as mutable more than once at a time

}

ミュータブルな参照は、2回以上使えない。

ミュータブルな参照とイミュータブルな参照は共存もできない

      • ライフタイム

参照先が存在しないと貸し出せない

ライフタイムとは、{ }で囲んだブロックや、関数のスコープで安全でない参照を作れなくする仕組み

fn main() {

// 本来は`s`のライフタイムはこの関数の最後まで。

let s = "owned data".to_string();

// `{ }`で囲んだブロックはライフタイムを区切る。

{

// `s`はここでムーブしてしまうのでここでライフタイムが終わる。

// `t`のライフタイムはこのブロックの終わりまで。

let t = s;

}

// ここでは`t`にも`s`にもアクセスできない。

// ライフタイムと参照の関係

{

let s = "owned data".to_string();

// ここで`s`への参照`ref_s`を作る。`ref_s`はこのブロックの最後で死ぬが、`s`のほうが長生きしないといけない。

let ref_s = &s;

// `s`を`t`へムーブして`ref_s`より先にライフタイムを終わらせようとするとエラーになる。

// let t = s; // cannot move out of `s` because it is borrowed

}

続く