Rubyを使ったHTTPサーバーの構築


その記事はもともと投稿されたhere . いくつかのGIFはここに表示されません.

どのようなWebサーバーですか?


Webサーバーは、ユーザーからあなたのウェブサイトに要求を受け取り、それにいくつかの処理を行うプログラムです.それから、アプリケーション層に要求を与えるかもしれません.最も人気のあるWebサーバのいくつかは、NGinx、Apacheです.(リバースプロキシ、ロードバランシング、その他多くの機能がありますが、主にWebサーバとして動作します).
さて、質問をさせてください.開発中にlocalhostで動作するサーバーは、Webサーバーですか?あなたが送ったどんな要求でも、それを処理して、それから適切なページをロードしてください.したがって、それはWebサーバーのように見えるかもしれないが、より技術的には、それはアプリケーションサーバーと呼ばれています.アプリケーションサーバーは、コードを読み込み、メモリ内のアプリケーションを保持します.あなたのアプリケーションサーバーがあなたのWebサーバからの要求を取得すると、それはそれについてのあなたのアプリを伝えます.あなたのアプリケーションは、要求を処理して行われた後、アプリケーションサーバーは、Webサーバー(そして最終的にはユーザーに応答)を送信します.特にRailsでは、ユニコーン、プーマ、細い、虹のような多くのアプリサーバーがあります.
しかし、コミュニティによってテストされ、数千人で使用されるように多くのサーバーがある場合は、なぜ我々は別の建物を悩ます必要がありますか?さて、ゼロから1つを構築することによって、我々はこれらの作品がどのように良い知識があります.

HTTPサーバは実際にどのような動作をしますか?


それで、HTTPサーバが何をするかを壊しましょう.

手順


それで、我々が特定のURLを訪問するとき、それは特定のHTTP要求をサーバーに送ります.さて、HTTPリクエストは何ですか?これは、アプリケーションレベルのプロトコルは、すべてのアプリケーションがインターネットに接続して同意する必要があります.FTP(ファイル転送プロトコル)、TCP(Transmission Control Protocol)、SMTP(Simple Mail Transfer Protocol)のような他の多くのプロトコルがあります.HTTPまたはHyperText Transfer Protocolはこれらの間でちょうど非常に人気があって、ウェブアプリケーションとウェブサーバによって使われます.
つまり、ブラウザでURLを1つ入力するとき.それはウェブサーバにHTTP「リクエスト」を作ります、そこで、ウェブサーバはその要求を処理して、ブラウザーでユーザに提出されるHTTP「レスポンス」を送り返します.

歴史


最初のHTTP規格は1996年にティム・バーナー・リーによってHTTP/1.0であった.現在、HTTP/2はHTTPの意味をより効率的に表現しています.また、ウェブサイトの4 %以上で既に使用されているHTTP 3である別の後継者が存在することを知っていましたか

どうやって始めるの?


