Closed44

Rackについて改めて

kappazkappaz

Rackについて掴みきれていないので調べてみる。

Rackは、RubyのWebアプリケーションに対して httpリクエストの標準的なインタフェースを提供するミドルウェア。Rack上に実装したWebアプリケーションは、Rackが定めた形式に従ってhttpリクエストを扱えば、その下のWebサーバーやアプリケーションサーバーが何であるかを気にする必要がなくなる。
環境の差異を吸収するためのミドルウェア。

ってイメージ。まぁ日本語の妥当性はともかく、内容としては大きくはずしていないはず。

気になっているのはじゃあその標準的なインタフェースってなんじゃい?という話。ここを抑えれば、ウェブアプリケーション全体としてhttpリクエストをどういう世界観で捉えるのが良いのか?みたいな話に知見が得られるような気がする。

kappazkappaz

Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby.
Rackは、RubyでWebアプリケーションを開発するための最小限の、モジュール化された、適応可能なインターフェイスを提供します。

まぁまずRackが提供するのは「インタフェース」ということだな。
最小限はいいとして、モジュール化されたというのは...まぁつけたり外したり必要なものだけ入れたりできる構成ということか?で適応可能というのは...Ruby製の色々なWebフレームワークやWebアプリケーション対象に使えるということか。まぁ色々な環境に適応できるよ、的な意味合いかな。

kappazkappaz

これもしや自分のブログに書いた方が良いやーつ?

kappazkappaz

By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the bridge between web servers, web frameworks, and web application into a single method call.
HTTPリクエストとレスポンスを可能な限りシンプルな方法でラップすることで、ウェブサーバー、ウェブフレームワーク、ウェブアプリケーション間の橋渡しを単一のメソッド呼び出しに統合し、抽出する。

「ウェブサーバー、ウェブフレームワーク、ウェブアプリケーション間の橋渡しを「単一のメソッド呼び出し」に集約させる」
ここが肝なのかな。callのことだよね。
で、それを可能にしているのが、「HTTPリクエストとレスポンスを可能な限りシンプルな方法でラップすること」って話になります?

僕が知りたいのはこのラップの部分かな...

kappazkappaz

読んだ上で手を動かした方が良いなぁ...

kappazkappaz

The exact details of this are described in the Rack Specification, which all Rack applications should conform to.
これの正確な詳細はラック仕様に記載されており、すべてのラック アプリケーションはこれに準拠する必要があります。

Rack の仕様書に準拠したRack アプリケーションであれば、Rackの抽象化したインタフェースを用いて、Webサーバーとのやりとりできる、的な感じ

まぁ言いたいことはわかるんやけどね。今の自分はなんか浅い理解。

Rack Specificationは後で読もう。

kappazkappaz

If you need features from Rack::Session or bin/rackup please add those gems separately.
$ gem install rack-session rackup

セッション管理をしたい場合は Rack::Session、rackup使いたい場合はrackup入れてねと。

...そういえばSession周りの話はRackミドルウェアの話だったな。これは...忘れてたわ。うーん記事に書いた方が良いかもとも思いつつ...

rackupって何だっけ???
-> rackアプリケーションの起動サポートツール?的なやつか。config.ruとかの話。

kappazkappaz

The environmentまで読了。
environmentには基本リクエストの内容がHash形式に整理して格納されている。任意の値を入れることも可能って感じ。

ChatGPT様補足
envハッシュには、HTTPメソッドやリクエストパス、ヘッダー情報など、リクエストに関するすべての基本情報が整理されて格納されています。また、Rackアプリケーションやミドルウェアは、必要に応じて独自のデータをこのハッシュに追加することも可能です。例えば、リクエストの処理中に生成された特定の値や、ミドルウェア間で共有するデータを格納するために使用できます。

kappazkappaz
run do |env|
  [200, {}, ["Hello World from Rack"]]
end

よくあるconfig.ruのサンプルだけど、RackアプリとRackの間にpumaなりrackupなりが入ってて、結局Rackが何してるのかいまいちピンときていない

rackとrackアプリケーションの間に1枚挟まる理由が今ひとつピンとこない

kappazkappaz
Rails::Server.new.tap do |server|
  require APP_PATH
  Dir.chdir(Rails.application.root)
  server.start
end

これを参考にすればrackup書かずに行ける系?

いやそんなことないな。結局server.startでは多分config.ru参照してるだろ。railsアプリの中にconfig.ruあるし

kappazkappaz
require 'rackup'

app = Proc.new do |env|
  [
    200,
    { 'Content-Type' => 'text/plain' },
    ["Hello, world2!\n"]
  ]
end

# Rack::Handler を使ってアプリケーションを実行
Rackup::Handler::WEBrick.run app, Port: 9292

とりあえずこれで動く

kappazkappaz

https://zenn.dev/link/comments/38f91f4c89467f
のバックトレース

