@m_seki の

I like ruby tooから引っ越し

WEBrickでchunked

WEBrickのレスポンスでchunkedする話

いろいろ探したけどchunkedしてる例が見つからなかったので書いた。

res.chunked=()

WEBrickのHTTPServerの話です。レスポンスにchunkedを指定してイベントを延々と返す方法を調べました。
転送をchunkedで行うにはレスポンスに次の操作をすればよい。

  res.chunked = true

問題はbodyの方。bodyにStringなど完成したバッファを渡してしまうと、結局一気に送ることになってしまって意味がない。

  res.body = "返信だよー"

ダラダラ送るにはIOぽいなにかを与える必要がある。bodyにreadpartialメソッドを持つオブジェクトを与えるとIOぽいと判断してくれる。(IOのサブクラスとか限定しないの、WEBrickさいこー!)

  res.body = MyStream.new(queue) # ← readpatial と close メソッド持つオブジェクト

雑に言うとこんなの。(ほんとのデータはQueueからくると想定してる場合の擬似コード

  class MyStream
    def initialize(queue)
      @queue = queue
    end

    def next_chunk
      @queue.pop
    rescue
      raise EOFError
    end

    def close
      # nop
    end

    def readpartial(size, buf='')
      buf.clear
      buf << next_chunk
      buf
    end
  end

ちゃんと書くならreadpartialのバッファサイズの指定があるので、それも考慮しなくてはならない。
Tofuに追加したChunkedStreamは次のように実装した。

  class ChunkedStream
    def initialize(stream)
      @data = nil
      @cursor = 0
      @stream = stream
    end

    def next_chunk
      @stream.pop
    rescue
      raise EOFError
    end

    def close
      @stream.close
    end

    def readpartial(size, buf='')
      buf.clear
      unless @data
        @cursor = 0
        @data = next_chunk
        @data.force_encoding("ascii-8bit")
      end
      if @data.bytesize <= size
        buf << @data
        @data = nil
      else
        slice = @data.byteslice(@cursor, size)
        @cursor += slice.bytesize
        buf << slice
        if @data.bytesize <= @cursor
          @data = nil
        end
      end
      buf
    end
  end

WEBrickのレスポンス ⇄ ChunkedStream ⇄ 本来アプリで表現したかったストリーム(@stream)、のように中間に入って使う。
WEBrickはレスポンスのチャンクを作るために、ChunkedStreamをreadpartialする。送っていないバッファがあればすぐに返却する。送るものがなければ、@streamからpopを試みる。
@streamがQueueのような待ち合わせ可能な同期メカニズムであれば、データがなければブロックするであろう。ブロックするということは、WEBrickのそのクライアント担当スレッドが止まるわけだけど、その他のスレッドには関係ないので問題ない。他のクライアントに対してはちゃんと仕事してくれる。WEBrickちゃんとしてる。

QueueのデータをServer Sent Eventsぽく返すなら次のようにする。(自分ならDripを使う)

class EventStream
  def initialize(queue)
    @queue = queue
  end

  def pop
    value = @queue.pop
    "data: #{value.to_json}\n\n"
  end

  def close;  end
end
  res.body = Tofu::ChunkedStream.new(EventStream.new(queue))

追記: もう少し親切な例

require 'webrick'
require 'tofu'
require 'drb'
require 'json'

class EventStream
  def initialize(queue)
    @queue = queue
  end

  def pop
    value = @queue.pop
    p value
    "data: #{value.to_json}\n\n"
  end

  def close; end
end

queue = Queue.new
DRb.start_service('druby://localhost:54321', queue)

s = WEBrick::HTTPServer.new(:Port => 8086)
s.mount_proc('/stream') {|req, res|
  res.content_type = 'text/event-stream'
  res.chunked = true
  res.body = Tofu::ChunkedStream.new(EventStream.new(queue))
}
s.start

別の端末からirbでデータ送る

% irb -r drb
irb(main):001:0> ro = DRbObject.new_with_uri('druby://localhost:54321')
irb(main):002:0> ro.push('hello')
irb(main):003:0> ro.push('world')

http://localhost:8086 表示してブラウザのコンソールで実験

var sse = new EventSource.new("/stream")
sse.onmessage = function(e) { console.log(e) }

そうそう

さて土曜日はとちぎRuby会議08ですね。
体調が悪くて参加できない方がいるようです。今から参加したくなった人はTL検索してチケットを譲ってもらおう!

dRuby 20th anniversary hands-on workshop, RubyKaigi2019編終了

RubyKaigi2019編終了

dRuby 20th anniversary hands-on workshop, more. - @m_seki の つづき。

 

dRuby20周年、ハンズオン全国ツアー松江に続き、2番目の開催はRubyKaigi2019の会場でした。RubyKaigi2019編ではこんな風にスペシャルでした。

  • おやつ
  • 豪華なスタッフ( @ktou @youchan @shugomaeda @drbrain @tenderlove )
  • Rubyチーム参加
  • drbrain のマルチコアを使い切るデモ

 

 

テキストがんばった

がんばって英語風のテキスト作ったけどほとんど日本語の大丈夫な人でした!当日は3つの課題をやる予定でした(実際そうでした)が、時間が余ってしまう人のために2つ余計に書いておきました。後半の2つはDockerを使った練習です。

なおモンgoとかのフレコ書いといたけど友だち増えなかった。

 

予行演習した

これは偶発的に起きたのだけど、前日のコード懇親会でハンズオンの内容をやりました。ハマりポイントなどを知ることができてお得でした。(@chioniyan ありがとう)

コード懇親会、いい感じでした。ほんとにコードで懇親してた。すばらしい。

 

 当日の質問・トラブル

macOSで、プロセスによりIPv4, IPv6のいずれかのポートを開くケースがあった。最初はわからなかったけど、優秀なスタッフが解明してくれた。すげー。

 

「DRb.start_serviceはなにするんですか?」と質問いただいたのですが、説明することがありすぎてうまく答えられませんでした。もうちょっと演習進んでからじゃないと伝わらないと思ったんだよ!ごめん!

それから、最後の方で「Nginxみたいなのを起動しなくてもいいんですか?」って聞かれたので、ああそうか、疑問を持った人の背景に合わせて説明できたらよかったなあと思いました。

 

あと何に使えるんですか?みたいなこと聞かれるけどマイクロサービスでいいようなところにはそのまま使えます、と答えたら雑だろうか(マイクロサービスは何に使えるんですか?と同じ問いになる)。Twitterで昔使ってた、っていうのは説明にとても便利。

RubyConf行った時に教えてもらったのは銀行のなにか、ビデオ配信のなにか、ほかにもいろいろあったけど忘れちゃった。(ユーザー数1なのはTofuだけど、最近2になった)

 

全国ツアー

全国ツアーとか言ってるけど、次の予定はありません。誰か誘って!

福岡はとても美味しかったけど、食べてないものの方が多いのでもう一度誘ってくれてもいいのよ!

 

RubyKaigiではなく、NIXONのリュックの話

SCRIPPS BACKPACKがすごくいい

ほぼ毎年誕生月にリュックを新しくしています。

NIXONのリュックが何度か続いてたのだけど、昨年は別ブランドにしてみたら、その華奢な造り(意外と早く痛んできた)にがっかりして、今年はまたNIXONにもどしました。

んで、LANDLOCK系からSCRIPPSにしました。カタログだとさりげなくてよくわからなかったけど、PCの収納とタブレットの収納が独立してそれぞれクッション入ってるのね。とても使いやすい。(NIXONはPCがサイドから出し入れできるのでメインの荷室とは違うアクセスができる。でもLANDLOCK系はPC用のしきりが薄い。)

あとね、カバンの底側にスニーカーを収納するための独立した部屋がある。これがまたよくて、ミラーレス一眼とその付属品が入れられる。Instagramのサボりたいが加速する感じ。

自分のはIIではなくて番号なしの旧型。せっかくなので実物を自慢してもいいよ?

 

 

 

dRuby 20th anniversary hands-on workshop, more.

ハンズオンの資料

RubyKaigi2019で使用する、ハンズオンの資料のドラフトを公開しました。事前にダウンロード、あるいは印刷してきてね!

スライドスポンサーの皆さんは文面教えてくださーい。

 

http://www.druby.org/fukuoka2019.pdf

 

そしてスライドスポンサーもまだまだ募集しています。

 

当日には間に合わないけどグッズもあるよ!

suzuri.jp

 

druby.booth.pm

 

dRuby 20th anniversary hands-on workshop

RubyKaigi 2019

I will do hands-on workshop. dRuby fans, Please help! (slide translation, interpreting, coaching..) 

dRubyのハンズオンワークショップをやります。dRubyファンのみなさん、当日空いてたら手伝って!英語圏の人も!

slide sponsors

毎年恒例のスライドスポンサーをGumroadで売ってます!つきあいで買おう!

https://gumroad.com/l/drb2019

wishlist

dRuby20周年だし誕生日だしwishlistです。遠慮なく!

https://www.amazon.co.jp/registry/wishlist/1R43BBPSPUEEE/ref=cm_sw_r_tw

Dropboxのアプリの練習をしたよ

DropboxAPIを使ったアプリ

そういうのがあるんですね。知りませんでした。

https://www.dropbox.com/developers/apps

上のリンクからcreate appすると、APIの種類とアクセスのタイプを聞かれました。よくわからないので次のようにしました。

あとはアプリの名前を決めればOK。

そのあとの画面でGenerate Tokenした文字列をメモしておく。OAuth2でもできるのか。まだ試してない。

実験

メモしたトークンを.dropboxに書いて実験。
これはラズパイで撮影した写真を定期的にアップロードする例。youchan にもらったカメラモジュールを組み込んだラズパイで動く。
5分おきにループして、"run"というファイルがある間はアップロードする。ないときはなにもしない。

dropbox APIを利用するgemはいくつかあるようだけど、このスクリプトでは"dropbox_api"を使いました。dropbox アンダースコア api だよ!他にハイフンもあるよ!

require 'dropbox_api'

key = File.read('.dropbox')
db = DropboxApi::Client.new(key)

while true 
  running = db.download('/zero/run') {|x| break x } rescue nil
  if running
    image = `raspistill -rot 90 -o -`
    db.upload(
      '/zero/test.jpg',
      image,
      :mode => :overwrite
    )
  end
  sleep 300
end

あとでFull Accessのアプリも試してみよう

追記

youchanも利用している、私あてwishlistはこちらです。

http://www.amazon.co.jp/registry/wishlist/1R43BBPSPUEEE/ref=cm_sw_r_tw

それから、Gumroadも。

恒例!スライドに名前を入れる券 2019-2020

今年はdRuby20周年だよ

体験dRuby - はじめての分散Rubyやりました

Rubyアソシエーションの前田さんに誘っていただいて、dRubyのワークショップをやりました。会場は松江駅前のオープンソースラボ。7年ぶりの松江。前回松江に来たのは島根大学の講義のとき。今回のワークショップも当時の講義をアップデートした内容で、複数の端末を使ってプロセス間でメソッドを呼んだり、オブジェクトを渡したり、返したりする様子を体験しました。

 

rubyassociation.doorkeeper.jp

 

Rubyを始めて間もない人から、仕事で使っている人まで、みんなそれなりに楽しめたみたい。ワークショップとしては成功だったと思うことにしました!

 

今年はdRuby20周年。初心者向けワークショップで全国ツアーしたい。

 

以下松江滞在メモ。

  • 当日の天候は雪で、今年も出雲大社はあきらめた。
  • 橋を徒歩で渡るとき、風に注意。
  • ミスドがなくなってたけど、セブンイレブンがあった。
  • ローソンにはポプラが追加されてる。
  • クルマだと15時間(休憩込み)
  • ドーミーイン快適だった。磁気活性水はなくていいと思う。
  • たべすぎた。

なおウイッシュリストはこちらです。