それで、我々はクライアントとサーバーの双方向通信を聞くツールを必要とするでしょう.基本的にソケット.ソケットは双方向通信チャネルのネットワークエンドポイント上で実行されている2つのプログラム間の双方向通信のための終点にすぎない.TCP層がデータが送られるアプリケーションを見つけることができるように、それはポートに縛られなければなりません、サーバーはリスナソケットを形成します、そして、クライアントはソケットに手を差し伸べます.ソケットを実装しません.Rubyには既に標準ライブラリで実装されているソケットがあります.
require "socket"
ソケットライブラリは、一般的なトランスポートを扱うための特定のクラスと、残りを処理するための汎用インターフェイスを提供します.基本的には、OSレベルと対話し、必要な動作を実行します.
Webサーバの基本的なプロセスは何ですか
  • 接続を聞く
  • リクエストをパースする
  • プロセスを処理し、レスポンスを送信する
  • 1 .接続をリッスンする


    まず、ポートを開いて、特定のポートに送られたすべてのメッセージを聞きましょう.我々はそれを使用することができますTCPServer.new or TCPServer.open メソッド.[医者によると、彼らは同義だ]
    require "socket"
    
    server = TCPServer.new("localhost", 8000)
    

    Feel free to choose any port, but make sure it is available. Use the command "netstat -lntu" to look for the ports that are currently used by a process, don't use those.


    今、私たちは着信接続を処理するために無限ループをしたいです.クライアントがサーバーに接続している場合.server.accept は他のRuby I/Oオブジェクトと同様に使用できるRubyソケットを返します.接続が要求によってなされたので、我々はまた、我々が使うことができるその要求を読むのが好きですgets メソッド.リクエストの最初の行を返します.
    では、次のようになります.
    require "socket"
    
    port = (ARGV[0] || 8000).to_i # to get a port from the ARG
    
    server = TCPServer.new("localhost", 8000)
    
    while (session = server.accept)
      puts "Client connected..."
      puts "Request: #{session.gets}"
    end
    
    これのテスト方法?
    つの端末をオープンし、Rubyスクリプトを起動し、他のオープンでirb . では、以下のコマンドに従ってください.
    他の端末では
    > require "socket"
    > soc = TCPSocket.open("localhost", 8000)
    > soc.puts "Hello There"
    

    テストするより簡単な方法は、スクリプトを実行し、ブラウザを使用してそのポートを訪問することです.ポートが8000 ジャスト訪問http://localhost:8000 . 次のようになります.
    Client connected...
    Request: GET / HTTP/1.1
    
    または、curlコマンドを使用することもできます.
    なぜちょうどGET / HTTP/1.1 ?
    リクエストを送信すると、複数行の文字列に解析されます.コマンドを実行しますcurl -v localhost:8000 次のようになります.
    *   Trying ::1:8000...
    * Connected to localhost (::1) port 8000 (#0)
    > GET / HTTP/1.1
    > Host: localhost:8000
    > User-Agent: curl/7.74.0
    > Accept: */*
    >
    
    そして我々のスクリプトでsession.gets 入力としてIOストリームの1行だけをとります.では、次のように置き換えましょうreadpartial(2048) . ここで、2048は我々が読むのが好きであるデータのバイトを表します.我々はそれを増やすことができます、しかし、我々のケースのために、それは十分です.
    これまでのところ、
    require "socket"
    
    port = (ARGV[0] || 8000).to_i
    
    server = TCPServer.new("localhost", 8000)
    
    while (session = server.accept)
      puts "Request: #{session.readpartial(2048)}"
    end
    
    スクリプトとcurlコマンドを再び実行します.HTTPリクエストデータの全てを出力します.

    HTTPリクエストの解析


    たった今、我々はちょうどストリングとして要求を受けています、我々のサーバーが理解することができて、さらにそれを処理するように、我々はそれを解析する必要があります.
    もう一度リクエストを見てみましょう.
     GET / HTTP/1.1  # GET is the method, the / is the path, the HTTP part is the protocol
     Host: localhost:8000 # Headers
     User-Agent: curl/7.74.0
     Accept: */*
    
    最初の行は
  • 方法
  • パス
  • プロトコル
  • その後のすべての行はヘッダーの下に来る.それで、我々は生の要求ストリングを解析するこの機能を書きます
    def parse(request_string)
      method, path, version = request_string.lines[0].split
      {
        method: method,
        version: version,
        path: path,
        headers: parse_headers(request_string),
      }
    end
    
    もう一つparse_headers ヘッダをパースするには
    def normalize(header)
      header.tr(":", "").to_sym
    end
    
    def parse_headers(request)
      headers = {}
      request.lines[1..-1].each do |line|
        return headers if line == "\r\n"
        header, value = line.split
        header = normalize(header)
        headers[header] = value
      end
    end
    
    今すぐリクエストの印刷だけではなく、この方法を行う
    server = TCPServer.new("localhost", 8000)
    
    while (session = server.accept)
      ap parse(session.readpartial(2048))
    end
    
    使用中awesome_print データをフォーマット形式で表示するには、puts . 今、あなたはこのようなものを得るでしょう.

    3 . HTTPレスポンスの処理と送信


    今、我々はすべてのデータを持っているので、我々は今準備し、応答を送信する必要があります.リクエストのパスがホームを参照している場合はindex.html それが他の何かであるならばlocalhost:8000/about.html その後、我々はそのパスで対応しますabout.html .
    def prepare(parsed_req)
        path = parsed_req[:path]
          if path == "/"
            respond_with("index.html")
          else
            respond_with(path)
          end
        end
    
    respond_with ファイルが存在しているかどうかをチェックすることになっています.
    def respond_with(path)
          if File.exists?(path)
            ok_response(File.binread(path))
          else
            error_response
          end
        end
    
    応答のために、我々はこの形式のストリングを送ります.http specに従っています.http specについてもっと読むことができますhere .
    def response(code, body="")
        "HTTP/1.1 #{code}\r\n" +
        "Content-Length: #{body.size}\r\n" +
        "\r\n" +
        "\#{body}\r\n"
    end
    
    それで我々はok_response and error_respnse 以下のようになります.
    def ok_response(body)
        MyServer::Response.new(code: 200, body: body)
    end
    
    def error_response
        MyServer::Response.new(code: 404)
    end
    
    今私たちの応答をした後、クライアントに送信することができます.コードをリファクタリングしたので、ここでコード全体を見つけることができます.

    一旦すべてが適切であるならば、我々は最終的にスクリプトを走らせて、URLを訪問することができますhttp://localhost:8000 それはすべてのコンテンツをレンダリングしますindex.html . 閉じるこの動画はお気に入りから削除されていますabout.html 訪問http://localhost:8000/about.html も同様にレンダリングされます.

    やあ!我々は正常に独自のHTTPサーバーを構築している