(rdbg) caller
[
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:415:in `eval'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:415:in `block in frame_eval_core'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:388:in `block in tp_allow_reentry'",
 ".......ry'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:387:in `tp_allow_reentry'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:411:in `frame_eval_core'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:444:in `frame_eval'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:1052:in `wait_next_action_'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:875:in `block in wait_next_action'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:866:in `block in fiber_blocking'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:866:in `blocking'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:866:in `fiber_blocking'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:875:in `wait_next_action'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:320:in `suspend'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:251:in `on_breakpoint'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/breakpoint.rb:69:in `suspend'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/breakpoint.rb:170:in `block in setup'",
 "......./lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/session.rb:2641:in `debugger'",
 "webrick_app.rb:5:in `block in <main>'",
 "......./lib/ruby/gems/3.3.0/gems/rackup-2.1.0/lib/rackup/handler/webrick.rb:111:in `service'",
 "......./lib/ruby/gems/3.3.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:140:in `service'",
 "......./lib/ruby/gems/3.3.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:96:in `run'",
 "......./lib/ruby/gems/3.3.0/gems/webrick-1.8.1/lib/webrick/server.rb:310:in `block in start_thread'"
 ]
kappazkappaz

普通のconfig.ruのケースだと、rackを通る様子。

config.ru
run do |env|
  [200, {}, ["Hello World from Rack"]]
end
[".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:415:in `eval'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:415:in `block in frame_eval_core'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:388:in `block in tp_allow_reentry'",
 ".........y'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:387:in `tp_allow_reentry'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:411:in `frame_eval_core'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:444:in `frame_eval'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:1052:in `wait_next_action_'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:875:in `block in wait_next_action'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:866:in `block in fiber_blocking'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:866:in `blocking'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:866:in `fiber_blocking'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:875:in `wait_next_action'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:320:in `suspend'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/thread_client.rb:251:in `on_breakpoint'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/breakpoint.rb:69:in `suspend'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/breakpoint.rb:170:in `block in setup'",
 ".........lib/ruby/gems/3.3.0/gems/debug-1.9.2/lib/debug/session.rb:2641:in `debugger'",
 ".........:4:in `block (3 levels) in <top (required)>'",
 ".........lib/ruby/gems/3.3.0/gems/rack-3.1.7/lib/rack/tempfile_reaper.rb:20:in `call'",
 ".........lib/ruby/gems/3.3.0/gems/rack-3.1.7/lib/rack/lint.rb:66:in `response'",
 ".........lib/ruby/gems/3.3.0/gems/rack-3.1.7/lib/rack/lint.rb:41:in `call'",
 ".........lib/ruby/gems/3.3.0/gems/rack-3.1.7/lib/rack/show_exceptions.rb:31:in `call'",
 ".........lib/ruby/gems/3.3.0/gems/rack-3.1.7/lib/rack/common_logger.rb:43:in `call'",
 ".........lib/ruby/gems/3.3.0/gems/rack-3.1.7/lib/rack/content_length.rb:20:in `call'",
 ".........lib/ruby/gems/3.3.0/gems/rackup-2.1.0/lib/rackup/handler/webrick.rb:111:in `service'",
 ".........lib/ruby/gems/3.3.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:140:in `service'",
 ".........lib/ruby/gems/3.3.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:96:in `run'",
 ".........lib/ruby/gems/3.3.0/gems/webrick-1.8.1/lib/webrick/server.rb:310:in `block in start_thread'"]
kappazkappaz

https://speakerdeck.com/coe401_/shi-lu-lets-build-a-simple-rack-compatible-server
これ自分でもやってみたいな

https://www.writesoftwarewell.com/definitive-guide-to-rack/
プロトコルとしてのrackとgemとしての rackがあってうまく分けて理解できてない感があるな。

pumaとrack applicationを動かすだけなら多分rackもrackupもいらないってこと?
-> いやそんなことなかったわ。pumaが内部でrack参照してた。

kappazkappaz

TCPServerのソースコードが見たい

server = TCPServer.new('localhost', 9292)
server.method(:accept).source_location

ってやったらnilになった。

どうやらCのコードだかららしい。

じゃあなんでCで定義されているTCPServerをみれるのか?というと、
https://github.com/ruby/ruby/blob/532af89e3b5b78dd3a6fe29c6cc64ad1b073afe2/ext/socket/tcpserver.c#L133
こんな感じでC言語からRubyの定義をしているらしい。へー。

いやこのネタ深堀しても良いな。

kappazkappaz
class Test::MyClass
  def test
    'test'
  end
end

puts Test::MyClass.new.test

これ動くと思ってた-

kappazkappaz

Webサーバーの中身読みたいっすね。
Thinが良いのかやっぱり。

Pumaを読んでいる方がおられますなぁ。
https://zenn.dev/stmn_inc/articles/27315965dd909c

2015年Webサーバアーキテクチャ序論
https://blog.yuuk.io/entry/2015-webserver-architecture

