Upgrade to Pro — share decks privately, control downloads, hide ads and more …

サーバーフレームワークの仕組みが気になったので車輪の再発明をしてみた

 サーバーフレームワークの仕組みが気になったので車輪の再発明をしてみた

2022/03/12 全国学生エンジニア交流会「NSEEM」にて発表
https://github.com/yt8492/NativeServer

Yuta Tomiyama

March 12, 2022
Tweet

More Decks by Yuta Tomiyama

Other Decks in Programming

Transcript

  1. HTTPリクエストを解釈する - TCPのリクエストから以下を解釈できればよい - リクエストライン - ヘッダ - ボディ -

    RFCではこのように定義されている - Request = Request-Line *(( general-header | request-header | entity-header ) CRLF) CRLF [ message-body ]
  2. リクエストのリクエストラインを解釈する - RFCではこのように定義されている - Request-Line = Method SP Request-URI SP

    HTTP-Version CRLF - メソッド、URI、HTTPバージョンが空白区切りで、最後に CRLF(改行)がある - つまり、ソケット通信で以下のことができればよさそう - CRLFが現れるまで通信を読み込む - 読み込んだバイト列を文字列として扱う - 空白で区切り、それをメソッド、 URI、HTTPバージョンとする
  3. リクエストのヘッダを解釈する - ヘッダフィールドのフォーマットは以下のように定義されている - message-header = field-name ":" [ field-value

    ] field-name = token field-value = *( field-content | LWS ) field-content = <field-value を構成し、*TEXT あるいはtoken, separators, quoted-string    を連結したものから成る OCTET> - 名前と値がコロン区切りになっていて、値は前後に 0個以上の空白を含む場合がある - リクエストのヘッダは 0個以上のヘッダフィールドが CRLF区切りになっていて、 CRLFのみの行がヘッ ダの終わりになる
  4. リクエストのボディを解釈する - ボディは以下のように定義されている - message-body = entity-body | <Transfer-Encoding にてエンコードされた

    entity-body> - ヘッダに Content-Length や Transfer-Encoding 各ヘッダフィールドを含むとボディが存在し、 な ければボディが空 - Content-Lengthが指定されている場合はそのバイト数だけボディをよみこむ
  5. レスポンスをHTTPの形式にする - 以下の情報をTCPのレスポンスとして返せればよい - ステータスライン - ヘッダ - ボディ -

    RFCではこのように定義されている - Response = Status-Line *(( general-header | response-header | entity-header ) CRLF) CRLF [ message-body ]
  6. ステータスラインをTCPのレスポンスに書き込む - RFCではこのように定義されている - Status-Line = HTTP-Version SP Status-Code SP

    Reason-Phrase CRLF - つまり、ソケット通信で以下のことができるとよさそう - HTTPバージョンと区切りの空白を書き込む - ステータスコードと区切りの空白を書き込む - 説明句と終端のCRLFを書き込む
  7. クエリパラメータの取得 - 今回は以下のようになっているという前提で扱う - URIを?で区切った後ろの部分 - 名前=値という形でフィールドが構成され、それが &で連結されている - 名前と値はURLエンコードされる

    - 以下のように実装する - URIを?で区切った後ろの部分を取得する - 空でなければさらに &で区切る - 更に=で区切り、それぞれを URLデコードする
  8. ルーティング - 今回はユーザーが以下のように使えるように実装する - get("/hoge") { … }, post("/fuga") {

    … } のように、メソッドごとに関数が用意されていて、それ に パスを指定し、ラムダ関数の中で実際にリクエストを受け取ったときの処理を書く - 次のように実装する - ユーザーが get("/hoge") { … } のように関数を呼び出すとハンドラが追加される - ソケットから受け取った TCPのリクエストをHTTPのリクエストとして解釈する - 受け取ったリクエストの URIをもとにどのハンドラで処理するべきか評価し、最も評価度が高いハン ドラに実際に処理をさせる - 処理の結果ユーザーが生成したレスポンスのインスタンスを実際に HTTPのレスポンスとしてTCP のソケットに書き込む
  9. リクエストをハンドラにマッチさせる - 同じリクエストでも複数のハンドラにマッチする可能性がある - /hoge/fuga は /hoge/fuga, /hoge/:param, /hoge/* にマッチする可能性がある

    - マッチ度が高いのは /hoge/fuga > /hoge/:param > /hoge/* - これを実際にリクエストが来るたびに行う - ハンドラのパスを/で区切り、それぞれが定数、パスパラメータ、ワイルドカードかど うかによってそのハンドラがマッチした場合の評価度を計算する - パスパラメータを持つ場合は実際にそれを取り出す - ソースコードは結構長くなったのでGitHubまで見に来て