Rackについて改めて

Rackについて掴みきれていないので調べてみる。
Rackは、RubyのWebアプリケーションに対して httpリクエストの標準的なインタフェースを提供するミドルウェア。Rack上に実装したWebアプリケーションは、Rackが定めた形式に従ってhttpリクエストを扱えば、その下のWebサーバーやアプリケーションサーバーが何であるかを気にする必要がなくなる。
環境の差異を吸収するためのミドルウェア。
ってイメージ。まぁ日本語の妥当性はともかく、内容としては大きくはずしていないはず。
気になっているのはじゃあその標準的なインタフェースってなんじゃい?という話。ここを抑えれば、ウェブアプリケーション全体としてhttpリクエストをどういう世界観で捉えるのが良いのか?みたいな話に知見が得られるような気がする。

読みますか。

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

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

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リクエストとレスポンスを可能な限りシンプルな方法でラップすること」って話になります?
僕が知りたいのはこのラップの部分かな...

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

The exact details of this are described in the Rack Specification, which all Rack applications should conform to.
これの正確な詳細はラック仕様に記載されており、すべてのラック アプリケーションはこれに準拠する必要があります。
Rack の仕様書に準拠したRack アプリケーションであれば、Rackの抽象化したインタフェースを用いて、Webサーバーとのやりとりできる、的な感じ
まぁ言いたいことはわかるんやけどね。今の自分はなんか浅い理解。
Rack Specificationは後で読もう。

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とかの話。

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

run do |env|
[200, {}, ["Hello World from Rack"]]
end
よくあるconfig.ruのサンプルだけど、RackアプリとRackの間にpumaなりrackupなりが入ってて、結局Rackが何してるのかいまいちピンときていない
rackとrackアプリケーションの間に1枚挟まる理由が今ひとつピンとこない

これ読んでる中
うーん、何も分かってなかったな。
Rack::Serverとか前読んだ時には目にもはいってなかったわ

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あるし

なんかこの辺で行けそうでは?
たしか...appとconfig両方あったらエラーします、的なのがどこかにあったような、なかったような...

これだ!
どこからかここに辿り着くのだろうか...?

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
とりあえずこれで動く

require 'rackup'
Rackup::Server.start(
:app => lambda do |e|
[200, {'content-type' => 'text/html'}, ['hello world']]
end,
:server => 'webrick'
)
これでもいける
内部的には、
Rackup::Handler::WEBrick.run
をやってそうだな。
...rackupだけで完結するのでは? rack gemは何してんの?

のバックトレース
(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'"
]

普通のconfig.ruのケースだと、rackを通る様子。
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'"]

webrickの場合はrackupを経由するがpumaの場合は経由しないな。
の記載と合致しているんだが...
rackupコマンドと、rackup gem ってもはや別物なのか

ちょっと色々記事見てみるか

これ自分でもやってみたいな
プロトコルとしてのrackとgemとしての rackがあってうまく分けて理解できてない感があるな。
pumaとrack applicationを動かすだけなら多分rackもrackupもいらないってこと?
-> いやそんなことなかったわ。pumaが内部でrack参照してた。

これを自分でもやってみるか!

まずはこれから

TCPServerのソースコードが見たい
server = TCPServer.new('localhost', 9292)
server.method(:accept).source_location
ってやったらnilになった。
どうやらCのコードだかららしい。
じゃあなんでCで定義されているTCPServerをみれるのか?というと、
こんな感じでC言語からRubyの定義をしているらしい。へー。いやこのネタ深堀しても良いな。

class Test::MyClass
def test
'test'
end
end
puts Test::MyClass.new.test
これ動くと思ってた-

というところで中断

Webサーバーの中身読みたいっすね。
Thinが良いのかやっぱり。
Pumaを読んでいる方がおられますなぁ。
2015年Webサーバアーキテクチャ序論
この記事も良さそうだ。
Rack周りを腹落ちさせるなら、ある程度Webサーバーそのものに対しても中身を理解している必要がありそうではある。

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

とりあえずsimpleなweb serverはできた。

改めてこれ読む。
その次は
に取り組む所存

「なるほどUNIXプロセス ― Rubyで学ぶUnixの基礎」
これちょっと欲しいな。さらにその次か...???
「Working With TCP Sockets」
これも欲しいな。どうやら無料で読める上にRubyらしい。最高。

TCPコネクションのソケットペア(socket pair)は、コネクションの両方のエンドポイントを定義する、ローカルIPアドレス、ローカルTCPポート、リモートIPアドレス、およびリモートTCPポートの4つ組である。あるソケットペアは、インターネットの中の特定のコネクションを一意に識別する。
各エンドポイントを識別する2つの値、すなわちIPアドレスとポート番号は、多くの場合ソケット (socket) と呼ばれる。
UNIXネットワークプログラミング 第2版 Vol.1 p43
なるほど、ソケットというのはIPアドレスとポート番号のセットのことで、エンドポイントを表していると解釈できるのか。

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

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

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

先にも述べたように、シリアルモデルでは並行にリクエストを処理できない。 そこで、リクエストを受信するたびに、forkにより子プロセスを生成し、子プロセスにリクエスト処理を任せる(1 connection per process)。
forkもぼんやりとしか理解してないんだよな。fork = プロセス生成というイメージはあるけども。
なんでforkという名前なの?OSへのコマンドだよね?詳細としては何をしているの?的な。
それこそ上述の「Working With TCP Sockets」を読むべきだろうか。

同時接続クライアント数が子プロセスの数を超えると、3-wayハンドシェイク済みの接続はカーネル内のキューにたまっていくが、acceptする人がいないため、接続は処理されないままとなる。すなわち、一般に詰まると言われるような現象が起きやすい。
個人的には人気なグッズの予約販売開始の際にアクセスがすごく重くなることがあるけど、Webサーバーアーキテクチャの話を理解するとこの辺り実際に起きていることが想像できるようになるだろうか?
この辺OS動作からしてイメージ持てるようにしておきたいな。

PerlならStarlet、RubyならUnicornなどがこのモデルに該当する。
おお、よく聞くUnicornはこれなのか。Pumaはどうなんだい?
-> Multi Thread & Multi Process っぽい

MySQLに対してPostgresのほうがコードが綺麗と言われる所以は、前者がマルチスレッドモデルなのに対して、後者がマルチプロセスモデルであるということもあるかもしれない。
そうなのかー。というかMySQLやPostgresの話、つまりDB周りの話ももっと抑えたいよな。
もっとinputしたら?
そういう意味だと...
Rails -> ActionDispatch -> Webサーバー
Rails -> ActiveRecord -> DB
という感じで興味が移っているのかもしれない。ActiveRecord周りもいつか観なきゃな...

イベント駆動モデルのメリットは、preforkやスレッドプールと違って、同時に接続できるクライアント数に上限がないことだ。
ここに引っ掛かりを覚える。上限に大きな差があるだけで、「上限がない」は言い過ぎな印象を受けたのだが...

たどり着いたのは
でした。こっちは一旦閉じるか。

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