この記事も良さそうだ。
Rack周りを腹落ちさせるなら、ある程度Webサーバーそのものに対しても中身を理解している必要がありそうではある。

kappazkappaz

#!/usr/bin/env ruby
これ入れればコマンドになるんか?
知らなかった。

kappazkappaz

「なるほどUNIXプロセス ― Rubyで学ぶUnixの基礎」
これちょっと欲しいな。さらにその次か...???

「Working With TCP Sockets」
これも欲しいな。どうやら無料で読める上にRubyらしい。最高。

kappazkappaz

TCPコネクションのソケットペア(socket pair)は、コネクションの両方のエンドポイントを定義する、ローカルIPアドレス、ローカルTCPポート、リモートIPアドレス、およびリモートTCPポートの4つ組である。あるソケットペアは、インターネットの中の特定のコネクションを一意に識別する。

各エンドポイントを識別する2つの値、すなわちIPアドレスとポート番号は、多くの場合ソケット (socket) と呼ばれる。

UNIXネットワークプログラミング 第2版 Vol.1 p43

なるほど、ソケットというのはIPアドレスとポート番号のセットのことで、エンドポイントを表していると解釈できるのか。

kappazkappaz

ソケットペアの概念は、コネクションを持たないUDPにも拡張することができる。ソケット関数(bind、connect、getpeernameなど)の説明をする場合、どの関数がソケットペアのどの要素を操作するのかに注目する。例えば、bindはTCPとUDPのどちらのソケットに対しても、アプリケーションがローカルIPとローカルポートを指定することを可能にしている。
UNIXネットワークプログラミング 第2版 Vol.1 p43

なるほど、あの手の関数はソケットの操作であり、関数によってソケットのどの要素を操作するのか異なる、と理解すると良いのか

kappazkappaz

並行処理するためには、OSが提供するプロセスやスレッド、言語のランタイム上の軽量スレッド、イベント駆動モデルにおけるハンドラなどのなんらかの実行コンテキストにリクエスト処理を委譲する必要がある。 並行処理の手法の数だけWebサーバアーキテクチャのモデルがあり、一番大雑把な分類がマルチプロセス、マルチスレッド、イベント駆動、もしくはこれらのハイブリッドしたモデルである。

Webサーバーアーキテクチャの主な関心は、並列処理の手法という表現ができそうだ

kappazkappaz

ソケットライフサイクルのうち、例えばsocket、bind、listenまでをIO::Socket::INETモジュールがやってしまうので、あまりソケットAPIを使っているという実感はないかもしれない。

あ...なんかあったかもこんな話...
忘れとった...

kappazkappaz

先にも述べたように、シリアルモデルでは並行にリクエストを処理できない。 そこで、リクエストを受信するたびに、forkにより子プロセスを生成し、子プロセスにリクエスト処理を任せる(1 connection per process)。

forkもぼんやりとしか理解してないんだよな。fork = プロセス生成というイメージはあるけども。
なんでforkという名前なの?OSへのコマンドだよね?詳細としては何をしているの?的な。
それこそ上述の「Working With TCP Sockets」を読むべきだろうか。

kappazkappaz

同時接続クライアント数が子プロセスの数を超えると、3-wayハンドシェイク済みの接続はカーネル内のキューにたまっていくが、acceptする人がいないため、接続は処理されないままとなる。すなわち、一般に詰まると言われるような現象が起きやすい。

個人的には人気なグッズの予約販売開始の際にアクセスがすごく重くなることがあるけど、Webサーバーアーキテクチャの話を理解するとこの辺り実際に起きていることが想像できるようになるだろうか?

この辺OS動作からしてイメージ持てるようにしておきたいな。

kappazkappaz

PerlならStarlet、RubyならUnicornなどがこのモデルに該当する。

おお、よく聞くUnicornはこれなのか。Pumaはどうなんだい?
-> Multi Thread & Multi Process っぽい

kappazkappaz

MySQLに対してPostgresのほうがコードが綺麗と言われる所以は、前者がマルチスレッドモデルなのに対して、後者がマルチプロセスモデルであるということもあるかもしれない。

そうなのかー。というかMySQLやPostgresの話、つまりDB周りの話ももっと抑えたいよな。
もっとinputしたら?

そういう意味だと...
Rails -> ActionDispatch -> Webサーバー
Rails -> ActiveRecord -> DB
という感じで興味が移っているのかもしれない。ActiveRecord周りもいつか観なきゃな...

kappazkappaz

イベント駆動モデルのメリットは、preforkやスレッドプールと違って、同時に接続できるクライアント数に上限がないことだ。

ここに引っ掛かりを覚える。上限に大きな差があるだけで、「上限がない」は言い過ぎな印象を受けたのだが...

kappazkappaz

やっぱこれブログでやった方が良いんじゃ...
でも殴り書きメモに近いこれはブログ記事にはならんか。

このスクラップは2024/09/21にクローズされました