社内勉強会のコピペ。
HTTP とは
HTML など をやり取りする時の決まりごと
Web は URL で指定した HTML など を HTTP という決まりごとに沿ってやり取りしている
詳しくは Webを支える技術 という本に書いてあります
今日話すこと
今日話さないこと
登場する標準ライブラリ
先に結論
HTTP 通信は実装的にはネットワーク越しに(ソケットを通して?)ファイルの読み書きをしているだけ!
たとえばこんなコード
require 'webrick' srv = WEBrick::HTTPServer.new({ :DocumentRoot => './', :BindAddress => '127.0.0.1', :Port => 20080}) srv.mount('/', WEBrick::HTTPServlet::FileHandler, 'index.html') trap("INT"){ srv.shutdown } srv.start
<div> Hello! </div>
require 'net/http' require 'uri' uri = URI.parse('http://localhost:20080/') puts Net::HTTP.get(uri)
実行するとこうなります
$ ruby http_server.rb [2019-03-22 07:40:24] INFO WEBrick 1.4.2 [2019-03-22 07:40:24] INFO ruby 2.5.3 (2018-10-18) [x86_64-darwin18] [2019-03-22 07:40:24] INFO WEBrick::HTTPServer#start: pid=1786 port=20080 $ ruby http_client.rb <div> Hello! </div>
この時何が起きているか、実装を覗いてみる。
Webrick サーバ起動まで@webrick
WEBrick::HTTPServer.new
でHTTPServer
インスタンスを生成-
lib/webrick/server.rb
にWEBrick::GenericServer
がいてWEBrick::HTTPServer
はそれを継承している
-
WEBrick::HTTPServer#mount
でサーバ上のディレクトリにサーブレット(サーバの機能をオブジェクト化したもの)を対応させるtrap("INT"){ srv.shutdown }
はCtrl+C
押されたらHTTPServer#shutdown
するよの意- 実際に動くのは
WEBrick::GenericServer#shutdown
- ref. Signal.#trap
- 実際に動くのは
WEBrick::HTTPServer#start
(実際に動くのはWEBrick::GenericServer#start
)で途中のIO.select
で待機状態になる
URI のパース@uri
URI.parse
でURI::RFC3986_Parser#parse
が動いて中で頑張って split して parse してるURI::RFC2396_Parser
もあるけどこちらは 既に廃止されている?っぽい- ref. Request for Comments
HTTP クライアントが TCP コネクションを開くとこまで@nethttp
- HTTP.get
- HTTP.get_response
- HTTP.start
- HTTP.new
- 中で呼ばれている
super
は真下の #initialize に行くという認識でよいのかな?
- 中で呼ばれている
- HTTP.new
- HTTP#start
HTTP#do_start
を挟んで HTTP#connect- 中で
TCPSocket.open
してる :eyes: - ソケット)についての理解が足りない…
- 中で
yield(self)
でようやくHTTP#request_get
が呼ばれる…
HTTP (GET) リクエスト送信まで@nethttp
HTTP#request_get
-
Get.new
(=Net::HTTP::Get.new
) を引数に HTTP#request-
Net::HTTP::Get.new
は親(Net::HTTPRequest
)の親クラス(Net::HTTPGenericRequest
)までたどって初期化される(Net::HTTPGenericRequest
はNet::HTTPHeader
を include していて、初期化時に#initialize_http_header
を実行 して HTTP ヘッダを一緒に初期化したりしている) - GET リクエストなので Net::HTTPGenericRequest#set_body_internal で body には何も入らず
nil
のまま - そしてようやく HTTP#transport_request …
-
-
- HTTP#transport_request
- ref. Kernel.#catch
req.exec
で Net::HTTPGenericRequest#exec を実行- body は
nil
なので#write_header
が呼ばれる sock.write
している…!!!(ちなみに TCPSocket クラスは IO クラスを継承している ので#write
ができます)
- body は
- 次の処理は
HTTPResponse.read_new
なのでリクエストはここまでで終わり
HTTP リクエストのパースまで@webrick
- リクエストが来ると
#start_thread
で新しいスレッドが始まる- スレッド) についての理解が足りない…
WEBrick::HTTPServer#start
した時にブロックが渡されていなければ、WEBrick::HTTPServer#run
が実行される- まずは
#create_request
の中でHTTPRequest.new
- ここは Webrick の設定を突っ込んで初期化してるだけ
-
#create_response
も同じ感じ
- そして
HTTPRequest#parse
でめっちゃ parse する#parse
の中の#read_request_line
とか#read_header
とかを追っていくと、、、-
#read_request_line
->#read_line
->#_read_data
で、、、 - 分かりづらいけど
io.__send__(method, *arg)
の部分でIO#gets
してる!!!読んでる!!! - ref. Object#__send__
HTTP レスポンスの作成まで@webrick
HTTPServer#service
の中でsi.service
(si
にはWEBrick::HTTPServlet::FileHandler
のインスタンスが入っている) が呼ばれ最終的に WEBrick::HTTPServlet::DefaultFileHandler#do_GET が実行されてその中でres.body = File.open(@local_path, "rb")
的な感じで指定パスのファイル読み込んで body に代入している- ちなみに
WEBrick::HTTPServlet::DefaultFileHandler#do_GET
が実行されるまでの道のりが面白いFileHandler#service
-> super でAbstractServlet#service
->FileHandler#do_GET
->#exec_handler
->#get_handler
->DefaultFileHandler#service
(動くのは再度AbstractServlet#service
)-> で、ようやく DefaultFileHandler#do_GET
- ちなみに
- HTTPResponse#send_response の
#send_header
や#send_body
->#send_body_string
内部でsocket.write
してる!!!書いてる!!!
HTTP クライアントでレスポンスを表示するまで@nethttp
- HTTP#transport_request に戻って HTTPResponse.read_new とかでレスポンスを読み込む
- 読み込まれたレスポンスがいろいろ通って
HTTP.get
の中の#get_response
の返り値としてもどってきて、その body を取り出している…!!!
結論
HTTP 通信は実装的にはネットワーク越しに(ソケットを通して?)ファイルの読み書きをしているだけ!